summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2021-02-27 17:40:26 -0500
committerKelly Rauchenberger <fefferburbia@gmail.com>2021-02-27 17:40:26 -0500
commite03683852cac9b31ca846fcf13ff53abf99232c7 (patch)
tree4f18e4f6d033547f8bf9210ff8466406ed6dbd49 /src
parent4be70b7d55493cdc2d5e909d5101e70a16bee6f1 (diff)
downloadtanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.tar.gz
tanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.tar.bz2
tanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.zip
Added A* pathfinding
Diffstat (limited to 'src')
-rw-r--r--src/behaviour_system.cpp171
-rw-r--r--src/behaviour_system.h12
-rw-r--r--src/character_system.cpp2
-rw-r--r--src/game.cpp2
-rw-r--r--src/input_system.cpp2
-rw-r--r--src/script_system.cpp15
-rw-r--r--src/sprite.h16
-rw-r--r--src/transform_system.cpp4
-rw-r--r--src/transform_system.h2
-rw-r--r--src/vector.h4
10 files changed, 221 insertions, 9 deletions
diff --git a/src/behaviour_system.cpp b/src/behaviour_system.cpp index 2d46205..3637b3e 100644 --- a/src/behaviour_system.cpp +++ b/src/behaviour_system.cpp
@@ -1,8 +1,16 @@
1#include "behaviour_system.h" 1#include "behaviour_system.h"
2#include <queue>
3#include <vector>
4#include <utility>
2#include <random> 5#include <random>
3#include "game.h" 6#include "game.h"
4#include "character_system.h" 7#include "character_system.h"
5#include "direction.h" 8#include "direction.h"
9#include "transform_system.h"
10
11bool pathfindingOptionsContains(PathfindingOptions options, PathfindingOptions value) {
12 return (static_cast<int>(options) & static_cast<int>(value)) != 0;
13}
6 14
7void BehaviourSystem::tick(double dt) { 15void BehaviourSystem::tick(double dt) {
8 if (game_.isGameplayPaused()) return; 16 if (game_.isGameplayPaused()) return;
@@ -11,7 +19,7 @@ void BehaviourSystem::tick(double dt) {
11 while (timer_.step()) { 19 while (timer_.step()) {
12 for (int spriteId : game_.getSprites()) { 20 for (int spriteId : game_.getSprites()) {
13 Sprite& sprite = game_.getSprite(spriteId); 21 Sprite& sprite = game_.getSprite(spriteId);
14 if (sprite.wander && !sprite.paused) { 22 if (!sprite.paused && sprite.behaviourType == BehaviourType::Wander) {
15 // 75% chance of changing what's happening 23 // 75% chance of changing what's happening
16 if (std::bernoulli_distribution(0.75)(game_.getRng())) { 24 if (std::bernoulli_distribution(0.75)(game_.getRng())) {
17 // 50% chance of choosing a direction or stopping 25 // 50% chance of choosing a direction or stopping
@@ -31,4 +39,165 @@ void BehaviourSystem::tick(double dt) {
31 } 39 }
32 } 40 }
33 } 41 }
42
43 for (int spriteId : game_.getSprites()) {
44 Sprite& sprite = game_.getSprite(spriteId);
45 if (!sprite.paused && sprite.behaviourType == BehaviourType::Path) {
46 while (!sprite.path.empty() && sprite.path.front().endpoint == sprite.loc) {
47 sprite.path.pop_front();
48 }
49 if (sprite.path.empty()) {
50 game_.getSystem<CharacterSystem>().stopDirecting(spriteId);
51 } else {
52 if (sprite.characterState == CharacterState::Still || sprite.movementDir != sprite.path.front().dir) {
53 game_.getSystem<CharacterSystem>().moveInDirection(spriteId, sprite.path.front().dir);
54 }
55 }
56 }
57 }
58}
59
60void BehaviourSystem::directSpriteToLocation(int spriteId, vec2i pos, PathfindingOptions options) {
61 Sprite& sprite = game_.getSprite(spriteId);
62 sprite.behaviourType = BehaviourType::Path;
63 sprite.pathfindingDestination = pos;
64 sprite.cardinalDirectionsOnly = pathfindingOptionsContains(options, PathfindingOptions::CardinalDirectionsOnly);
65
66 createPath(spriteId);
67}
68
69bool BehaviourSystem::isFollowingPath(int spriteId) {
70 Sprite& sprite = game_.getSprite(spriteId);
71 return sprite.behaviourType == BehaviourType::Path && !sprite.path.empty();
72}
73
74struct PathNodeInfo {
75 int cheapestPathCost = INT_MAX;
76 int estimatedRemainingCost = INT_MAX;
77 vec2i previousPoint;
78 Direction dirFromPreviousPoint;
79};
80
81struct SearchNode {
82 vec2i point;
83 int estimatedCost = INT_MAX;
84 int tiebreaker; // this actually counts downward. wow
85
86 SearchNode(vec2i point, int estimatedCost) : point(point), estimatedCost(estimatedCost) {
87 static int tiebreakerCounter = 0;
88 tiebreaker = tiebreakerCounter--;
89 }
90
91 bool operator>(const SearchNode& rhs) const {
92 return std::tie(estimatedCost, tiebreaker) > std::tie(rhs.estimatedCost, rhs.tiebreaker);
93 }
94};
95
96int estimateRemainingCost(vec2i current, vec2i dest, int movementSpeed, bool cardinalDirectionsOnly) {
97 vec2i difference = dest - current;
98 if (cardinalDirectionsOnly) {
99 return (std::abs(difference.x()) + std::abs(difference.y())) / movementSpeed;
100 } else {
101 return std::max(std::abs(difference.x()), std::abs(difference.y())) / movementSpeed;
102 }
103}
104
105void BehaviourSystem::createPath(int spriteId) {
106 Sprite& sprite = game_.getSprite(spriteId);
107 sprite.path.clear();
108
109 const Map& map = game_.getMap();
110 vec2i mapBounds = map.getMapSize() * map.getTileSize();
111
112 // If it is not possible to reach the destination because of the parity (if
113 // the movement speed is above 1), then adjust the destination.
114 if (sprite.movementSpeed > 1) {
115 if ((sprite.loc.x() % sprite.movementSpeed) != (sprite.pathfindingDestination.x() % sprite.movementSpeed)) {
116 sprite.loc.x() = (sprite.loc.x() / sprite.movementSpeed) * sprite.movementSpeed + (sprite.pathfindingDestination.x() % sprite.movementSpeed);
117 }
118 if ((sprite.loc.y() % sprite.movementSpeed) != (sprite.pathfindingDestination.y() % sprite.movementSpeed)) {
119 sprite.loc.y() = (sprite.loc.y() / sprite.movementSpeed) * sprite.movementSpeed + (sprite.pathfindingDestination.y() % sprite.movementSpeed);
120 }
121 }
122
123 int initialCostGuess = estimateRemainingCost(sprite.loc, sprite.pathfindingDestination, sprite.movementSpeed, sprite.cardinalDirectionsOnly);
124
125 std::map<vec2i, PathNodeInfo> pathNodes;
126 pathNodes[sprite.loc] = PathNodeInfo{.cheapestPathCost = 0, .estimatedRemainingCost = initialCostGuess};
127
128 std::priority_queue<SearchNode, std::vector<SearchNode>, std::greater<SearchNode>> openSet;
129 openSet.emplace(sprite.loc, initialCostGuess);
130
131 while (!openSet.empty()) {
132 SearchNode searchNode = openSet.top();
133 openSet.pop();
134
135 if (searchNode.point == sprite.pathfindingDestination) {
136 // We're there!
137 break;
138 }
139
140 PathNodeInfo& nodeInfo = pathNodes[searchNode.point];
141 int newCost = nodeInfo.cheapestPathCost + 1;
142
143 static const std::vector<Direction> allDirections = {
144 Direction::down,
145 Direction::down_left,
146 Direction::left,
147 Direction::up_left,
148 Direction::up,
149 Direction::up_right,
150 Direction::right,
151 Direction::down_right };
152
153 static const std::vector<Direction> cardinalDirections = {
154 Direction::down,
155 Direction::left,
156 Direction::up,
157 Direction::right };
158
159 const std::vector<Direction>* directionList = sprite.cardinalDirectionsOnly ? &cardinalDirections : &allDirections;
160 for (Direction dir : *directionList) {
161 vec2i newPos = searchNode.point + unitVecInDirection(dir) * sprite.movementSpeed;
162
163 if (newPos.x() < 0 || newPos.y() < 0 || newPos.x() >= mapBounds.w() || newPos.y() >= mapBounds.h()) {
164 // The path can't go outside the map.
165 continue;
166 }
167
168 PathNodeInfo& neighborInfo = pathNodes[newPos];
169
170 if (neighborInfo.cheapestPathCost <= newCost) {
171 // There is already a faster path through this neighbor.
172 continue;
173 }
174
175 CollisionResult collision = game_.getSystem<TransformSystem>().checkCollision(spriteId, searchNode.point, newPos, dir);
176 if (collision.horiz.blocked || collision.vert.blocked) {
177 // There isn't actually an edge to this neighbor.
178 continue;
179 }
180
181 neighborInfo.cheapestPathCost = newCost;
182 neighborInfo.estimatedRemainingCost = estimateRemainingCost(newPos, sprite.pathfindingDestination, sprite.movementSpeed, sprite.cardinalDirectionsOnly);
183 neighborInfo.previousPoint = searchNode.point;
184 neighborInfo.dirFromPreviousPoint = dir;
185
186 openSet.emplace(newPos, neighborInfo.cheapestPathCost + neighborInfo.estimatedRemainingCost);
187 }
188 }
189
190 if (!pathNodes.count(sprite.pathfindingDestination)) {
191 // There was no path to the destination.
192 return;
193 }
194
195 vec2i curPos = sprite.pathfindingDestination;
196 while (curPos != sprite.loc) {
197 PathNodeInfo& nodeInfo = pathNodes[curPos];
198 if (sprite.path.empty() || sprite.path.front().dir != nodeInfo.dirFromPreviousPoint) {
199 sprite.path.push_front(PathfindingInstruction{.dir = nodeInfo.dirFromPreviousPoint, .endpoint = curPos});
200 }
201 curPos = nodeInfo.previousPoint;
202 }
34} 203}
diff --git a/src/behaviour_system.h b/src/behaviour_system.h index 0eb4772..526a09b 100644 --- a/src/behaviour_system.h +++ b/src/behaviour_system.h
@@ -3,6 +3,12 @@
3 3
4#include "system.h" 4#include "system.h"
5#include "timer.h" 5#include "timer.h"
6#include "vector.h"
7
8enum class PathfindingOptions {
9 None = 0,
10 CardinalDirectionsOnly = 1 << 0
11};
6 12
7class Game; 13class Game;
8 14
@@ -15,8 +21,14 @@ public:
15 21
16 void tick(double dt) override; 22 void tick(double dt) override;
17 23
24 void directSpriteToLocation(int spriteId, vec2i pos, PathfindingOptions options = PathfindingOptions::None);
25
26 bool isFollowingPath(int spriteId);
27
18private: 28private:
19 29
30 void createPath(int spriteId);
31
20 Game& game_; 32 Game& game_;
21 Timer timer_ { 500 }; 33 Timer timer_ { 500 };
22}; 34};
diff --git a/src/character_system.cpp b/src/character_system.cpp index 9b91716..53debb2 100644 --- a/src/character_system.cpp +++ b/src/character_system.cpp
@@ -150,7 +150,7 @@ void CharacterSystem::tick(double dt) {
150 pLoc += (unitVecInDirection(sprite.movementDir) * speed); 150 pLoc += (unitVecInDirection(sprite.movementDir) * speed);
151 151
152 // Check collision. 152 // Check collision.
153 CollisionResult collision = game_.getSystem<TransformSystem>().checkCollision(spriteId, pLoc, sprite.movementDir); 153 CollisionResult collision = game_.getSystem<TransformSystem>().checkCollision(spriteId, sprite.loc, pLoc, sprite.movementDir);
154 bool blocked = collision.horiz.blocked || collision.vert.blocked; 154 bool blocked = collision.horiz.blocked || collision.vert.blocked;
155 155
156 if (collision.horiz.blocked && !sprite.clipping) { 156 if (collision.horiz.blocked && !sprite.clipping) {
diff --git a/src/game.cpp b/src/game.cpp index 6564da6..8ba4c85 100644 --- a/src/game.cpp +++ b/src/game.cpp
@@ -69,7 +69,7 @@ void Game::loadMap(std::string filename) {
69 if (p.movementSpeed != -1) { 69 if (p.movementSpeed != -1) {
70 getSystem<CharacterSystem>().initSprite(spriteId, p.movementSpeed); 70 getSystem<CharacterSystem>().initSprite(spriteId, p.movementSpeed);
71 if (p.wander) { 71 if (p.wander) {
72 getSprite(spriteId).wander = true; 72 getSprite(spriteId).behaviourType = BehaviourType::Wander;
73 } 73 }
74 } 74 }
75 if (!p.enclosureZone.empty()) { 75 if (!p.enclosureZone.empty()) {
diff --git a/src/input_system.cpp b/src/input_system.cpp index 49b27ec..38423a2 100644 --- a/src/input_system.cpp +++ b/src/input_system.cpp
@@ -85,7 +85,7 @@ void InputSystem::tick(double dt) {
85 if (sprite.controllable) { 85 if (sprite.controllable) {
86 // Interacting with objects always uses Lucas's movement speed. 86 // Interacting with objects always uses Lucas's movement speed.
87 vec2i checkLoc = sprite.loc + (unitVecInDirection(sprite.dir) * LUCAS_MOVEMENT_SPEED); 87 vec2i checkLoc = sprite.loc + (unitVecInDirection(sprite.dir) * LUCAS_MOVEMENT_SPEED);
88 CollisionResult collision = game_.getSystem<TransformSystem>().checkCollision(spriteId, checkLoc, sprite.dir); 88 CollisionResult collision = game_.getSystem<TransformSystem>().checkCollision(spriteId, sprite.loc, checkLoc, sprite.dir);
89 89
90 if (collision.horiz.blocked || collision.vert.blocked) { 90 if (collision.horiz.blocked || collision.vert.blocked) {
91 inFrontOfSomething = true; 91 inFrontOfSomething = true;
diff --git a/src/script_system.cpp b/src/script_system.cpp index 7f9f908..931759d 100644 --- a/src/script_system.cpp +++ b/src/script_system.cpp
@@ -7,6 +7,7 @@
7#include "transform_system.h" 7#include "transform_system.h"
8#include "effect_system.h" 8#include "effect_system.h"
9#include "camera_system.h" 9#include "camera_system.h"
10#include "behaviour_system.h"
10#include "vector.h" 11#include "vector.h"
11 12
12ScriptSystem::ScriptSystem(Game& game) : game_(game) { 13ScriptSystem::ScriptSystem(Game& game) : game_(game) {
@@ -42,7 +43,8 @@ ScriptSystem::ScriptSystem(Game& game) : game_(game) {
42 "clipping", &Sprite::clipping, 43 "clipping", &Sprite::clipping,
43 "cantCrouch", &Sprite::cantCrouch, 44 "cantCrouch", &Sprite::cantCrouch,
44 "bobsWhenNormal", &Sprite::bobsWhenNormal, 45 "bobsWhenNormal", &Sprite::bobsWhenNormal,
45 "animSlowdown", &Sprite::animSlowdown); 46 "animSlowdown", &Sprite::animSlowdown,
47 "enclosureZone", &Sprite::enclosureZone);
46 48
47 engine_.new_usertype<MessageSystem>( 49 engine_.new_usertype<MessageSystem>(
48 "message", 50 "message",
@@ -93,6 +95,11 @@ ScriptSystem::ScriptSystem(Game& game) : game_(game) {
93 "unlockCamera", &CameraSystem::unlockCamera, 95 "unlockCamera", &CameraSystem::unlockCamera,
94 "setFollowingSprite", &CameraSystem::setFollowingSprite); 96 "setFollowingSprite", &CameraSystem::setFollowingSprite);
95 97
98 engine_.new_usertype<BehaviourSystem>(
99 "behaviour",
100 "directSpriteToLocation", &BehaviourSystem::directSpriteToLocation,
101 "isFollowingPath", &BehaviourSystem::isFollowingPath);
102
96 engine_.new_usertype<Mixer>( 103 engine_.new_usertype<Mixer>(
97 "mixer", 104 "mixer",
98 "playSound", &Mixer::playSound, 105 "playSound", &Mixer::playSound,
@@ -146,6 +153,12 @@ ScriptSystem::ScriptSystem(Game& game) : game_(game) {
146 }); 153 });
147 154
148 engine_.set_function( 155 engine_.set_function(
156 "behaviour",
157 [&] () -> BehaviourSystem& {
158 return game_.getSystem<BehaviourSystem>();
159 });
160
161 engine_.set_function(
149 "mixer", 162 "mixer",
150 [&] () -> Mixer& { 163 [&] () -> Mixer& {
151 return game_.getMixer(); 164 return game_.getMixer();
diff --git a/src/sprite.h b/src/sprite.h index 733c792..13d0383 100644 --- a/src/sprite.h +++ b/src/sprite.h
@@ -40,12 +40,23 @@ enum class CharacterMedium {
40 Water 40 Water
41}; 41};
42 42
43enum class BehaviourType {
44 None,
45 Wander,
46 Path
47};
48
43struct Movement { 49struct Movement {
44 vec2i pos; 50 vec2i pos;
45 Direction dir; 51 Direction dir;
46 CharacterMedium medium; 52 CharacterMedium medium;
47}; 53};
48 54
55struct PathfindingInstruction {
56 Direction dir;
57 vec2i endpoint;
58};
59
49class Sprite { 60class Sprite {
50public: 61public:
51 62
@@ -106,7 +117,10 @@ public:
106 bool player = false; 117 bool player = false;
107 118
108 // Behaviour 119 // Behaviour
109 bool wander = false; 120 BehaviourType behaviourType = BehaviourType::None;
121 vec2i pathfindingDestination;
122 bool cardinalDirectionsOnly = false;
123 std::deque<PathfindingInstruction> path;
110}; 124};
111 125
112#endif /* end of include guard: SPRITE_H_70503825 */ 126#endif /* end of include guard: SPRITE_H_70503825 */
diff --git a/src/transform_system.cpp b/src/transform_system.cpp index 4056f46..71b3a4f 100644 --- a/src/transform_system.cpp +++ b/src/transform_system.cpp
@@ -47,7 +47,7 @@ void TransformSystem::moveSprite(int spriteId, vec2i newLoc) {
47 } 47 }
48} 48}
49 49
50CollisionResult TransformSystem::checkCollision(int spriteId, vec2i newLoc, Direction dir) { 50CollisionResult TransformSystem::checkCollision(int spriteId, vec2i curLoc, vec2i newLoc, Direction dir) {
51 CollisionResult result; 51 CollisionResult result;
52 52
53 Sprite& sprite = game_.getSprite(spriteId); 53 Sprite& sprite = game_.getSprite(spriteId);
@@ -57,7 +57,7 @@ CollisionResult TransformSystem::checkCollision(int spriteId, vec2i newLoc, Dire
57 57
58 vec2i mapBounds = map.getMapSize() * map.getTileSize(); 58 vec2i mapBounds = map.getMapSize() * map.getTileSize();
59 59
60 vec2i oldColUL = sprite.loc + sprite.collisionOffset; 60 vec2i oldColUL = curLoc + sprite.collisionOffset;
61 vec2i oldColDR = oldColUL + sprite.collisionSize; 61 vec2i oldColDR = oldColUL + sprite.collisionSize;
62 vec2i newColUL = newLoc + sprite.collisionOffset; 62 vec2i newColUL = newLoc + sprite.collisionOffset;
63 vec2i newColDR = newColUL + sprite.collisionSize; 63 vec2i newColDR = newColUL + sprite.collisionSize;
diff --git a/src/transform_system.h b/src/transform_system.h index a07447c..6707347 100644 --- a/src/transform_system.h +++ b/src/transform_system.h
@@ -45,7 +45,7 @@ public:
45 }); 45 });
46 } 46 }
47 47
48 CollisionResult checkCollision(int spriteId, vec2i newLoc, Direction dir); 48 CollisionResult checkCollision(int spriteId, vec2i curLoc, vec2i newLoc, Direction dir);
49 49
50 CharacterMedium getMediumAtPosition(int spriteId, vec2i newLoc); 50 CharacterMedium getMediumAtPosition(int spriteId, vec2i newLoc);
51 51
diff --git a/src/vector.h b/src/vector.h index 9f6d54f..c7b58dc 100644 --- a/src/vector.h +++ b/src/vector.h
@@ -109,6 +109,10 @@ public:
109 return (x() != other.x()) || (y() != other.y()); 109 return (x() != other.x()) || (y() != other.y());
110 } 110 }
111 111
112 bool operator<(const vec2& other) const {
113 return std::tie(x(), y()) < std::tie(other.x(), other.y());
114 }
115
112}; 116};
113 117
114using vec2i = vec2<int>; 118using vec2i = vec2<int>;