Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cooperative Path Finding

Matt Comi
February 27, 2017

Cooperative Path Finding

Big Bucket's game Space Age can be described as a mashup of an Real-time strategy and an adventure game. Space Age has units and missions, but also inventory and dialogue. When a unit needs to navigate between map locations, it uses the A* pathfinding algorithm. As well as A*, Space Age employs some novel techniques to ensure that units don't bump each other while also ensuring that their navigation appear natural and ad-hoc, not coordinated or precognitive. I will be discussing some of these techniques.

Matt Comi

February 27, 2017
Tweet

Other Decks in Technology

Transcript

  1. Tiles that are on the fringe of the explored path.

    Open Set Closed Set G Score F Score
  2. Cost of getting from the origin to the destination, via

    this tile. (G Score + Heuristic) Open Set Closed Set G Score F Score
  3. struct PointI { var x: Int var y: Int //

    use your imagination for these static func ==(lhs: PointI, rhs: PointI) -> Bool { } var neighbors : [PointI] { get } } Tile Map PointI Node Set
  4. class Node { var position: PointI var fScore: Float var

    gScore: Float var parent: Node? = nil init(position: PointI, fScore: Float, gScore: Float, parent: Node?) { self.position = position self.fScore = fScore self.gScore = gScore self.parent = parent } } Tile Map PointI Node Set
  5. Tile Map PointI Node Set protocol Set { func insert(node:

    Node) func remove(node: Node) func find(position: PointI) -> Node? var nodeWithLowestFScore : Node? { get } var isEmpty: Bool { get } }
  6. protocol PathFinderDelegate { func isValidPosition(position: PointI) -> Bool func isDestination(position:

    PointI) -> Bool func movementCost(from: PointI, to: PointI) -> Float func heuristicCost(from: PointI) -> Float }
  7. class MoveToPathFinderDelegate : PathFinderDelegate { let map: Map let destination:

    PointI init(map: Map, destination: PointI) // within the bounds of the map and unobstructed. func isValidPosition(position: PointI) -> Bool // position == destination. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // manhattan distance between from and destination. func heuristicCost(from: PointI) -> Float }
  8. class MoveToPathFinderDelegate : PathFinderDelegate { let map: Map let destination:

    PointI init(map: Map, destination: PointI) // within the bounds of the map and unobstructed. func isValidPosition(position: PointI) -> Bool // position == destination. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // manhattan distance between from and destination. func heuristicCost(from: PointI) -> Float }
  9. class MoveToPathFinderDelegate : PathFinderDelegate { let map: Map let destination:

    PointI init(map: Map, destination: PointI) // within the bounds of the map and unobstructed. func isValidPosition(position: PointI) -> Bool // position == destination. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // manhattan distance between from and destination. func heuristicCost(from: PointI) -> Float }
  10. class MoveToPathFinderDelegate : PathFinderDelegate { let map: Map let destination:

    PointI init(map: Map, destination: PointI) // within the bounds of the map and unobstructed. func isValidPosition(position: PointI) -> Bool // position == destination. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // manhattan distance between from and destination. func heuristicCost(from: PointI) -> Float }
  11. class MoveToPathFinderDelegate : PathFinderDelegate { let map: Map let destination:

    PointI init(map: Map, destination: PointI) // within the bounds of the map and unobstructed. func isValidPosition(position: PointI) -> Bool // position == destination. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // manhattan distance between from and destination. func heuristicCost(from: PointI) -> Float }
  12. func findPath(origin: PointI) -> [PointI] { let open = SimpleSet()

    let closed = SimpleSet() open.insert(node: Node(position: origin, fScore: delegate.heuristicCost(from: origin), gScore: 0, parent: nil)) while open.count != 0 { guard let node = open.nodeWithLowestFScore else { return [] } if delegate.isDestination(position: node.position) { return makePath(node) } open.remove(node: node) closed.insert(node: node) for neighborPosition in node.position.neighbors { if !delegate.isValidPosition(position: neighborPosition) { continue } if closed.find(position: neighborPosition) != nil { continue } let g = node.gScore + delegate.movementCost(from: node.position, to: neighborPosition) if let existingNode = open.find(position: neighborPosition) { if g >= existingNode.gScore { continue } open.remove(node: existingNode) } let f = g + delegate.heuristicCost(from: neighborPosition) open.insert(node: Node(position: neighborPosition, fScore: f, gScore: g, parent: node)) } } return [] }
  13. // True if the tile is: // - Within the

    bounds of map // - Unobstructed // - Unoccupied or occupied by a unit that is *not* attacking. func isValidPosition(position: PointI) -> Bool
  14. // Normally 1 if horizontal or vertical, 1.5 if diagonal.

    // If the destination is occupied, add 6. func movementCost(from: PointI, to: PointI) -> Float
  15. enum UnitState { case Idle case Moving case Attacking }

    protocol Unit { var position: PointI { get } var path: [PointI] { get } var state: UnitState { get } }
  16. class MoveAwayPathFinderDelegate : PathFinderDelegate { let map: Map let otherUnits:

    [Unit] init(map: Map, otherUnits: [Unit]) // within the bounds of the map, unobstructed and not occupied by any // Unit in otherUnits. func isValidPosition(position: PointI) -> Bool // any position that isn’t along any of otherUnits’ paths. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // always 0. func heuristicCost(from: PointI) -> Float }
  17. class MoveAwayPathFinderDelegate : PathFinderDelegate { let map: Map let otherUnits:

    [Unit] init(map: Map, otherUnits: [Unit]) // within the bounds of the map, unobstructed and not occupied by any // Unit in otherUnits. func isValidPosition(position: PointI) -> Bool // any position that isn’t along any of otherUnits’ paths. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // always 0. func heuristicCost(from: PointI) -> Float }
  18. class MoveAwayPathFinderDelegate : PathFinderDelegate { let map: Map let otherUnits:

    [Unit] init(map: Map, otherUnits: [Unit]) // within the bounds of the map, unobstructed and not occupied by any // Unit in otherUnits. func isValidPosition(position: PointI) -> Bool // any position that isn’t along any of otherUnits’ paths. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // always 0. func heuristicCost(from: PointI) -> Float }
  19. class MoveAwayPathFinderDelegate : PathFinderDelegate { let map: Map let otherUnits:

    [Unit] init(map: Map, otherUnits: [Unit]) // within the bounds of the map, unobstructed and not occupied by any // Unit in otherUnits. func isValidPosition(position: PointI) -> Bool // any position that isn’t along any of otherUnits’ paths. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // always 0. func heuristicCost(from: PointI) -> Float }
  20. class MoveAwayPathFinderDelegate : PathFinderDelegate { let map: Map let otherUnits:

    [Unit] init(map: Map, otherUnits: [Unit]) // within the bounds of the map, unobstructed and not occupied by any // Unit in otherUnits. func isValidPosition(position: PointI) -> Bool // any position that isn’t along any of otherUnits’ paths. func isDestination(position: PointI) -> Bool // 1 if horizontal or vertical, 1.5 if diagonal. func movementCost(from: PointI, to: PointI) -> Float // always 0. func heuristicCost(from: PointI) -> Float }