Portuguese (Português) translation by David Batista (you can also view the original English article)
Introdução
Neste tutorial, a segunda edição do SpriteKit from Scratch, você aprenderá sobre restrições e actions. Estes recursos são usados para facilitar a adição de movimento e animações em seu jogo no SpriteKit enquanto limita a posição e orientação dos nós no jogo.
Para me seguir durante o tutorial, você pode usar o projeto que criou no primeiro tutorial desta série ou baixar uma nova cópia no GitHub.
1. Nó personalizado e Classes Scene
Antes de podermos adicionar restrições e actions a uma cena, primeiro precisamos criar algumas classes para que possamos trabalhar com nossos nós no código. Crie uma nova classe, PlayerNode, baseada no template iOS > Source > Cocoa Touch Class e certifique-se que ela seja uma subclasse da SKSpriteNode
.



Se o Xcode lançar um erro após criar a classe, adicione uma instrução de importação do framework SpriteKit abaixo da instrução import UIKit
.
1 |
import UIKit |
2 |
import SpriteKit |
Em seguida, declare as três propriedades abaixo na classe PlayerNode
. Estas propriedades manterão as restrições usadas para limitar o movimento horizontal do carro.
1 |
import UIKit |
2 |
import SpriteKit |
3 |
|
4 |
class PlayerNode: SKSpriteNode { |
5 |
|
6 |
var leftConstraint: SKConstraint! |
7 |
var middleConstraint: SKConstraint! |
8 |
var rightConstraint: SKConstraint! |
9 |
|
10 |
}
|
Crie outra Classe Cocoa Touch e a chame de MainScene, tornando ela uma subclasse da SKScene
.



Na parte superior, adicione uma instrução de importação para o framework SpriteKit.
1 |
import UIKit |
2 |
import SpriteKit |
Com essas classes criadas, abra a MainScene.sks, clique no fundo cinza e selecione a cena, abra o Custom Class Inspector na direita e atribua MainScene para o Custom Class.



Selecione o carro e defina a ele a classe PlayerNode da mesma forma que você fez para a cena. Por fim, com o carro ainda selecionado, abra o Attributes Inspector e altere o Name para Player.



Agora que temos as classes básicas configuradas, podemos começar criando algumas restrições no código.
2. Restrições
As restrições no SpriteKit são representadas pela classe SKConstraint
e são usadas para limitar a posição e a orientação de nós específicos. A uma grande variedade de coisas que pode ser conseguida com restrições já que elas podem ser relacionadas a cena ou a outros nós. Restrições também funcionam com intervalos de valores, além de valores constantes para que os sprites dentro de sua cena possam ser fixados em um local específico ou permitir que se movam dentro de uma determinada área.
Vamos estar adicionando três restrições na classe PlayerNode
. Estas restrições serão usadas para travar o carro nas três pistas no jogo.
Abra o MainScene.swift e crie uma propriedade para o jogador do tipo PlayerNode!
. Esta propriedade armazenará uma referência do nó do jogador.
1 |
import UIKit |
2 |
import SpriteKit |
3 |
|
4 |
class MainScene: SKScene { |
5 |
|
6 |
var player: PlayerNode! |
7 |
|
8 |
}
|
Em seguida, sobrepomos o método didMoveToView(_:)
da classe MainScene
:
1 |
override func didMoveToView(view: SKView) { |
2 |
super.didMoveToView(view) |
3 |
|
4 |
size = view.frame.size |
5 |
|
6 |
if let foundPlayer = childNodeWithName("Player") as? PlayerNode { |
7 |
player = foundPlayer |
8 |
}
|
9 |
|
10 |
let center = size.width/2.0, difference = CGFloat(70.0) |
11 |
|
12 |
player.leftConstraint = SKConstraint.positionX(SKRange(constantValue: center - difference)) |
13 |
player.middleConstraint = SKConstraint.positionX(SKRange(constantValue: center)) |
14 |
player.rightConstraint = SKConstraint.positionX(SKRange(constantValue: center + difference)) |
15 |
|
16 |
player.leftConstraint.enabled = false |
17 |
player.rightConstraint.enabled = false |
18 |
|
19 |
player.constraints = [player.leftConstraint, player.middleConstraint, player.rightConstraint] |
20 |
}
|
Vamos percorrer o código passo a passo. O método didMoveToView(_:)
é chamado sempre que a cena for apresentada por uma view. Após chamar o método didMoveToView(_:)
da superclasse, redimensionamos a cena para o mesmo tamanho da view atual. Isso garante que a cena sempre se estenda devidamente ao tamanho e dimensão da tela do dispositivo atual.
Acessamos o sprite do jogador que adicionamos no editor de cena do Xcode, procurando por ele pelo nome que demos anteriormente. Então atribuímos este valor para a propriedade player
.
Após calcular o centro da cena e especificar uma restrição diferente de 70.0
, criamos as restrições do sprite. Usando o método de classe positionX(_:)
da classe SKConstraint
, criamos as restrições da esquerda, meio e direita para o sprite do jogador. Este método requer uma instância SKRange
como parâmetro que, no nosso caso, é um range com um valor constante. Se você quer dar uma olhada nas restrições e intervalos possíveis em SpriteKit, eu recomento dar uma olhada na referência das classes SKRange
e SKConstraint
.
Desabilitamos as restrições da esquerda e da direita, pois não queremos que elas atuem quando o jogo começar. Por último, atribuímos estas restrições á propriedade constraints
do nó do jogador. Esta propriedade é definida na classe SKNode
.
Compile e rode seu jogo em qualquer simulador ou dispositivo fisico. Você poderá ver agora que a sua cena está dimensionada corretamente com o carro centralizado na parte inferior.



Você pode ver que o carro está restringido ao centro horizontal da cena e é capaz de ser restringido nas pistas da direita e da esquerda uma vez que adicionarmos algum movimento ao jogo.
2. Actions
Actions em SpriteKit são representadas pela poderosa classe SKAction
. As actions nos permitem animar e mover facilmente os sprites em uma cena. Elas são executadas por nós e calculadas pelas APIs do SpriteKit e funcionam ao lado das restrições e simulações de física.
Além de especificar o que uma action faz, você também pode configurar e programar como a action funciona. Você pode, por exemplo, pausar e retomar uma action ou configurar um comportamento ateanuado da action. Isto dá um maior grau de controle para que você possa facilmente acelerar ou desacelerar determinadas actions para produzir alguns elementos de jogabilidade interessantes.
Da mesma forma que os nós podem ter nós filhos, existem três tipos de actions que podem ter actions filhas:
- sequence actions, que executa um array de actions, uma após a outra
- group actions, que executa um array de actions, todas de uma vez
- repeating actions, que repete uma única action por uma quantidade de vezes ou indefinitivamente
Você pode criar actions via programação ou no editor de cena do Xcode, que foi usado no tutorial anterior. Vamos usar ambas as técnicas neste tutorial.
Abra o MainScene.sks e clique no ícone próximo ao botão Animate na parte inferior esquerdo da cena para abrir o Action Editor View.






Em seguida, navegue para o final do Object Library na direita e ache o item Move Action. Clique e arraste ele para a timeline do Action Editor View e o coloque no canto esquerdo como mostrado abaixo.



Isso faz com que a action comece a execução no 0:00, isso é, quando a cena for apresentada. Se coloca-la em outro lugar, a action começará a execução após o intervalo de tempo mostrado na parte superior da timeline.
Passe o mouse sobre a action e clique no ícone pequeno de seta no canto inferior esquerdo. Um popup irá aparecer, clique no botão infinity na esquerda. Isso faz com que a action se repita para sempre.



Com a action ainda selecionada, abra o Attributes Inspector na direita e altere o valor do Y Offset para 100.



Os outros valores especificam que o carro começará a animação imediatamente (Start Time) e a cada 1 segundo (Duration) moverá 0 pontos na direção X e 100 na direção Y (Offset). A propriedade Timing Function pode ser usada para iniciar e/ou parar gradativamente uma action. Neste caso, estamos usando Linear, o que significa que o carro sempre se move na mesma velocidade.
Finalmente, teste a action, clique no botão Animate na parte inferior esquerda do editor de cena. A toolbar na parte inferior ficará azul e o carro começará a se mover para cima.



Com a action de movimento implementada, é hora de criar as actions horizontais via programação. Antes de fazer isso, precisamos adicionar alguma lógica para que os botões do jogo possam controlar o carro.
Crie um novo arquivo escolhendo o template iOS > Source > Swift File e nomeie de LaneStateMachine.



Adicione o seguinte código no novo arquivo:
1 |
import GameplayKit |
2 |
|
3 |
class LaneStateMachine: GKStateMachine { |
4 |
|
5 |
}
|
6 |
|
7 |
class LaneState: GKState { |
8 |
var playerNode: PlayerNode |
9 |
|
10 |
init(player: PlayerNode) { |
11 |
playerNode = player |
12 |
}
|
13 |
}
|
14 |
|
15 |
class LeftLane: LaneState { |
16 |
override func isValidNextState(stateClass: AnyClass) -> Bool { |
17 |
if stateClass == MiddleLane.self { |
18 |
return true |
19 |
}
|
20 |
|
21 |
return false |
22 |
}
|
23 |
|
24 |
override func didEnterWithPreviousState(previousState: GKState?) { |
25 |
playerNode.moveInDirection(.Left, toLane: self) |
26 |
}
|
27 |
}
|
28 |
|
29 |
class MiddleLane: LaneState { |
30 |
override func isValidNextState(stateClass: AnyClass) -> Bool { |
31 |
if stateClass == LeftLane.self || stateClass == RightLane.self { |
32 |
return true |
33 |
}
|
34 |
|
35 |
return false |
36 |
}
|
37 |
|
38 |
override func didEnterWithPreviousState(previousState: GKState?) { |
39 |
if previousState is LeftLane { |
40 |
playerNode.moveInDirection(.Right, toLane: self) |
41 |
} else if previousState is RightLane { |
42 |
playerNode.moveInDirection(.Left, toLane: self) |
43 |
}
|
44 |
}
|
45 |
}
|
46 |
|
47 |
class RightLane: LaneState { |
48 |
override func isValidNextState(stateClass: AnyClass) -> Bool { |
49 |
if stateClass == MiddleLane.self { |
50 |
return true |
51 |
}
|
52 |
|
53 |
return false |
54 |
}
|
55 |
|
56 |
override func didEnterWithPreviousState(previousState: GKState?) { |
57 |
playerNode.moveInDirection(.Right, toLane: self) |
58 |
}
|
59 |
}
|
O que todo esse código faz, é utilizar o novo framework GameplayKit para criar uma máquina de estado que represente as três pistas e o movimento entre elas no jogo. Se você quer entender melhor sobe o que este código está fazendo, verifique meu tutorial sobre o GameplayKit.
Em seguida, abra o PlayerNode.swift e adicione os dois métodos a seguir na classe PalyerNode
.
1 |
func disableAllConstraints() { |
2 |
leftConstraint.enabled = false |
3 |
middleConstraint.enabled = false |
4 |
rightConstraint.enabled = false |
5 |
}
|
6 |
|
7 |
func moveInDirection(direction: ButtonDirection, toLane lane: LaneState) { |
8 |
disableAllConstraints() |
9 |
|
10 |
let changeInX = (direction == .Left) ? -70.0 : 70.0 |
11 |
let rotation = (direction == .Left) ? M_PI/4 : -M_PI/4 |
12 |
|
13 |
let duration = 0.5 |
14 |
let moveAction = SKAction.moveByX(CGFloat(changeInX), y: 0.0, duration: duration) |
15 |
let rotateAction = SKAction.rotateByAngle(CGFloat(rotation), duration: duration/2) |
16 |
rotateAction.timingMode = .EaseInEaseOut |
17 |
let rotateSequence = SKAction.sequence([rotateAction, rotateAction.reversedAction()]) |
18 |
let moveGroup = SKAction.group([moveAction, rotateSequence]) |
19 |
|
20 |
let completion = SKAction.runBlock { () -> Void in |
21 |
switch lane { |
22 |
case is LeftLane: |
23 |
self.leftConstraint.enabled = true |
24 |
case is MiddleLane: |
25 |
self.middleConstraint.enabled = true |
26 |
case is RightLane: |
27 |
self.rightConstraint.enabled = true |
28 |
default: |
29 |
break
|
30 |
}
|
31 |
}
|
32 |
|
33 |
let sequenceAction = SKAction.sequence([moveGroup, completion]) |
34 |
runAction(sequenceAction) |
35 |
}
|
O método disableAllConstraints()
é um método conveniente para desabilitar as restrições do nó do jogador.
No moveInDirection(_:toLane:)
, determinamos qual direção o carro irá se mover na horizontal, -70.0 quando se mover para a esquerda e +70.0 quando mover para a direita. Então calculamos o angulo correto (em radianos) para rodar o carro, quando em movimento. Note que o número positivo representa uma rotação no sentido anti-horário.
Após especificar uma duração constante, criamos as actions de movimento e rotação usando os métodos de classes moveByX(_:duration:)
e rotateByAngle(_:duration)
. Criamos uma sequência de rotação para rodar o carro de volta a como estava antes do movimento. O método reversedAction()
cria automaticamente o inverso de uma action para você.
Em seguida, criamos um grupo de actions de movimento para executar o movimento horizontal e rodar ao mesmo tempo. Finalmente, criamos uma action complementar para executar uma closure quando executada. Nesta closure, identificamos em qual pista o carro esta e ativamos a restrição correta para esta pista.
Abra o ViewController.swift e adicione uma propriedade, stateMachine
, do tipo LaneStateMachine!
à classe ViewController
.
1 |
class ViewController: UIViewController { |
2 |
|
3 |
var stateMachine: LaneStateMachine! |
4 |
|
5 |
...
|
6 |
|
7 |
}
|
Substitua as implementações dos métodos viewDidLoad()
e didPressButton(_:)
na classe ViewController
conforme abaixo:
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
let skView = SKView(frame: view.frame) |
5 |
let scene = MainScene(fileNamed: "MainScene")! |
6 |
skView.presentScene(scene) |
7 |
view.insertSubview(skView, atIndex: 0) |
8 |
|
9 |
let left = LeftLane(player: scene.player) |
10 |
let middle = MiddleLane(player: scene.player) |
11 |
let right = RightLane(player: scene.player) |
12 |
|
13 |
stateMachine = LaneStateMachine(states: [left, middle, right]) |
14 |
stateMachine.enterState(MiddleLane) |
15 |
}
|
16 |
|
17 |
@IBAction func didPressButton(sender: UIButton) { |
18 |
switch sender.tag { |
19 |
case ButtonDirection.Left.rawValue: |
20 |
switch stateMachine.currentState { |
21 |
case is RightLane: |
22 |
stateMachine.enterState(MiddleLane) |
23 |
case is MiddleLane: |
24 |
stateMachine.enterState(LeftLane) |
25 |
default: |
26 |
break
|
27 |
}
|
28 |
case ButtonDirection.Right.rawValue: |
29 |
switch stateMachine.currentState { |
30 |
case is LeftLane: |
31 |
stateMachine.enterState(MiddleLane) |
32 |
case is MiddleLane: |
33 |
stateMachine.enterState(RightLane) |
34 |
default: |
35 |
break
|
36 |
}
|
37 |
default: |
38 |
break
|
39 |
}
|
40 |
}
|
No viewDidLoad()
, adicionamos o objeto SKView
no índice 0, de modo que os botões de controles se tornam visíveis e também inicializamos a máquina de estado.
No didPressButton(_:)
, identificamos qual botão o usuário pressiona, baseado na tag do botão, e entramos na pista correta a partir de onde o carro esta.
Compile e rode o jogo. Pressione ambos os botões, esquerdo e direito, no fundo da tela para fazer o carro se mover. Você pode ver o carro se virar e se mover na direção do botão que você pressionou.



Note que os ícones de botão podem ser incompatíveis, como mostrado abaixo.



Para corrigir isso, abra o catalogo de ativos (Image.xcassets) e para ambas as imagens (Left Arrow e Right Arrow) mude o Rendering Mode para Original Image.



Conclusão
Agora você deve estar confiante usando restrições e actions no SpriteKit. Como você pode ver, estes recursos do framework torna muito fácil adicionar animações e movimentos em um jogo no SpriteKit.
No próximo tutorial desta série, iremos dar uma olhada nos nós de câmera do SpriteKit, assim nosso carro não saíra pelo topo da tela. Após isso, iremos nos aprofundar no sistema de simulação de física do SpriteKit com foco nos corpos de física e na detecção de colisão.
Como sempre, deixe seus comentários e feedback nos comentários abaixo.
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!