Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pathfinding ability #119

Open
AnotherCoolDude opened this issue Dec 6, 2017 · 8 comments
Open

Add pathfinding ability #119

AnotherCoolDude opened this issue Dec 6, 2017 · 8 comments

Comments

@AnotherCoolDude
Copy link

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.

@mattiashagstrand
Copy link
Collaborator

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!

@JohnSundell
Copy link
Owner

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

@mattiashagstrand
Copy link
Collaborator

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?

@AnotherCoolDude
Copy link
Author

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

@raulriera
Copy link

raulriera commented Dec 27, 2017

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

@JohnSundell
Copy link
Owner

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

@mattiashagstrand
Copy link
Collaborator

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.

@raulriera
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants