@@ -84,18 +84,26 @@ bool Map::Update(float dt)
8484 LOG (" PARALLAX DEBUG: Pinned to camera position X: %f, Y: %f" , initCameraX, initCameraY);
8585 }
8686
87+ if (!hasInitCamera) return true ; // Don't draw parallax until camera is pinned
88+
89+ Vector2D playerPos (0 .0f , 0 .0f );
90+ bool hasPlayer = (Engine::GetInstance ().scene ->player != nullptr );
91+ if (hasPlayer) playerPos = Engine::GetInstance ().scene ->player ->position ;
92+ const float renderRadiusSq = 1500 .0f * 1500 .0f ; // Squared distance for performance
93+
8794 for (const auto & imgLayer : mapData.imageLayers ) {
8895 if (imgLayer->texture ) {
89- // Pin parallax to start position: offset = x + initCam * (1 - speed)
96+ // Image layers are typically global backgrounds, always draw them
9097 float pinnedX = imgLayer->offsetX + initCameraX * (1 .0f - imgLayer->parallaxFactorX );
98+ float pinnedY = imgLayer->offsetY + initCameraY * (1 .0f - imgLayer->parallaxFactorY );
9199
92100 Engine::GetInstance ().render ->DrawTexture (
93101 imgLayer->texture ,
94102 static_cast <int >(pinnedX),
95- static_cast <int >(imgLayer-> offsetY ),
103+ static_cast <int >(pinnedY ),
96104 nullptr ,
97105 imgLayer->parallaxFactorX ,
98- 1 . 0f // Force no Y parallax
106+ imgLayer-> parallaxFactorY
99107 );
100108 }
101109 }
@@ -106,33 +114,56 @@ bool Map::Update(float dt)
106114
107115 for (const auto & deco : mapData.decorationObjects ) {
108116 if (deco->texture && !deco->isFront ) {
117+ float px = deco->parallaxSpeed ;
118+
119+ // Optimization: Distance check from player
120+ // Always draw fondo (px=0) or objects within radius
121+ if (hasPlayer && px > 0 .0f ) {
122+ float dx = deco->x - playerPos.getX ();
123+ float dy = deco->y - playerPos.getY ();
124+ if ((dx*dx + dy*dy) > renderRadiusSq) continue ;
125+ }
126+
127+ float py = 1 .0f ; // All backgrounds scroll 1:1 vertically with the camera
128+ if (px > 1 .0f ) py = 1 .0f + (px - 1 .0f ) * 0 .5f ; // Keep subtle FG vertical parallax
129+
109130 // Pin parallax to start position
110- float pinnedX = deco->x + initCameraX * (1 .0f - deco->parallaxSpeed );
131+ float pinnedX = deco->x + initCameraX * (1 .0f - px);
132+ float pinnedY = deco->y + initCameraY * (1 .0f - py);
111133
112- // TEMPORARILY DISABLE CULLING FOR DECORATIONS TO ENSURE VISIBILITY
113- // if (!render->IsOnScreenWorldRect(pinnedX, deco->y - deco->height, deco->width, deco->height, 2000))
114- // continue;
115-
116134 int tw = 0 , th = 0 ;
117135 Engine::GetInstance ().textures ->GetSize (deco->texture , tw, th);
118136 float drawScaleX = (tw > 0 ) ? (deco->width / (float )tw) : 1 .0f ;
119137 float drawScaleY = (th > 0 ) ? (deco->height / (float )th) : 1 .0f ;
120138
121- render->DrawTexture (deco->texture , (int )pinnedX, (int )(deco-> y - deco->height ), nullptr , deco-> parallaxSpeed , 1 . 0f , deco->rotation , 0 , (int )deco->height , flipMode (deco->flipH , deco->flipV ), drawScaleX, drawScaleY);
139+ render->DrawTexture (deco->texture , (int )pinnedX, (int )(pinnedY - deco->height ), nullptr , px, py , deco->rotation , 0 , (int )deco->height , flipMode (deco->flipH , deco->flipV ), drawScaleX, drawScaleY);
122140 }
123141 }
124142
125143 for (const auto & plant : mapData.animatedPlants ) {
126144 if (plant->isFront ) continue ;
127145 plant->anim .Update (dt);
128146
129- float pinnedX = plant->x + initCameraX * (1 .0f - plant->parallaxSpeed );
147+ float px = plant->parallaxSpeed ;
148+
149+ // Optimization: Distance check from player
150+ if (hasPlayer && px > 0 .0f ) {
151+ float dx = plant->x - playerPos.getX ();
152+ float dy = plant->y - playerPos.getY ();
153+ if ((dx*dx + dy*dy) > renderRadiusSq) continue ;
154+ }
155+
156+ float py = 1 .0f ; // Background plants scroll 1:1 vertically with the camera
157+ if (px > 1 .0f ) py = 1 .0f + (px - 1 .0f ) * 0 .5f ; // Keep subtle FG vertical parallax
130158
131- if (!render->IsOnScreenWorldRect (pinnedX, plant->y , plant->w , plant->h , 1000 ))
159+ float pinnedX = plant->x + initCameraX * (1 .0f - px);
160+ float pinnedY = plant->y + initCameraY * (1 .0f - py);
161+
162+ if (!render->IsOnScreenWorldRect (pinnedX, pinnedY, plant->w , plant->h , 1000 ))
132163 continue ;
133164
134165 const SDL_Rect& frame = plant->anim .GetCurrentFrame ();
135- render->DrawTexture (plant->texture , (int )pinnedX, (int )plant-> y , &frame, plant-> parallaxSpeed , 1 . 0f );
166+ render->DrawTexture (plant->texture , (int )pinnedX, (int )pinnedY , &frame, px, py );
136167 }
137168
138169 // Draw Checkpoints BEFORE map layers so they appear behind the floor
@@ -156,14 +187,30 @@ bool Map::Update(float dt)
156187 std::string lowerName = mapLayer->name ;
157188 for (char & c : lowerName) c = ::tolower (c);
158189 if (px == 1 .0f ) {
159- if (lowerName.find (" background" ) != std::string::npos || lowerName.find (" back" ) != std::string::npos || lowerName.find (" fondo" ) != std::string::npos) px = 1 .0f ;
190+ if (lowerName.find (" fondo" ) != std::string::npos) px = 0 .0f ;
191+ else if (lowerName.find (" background" ) != std::string::npos || lowerName.find (" back" ) != std::string::npos) px = 1 .0f ;
160192 else if (lowerName.find (" middle" ) != std::string::npos || lowerName.find (" medio" ) != std::string::npos) px = 1 .0f ;
161- else if (lowerName.find (" foreground" ) != std::string::npos || lowerName.find (" front" ) != std::string::npos) px = 1 .1f ;
193+ else if (lowerName.find (" foreground" ) != std::string::npos || lowerName.find (" front" ) != std::string::npos) px = 1 .2f ;
194+ }
195+
196+ if (py == 1 .0f ) {
197+ if (px > 1 .0f ) py = 1 .0f + (px - 1 .0f ) * 0 .5f ; // Subtle Y parallax for foregrounds
198+ // Otherwise py stays 1.0f (scrolls with camera)
162199 }
163200
164- // Process the whole map for now to ensure visibility
165- for (int i = 0 ; i < mapData.width ; i++) {
166- for (int j = 0 ; j < mapData.height ; j++) {
201+ // Optimization: Pre-calculate tile bounds based on player radius
202+ int minI = 0 , maxI = mapData.width ;
203+ int minJ = 0 , maxJ = mapData.height ;
204+
205+ if (hasPlayer && px > 0 .0f ) {
206+ minI = std::max (0 , (int )((playerPos.getX () - 1500 .0f ) / mapData.tileWidth ));
207+ maxI = std::min (mapData.width , (int )((playerPos.getX () + 1500 .0f ) / mapData.tileWidth ) + 1 );
208+ minJ = std::max (0 , (int )((playerPos.getY () - 1500 .0f ) / mapData.tileHeight ));
209+ maxJ = std::min (mapData.height , (int )((playerPos.getY () + 1500 .0f ) / mapData.tileHeight ) + 1 );
210+ }
211+
212+ for (int i = minI; i < maxI; i++) {
213+ for (int j = minJ; j < maxJ; j++) {
167214 unsigned int rawGid = mapLayer->Get (i, j);
168215 if (rawGid != 0 ) {
169216 const unsigned int FLIPPED_HORIZONTALLY_FLAG = 0x80000000 ;
@@ -179,12 +226,13 @@ bool Map::Update(float dt)
179226
180227 // Pin parallax to start position
181228 float pinnedX = mapCoord.getX () + initCameraX * (1 .0f - px);
229+ float pinnedY = mapCoord.getY () + initCameraY * (1 .0f - py);
182230
183231 SDL_FlipMode flip = SDL_FLIP_NONE ;
184232 if (rawGid & FLIPPED_HORIZONTALLY_FLAG ) flip = (SDL_FlipMode)(flip | SDL_FLIP_HORIZONTAL );
185233 if (rawGid & FLIPPED_VERTICALLY_FLAG ) flip = (SDL_FlipMode)(flip | SDL_FLIP_VERTICAL );
186234
187- render->DrawTexture (tileSet->texture , (int )pinnedX, (int )mapCoord. getY () , &tileRect, px, 1 . 0f , 0.0 , INT_MAX , INT_MAX , flip);
235+ render->DrawTexture (tileSet->texture , (int )pinnedX, (int )pinnedY , &tileRect, px, py , 0.0 , INT_MAX , INT_MAX , flip);
188236 }
189237 }
190238 }
@@ -209,26 +257,33 @@ bool Map::PostUpdate() {
209257
210258 // Find player position for player-centric parallax
211259 Vector2D playerPos (0 .0f , 0 .0f );
212- bool foundPlayer = false ;
213- if (Engine::GetInstance ().entityManager ) {
214- for (const auto &entity : Engine::GetInstance ().entityManager ->entities ) {
215- if (entity && entity->type == EntityType::PLAYER ) {
216- playerPos = entity->position ;
217- foundPlayer = true ;
218- break ;
219- }
220- }
260+ bool hasPlayer = false ;
261+ if (Engine::GetInstance ().scene ->player ) {
262+ playerPos = Engine::GetInstance ().scene ->player ->position ;
263+ hasPlayer = true ;
221264 }
265+ const float renderRadiusSq = 1500 .0f * 1500 .0f ;
222266
223267 for (const auto &deco : mapData.decorationObjects ) {
224268 if (deco->texture && deco->isFront ) {
225- int drawX = (int )deco->x ;
226- if (foundPlayer) {
227- drawX = (int )(deco->x + (playerPos.getX () - deco->x ) *
228- (deco->parallaxSpeed - 1 .0f ));
269+ float px = deco->parallaxSpeed ;
270+
271+ // Optimization: Distance check from player
272+ if (hasPlayer && px > 0 .0f ) {
273+ float dx = deco->x - playerPos.getX ();
274+ float dy = deco->y - playerPos.getY ();
275+ if ((dx * dx + dy * dy) > renderRadiusSq)
276+ continue ;
229277 }
230278
231- if (!render->IsOnScreenWorldRect ((float )drawX, deco->y - deco->height ,
279+ float py = 1 .0f ;
280+ if (px != 1 .0f ) py = 1 .0f + (px - 1 .0f ) * 0 .5f ;
281+
282+ // Pin parallax to start position
283+ float pinnedX = deco->x + initCameraX * (1 .0f - px);
284+ float pinnedY = deco->y + initCameraY * (1 .0f - py);
285+
286+ if (!render->IsOnScreenWorldRect (pinnedX, pinnedY - deco->height ,
232287 deco->width , deco->height ))
233288 continue ;
234289
@@ -237,8 +292,8 @@ bool Map::PostUpdate() {
237292 float drawScaleX = (tw > 0 ) ? (deco->width / (float )tw) : 1 .0f ;
238293 float drawScaleY = (th > 0 ) ? (deco->height / (float )th) : 1 .0f ;
239294
240- render->DrawTexture (deco->texture , drawX , (int )(deco-> y - deco->height ),
241- nullptr , 1 . 0f , 1 . 0f , deco->rotation , 0 , (int )deco->height ,
295+ render->DrawTexture (deco->texture , ( int )pinnedX , (int )(pinnedY - deco->height ),
296+ nullptr , px, py , deco->rotation , 0 , (int )deco->height ,
242297 flipMode (deco->flipH , deco->flipV ), drawScaleX,
243298 drawScaleY);
244299 }
@@ -247,11 +302,28 @@ bool Map::PostUpdate() {
247302 for (const auto &plant : mapData.animatedPlants ) {
248303 if (!plant->isFront )
249304 continue ;
250- if (!render->IsOnScreenWorldRect (plant->x , plant->y , plant->w , plant->h ))
305+
306+ float px = plant->parallaxSpeed ;
307+
308+ // Optimization: Distance check from player
309+ if (hasPlayer && px > 0 .0f ) {
310+ float dx = plant->x - playerPos.getX ();
311+ float dy = plant->y - playerPos.getY ();
312+ if ((dx * dx + dy * dy) > renderRadiusSq)
313+ continue ;
314+ }
315+
316+ float py = 1 .0f ;
317+ if (px != 1 .0f ) py = 1 .0f + (px - 1 .0f ) * 0 .5f ;
318+
319+ float pinnedX = plant->x + initCameraX * (1 .0f - px);
320+ float pinnedY = plant->y + initCameraY * (1 .0f - py);
321+
322+ if (!render->IsOnScreenWorldRect (pinnedX, pinnedY, plant->w , plant->h ))
251323 continue ;
252324
253325 const SDL_Rect &frame = plant->anim .GetCurrentFrame ();
254- render->DrawTexture (plant->texture , (int )plant-> x , (int )plant->y , &frame);
326+ render->DrawTexture (plant->texture , (int )pinnedX , (int )plant->y , &frame, px, py );
255327 }
256328
257329 return true ;
@@ -731,8 +803,9 @@ void Map::LoadEntities(std::shared_ptr<Player> &player, bool portalTransition,
731803 Render* render = Engine::GetInstance ().render .get ();
732804 float vW = render->GetWorldViewportWidth ();
733805 float vH = render->GetWorldViewportHeight ();
734- initCameraX = -(player->position .getX () - vW / 2 .0f );
735- initCameraY = -(player->position .getY () - vH * 0 .75f );
806+ // Use exact math from Render::FollowTarget to avoid any pixel shift
807+ initCameraX = (float )static_cast <int >(-(player->position .getX () - vW / 2 .0f ));
808+ initCameraY = (float )static_cast <int >(-(player->position .getY () - vH * 0 .75f ));
736809 hasInitCamera = true ;
737810 LOG (" PARALLAX PINNED in LoadEntities: %f, %f" , initCameraX, initCameraY);
738811 } else if (entityType == " Enemy" || entityType == " SpiderCandy" ) {
@@ -1356,30 +1429,34 @@ void Map::LoadImageLayers() {
13561429 for (char &c : lowerName)
13571430 c = ::tolower (c);
13581431
1359- if (lowerName.find (" background" ) != std::string::npos ||
1360- lowerName.find (" back" ) != std::string::npos ||
1361- lowerName.find (" fondo" ) != std::string::npos)
1432+ if (lowerName.find (" fondo" ) != std::string::npos)
1433+ {
1434+ defaultParallax = 0 .0f ; // Fondo is static on screen
1435+ }
1436+ else if (lowerName.find (" background" ) != std::string::npos ||
1437+ lowerName.find (" back" ) != std::string::npos)
13621438 {
1363- defaultParallax = 0 . 8f ; // "A bit of parallax" for background image
1439+ defaultParallax = 1 . 0f ; // Background is static in the world
13641440 }
13651441 else if (lowerName.find (" middle" ) != std::string::npos ||
13661442 lowerName.find (" medio" ) != std::string::npos)
13671443 {
1368- defaultParallax = 1 .0f ; // Eliminate middleground effect
1444+ defaultParallax = 1 .0f ; // Middleground is static in the world
13691445 }
13701446 else if (lowerName.find (" foreground" ) != std::string::npos ||
13711447 lowerName.find (" front" ) != std::string::npos)
13721448 {
1373- defaultParallax = 1 . 1f ; // Foreground moves faster than camera
1449+ defaultParallax = 0 . 5f ; // Foreground moves fast (at old fondo speed)
13741450 }
13751451
13761452 // Check if parallaxx attribute is explicitly set in TMX, otherwise use
1377- // defaultParallax Apply a 0.25f damping factor to prevent aggressive
1378- // layouts from detaching
1453+ // defaultParallax
13791454 pugi::xml_attribute pxAttr = imgNode.attribute (" parallaxx" );
1380- float rawParallax = pxAttr ? pxAttr.as_float () : defaultParallax;
1381- imgLayer->parallaxFactorX = 1 .0f + (rawParallax - 1 .0f ) * 0 .25f ;
1455+ imgLayer->parallaxFactorX = pxAttr ? pxAttr.as_float () : defaultParallax;
1456+
1457+ // Set vertical parallax to 1.0f so background scrolls with the camera vertically
13821458 imgLayer->parallaxFactorY = imgNode.attribute (" parallaxy" ).as_float (1 .0f );
1459+
13831460 imgLayer->source = imgNode.child (" image" ).attribute (" source" ).as_string ();
13841461
13851462 std::string fullPath = mapPath + imgLayer->source ;
@@ -1417,11 +1494,17 @@ void Map::LoadDecorationObjects() {
14171494 for (char &c : lowerGroupName)
14181495 c = ::tolower (c);
14191496
1420- if (lowerGroupName.find (" background" ) != std::string::npos ||
1421- lowerGroupName.find (" back" ) != std::string::npos ||
1422- lowerGroupName.find (" fondo" ) != std::string::npos ||
1423- lowerGroupName.find (" bakground" ) != std::string::npos) {
1424- layerParallax = 0 .98f ; // Extremely subtle background decoration parallax
1497+ if (lowerGroupName.find (" fondo" ) != std::string::npos) {
1498+ layerParallax = 0 .0f ; // Fondo is static on screen
1499+ layerIsFront = false ;
1500+ } else if (lowerGroupName.find (" background" ) != std::string::npos ||
1501+ lowerGroupName.find (" back" ) != std::string::npos ||
1502+ lowerGroupName.find (" bakground" ) != std::string::npos) {
1503+ layerParallax = 1 .0f ; // Background is static in the world
1504+ layerIsFront = false ;
1505+ } else if (lowerGroupName.find (" middle" ) != std::string::npos ||
1506+ lowerGroupName.find (" medio" ) != std::string::npos) {
1507+ layerParallax = 1 .0f ; // Middleground is static in the world
14251508 layerIsFront = false ;
14261509 } else if (lowerGroupName.find (" foreground" ) != std::string::npos ||
14271510 lowerGroupName.find (" front" ) != std::string::npos) {
@@ -1431,10 +1514,9 @@ void Map::LoadDecorationObjects() {
14311514 // Middleground and everything else stays at 1.0, isFront = false
14321515
14331516 // TMX parallaxx attribute overrides the default if present
1434- // Apply a 0.25f damping factor to prevent aggressive layouts from detaching
14351517 float tmxParallaxx = groupNode.attribute (" parallaxx" ).as_float (0 .0f );
14361518 if (tmxParallaxx > 0 .0f ) {
1437- layerParallax = 1 . 0f + ( tmxParallaxx - 1 . 0f ) * 0 . 25f ;
1519+ layerParallax = tmxParallaxx;
14381520 }
14391521
14401522 std::vector<DecorationObject *> layerDecos;
0 commit comments