Search code examples
iosgameplay-kit

GameplayKit > Make agent stop moving after goal to follow path


I am making an agent follow a GKPath by adding to its behavior the goal toFollow: GKPath. Everything works fine, however the agent continues moving in a 8-like shape when reaching the target point on the path. How do I prevent the agent to wander when reaching the target point on a GKPath?

A solution I have thought off would be to set another goal toReachTargetSpeed: 0.0 when close to approaching the target point. However, I am not sure what would be the best practice, since there is no built in way to see when the goal is "completed".

  1. Do I use an IF statement to check the distance to target at each update:? What about performance?
  2. Do I use some kind of a GKRule?

Your suggestions would be very much appreciated. Thanks.


Solution

  • I went with the first option, i.e. to track the distance to target in the movementComponent update function:

    override func update(deltaTime seconds: TimeInterval) {
        guard let entity = self.entity as? BaseEntity else { return }
        if entity.moving {
            let targetPoint = CGPoint(x: 100, y: 100)
            let distanceToTarget = distanceFrom(point: point)
            if distanceToTarget < GameConfiguration.Movement.targetProximity {
                stopMovement(afterTraversing: distanceToTarget)
            }
        }
    }
    
    func distanceFrom(point: CGPoint) -> Float {
        guard let entity = self.entity as? BaseEntity else { return CGFloat.greatestFiniteMagnitude }
        let dx = point.x - entity.visualComponent.node.position.x
        let dy = point.y - entity.visualComponent.node.position.y
        return hypotf(Float(dx), Float(dy))
    }
    

    When the agent is close enough to the target, run the stopMovement function. In turn, this will calculate the time needed to traverse the distance left to target (having a constant speed), and will put a timer as to when to force stop the agent:

    func stopMovement(afterTraversing distance: Float) {
        guard let entity = self.entity as? BaseEntity else { return }
        guard (entity.moving) else { return }
        guard let behavior = entity.agent.behavior as? MovingBehavior else { return }
        let timeToTarget = TimeInterval(distance / entity.agent.maxSpeed)
        Timer.scheduledTimer(withTimeInterval: timeToTarget, repeats: false, block: {_ in
            behavior.stopMoving()
            entity.agent.maxSpeed = 0.0
            entity.moving = false
        })
    }
    
    class MovingBehavior: GKBehavior {
    
        var reachMaxSpeed: GKGoal
        var followPathGoal: GKGoal
        var stopMovingGoal = GKGoal(toReachTargetSpeed: 0.0)
    
        init(path: GKPath, target: GKGraphNode, maxSpeed: Float) {
            reachMaxSpeed = GKGoal(toReachTargetSpeed: maxSpeed)
            followPathGoal = GKGoal(toFollow: path, maxPredictionTime: 1.0, forward: true)
            super.init()
            setWeight(1.0, for: reachMaxSpeed)
            setWeight(0.8, for: followPathGoal)
            setWeight(0.0, for: stopMovingGoal)
        }
    
        func stopMoving() {
            setWeight(0.0, for: reachMaxSpeed)
            setWeight(0.0, for: followPathGoal)
            setWeight(1.0, for: stopMovingGoal)
        }
    }