diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2021-02-27 17:40:26 -0500 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2021-02-27 17:40:26 -0500 |
commit | e03683852cac9b31ca846fcf13ff53abf99232c7 (patch) | |
tree | 4f18e4f6d033547f8bf9210ff8466406ed6dbd49 /src/behaviour_system.cpp | |
parent | 4be70b7d55493cdc2d5e909d5101e70a16bee6f1 (diff) | |
download | tanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.tar.gz tanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.tar.bz2 tanetane-e03683852cac9b31ca846fcf13ff53abf99232c7.zip |
Added A* pathfinding
Diffstat (limited to 'src/behaviour_system.cpp')
-rw-r--r-- | src/behaviour_system.cpp | 171 |
1 files changed, 170 insertions, 1 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 | |||
11 | bool pathfindingOptionsContains(PathfindingOptions options, PathfindingOptions value) { | ||
12 | return (static_cast<int>(options) & static_cast<int>(value)) != 0; | ||
13 | } | ||
6 | 14 | ||
7 | void BehaviourSystem::tick(double dt) { | 15 | void 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 | |||
60 | void 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 | |||
69 | bool BehaviourSystem::isFollowingPath(int spriteId) { | ||
70 | Sprite& sprite = game_.getSprite(spriteId); | ||
71 | return sprite.behaviourType == BehaviourType::Path && !sprite.path.empty(); | ||
72 | } | ||
73 | |||
74 | struct PathNodeInfo { | ||
75 | int cheapestPathCost = INT_MAX; | ||
76 | int estimatedRemainingCost = INT_MAX; | ||
77 | vec2i previousPoint; | ||
78 | Direction dirFromPreviousPoint; | ||
79 | }; | ||
80 | |||
81 | struct 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 | |||
96 | int 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 | |||
105 | void 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 | } |