From c8460abb90a20673b2071c12511d9b952f7366bc Mon Sep 17 00:00:00 2001 From: Tamir Atias Date: Mon, 20 Feb 2012 21:59:51 +0200 Subject: [PATCH] [Add] Some pathfinding code. --- Unuk-QT/Unuk-QT.pro | 3 +- Win32/Unuk/LibUnuk/LibUnuk.vcproj | 12 ++- src/libUnuk/Engine/Character.cpp | 8 +- src/libUnuk/Engine/NPC.cpp | 118 ++++++++++++++++------------ src/libUnuk/Engine/NPC.h | 18 ++--- src/libUnuk/Engine/Pathfinding.h | 2 +- src/libUnuk/Engine/WorldManager.cpp | 1 + src/libUnuk/LevelGen/AStarTile.cpp | 53 +++++++++++++ src/libUnuk/LevelGen/AStarTile.h | 29 +++++++ src/libUnuk/LevelGen/LevelGen.cpp | 50 ++++++++---- src/libUnuk/LevelGen/LevelGen.h | 11 ++- src/libUnuk/LevelGen/MapTile.cpp | 68 ---------------- src/libUnuk/LevelGen/MapTile.h | 16 +--- 13 files changed, 221 insertions(+), 168 deletions(-) create mode 100644 src/libUnuk/LevelGen/AStarTile.cpp create mode 100644 src/libUnuk/LevelGen/AStarTile.h delete mode 100644 src/libUnuk/LevelGen/MapTile.cpp diff --git a/Unuk-QT/Unuk-QT.pro b/Unuk-QT/Unuk-QT.pro index cb08ce9..9bbd9a2 100644 --- a/Unuk-QT/Unuk-QT.pro +++ b/Unuk-QT/Unuk-QT.pro @@ -41,6 +41,7 @@ HEADERS += ../src/Libs/wglext.h \ ../src/libUnuk/LevelGen/MapEntities.h \ ../src/libUnuk/LevelGen/MapElement.h \ ../src/libUnuk/LevelGen/MapTile.h \ + ../src/libUnuk/LevelGen/AStarTile.h \ ../src/libUnuk/UI/EventHistory.h \ ../src/libUnuk/UI/Bar.h \ ../src/libUnuk/System/Vec2.h \ @@ -77,7 +78,7 @@ SOURCES += ../src/libUnuk/Engine/WorldManager.cpp \ ../src/libUnuk/LevelGen/LevelGen.cpp \ ../src/libUnuk/LevelGen/MapEntities.cpp \ ../src/libUnuk/LevelGen/MapElement.cpp \ - ../src/libUnuk/LevelGen/MapTile.cpp \ + ../src/libUnuk/LevelGen/AStarTile.cpp \ ../src/libUnuk/UI/EventHistory.cpp \ ../src/libUnuk/UI/Bar.cpp \ ../src/libUnuk/System/Vec2.cpp \ diff --git a/Win32/Unuk/LibUnuk/LibUnuk.vcproj b/Win32/Unuk/LibUnuk/LibUnuk.vcproj index 175ce34..d61567c 100644 --- a/Win32/Unuk/LibUnuk/LibUnuk.vcproj +++ b/Win32/Unuk/LibUnuk/LibUnuk.vcproj @@ -208,6 +208,14 @@ + + + + @@ -232,10 +240,6 @@ RelativePath="..\..\..\src\libUnuk\LevelGen\MapEntities.h" > - - diff --git a/src/libUnuk/Engine/Character.cpp b/src/libUnuk/Engine/Character.cpp index d74d539..d815fbb 100644 --- a/src/libUnuk/Engine/Character.cpp +++ b/src/libUnuk/Engine/Character.cpp @@ -15,9 +15,13 @@ Character::Character(LevelGen* mapArg) { _leftFoot = false; _health = 100; - xVel = 0.0f; + x = 0; + y = 0; + w = 40; + h = 45; + xVel = 0.0f; yVel = 0.0f; - + _texture = NULL; collisionList.push_front(this); diff --git a/src/libUnuk/Engine/NPC.cpp b/src/libUnuk/Engine/NPC.cpp index 4f59627..0aab773 100644 --- a/src/libUnuk/Engine/NPC.cpp +++ b/src/libUnuk/Engine/NPC.cpp @@ -1,18 +1,21 @@ #include "NPC.h" -#include "../Unuk/Player.h" +#include "../../Unuk/Player.h" +#include "../System/Vec2.h" NPC::NPC(LevelGen* mapArg) : Character(mapArg) { - _moveTimer.Start(); - - _moveChangeFrequency = 14000; - _moveDurationMax = 3000; - _moveDurationMin = 1000; + _walkInPath = false; + _moving = false; } NPC::~NPC(void) { } +void NPC::ForceMove(void) { + tileX = x / AStarTile::FAKE_SIZE; + tileY = y / AStarTile::FAKE_SIZE; +} + void NPC::Update(void) { // Store the NPC's health. // int health = GetHealth(); // not referenced @@ -28,66 +31,83 @@ void NPC::Update(void) { } void NPC::Move(void) { - if(_moving && _moveTimer.GetTicks() > _moveDurationMax) { - xVel = 0.0f; - yVel = 0.0f; - _moving = false; - } - - if(_moving && _moveTimer.GetTicks() >= _moveDurationCurrent) { - xVel = 0.0f; - yVel = 0.0f; - _moving = false; - } - - if(_moveTimer.GetTicks() > _moveChangeFrequency) { - _moveTimer.Start(); - _moveDurationCurrent = _moveDurationMin + (rand() % (_moveDurationMax - _moveDurationMin)); - if(rand() % 2) { - yVel = 0.0f; - if(rand() % 2) - xVel = CHARACTER_SPEED; - else - xVel = -CHARACTER_SPEED; + Vec2 realPos(x, y); + Vec2 tilePos(tileX, tileY); + + if(realPos != tilePos) { + float targetX = (float)(tileX * AStarTile::FAKE_SIZE); + float targetY = (float)(tileY * AStarTile::FAKE_SIZE); + + float dx = targetX - realPos.x; + float dy = targetY - realPos.y; + + float distance = sqrtf(dx*dx + dy*dy); + + if(fabs(distance) > CHARACTER_SPEED) { + dx /= distance; + dy /= distance; + dx *= CHARACTER_SPEED; + dy *= CHARACTER_SPEED; + + xVel = dx; + yVel = dy; + + map->MoveIfPossible(this, xVel, yVel, false); + + Character::HealthBarScroll(); + + _moving = true; } else { xVel = 0.0f; - if(rand() % 2) - yVel = CHARACTER_SPEED; - else - yVel = -CHARACTER_SPEED; + yVel = 0.0f; + _moving = false; + } + } else { + xVel = 0.0f; + yVel = 0.0f; + _moving = false; + } + + if(_walkInPath && !_moving) { + Vec2 targetPos((float)_target->GetX(), (float)_target->GetY()); + if(abs(Vec2::Distance(targetPos, realPos)) <= CHARACTER_SPEED) { + _target = _astar.GetSolutionNext(); + if(_target == NULL || _target == _lastTarget) { + _walkInPath = false; + } else { + tileX = _target->GetX() / AStarTile::FAKE_SIZE; + tileY = _target->GetY() / AStarTile::FAKE_SIZE; + } } - _moving = true; } - - map->MoveIfPossible(this, xVel, yVel, false); - Character::HealthBarScroll(); } void NPC::OnPlayerMove(Player* player) { - MapTile& start = map->GetTile(x / TILE_WIDTH, y / TILE_HEIGHT); - MapTile& goal = map->GetTile(player->GetX() / TILE_WIDTH, player->GetY() / TILE_HEIGHT); + AStarTile& start = map->GetAStarTile(x / AStarTile::FAKE_SIZE, y / AStarTile::FAKE_SIZE); + AStarTile& goal = map->GetAStarTile(player->GetX() / AStarTile::FAKE_SIZE, player->GetY() / AStarTile::FAKE_SIZE); _astar.SetStartAndGoalStates(start, goal); - bool solutionFound = false; + _walkInPath = false; // Dirty loop to calculate the path while(true) { - _astar.SearchStep(); - - int state = _astar.GetState(); - if(state == AStarSearch::SEARCH_STATE_SUCCEEDED) { - solutionFound = true; + unsigned int state = _astar.SearchStep(); + if(state == AStarSearch::SEARCH_STATE_SUCCEEDED) { + _walkInPath = true; break; - } else if(state == AStarSearch::SEARCH_STATE_SEARCHING) { - continue; - } else { + } else if(state != AStarSearch::SEARCH_STATE_SEARCHING) { break; } } - if(solutionFound) { - x = _astar.GetSolutionEnd()->GetTileX(); - y = _astar.GetSolutionEnd()->GetTileY(); + _lastTarget = _astar.GetSolutionEnd(); + _target = _astar.GetSolutionStart(); + _target = _astar.GetSolutionNext(); + if(!_target) { + _walkInPath = false; + } else { + tileX = _target->GetX() / AStarTile::FAKE_SIZE; + tileY = _target->GetY() / AStarTile::FAKE_SIZE; } } diff --git a/src/libUnuk/Engine/NPC.h b/src/libUnuk/Engine/NPC.h index 55010d6..b11cc0c 100644 --- a/src/libUnuk/Engine/NPC.h +++ b/src/libUnuk/Engine/NPC.h @@ -2,7 +2,7 @@ #include "Character.h" #include "Pathfinding.h" -#include "../LevelGen/MapTile.h" +#include "../LevelGen/AStarTile.h" class Player; @@ -11,7 +11,8 @@ public: NPC(LevelGen* mapArg); ~NPC(void); - void Update(void); + void ForceMove(void); + void Update(void); void OnPlayerMove(Player* player); @@ -19,15 +20,10 @@ protected: void Move(void); private: - int _moveChangeFrequency; - - int _moveDurationCurrent; - int _moveDurationMin; - int _moveDurationMax; - bool _moving; - Timer _moveTimer; - - AStarSearch _astar; + AStarSearch _astar; + bool _walkInPath; + AStarTile* _target; + AStarTile* _lastTarget; }; diff --git a/src/libUnuk/Engine/Pathfinding.h b/src/libUnuk/Engine/Pathfinding.h index d30f3ab..e24f407 100644 --- a/src/libUnuk/Engine/Pathfinding.h +++ b/src/libUnuk/Engine/Pathfinding.h @@ -152,7 +152,7 @@ public: // The user provides this functions and uses AddSuccessor to add each // successor of node 'n' to _successors. - bool ret = n->_userState.GetSuccessors(this, n->parent ? &n->parent->_userState : NULL); + bool ret = n->_userState.GetSuccessors(this/*, n->parent ? &n->parent->_userState : NULL*/); if(!ret) { typename vector::iterator successor; diff --git a/src/libUnuk/Engine/WorldManager.cpp b/src/libUnuk/Engine/WorldManager.cpp index b9f164b..05bb3b5 100644 --- a/src/libUnuk/Engine/WorldManager.cpp +++ b/src/libUnuk/Engine/WorldManager.cpp @@ -71,6 +71,7 @@ NPC* WorldManager::GetNPCAt(int xArg, int yArg) { void WorldManager::CreateNPC(int x, int y) { NPC* npc = new NPC(_level); npc->SetXY(x, y); + npc->ForceMove(); npc->LoadSprites("../Data/Media/Images/Characters/template.png", 40,45); _npcs.push_back(npc); } diff --git a/src/libUnuk/LevelGen/AStarTile.cpp b/src/libUnuk/LevelGen/AStarTile.cpp new file mode 100644 index 0000000..d6c49ad --- /dev/null +++ b/src/libUnuk/LevelGen/AStarTile.cpp @@ -0,0 +1,53 @@ +#include "AStarTile.h" +#include "../System/Vec2.h" +#include "../LevelGen/LevelGen.h" + +bool AStarTile::IsSameState(AStarTile& tile) { + return tile.x == x && tile.y == y; +} + +bool AStarTile::IsGoal(AStarTile& tile) { + return IsSameState(tile); +} + +float AStarTile::GoalDistanceEstimate(AStarTile& goal) { + Vec2 goalPos(goal.x * FAKE_SIZE, goal.y * FAKE_SIZE); + Vec2 thisPos(x * FAKE_SIZE, y * FAKE_SIZE); + return fabs(Vec2::DistanceSquared(thisPos, goalPos)); +} + +float AStarTile::GetCost(AStarTile& goal) { + return FAKE_SIZE * FAKE_SIZE; +} + +bool AStarTile::GetSuccessors(AStarSearch* search) { + if(_level->MetaTilePassable((x - 1) * FAKE_SIZE, y * FAKE_SIZE)) { + AStarTile& successor = _level->GetAStarTile(x - 1, y); + if(!search->AddSuccessor(successor)) { + return false; + } + } + + if(_level->MetaTilePassable((x + 1) * FAKE_SIZE, y * FAKE_SIZE)) { + AStarTile& successor = _level->GetAStarTile(x + 1, y); + if(!search->AddSuccessor(successor)) { + return false; + } + } + + if(_level->MetaTilePassable(x * FAKE_SIZE, (y - 1) * FAKE_SIZE)) { + AStarTile& successor = _level->GetAStarTile(x, y - 1); + if(!search->AddSuccessor(successor)) { + return false; + } + } + + if(_level->MetaTilePassable(x * FAKE_SIZE, (y + 1) * FAKE_SIZE)) { + AStarTile& successor = _level->GetAStarTile(x, y + 1); + if(!search->AddSuccessor(successor)) { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/src/libUnuk/LevelGen/AStarTile.h b/src/libUnuk/LevelGen/AStarTile.h new file mode 100644 index 0000000..ea6e007 --- /dev/null +++ b/src/libUnuk/LevelGen/AStarTile.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../Engine/Pathfinding.h" + +class LevelGen; + +class AStarTile { +public: + AStarTile() {} + AStarTile(LevelGen* level, int xArg, int yArg, bool passable) : _level(level), x(xArg), y(yArg), _passable(passable) {} + + bool IsSameState(AStarTile& tile); + bool IsGoal(AStarTile& tile); + float GoalDistanceEstimate(AStarTile& goal); + float GetCost(AStarTile& goal); + bool GetSuccessors(AStarSearch* search); + + int GetX() { return x * FAKE_SIZE; } + int GetY() { return y * FAKE_SIZE; } + + static const int FAKE_SIZE = 64 + ; + +private: + LevelGen* _level; + bool _passable; + int x; + int y; +}; diff --git a/src/libUnuk/LevelGen/LevelGen.cpp b/src/libUnuk/LevelGen/LevelGen.cpp index 6b7ca76..902c3a2 100644 --- a/src/libUnuk/LevelGen/LevelGen.cpp +++ b/src/libUnuk/LevelGen/LevelGen.cpp @@ -40,6 +40,9 @@ void LevelGen::New(void) { // procedural generation DoMagic(); + + // pathfinding + UpdateAStarTiles(); } void LevelGen::Load(const string& filename) { @@ -146,6 +149,8 @@ void LevelGen::Load(const string& filename) { _world = WorldManager(this); GenerateEnemies(); + + UpdateAStarTiles(); } void LevelGen::Save(const string& filename){ @@ -255,7 +260,7 @@ void LevelGen::Unload(void) { _entityTextures.Unload(); for(int x = 0; x < TILE_ARRAY_SIZE; x++) { for(int y = 0; y < TILE_ARRAY_SIZE; y++) { - _tile[x][y] = MapTile(this); + _tile[x][y] = MapTile(); } } } @@ -269,7 +274,7 @@ void LevelGen::DoMagic(void) { GenerateEntities("stone2", 35); GenerateEntities("closedChest", 100); GenerateEntities("closedChestMetal", 150); - GenerateEntities("closedChestMetal2", 250); + GenerateEntities("closedChestMetal2", 250); MakeWalkingPaths(); GenerateEnemies(); } @@ -448,19 +453,18 @@ void LevelGen::MoveIfPossible(Character* character, float xVel, float yVel, bool character->SetXY(targetX, targetY); } -bool LevelGen::CanMoveToPoint(int xArg, int yArg) { +bool LevelGen::MetaTilePassable(int xArg, int yArg) { if(xArg < 0 || xArg > SCREEN_WIDTH || yArg < 0 || yArg > SCREEN_HEIGHT) { return false; } - - int tileX = xArg / TILE_WIDTH; - int tileY = yArg / TILE_HEIGHT; +/* + SDL_Rect moverRect; + moverRect.x = xArg; + moverRect.y = yArg; + moverRect.w = 40; + moverRect.h = 45; - if(_tile[tileX][tileY].GetTileSolidity()) { - return false; - } - for(int i = 0; i < BOUNDARIES_X; i++) { for(int j = 0; j < BOUNDARIES_Y; j++) { if(!_tile[i][j].GetEntitySolitity()) { @@ -473,16 +477,34 @@ bool LevelGen::CanMoveToPoint(int xArg, int yArg) { entityRect.w = _tile[i][j].GetEntityWidth(); entityRect.h = _tile[i][j].GetEntityHeight(); - if(xArg >= entityRect.x && xArg <= (entityRect.x + entityRect.w) && - yArg >= entityRect.y && yArg <= (entityRect.y + entityRect.h)) { + if(CheckCollisionRect(moverRect, entityRect)) { return false; } } } + SDL_Rect playerRect; + playerRect.x = _player->GetX(); + playerRect.y = _player->GetY(); + playerRect.w = _player->GetWidth(); + playerRect.h = _player->GetHeight(); + + if(CheckCollisionRect(moverRect, playerRect)) { + return false; + } +*/ return true; } +void LevelGen::UpdateAStarTiles(void) { + for(int x = 0; x < ASTAR_ARRAY_SIZE; x++) { + for(int y = 0; y < ASTAR_ARRAY_SIZE; y++) { + bool passable = MetaTilePassable(x * AStarTile::FAKE_SIZE, y * AStarTile::FAKE_SIZE); + _astarTile[x][y] = AStarTile(this, x, y, passable); + } + } +} + string LevelGen::GetCurrentMap(void) { return _currentMap; } @@ -527,6 +549,6 @@ int LevelGen::GetTileZLevel(int xArg, int yArg) { return _tile[xArg + 1][yArg + 1].GetZLevel(); } -MapTile& LevelGen::GetTile(int xArg, int yArg) { - return _tile[xArg][yArg]; +AStarTile& LevelGen::GetAStarTile(int xArg, int yArg) { + return _astarTile[xArg][yArg]; } diff --git a/src/libUnuk/LevelGen/LevelGen.h b/src/libUnuk/LevelGen/LevelGen.h index a3b1bb0..e64746e 100644 --- a/src/libUnuk/LevelGen/LevelGen.h +++ b/src/libUnuk/LevelGen/LevelGen.h @@ -12,6 +12,7 @@ #include "../Sprite/ImageLoader.h" #include "../Sprite/ApplySurface.h" #include "../LevelGen/MapTile.h" +#include "../LevelGen/AStarTile.h" #include "../System/Debug.h" #include "../Engine/WorldManager.h" using namespace std; @@ -32,7 +33,7 @@ public: void FindSpawnPoint(int& xArg, int& yArg, int objWidth, int objHeight); void MoveIfPossible(Character* character, float xVel, float yVel, bool isPlayer = false); - bool CanMoveToPoint(int xArg, int yArg); + bool MetaTilePassable(int xArg, int yArg); bool GetTileSolidity(int xArg, int yArg); int GetTileX(int xArg, int yArg); @@ -46,7 +47,7 @@ public: int GetTileZLevel(int xArg, int yArg); - MapTile& GetTile(int xArg, int yArg); + AStarTile& GetAStarTile(int xArg, int yArg); string GetCurrentMap(void); @@ -60,13 +61,17 @@ private: void GenerateEntities(const std::string& name, int frequency); void MakeWalkingPaths(void); void GenerateEnemies(void); + void UpdateAStarTiles(void); string _currentMap; int x; int y; - static const int TILE_ARRAY_SIZE = 150; + static const int TILE_ARRAY_SIZE = 15; MapTile _tile[TILE_ARRAY_SIZE][TILE_ARRAY_SIZE]; + + static const int ASTAR_ARRAY_SIZE = TILE_ARRAY_SIZE * (TILE_WIDTH / AStarTile::FAKE_SIZE); + AStarTile _astarTile[ASTAR_ARRAY_SIZE][ASTAR_ARRAY_SIZE]; static const int BOUNDARIES_X = (SCREEN_WIDTH / TILE_WIDTH); static const int BOUNDARIES_Y = (SCREEN_HEIGHT / TILE_HEIGHT); diff --git a/src/libUnuk/LevelGen/MapTile.cpp b/src/libUnuk/LevelGen/MapTile.cpp deleted file mode 100644 index e861204..0000000 --- a/src/libUnuk/LevelGen/MapTile.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "MapTile.h" -#include "LevelGen.h" - -MapTile::MapTile(const MapTile& source) { - _level = source._level; - _tile = source._tile; - _entity = source._entity; - _zLevel = source._zLevel; -} - -bool MapTile::IsSameState(MapTile& tile) { - return (tile.GetTileX() == _tile.GetX()) && (tile.GetTileY() == _tile.GetY()); -} - -bool MapTile::IsGoal(MapTile& tile) { - return IsSameState(tile); -} - -float MapTile::GoalDistanceEstimate(MapTile& goal) { - Vec2 thisPos(_tile.GetX(), _tile.GetY()); - Vec2 goalPos(goal.GetTileX(), goal.GetTileY()); - return abs(Vec2::DistanceSquared(thisPos, goalPos)); -} - -float MapTile::GetCost(MapTile& goal) { - return 64.0f*64.0f; -} - -bool MapTile::GetSuccessors(AStarSearch* search, MapTile* parent) { - int tileX = _tile.GetX() / TILE_WIDTH; - int tileY = _tile.GetY() / TILE_HEIGHT; - - // Add tile to the left if possible. - if(tileX > 0) { - MapTile& successor = _level->GetTile(tileX - 1, tileY); - if(successor.GetTileSolidity() || successor.GetEntitySolitity()) { - search->AddSuccessor(successor); - } - } - - // Add tile to the right if possible - // TODO: replace 64 with map width - if(tileX < 64) { - MapTile& successor = _level->GetTile(tileX + 1, tileY); - if(successor.GetTileSolidity() || successor.GetEntitySolitity()) { - search->AddSuccessor(successor); - } - } - - // Add tile to the bottom if possible - if(tileY > 0) { - MapTile& successor = _level->GetTile(tileX, tileY - 1); - if(successor.GetTileSolidity() || successor.GetEntitySolitity()) { - search->AddSuccessor(successor); - } - } - - // Add tile to the top if possible - // TODO: replace 64 with map height - if(tileY < 64) { - MapTile& successor = _level->GetTile(tileX, tileY + 1); - if(successor.GetTileSolidity() || successor.GetEntitySolitity()) { - search->AddSuccessor(successor); - } - } - - return true; -} diff --git a/src/libUnuk/LevelGen/MapTile.h b/src/libUnuk/LevelGen/MapTile.h index 01f506c..10682e4 100644 --- a/src/libUnuk/LevelGen/MapTile.h +++ b/src/libUnuk/LevelGen/MapTile.h @@ -7,15 +7,11 @@ #include "../Sprite/ApplySurface.h" #include "../LevelGen/MapElement.h" #include "../LevelGen/MapEntities.h" -#include "../Engine/Pathfinding.h" -#include "../System/Vec2.h" using namespace std; -class LevelGen; - class MapTile { public: - MapTile(LevelGen* level = NULL) : _level(level) { } + MapTile(void) { } ~MapTile(void) { } void Render(void) { _tile.Render(), _entity.Render(); } @@ -32,7 +28,6 @@ public: void SetTileXY(int xArg, int yArg) { _tile.SetXY(xArg, yArg); } int GetTileX(void) { return _tile.GetX(); } int GetTileY(void) { return _tile.GetY(); } - // Entity Mutators. void SetEntityTexture(SDL_Surface* arg) { _entity.SetTexture(arg); } @@ -52,16 +47,7 @@ public: void SetZLevel(int arg) { _zLevel = arg; } int GetZLevel(void) { return _zLevel; } - // Pathfinding stuff. - MapTile(const MapTile& source); - bool IsSameState(MapTile& tile); - bool IsGoal(MapTile& tile); - float GoalDistanceEstimate(MapTile& goal); - float GetCost(MapTile& goal); - bool GetSuccessors(AStarSearch* search, MapTile* parent); - private: - LevelGen* _level; MapElement _tile; MapEntityGeneric _entity;