ImagineEngine icon indicating copy to clipboard operation
ImagineEngine copied to clipboard

Add pathfinding ability

Open AnotherCoolDude opened this issue 6 years ago • 8 comments

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.

AnotherCoolDude avatar Dec 06 '17 12:12 AnotherCoolDude

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 GKGraphNodes. 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!

mattiashagstrand avatar Dec 09 '17 11:12 mattiashagstrand

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? 🙂

JohnSundell avatar Dec 09 '17 13:12 JohnSundell

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?

mattiashagstrand avatar Dec 09 '17 14:12 mattiashagstrand

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

AnotherCoolDude avatar Dec 11 '17 12:12 AnotherCoolDude

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 avatar Dec 27 '17 13:12 raulriera

@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? 🙂

JohnSundell avatar Jan 11 '18 20:01 JohnSundell

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.

mattiashagstrand avatar Jan 12 '18 14:01 mattiashagstrand

Thanks for the help guys! I'll try it out! ❤️

raulriera avatar Jan 12 '18 15:01 raulriera