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)
}
}
}
Probably something like this (
GameScene
):The variable
prev
is a property of GameScene.And change
chase()
method inFish
class:I have added the delta time parameter. You may wonder what is delta time? I will quote LearnCocos2d from that article:
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(forgetkFishSpeed
, 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, andtime = distance / speed
, so we calculate the time like this currently: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 methodchase()
is ideally called 60 time per second, our speed now have to beFish.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):
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!