Как правильно управлять вертикальным смещением призраков, колеблющихся между плитками в доме призраков Pac-ManC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Как правильно управлять вертикальным смещением призраков, колеблющихся между плитками в доме призраков Pac-Man

Сообщение Anonymous »

Я реализую поведение призраков в клоне Pac-Man, в частности поведение, когда призраки находятся внутри дома с привидениями во время фазы ожидания.
Проблема в том, что призраки внутри дома с привидениями не перемещаются по стандартной сетке плиток. Вместо этого они колеблются вертикально между центрами двух плиток, что требует применения вертикального смещения примерно на 8 пикселей (половина плитки) к их положению Y. Это смещение необходимо, поскольку дверной проем дома с привидениями расположен таким образом, что призракам кажется, что они движутся в пространстве между двумя плитками.
Однако мне трудно поддерживать правильное положение во время функции wait(). Когда я применяю 8-пиксельное вертикальное смещение для расчета текущего положения плитки и возвращаю призрака в заданное положение после завершения цикла движения, призраки либо выходят за пределы своих границ, либо сталкиваются со стенами.

Код: Выделить всё

#include "../include/Ghost.h"
#include "../include/TextureManager.h"
#include "../include/Map.h"
#include "../include/Direction.h"
#include "../include/GhostManager.h"
#include "../include/GameRules.h"
#include 
#include 
#include 
#include 

Ghost::Ghost(float initX, float initY, int w, int h)
: initialTileX(initX), initialTileY(initY), w(w), h(h), pixelsMoved(0.0f),
posX(initX * 16 + 8 - w / 2), posY(initY * 16 + 3*16 + 8 - h/2),
speed(GameRules::GHOST_SPEED_NORMAL)
{
endOfFrightening = false;
canGotoGhostHouse = true;
readyToExit = false;
ghostEaten = false;
state = WAIT;
rect = {static_cast(posX) - 8, static_cast(posY) + 8, w, h};
updateHitbox();
targetTexture = nullptr;
currentTile = {(int)initX, (int)initY};
targetTile = currentTile;
currentDirection = STOP;
scoreTexture200 = nullptr;
scoreTexture400 = nullptr;
scoreTexture800 = nullptr;
scoreTexture1600 = nullptr;
showingScore = false;
scoreDisplayDuration = 1000;
currentScoreValue = 0;
}

//---------------- Score Handling ----------------

void Ghost::loadScoreTextures(TextureManager* textureManager,
const std::string& score200Path,
const std::string& score400Path,
const std::string& score800Path,
const std::string& score1600Path) {
scoreTexture200 = textureManager->loadTexture(score200Path);
scoreTexture400 = textureManager->loadTexture(score400Path);
scoreTexture800 = textureManager->loadTexture(score800Path);
scoreTexture1600 = textureManager->loadTexture(score1600Path);
}

void Ghost::startShowingScore(int score) {
showingScore = true;
scoreBeingDisplayed = true;
scoreDisplayStartTime = SDL_GetTicks();
currentScoreValue = score;
}

void Ghost::updateScoreDisplay() {
if(showingScore) {
Uint32 now = SDL_GetTicks();
if(now - scoreDisplayStartTime >= scoreDisplayDuration) {
showingScore = false;
scoreBeingDisplayed = false;
setState(EATEN);
}
}
}

void Ghost::renderScore(SDL_Renderer* renderer) {
if(!showingScore) return;
SDL_Texture* scoreTex = nullptr;
switch(currentScoreValue) {
case 200: scoreTex = scoreTexture200; break;
case 400: scoreTex = scoreTexture400; break;
case 800: scoreTex = scoreTexture800; break;
case 1600: scoreTex = scoreTexture1600; break;
}
if(scoreTex) {
SDL_Rect dstRect = rect;
SDL_RenderCopy(renderer, scoreTex, nullptr, &dstRect);
}
}

//---------------- State / Reset ----------------

bool Ghost::ghostInGhostHouse() {
const SDL_Rect ghostHouseTiles = {11, 16, 8, 5};
const int tileSize = 16;
SDL_Rect ghostHouse = {
ghostHouseTiles.x * tileSize,
ghostHouseTiles.y * tileSize,
ghostHouseTiles.w * tileSize,
ghostHouseTiles.h * tileSize
};
SDL_Rect ghostRect = rect;
return (ghostRect.x + ghostRect.w > ghostHouse.x &&
ghostRect.x < ghostHouse.x + ghostHouse.w &&
ghostRect.y + ghostRect.h > ghostHouse.y &&
ghostRect.y <  ghostHouse.y + ghostHouse.h);
}

bool Ghost::checkCollisionWithPacman(Pacman* pacman) {
SDL_Rect pacHitbox = pacman->getHitbox();
if(SDL_HasIntersection(&hitbox, &pacHitbox)) {
if(state == FRIGHTENED) {
ghostEaten = true;
return true;
} else if(state == CHASE || state == SCATTER) {
pacman->isAlive = false;
}
return true;
}
return false;
}

void Ghost::reset() {
endOfFrightening = false;
canGotoGhostHouse = true;
readyToExit = false;
ghostEaten = false;
state = WAIT;
pixelsMoved = 0.0f;
currentDirection = STOP;
speed = GameRules::GHOST_SPEED_NORMAL;
posX = initialTileX * 16 + 8 - w/2;
posY = initialTileY * 16 + 3*16 + 8 - h/2;
rect = {static_cast(posX) - 8, static_cast(posY) + 8, w, h};
updateHitbox();
currentTile = {(int)initialTileX, (int)initialTileY};
targetTile = currentTile;
}

//---------------- Wait State ----------------

void Ghost::wait() {
const float tileSize = 16.0f;
const int offset = 8; // وسط تایل

// تعیین جهت اولیه
if(currentDirection == STOP) currentDirection = UP;

// حرکت واقعی
switch(currentDirection) {
case UP:
posY -= speed;
currentEye = eyeUp;
break;
case DOWN:
posY += speed;
currentEye = eyeDown;
break;
default:
break;
}

pixelsMoved += speed;

if(pixelsMoved >= tileSize) {
pixelsMoved = 0.0f;
currentDirection = (currentDirection == UP) ? DOWN : UP;

// تراز کردن در وسط تایل
currentTile.x = static_cast((posX + offset) / tileSize);
currentTile.y = static_cast((posY + offset) / tileSize);

posX = currentTile.x * tileSize + offset - w / 2;
posY = currentTile.y * tileSize + offset - h / 2;
}

rect.x = static_cast(posX);
rect.y = static_cast(posY);
updateHitbox();
}

//---------------- Update ----------------

void Ghost::update(const Map& map) {
if(frozen) return;

switch(state) {
case WAIT:
wait();
if(readyToExit) state = EXIT;
break;
case EXIT:
speed = GameRules::GHOST_SPEED_NORMAL;
getOutOfHouse(map);
canGotoGhostHouse = true;
break;
case CHASE:
case SCATTER:
speed = GameRules::GHOST_SPEED_NORMAL;
canGotoGhostHouse = false;
updateChaseScatter(map);
break;
case FRIGHTENED:
speed = GameRules::GHOST_SPEED_FRIGHTENED;
canGotoGhostHouse = false;
updateFrightened(map);
break;
case EATEN:
speed = GameRules::GHOST_SPEED_EATEN;
canGotoGhostHouse = true;
updateChaseScatter(map);
if(currentTile.x == 13 && currentTile.y == 14) {
speed = 1.0f;
setMode(EXIT);
}
break;
}
updateBodyAnimation();
}

//---------------- Movement ----------------

void Ghost::getOutOfHouse(const Map& map) {
const float tileSize = 16.0f;
const int mapWidthTiles = 28;
const int mapHeightTiles = 31;
const int exitTileX = 14;
const int exitTileY = 11;
const int offsetX = -8;

if(pixelsMoved == 0.0f) {
int rawTileX = static_cast((posX + 8 - offsetX) / tileSize);
currentTile.x = ((rawTileX % mapWidthTiles) + mapWidthTiles) % mapWidthTiles;
currentTile.y = static_cast((posY + 8 - 3 * tileSize) / tileSize);

Direction nextDir = STOP;
if(currentTile.x < exitTileX) nextDir = RIGHT;
else if(currentTile.x > exitTileX) nextDir = LEFT;
else if(currentTile.y >  exitTileY) nextDir = UP;
else if(currentTile.y == exitTileY) {
state = SCATTER;
pixelsMoved = 0.0f;
currentDirection = LEFT;
currentEye = eyeLeft;
return;
}
currentDirection = nextDir;
}

if(currentDirection != STOP) {
switch(currentDirection) {
case UP: posY -= speed; currentEye = eyeUp; break;
case DOWN: posY += speed; currentEye = eyeDown; break;
case LEFT: posX -= speed; currentEye = eyeLeft; break;
case RIGHT: posX += speed; currentEye = eyeRight; break;
default: break;
}
pixelsMoved += speed;
}

if(pixelsMoved >= tileSize) {
pixelsMoved = 0.0f;
currentTile.x = static_cast((posX + 8 - offsetX) / tileSize);
currentTile.y = static_cast((posY + 8 - 3*tileSize) / tileSize);
posX = currentTile.x * tileSize + 8 - w/2 + offsetX;
posY = currentTile.y * tileSize + 3*tileSize + 8 - h/2;
}

rect.x = static_cast(posX);
rect.y = static_cast(posY);
updateHitbox();
}

void Ghost::updateChaseScatter(const Map& map) {
const int mapWidthTiles = 28;
const int mapHeightTiles = 31;
const float tileSize = 16.0f;
const int tunnelRow = 14;

if(pixelsMoved == 0.0f) {
currentTile.x = static_cast((posX + 8) / tileSize) % mapWidthTiles;
currentTile.y = static_cast((posY + 8 - 3*tileSize) / tileSize);

int adjustedTargetX = targetTile.x;
int adjustedTargetY = targetTile.y;

bool targetInTunnel = (targetTile.y == tunnelRow) &&
(targetTile.x == 0 || targetTile.x == mapWidthTiles - 1);
if(targetInTunnel) adjustedTargetX = (targetTile.x == 0) ? mapWidthTiles - 1 : 0;

Direction reverseDir;
switch(currentDirection) {
case UP: reverseDir = DOWN; break;
case DOWN: reverseDir = UP; break;
case LEFT: reverseDir = RIGHT; break;
case RIGHT: reverseDir = LEFT; break;
default: reverseDir = STOP; break;
}

const SDL_Point dirs[4] = { {0,-1}, {-1,0}, {0,1}, {1,0} };
Direction dirMap[4] = { UP, LEFT, DOWN, RIGHT };
Direction bestDir = STOP;
int bestDist = INT_MAX;

for(int i=0;i= mapWidthTiles) actualNx = 0;
isValidTile = map.isWalkable(actualNx, ny);
} else {
if(nx>=0 && nx=0 && ny0)? -wrapDist : wrapDist;
}

int dist = dx*dx + dy*dy;
if(dist < bestDist) {
bestDist = dist;
bestDir = dir;
}
}
if(bestDir == STOP && reverseDir != STOP) bestDir = reverseDir;
currentDirection = bestDir;
}

if(currentDirection != STOP) {
switch(currentDirection) {
case UP: posY -= speed; currentEye = eyeUp; break;
case DOWN: posY += speed; currentEye = eyeDown; break;
case LEFT: posX -= speed; currentEye = eyeLeft; break;
case RIGHT: posX += speed; currentEye = eyeRight;  break;
}
pixelsMoved += speed;
}

if(pixelsMoved >= tileSize) {
pixelsMoved = 0.0f;
currentTile.x = static_cast((posX + 8) / tileSize);
currentTile.y = static_cast((posY + 8 - 3*tileSize) / tileSize);
posX = currentTile.x * tileSize + 8 - w/2;
posY = currentTile.y * tileSize + 3*tileSize + 8 - h/2;
}

int mapWidth = mapWidthTiles * static_cast(tileSize);
if(posX + w/2 < 0) posX = mapWidth - w/2;
else if(posX + w/2 >= mapWidth) posX = -w/2;

rect.x = static_cast(posX);
rect.y = static_cast(posY);
updateHitbox();
}

//---------------- Update Frightened ----------------
void Ghost::updateFrightened(const Map& map) {
const int mapWidthTiles = 28;
const int mapHeightTiles = 31;
const float tileSize = 16.0f;
const int tunnelRow = 14;

if(pixelsMoved == 0.0f) {
currentTile.x = static_cast((posX + 8) / tileSize);
currentTile.y = static_cast((posY + 8 - 3*tileSize) / tileSize);

Direction reverseDir;
switch(currentDirection) {
case UP: reverseDir = DOWN; break;
case DOWN: reverseDir = UP; break;
case LEFT: reverseDir = RIGHT; break;
case RIGHT: reverseDir = LEFT; break;
default: reverseDir = STOP; break;
}

std::vector possibleDirs;
const SDL_Point dirs[4] = { {0,-1}, {-1,0}, {0,1}, {1,0} };
Direction dirMap[4] = { UP, LEFT, DOWN, RIGHT };

for(int i=0;i=0 && nx=0 && ny= tileSize) {
pixelsMoved = 0.0f;
currentTile.x = static_cast((posX + 8) / tileSize);
currentTile.y = static_cast((posY + 8 - 3*tileSize) / tileSize);
posX = currentTile.x*tileSize + 8 - w/2;
posY = currentTile.y*tileSize + 3*tileSize + 8 - h/2;
}

int mapWidth = mapWidthTiles * static_cast(tileSize);
if(posX + w/2 < 0) posX = mapWidth - w/2;
else if(posX + w/2 >= mapWidth) posX = -w/2;

rect.x = static_cast(posX);
rect.y = static_cast(posY);
updateHitbox();
}

//---------------- Render ----------------
void Ghost::render(SDL_Renderer* renderer) {
if(showingScore) { renderScore(renderer); return; }

SDL_Texture* texToRender = (bodyFrame==0)? bodyTex1: bodyTex2;
SDL_Texture* frightenedToRender = (bodyFrame==0)? frightenedTex: frightenedTex2;
SDL_Texture* endFrightenedToRender = (bodyFrame==0)? endFrightened: endFrightened2;
SDL_Texture* endFrightToRender = (bodyFrame==0)? frightenedToRender:endFrightenedToRender;

if(getState() != FRIGHTENED &&  getState() != EATEN) {
if(texToRender) SDL_RenderCopy(renderer, texToRender, nullptr, &rect);
if(currentEye) SDL_RenderCopy(renderer, currentEye, nullptr, &rect);
}
if(getState() == FRIGHTENED) {
if(frightenedToRender) SDL_RenderCopy(renderer, frightenedToRender, nullptr, &rect);
if(endOfFrightening) SDL_RenderCopy(renderer, endFrightToRender, nullptr, &rect);
}
if(getState() == EATEN) {
if(currentEye) SDL_RenderCopy(renderer, currentEye, nullptr, &rect);
}
renderTarget(renderer);
}

//---------------- Load Textures ----------------
bool Ghost::loadTextures(TextureManager* texManager, const std::vector& paths) {
if(paths.size() < 10) return false;

eyeUp    = texManager->loadTexture(paths[0]);
eyeDown  = texManager->loadTexture(paths[1]);
eyeLeft  = texManager->loadTexture(paths[2]);
eyeRight = texManager->loadTexture(paths[3]);
bodyTex1 = texManager->loadTexture(paths[4]);
bodyTex2 = texManager->loadTexture(paths[5]);
frightenedTex = texManager->loadTexture(paths[6]);
frightenedTex2 = texManager->loadTexture(paths[7]);
endFrightened = texManager->loadTexture(paths[8]);
endFrightened2 = texManager->loadTexture(paths[9]);
currentEye = eyeDown;

if(paths.size()>=14){
scoreTexture200 = texManager->loadTexture(paths[10]);
scoreTexture400 = texManager->loadTexture(paths[11]);
scoreTexture800 = texManager->loadTexture(paths[12]);
scoreTexture1600 = texManager->loadTexture(paths[13]);
}

bool mainLoaded = eyeUp && eyeDown && eyeLeft && eyeRight &&
bodyTex1 && bodyTex2 &&
frightenedTex && frightenedTex2 &&
endFrightened && endFrightened2;

if(paths.size()>=14){
return mainLoaded && scoreTexture200 && scoreTexture400 &&
scoreTexture800 && scoreTexture1600;
}
return mainLoaded;
}
// -------------------- Target Handling --------------------
bool Ghost::loadTargetTexture(SDL_Renderer* renderer, const std::string& path) {
if(targetTexture) SDL_DestroyTexture(targetTexture);
targetTexture = SDL_CreateTextureFromSurface(renderer, SDL_LoadBMP(path.c_str()));
return targetTexture != nullptr;
}

void Ghost::renderTarget(SDL_Renderer* renderer) {
if(targetTexture) SDL_RenderCopy(renderer, targetTexture, nullptr, &rect);
}

void Ghost::clearTargetTexture() {
if(targetTexture) {
SDL_DestroyTexture(targetTexture);
targetTexture = nullptr;
}
}

// -------------------- State & Mode --------------------
void Ghost::setState(GhostState ghostState) {
state = ghostState;
updateHitbox();
}

void Ghost::setMode(GhostState mode) {
state = mode;
}

// -------------------- Hitbox --------------------
void Ghost::updateHitbox() {
hitbox.x = static_cast(posX);
hitbox.y = static_cast(posY);
hitbox.w = w;
hitbox.h = h;
}

SDL_Rect* Ghost::getHitBox() { return &hitbox; }

// -------------------- Position / Target --------------------
void Ghost::setPosition(int x, int y) {
posX = static_cast(x);
posY = static_cast(y);
rect.x = x;
rect.y = y;
updateHitbox();
}

SDL_Point Ghost::getCurrentTile() const { return currentTile; }

void Ghost::setTarget(const SDL_Rect& targetRect) {
targetTile.x = targetRect.x / 16;
targetTile.y = targetRect.y / 16;
}

void Ghost::setTargetTile(int tileX, int tileY) {
targetTile.x = tileX;
targetTile.y = tileY;
}

void Ghost::updateBodyAnimation() {
frameCounter++;
if(frameCounter >= frameSpeed) {
frameCounter = 0;
bodyFrame = (bodyFrame + 1) % 2;
}
bodyTex = (bodyFrame == 0) ? bodyTex1 : bodyTex2;
}
введите здесь описание изображения

Подробнее здесь: https://stackoverflow.com/questions/798 ... tiles-in-p
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»