Reputation: 1748
I am trying to make a Snake game in SpriteKit. I thought this would be super easy to do, while utilising all of SpriteKit's SKAction
features such as timing modes, sequences, groups, etc. However, I found myself creating a lot of custom actions and it felt very unnecessary. Here are the basics; it seems like a lot of code but it's super easy to understand:
class SOSnake: SKNode {
let size: Double // A snake is a bunch of squares. Thus size represents an individual square's length
var body: [SOBodyPart] // All the body parts will be stored here so we can interate over them
init(ofSize size: Double) {
self.size = size
self.body = [SOBodyPart(type: .head, size: size)]
super.init()
addChild(head)
}
var head: SOBodyPart { body.first! } // for convenience
var tail: SOBodyPart { body.last! } // for convenience. When body.count == 1, head === tail
}
// In my actual game, there is a `type` property which is either "head", "body", or "tail".
// This determines the colour of the SKSpriteNode.
// This isn't relevant for the problem so I omitted it.
class SOBodyPart: SKSpriteNode {
init(id: Int, size: Double) { // id is used for its name
super.init(texture: nil, color: .yellow, size: CGSizeMake(size, size))
self.name = String(id)
}
}
Finally, I created a method called move()
. This function returns an SKAction
, which when run on the Snake node, will cause each body part to move in the direction it's facing. Here is its simple implementation:
// Since each body part's name is `id`, I can use String(indexInBodyArray) to reference it
func move() -> SKAction {
let actions = body.indices.map { SKAction.run(.move(Direction.of(body[$0]), by: size, duration: 1), onChildWithName: String($0)) }
return SKAction.group(actions)
}
(Can be skipped. Only posted for context)
enum Direction {
case up, down, left, right
static func of(_ node: SKNode) -> Direction {
// if node.zRotation is within 0º - 45º ... 0º + 45º it'll return `right`
// if node.zRotation is within 90º - 45º ... 90º + 45º it'll return `up`
// and so on...
}
}
extension SKAction {
static func move(_ direction: Direction, by amount: Double, duration: TimeInterval) -> SKAction {
switch direction {
case .up:
return .move(by: .init(dx: 0, dy: amount), duration: duration)
case .down:
return .move(by: .init(dx: 0, dy: -amount), duration: duration)
case .left:
return .move(by: .init(dx: -amount, dy: 0), duration: duration)
case .right:
return .move(by: .init(dx: amount, dy: 0), duration: duration)
}
}
}
The problem comes when I try calling the move function from my SKScene:
private func onTap() {
let action = snake.move()
action.duration = 10.0
action.timingMode = .easeInEaseOut
snake.run(action)
}
The changes I make to action
are not reflected when the action is run. This is because run(_:onChildWithName:)
is executed instantaneously. Thus, in my game, I was forced to create custom actions using customAction(withDuration:actionBlock:)
in order to utilise all of SpriteKit's action customisations.
This felt very strange as I'm writing code for things that already exist in SpriteKit, namely translating/moving a node. Is there any built-in way to implement the move()
function such that changes made to the returned SKAction
reflect when it's run? Or are custom actions the only way to achieve what I'm trying to achieve?
Please let me know what's the simplest way to implement a move()
function while still being able to utilise things such as timing modes, sequences, etc.
Upvotes: 0
Views: 118