ImagineEngine
ImagineEngine copied to clipboard
Add pathfinding ability
Hey there,
i noticed an obstacle-texture in the second tutorial. I suppose you allready had the idea of implementing pathfinding in the tutorial. I tried to implement it myself with help from a guide from raywenderlich.com, but it's a bit more complex than i thought. I think pathfinding would give the engine a whole lot more capability and usefullness.
Hi @AnotherCoolDude!
Path finding would be nice but have you tried using GameplayKit?
You can create a GKGraph
and use the findPath
method to get an array of GKGraphNode
s. Then create a GKPath
and use it to create a GKGoal
to follow the path. Finally create a GKBehavior
with the path following goal.
I have created a plugin that can be used to connect a GKBehavior
ta an Actor
:
import ImagineEngine
import GameplayKit
class BehaviorPlugin: NSObject, Plugin {
private let behavior: GKBehavior
private var agent: GKAgent2D?
private var actor: Actor?
private var actionToken: ActionToken?
init(behavior: GKBehavior) {
self.behavior = behavior
super.init()
}
func activate(for object: Actor, in game: Game) {
actor = object
let agent = GKAgent2D()
agent.delegate = self
self.agent = agent
// Update the values below to change how the actor moves
agent.mass = 10
agent.maxSpeed = 50
agent.maxAcceleration = 60
agent.radius = 20
agent.behavior = behavior
let updateBehaviorAction = ClosureAction<Actor>(duration: .infinity) { context in
agent.update(deltaTime: context.timeSinceLastUpdate)
}
actionToken = object.perform(updateBehaviorAction)
}
func deactivate() {
actionToken?.cancel()
actionToken = nil
actor = nil
agent = nil
}
}
extension BehaviorPlugin: GKAgentDelegate {
func agentWillUpdate(_ agent: GKAgent) {
guard let agent2d = agent as? GKAgent2D else { return }
guard let actor = actor else { return }
agent2d.rotation = Float(actor.rotation)
agent2d.position.x = Float(actor.position.x)
agent2d.position.y = Float(actor.position.y)
}
func agentDidUpdate(_ agent: GKAgent) {
guard let agent2d = agent as? GKAgent2D else { return }
guard let actor = actor else { return }
actor.rotation = Metric(agent2d.rotation)
actor.position = Point(x: Metric(agent2d.position.x), y: Metric(agent2d.position.y))
}
}
Hope that helps!
That's an awesome idea @mattiashagstrand 👍 This is exactly what the plugin system is for, so that the engine doesn't have to contain functionality like this itself, but rather things can be mixed and matched as you need them. I'd like to keep the core of the engine as thin as possible and then keep expanding the plugin API as needed to support these type of use cases. @AnotherCoolDude how does that sound? 🙂
I agree with you @JohnSundell, one of the big advantages of ImagineEngine is that is so easy to get started. It would be nice though to create a catalog of plugins for common things that most games need. Have you thought about adding that or maybe to create a separate plugins repository?
That's awesome. It really shows the potential of Plugins. I agree, we should create a catalog of examples. It will boost motivation for people to get started with ImagineEngine. @mattiashagstrand thanks for the snippet, ill give it a try
Hey guys,
A question about implementing this, I tried following the comment about using gameplay kit, but I can't get it to work.. the path returns zero and then it crashes. Am I missing a step here?
events.clicked.observe { scene, point in
let playerPosVector = vector_float2(x: Float(player.position.x), y: Float(player.position.y))
let posVector = vector_float2(x: Float(point.x), y: Float(point.y))
let origin = GKGraphNode2D(point: playerPosVector)
let destination = GKGraphNode2D(point: posVector)
let nodesPath = origin.findPath(to: destination)
let path = GKPath(graphNodes: nodesPath, radius: 1)
let goal = GKGoal(toFollow: path, maxPredictionTime: 100, forward: true)
let followPath = GKBehavior(goal: goal, weight: 1)
let behaviorPlugin = BehaviorPlugin(behavior: followPath)
player.add(behaviorPlugin)
}
Also, would this work with obstacles? I guess that also needs to be added to GameplayKit in order to calculate them. I added them manually to the project like so
let obstacules = Group.name("Obstacules")
for i in stride(from: 0, to: 10, by: 1) {
let obstacule = Block(size: Size(width: 50, height: 50), spriteSheetName: "Obstacle")
let randomPos = Metric(arc4random() % UInt32(size.width))
obstacule.position = Point(x: CGFloat(randomPos), y: CGFloat(randomPos))
obstacule.group = obstacules
add(obstacule)
}
player.constraints = [.scene, .neverOverlapBlockInGroup(obstacules)]
Which works, but I guess it won't prevent the path finding from using those locations
@raulriera What kind of crash are you getting? Yeah you need to tell GamePlayKit about your obstacles as well, since otherwise its path finding algorithm isn't aware of what zones to avoid. You should be able to create obstacles using the rect
of your blocks. @mattiashagstrand any other input here? Maybe @duemunk has some input here as I know he has used GamePlayKit's path finding abilities before? 🙂
Hi @raulriera
I think the reason the path returns zero is that you use the findPath
method without adding the nodes to a GKGraph
. Try something like this:
let graph = GKGraph([origin, destination])
let nodesPath = graph.findPath(from: origin, to: destination)
And as @JohnSundell pointed out, you need to add the obstacles to the graph before calling findPath
if you want the actor to move around them.
I also noticed that you use player.add(behaviorPlugin)
to add the plugin each time you get a click event.
You probably want to use player.add(behaviorPlugin, reuseExistingOfSameType: false)
otherwise the existing BehaviorPlugin
will be reused and nothing will happen after the first click.
Thanks for the help guys! I'll try it out! ❤️