Need objects to move with a constant speed

2019-06-05 04:37发布

I'm trying to create a game, where the objects need to chase food. Right now the objects speeds up, when the food is within the given radius. But I need the speed to always be the same.

Any suggestions how to fix this? I have tried to add an SKAction under the chase function, where I set the position.x and position.y, but I can't make it work correct.

Fish class:

class Fish:SKSpriteNode{
private let kMovingAroundKey = "movingAround"
private let kFishSpeed:CGFloat = 4.5
private var swimmingSpeed:CGFloat = 100.0
private let sensorRadius:CGFloat = 100.0
private weak var food:SKSpriteNode! = nil //the food node that this fish currently chase

override init(texture: SKTexture?, color: UIColor, size: CGSize) {
    super.init(texture: texture, color: color, size: size)

    physicsBody = SKPhysicsBody(rectangleOf: size)
    physicsBody?.affectedByGravity = false
    physicsBody?.categoryBitMask = Collider.fish
    physicsBody?.contactTestBitMask = Collider.food
    physicsBody?.collisionBitMask = 0x0 //No collisions with fish, only contact detection
    name = "fish"

    let sensor = SKShapeNode(circleOfRadius: 100)
    sensor.fillColor = .red
    sensor.zPosition = -1
    sensor.alpha = 0.1
    addChild(sensor)
}

func getDistanceFromFood()->CGFloat? {

    if let food = self.food {

        return self.position.distance(point: food.position)
    }
    return nil

}

func lock(food:SKSpriteNode){

    //We are chasing a food node at the moment
    if let currentDistanceFromFood = self.getDistanceFromFood() {

        if (currentDistanceFromFood > self.position.distance(point: food.position)){
            //chase the closer food node
             self.food = food
            self.stopMovingAround()
        }//else, continue chasing the last locked food node

    //We are not chasing the food node at the moment
    }else{
         //go and chase then
         if food.position.distance(point: self.position) <= self.sensorRadius {

            self.food = food
            self.stopMovingAround()
        }
    }
}

//Helper method. Not used currently. You can use this method to prevent chasing another (say closer) food while already chasing one
func isChasing(food:SKSpriteNode)->Bool{

    if self.food != nil {

        if self.food == food {
            return true
        }
    }

    return false
}

func stopMovingAround(){

    if self.action(forKey: kMovingAroundKey) != nil{
       removeAction(forKey: kMovingAroundKey)
    }
}


//MARK: Chasing the food
//This method is called many times in a second
func chase(within rect:CGRect){

    guard let food = self.food else {

        if action(forKey: kMovingAroundKey) == nil {
            self.moveAround(within: rect)
        }
        return
    }

    //Check if food is in the water
    if rect.contains(food.frame.origin) {

        //Take a detailed look in my Stackoverflow answer of how chasing works : https://stackoverflow.com/a/36235426

        let dx = food.position.x - self.position.x
        let dy = food.position.y - self.position.y

        let angle = atan2(dy, dx)

        let vx = cos(angle) * kFishSpeed
        let vy = sin(angle) * kFishSpeed

        position.x += vx
        position.y += vy

    }
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func moveAround(within rect:CGRect){

    if scene != nil {

        //Go randomly around the screen within view bounds
        let point = rect.randomPoint()

        //Formula: time = distance / speed
        let duration = TimeInterval(point.distance(point: position) / self.swimmingSpeed)
        let move = SKAction.move(to: point, duration: duration)
        let block = SKAction.run {
            [unowned self] in

            self.moveAround(within: rect)
        }
        let loop = SKAction.sequence([move,block])

        run(loop, withKey: kMovingAroundKey)
    }
}
}

Gamescene where you can see the update function.

 override func update(_ currentTime: TimeInterval) {

    self.enumerateChildNodes(withName: "fish") {
        [unowned self] node, stop in

        if let fish = node as? Fish {

            self.enumerateChildNodes(withName: "food") {
                node, stop in

                fish.lock(food: node as! SKSpriteNode)
            }

            fish.chase(within: self.water.frame)
        }
    }
}

1条回答
Deceive 欺骗
2楼-- · 2019-06-05 05:22

Probably something like this (GameScene):

 var prev : TimeInterval!

    //MARK: Chasing the food
    override func update(_ currentTime: TimeInterval) {

        defer { prev = currentTime }
        guard prev != nil else { return }

        let dt = currentTime - prev

        print("delta time \(dt)")

        self.enumerateChildNodes(withName: "fish") {
            [unowned self] node, stop in

            if let fish = node as? Fish {

                self.enumerateChildNodes(withName: "food") {
                    node, stop in

                    fish.lock(food: node as! SKSpriteNode)
                }

                fish.chase(within: self.water.frame, delta:CGFloat(dt))
            }
        }
    }

The variable prev is a property of GameScene.

And change chase() method in Fish class:

 //MARK: Chasing the food
    func chase(within rect:CGRect, delta:CGFloat){

        guard let food = self.food else {

            if action(forKey: kMovingAroundKey) == nil {
                self.moveAround(within: rect)
            }
            return
        }

        //Check if food is in the water
        if rect.contains(food.frame.origin) {

            //Take a detailed look in my Stackoverflow answer of how chasing works : https://stackoverflow.com/a/36235426

            //check for collision

            if self.frame.contains(food.frame.origin) {
               food.removeFromParent()
            }else {
                let dx = food.position.x - self.position.x
                let dy = food.position.y - self.position.y

                let angle = atan2(dy, dx)



                let vx = cos(angle) * self.swimmingSpeed * delta
                let vy = sin(angle) * self.swimmingSpeed * delta

                print("vx \(vx), vy (\(vy)")



                position.x += vx
                position.y += vy

                //time = distance / speed
            }
        }
    }

I have added the delta time parameter. You may wonder what is delta time? I will quote LearnCocos2d from that article:

Delta time is simply the time difference between the previous and the current frame.

Why is this important to maintain the constant speed of a node? Well, we use our Fish.swimmingSpeed variable to determine the speed of fish(forget kFishSpeed, it doesn't have a purpose now).

Now in the case of SKAction, a duration parameter directly determines the speed of fish, because duration applies to time, and time = distance / speed, so we calculate the time like this currently:

let duration = TimeInterval(point.distance(point: position) / self.swimmingSpeed)

Now lets say that duration equals to 1. That means, the fish is going to move 100 pts per second. Now, the difference between update() method and actions, is that it is executed 60 times per second. And because our method chase() is ideally called 60 time per second, our speed now have to be Fish.swimmingSpeed / 60.

And this is where delta time comes in. Because it may happen that frame is not rendered in 1/60 second (0.016667), but rather rendering may take longer (eg. 0.02,0.03 sec), we calculate that difference, and use it to adjust the movement. Kind of cheating IMO in compare to normal behaviour without using delta time, because player loses the control on moments if game lags a lot (eg. its hero teleports), but that part is off topic :) It is up to you to test what works / looks better for you.

So we do (in order to calculate the distance):

let vx = cos(angle) * self.swimmingSpeed * delta
let vy = sin(angle) * self.swimmingSpeed * delta

and that will give you a constant speed.

I could go more into detail but it is late here, and you probably got an idea how things are functioning, so I will stop here. Happy coding!

查看更多
登录 后发表回答