Skip to content

Commit 174ebd6

Browse files
Merge pull request #413 from HiddenDreamStudio/level/paralax
Level/paralax
2 parents 30a45dc + 8b84f68 commit 174ebd6

1 file changed

Lines changed: 137 additions & 55 deletions

File tree

src/Map.cpp

Lines changed: 137 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)