需要物体以恒定的速度移动

Posted

技术标签:

【中文标题】需要物体以恒定的速度移动【英文标题】:Need objects to move with a constant speed 【发布时间】:2017-05-28 13:56:29 【问题描述】:

我正在尝试创建一个游戏,其中对象需要追逐食物。现在,当食物在给定半径内时,物体会加速。但我需要速度始终保持不变。

有什么建议可以解决这个问题吗?我曾尝试在chasing函数下添加一个SKAction,我在其中设置了position.x和position.y,但无法使其正常工作。

鱼类:

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 *** answer of how chasing works : https://***.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)
    


可以看到更新功能的游戏场景。

 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)
        
    

【问题讨论】:

@Whirlwind 请看这里:) 查看答案,我刚刚测试过,效果不错。 【参考方案1】:

大概是这样的(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))
            
        
    

变量prev是GameScene的一个属性。

并更改Fish 类中的chase() 方法:

 //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 *** answer of how chasing works : https://***.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
            
        
    

我添加了delta time 参数。您可能想知道什么是增量时间?我将引用那篇文章中的 LearnCocos2d:

Delta 时间只是上一个时间和上一个时间之间的时间差 当前帧。

为什么保持节点的恒定速度很重要?好吧,我们使用我们的Fish.swimmingSpeed 变量来确定鱼的速度(忘记kFishSpeed,它现在没有目的)。

现在SKAction的情况下,一个duration参数直接决定了鱼的速度,因为duration适用于时间,而time = distance / speed,所以我们目前这样计算时间:

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

现在假设持续时间等于 1。这意味着,鱼将每秒移动 100 点。现在,update() 方法和动作之间的区别在于它每秒执行 60 次。因为我们的方法chase() 理想情况下被称为每秒60 次,所以我们的速度现在必须是Fish.swimmingSpeed / 60

这就是 delta 时间的用武之地。因为可能会发生帧未在 1/60 秒 (0.016667) 内渲染,而是渲染可能需要更长的时间(例如 0.02,0.03 秒),所以我们计算该差异,并用它来调整运动。与不使用增量时间的正常行为相比,有点作弊 IMO,因为如果游戏滞后很多(例如,它的英雄传送),玩家会失去对时刻的控制,但这部分是题外话:) 由你来测试什么工作/看起来更适合你。

所以我们这样做(为了计算距离):

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

这会给你一个恒定的速度。

我可以更详细地介绍,但这里已经很晚了,你可能已经知道事情是如何运作的,所以我会在这里停下来。编码愉快!

【讨论】:

如果您不想使用增量时间,只需将swimmingSpeed 除以 60(或您的首选帧速率),而不是乘以增量。

以上是关于需要物体以恒定的速度移动的主要内容,如果未能解决你的问题,请参考以下文章

IOS - 如何应用恒定速度 - Swift 3

以一定速度在屏幕上随机移动物体

垂直于物体移动

速度从另一个移动物体跟踪一个移动物体

corona sdk / solar2d 多个移动物体

随机游走模型(RandomWalk Mobility)