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 | } |
