Portuguese (Português) translation by David Batista (you can also view the original English article)
Introdução
Juntamente com todos os novos recursos e frameworks do iOS 9 e OS X El Capitan, com os lançamentos deste ano a Apple também criou um framework inteiramente novo direcionado aos desenvolvedores de jogos, GameplayKit. Com as APIs gráficas existentes (SpriteKit, SceneKit e Metal) facilitando a criação de grandes jogos no iOS e OS X, a Apple lançou agora o GameplayKit para tornar mais fácil a criação de jogos que funcionem bem. Este novo framework contém muitas classes e funcionalidades que podem ser usadas para acrescentar facilmente lógicas complexas em seus jogos.
Neste primeiro tutorial, eu irei ensinar a você dois aspectos principais do framework Gameplaykit.
- entidades e componentes
- máquinas de estado
Pré-requisitos
Este tutorial requer que você esteja rodando o Xcode 7 no OS X Yosemite ou superior. Apesar de não ser obrigatório, é recomendado que você tenha um aparelho físico rodando o iOS 9 para obter um desempenho muito melhor quando testar o jogo baseado no SpriteKit usado neste tutorial.
1. Começando
Você primeiramente precisa baixar o projeto inicial para esta série de tutoriais no GitHub. Uma vez feito isto, abra o projeto no Xcode e rode ele no Simulador iOS ou em seu aparelho.
Você verá que ele é um jogo muito básico em que você controla um ponto azul e navega pelo mapa. Quando você colide com um ponto vermelho, você perde dois pontos. Quando você colide com um ponto verde, você ganha um ponto. Quando você colide com um ponto amarelo, o seu próprio ponto ficará congelado por alguns segundos.
Até este estágio, ele é um jogo muito básico, mas ao longo do curso desta série de tutoriais e com a ajuda do GameplayKit, vamos adicionar muitas funcionalidades e elementos de jogabilidade.
2. Entidades e Componentes
O primeiro grande aspecto do novo framework GameplayKit é um conceito de código-estruturação baseado em entidades e componentes. Ele trabalha para permitir que você, desenvolvedor, escreva código comum que será usado por muitos tipos de objetos diferentes em seu jogo enquanto mantém ele bem organizado e gerenciável. O conceito de entidades e componentes se destina a eliminar a abordagem comum baseada na herança para compartilhar funcionalidades entre tipos de objetos. A forma mais fácil de entender este conceito é com alguns exemplos, então imagina o seguinte cenário:
Você está construindo um jogo de tower defense com três tipos principais de torres, Fogo, Gelo e Cura. Os três tipos de torres podem compartilhar alguns dados em comum, como vida, tamanho e força. Suas torres de Fogo e Gelo precisam ser capazes de se direcionar para novos inimigos para atirar enquanto que sua torre de Cura não precisa. Tudo que sua torre de Cura precisa é ser capaz de reparar outras torres que recebam danos dentro de um determinado raio.
Com este modelo básico de jogo em mente, vamos ver como seu código seria organizado usando uma estrutura de herança.
- Uma classe pai
Tower
contendo os dados em comum, tal como vida, tamanho e força. - Classes
FireTower
,IceTower
eHealTower
que irão herdar da classeTower
. - Na classe
HealTower
, você tem uma lógica que é responsável por curar suas outras torres que estejam dentro de uma certá área.
Até agora, esta estrutura está ok, mas um problema surge quando você precisa implementar a habilidade de direcionamento das torres de Fogo e Gelo. Você só precisa copiar e colar o mesmo código em ambas as classes FireTower
e IceTower
? E se você precisar fazer qualquer alteração, então você terá que mudar seu código em mais de um lugar, o que é tedioso e propenso a erros. Além de tudo isso, o que acontece se você quiser adicionar um novo tipo de torre que também precisa desta funcionalidade de direcionamento. Você copia e coloca isto uma terceira vez?
A melhora maneira parecer ser colocar a lógica de direcionamento na classe pai Tower
. Isso permitiria à você ter apenas uma cópia do código, que precisaria ser editado em um único lugar. Adicionando este código aqui, entretanto, tornaria a classe Tower
grande e mais complicada do que ela precisar ser, já que nem todas as suas subclasses precisam desta funcionalidade. Se você desejar adicionar mais funcionalidades compartilhadas entre seus tipos de torres, sua classe Tower
iria progressivamente se tornar maior e maior, o que tornaria difícil de se trabalhar.
Como você pode ver, apesar de ser possível criar um jogo baseado no modelo de herança, ele pode muito rapidamente e facilmente tornar-se desorganizado e difícil de gerir.
Agora, vamos ver como este mesmo jogo modelo poderia ser usando a estrutura de entidades e componentes.
- Nós criaríamos as entidades
FireTower
,IceTower
eHealTower
. Mais entidades poderiam ser criadas se você quisesse mais tipos de torres mais tarde. - Nós também criaríamos um componente
BasicTower
que conteria a vida, tamanho, força, etc.
- Para lidar com a cura de suas torres dentro de um determinada área, poderiamos acrescentar um componente
Healing
. - Um componente
Targeting
que conteria o código necessário para direcionar para os inimigos.
Usando o GameplayKit e esta estrutura, você então poderia ter um único tipo de entidade para cada tipo de torre em seu jogo. Para cada entidade individual, você pode adicionar os componentes desejados. Por exemplo:
- Suas entidades
FireTower
eIceTower
teria cada uma um componenteBasicTower
eTargeting
ligados a ela. - Sua entidade
HealTower
teria ambos os componentesBasicTower
eHealing
.
Como você pode ver, usando uma estrutura baseada em entidade e componente, seu modelo de jogo agora é mais simples e mais versátil. Sua lógica de direcionamento precisar ser escrita apenas umas vez e ligada as entidades que precisem dela. Da mesma forma, seus dados básicos de torre podem ainda ser facilmente compartilhados entre todas as suas torres sem ter um amontoamento de todas as suas outras funcionalidades comuns.
Outra grande coisa sobre esta estrutura baseada em entidade e componente é que os componentes podem ser adicionados e removidos das entidades sempre que você quiser. Por exemplo, se você precisar que suas torres de Cura sejam desativas em certas condições, você poderia simplesmente remover o componente Healing
da sua entidade até que estas condições terminem. Da mesma forma, se você precisar que uma de suas torres de Fogo ganhe temporariamente a habilidade de cura, você poderia apenas adicionar um componente Healing
em sua entidade FireTower
por um tempo específico.
Agora que você está confortável com o conceito de um modelo de jogo baseado em entidade e componente, vamos criar em nosso próprio jogo. No File Inspector do Xcode, procure pela pasta Entities dentro do seu projeto. Por conveniência, existem três classes de entidade para você, mas agora você irá criar uma nova entidade do princípio.
Escolha File > New > File... ou pressione Command-N para criar uma nova classe. Certifique-se de selecionar o template Cocoa Touch Class na seção iOS > Source. Chame a classe de Player e certifique que seja uma subclasse da GKEntity
.



Você verá que imediatamente após a abertura do seu novo arquivo o Xcode irá mostrar um erro. Para corrigir isto, adicione a instrução de importação a seguir, abaixo da instrução existente import UIKit
.
1 |
import GameplayKit |
Volte ao arquivo PlayerNode.swift e adicione a seguinte propriedade à classe PlayerNode
.
1 |
var entity = Player() |
Em seguida, navegue até a pasta Components em seu projeto no Xcode e crie uma nova classe exatamente como você fez anteriormente. Desta vez, chame a classe de FlashingComponent e certifique que seja uma subclasse da GKComponent
como mostrado a seguir.



O componente que você acaba de criar vai lidar com o efeito de piscar do nosso ponto azul quando ele é acertado por um ponto vermelho e entra em seu estado invulnerável. Substitua o conteúdo do arquivo FlashingComponent.swift com o seguinte:
1 |
import UIKit |
2 |
import SpriteKit |
3 |
import GameplayKit |
4 |
|
5 |
class FlashingComponent: GKComponent { |
6 |
|
7 |
var nodeToFlash: SKNode! |
8 |
|
9 |
func startFlashing() { |
10 |
|
11 |
let fadeAction = SKAction.sequence([SKAction.fadeOutWithDuration(0.75), SKAction.fadeInWithDuration(0.75)]) |
12 |
nodeToFlash.runAction(SKAction.repeatActionForever(fadeAction), withKey: "flash") |
13 |
|
14 |
}
|
15 |
|
16 |
deinit { |
17 |
nodeToFlash.removeActionForKey("flash") |
18 |
}
|
19 |
|
20 |
}
|
A implementação simplesmente mantem uma referência a um objeto SKNode
e repete uma sequência de ação de piscar, enquanto o componente estiver ativo.
Volte ao arquivo GameScene.swift e adicione o código a seguir, em algum lugar dentro do método didMoveToView(_:)
:
1 |
// Adding Component
|
2 |
let flash = FlashingComponent() |
3 |
flash.nodeToFlash = playerNode |
4 |
flash.startFlashing() |
5 |
playerNode.entity.addComponent(flash) |
Nós criamos um objeto FlashingComponent
e definimos ele para executar um piscar no ponto do jogador. A ultima linha adiciona o componente à entidade para mante-lo ativo e executando.
Compile e rode o app. Você poderá ver agora que seu ponto azul pisca lentamente repetidas vezes.
Antes de presseguir, delete o código que você acaba de adicionar no método didMoveToView(_:)
. Mais tarde você irá adicionar este código de volta, mas apenas quando o seu ponto azul entrar em estado de invulnerabilidade.
3. Máquina de estado
No Gameplaykit, a máquina de estado fornece uma forma para você identificar e realizar facilmente tarefas com base no estado atual de um objeto específico. Desenhando para nosso exemplo anterior de tower defense, alguns estados possíveis para cada torre poderia incluir Ativo
, Inativo
e Destruído
. Uma grande vantagem da máquina de estados é que você pode especificar para quais estados outro estado pode se mover. Com os três estados de exemplo mencionados acima, usando uma máquina de estado, você poderia definir a máquina de estado da seguinte forma:
- uma torre pode torna-se
Inativa
quandoAtiva
ou vice versa. - uma torre pode torna-se
Destruída
quando estiverAtiva
ouInativa
- um torre não pode tornar-se
Ativa
ouInativa
uma vez que estiverDestruída
.
No jogo para este tutorial, nós iremos manter-lo muito simples e ter apenas os estados normal e invulnerável.
Em sua pasta State Machine do projeto, crie duas novas classes. Nomeia-as respectivamente de NormalState
e InvulnerableState
, com ambos sendo uma subclasse da classe GKState
.



Substitua o conteúdo do arquivo NormalState.Swift com o seguinte:
1 |
import UIKit |
2 |
import SpriteKit |
3 |
import GameplayKit |
4 |
|
5 |
class NormalState: GKState { |
6 |
|
7 |
var node: PlayerNode |
8 |
|
9 |
init(withNode: PlayerNode) { |
10 |
node = withNode |
11 |
}
|
12 |
|
13 |
override func isValidNextState(stateClass: AnyClass) -> Bool { |
14 |
switch stateClass { |
15 |
case is InvulnerableState.Type: |
16 |
return true |
17 |
|
18 |
default: |
19 |
return false |
20 |
}
|
21 |
}
|
22 |
|
23 |
override func didEnterWithPreviousState(previousState: GKState?) { |
24 |
if let _ = previousState as? InvulnerableState { |
25 |
node.entity.removeComponentForClass(FlashingComponent) |
26 |
node.runAction(SKAction.fadeInWithDuration(0.5)) |
27 |
}
|
28 |
}
|
29 |
}
|
A classe NormalState
contém o seguinte:
- Ele implementa um simples inicializador para manter uma referência para o nó corrente do jogador.
- Ele tem uma implementação para o método
isValidNextState(_:)
. Este método retorna um valor booleano, indicando se a classe do estado atual pode ou não se mover para a classe do estado fornecido no parâmetro. - A classe também inclui uma implementação para o método de retorno
didEnterWithPreviousState(_:)
. Na implementação do método, nós verificamos se o estado anterior era o estadoInvulnerableState
e, se for, removemos o componente de piscar da entidade do jogador.
Agora abra o arquivo InvulnerableState.swift e substitua o seu conteúdo com o seguinte:
1 |
import UIKit |
2 |
import GameplayKit |
3 |
|
4 |
class InvulnerableState: GKState { |
5 |
|
6 |
var node: PlayerNode |
7 |
|
8 |
init(withNode: PlayerNode) { |
9 |
node = withNode |
10 |
}
|
11 |
|
12 |
override func isValidNextState(stateClass: AnyClass) -> Bool { |
13 |
switch stateClass { |
14 |
case is NormalState.Type: |
15 |
return true |
16 |
|
17 |
default: |
18 |
return false |
19 |
}
|
20 |
}
|
21 |
|
22 |
override func didEnterWithPreviousState(previousState: GKState?) { |
23 |
if let _ = previousState as? NormalState { |
24 |
// Adding Component
|
25 |
let flash = FlashingComponent() |
26 |
flash.nodeToFlash = node |
27 |
flash.startFlashing() |
28 |
node.entity.addComponent(flash) |
29 |
}
|
30 |
}
|
31 |
}
|
A classe InvulnerableState
é muito similar a classe NormalState
. A principal diferença é que ao entrar no estado você adiciona o componente de piscar para a entidade jogador ao invés de remove-la.
Agora que suas classes de estado estão ambas completas, abra o arquivo PlayerNode.swift novamente e adicione as seguintes linhas à classe PlayerNode
:
1 |
var stateMachine: GKStateMachine! |
2 |
|
3 |
func enterNormalState() { |
4 |
self.stateMachine.enterState(NormalState) |
5 |
}
|
Este trecho de código adiciona uma nova propriedade à classe PlayerNode
e implementa um método de conveniência para voltar ao estado normal.
Agora abra o arquivo GameScene.swift e, no final do método didMoveToView(_:)
, adicione as duas linhas a seguir:
1 |
playerNode.stateMachine = GKStateMachine(states: [NormalState(withNode: playerNode), InvulnerableState(withNode: playerNode)]) |
2 |
playerNode.stateMachine.enterState(NormalState) |
Nestas duas linhas de código, nós criamos um novo GKStateMachine
com os dois estados e dizemos a ele para entrar no NormalState
.
Por ultimo, substituímos a implementação do método handleContactWithNode(_:)
da classe GameScene
com a implementação a seguir:
1 |
func handleContactWithNode(contact: ContactNode) { |
2 |
if contact is PointsNode { |
3 |
NSNotificationCenter.defaultCenter().postNotificationName("updateScore", object: self, userInfo: ["score": 1]) |
4 |
}
|
5 |
else if contact is RedEnemyNode && playerNode.stateMachine.currentState! is NormalState { |
6 |
NSNotificationCenter.defaultCenter().postNotificationName("updateScore", object: self, userInfo: ["score": -2]) |
7 |
|
8 |
playerNode.stateMachine.enterState(InvulnerableState) |
9 |
playerNode.performSelector("enterNormalState", withObject: nil, afterDelay: 5.0) |
10 |
}
|
11 |
else if contact is YellowEnemyNode && playerNode.stateMachine.currentState! is NormalState { |
12 |
self.playerNode.enabled = false |
13 |
}
|
14 |
|
15 |
contact.removeFromParent() |
16 |
}
|
Quando o ponto azul do jogador colidir com um ponto vermelho, o jogador entrará no estado InvulnerableState
por cinco segundos e então volta para o estado NormalState
. Também verificamos qual o estado atual do jogador e apenas executamos qualquer lógica relacionada ao inimigo se ele estiver no estado NormalState
.
Compile e execute seu app uma ultima vez e se mova pelo mapa até achar um ponto vermelho. Quando você colidir com um ponto vermelho, você verá que o seu ponto azul entrará no estado invulnerável e piscará por cinco segundos.
Conclusão
Neste tutorial, eu introduzi à você dois dos aspectos principais do framework GameplayKit, entidades e componentes, e máquina de estados. Eu mostrei a você como você pode usar as entidades e os componentes para estruturar seu modelo de jogo e manter tudo organizado. O uso dos componentes é uma forma muito fácil de compartilhar funcionalidades entre objetos do seu jogo.
Eu também mostrei o básico das máquinas de estado, inclusive como você pode especificar que um determinado estado pode fazer a transição para outro, bem como executar um código quando um determinado estado é inserido.
Fique atento para a segunda parte desta série onde nós iremos levar este jogo para um novo nível adicionando um pouco de inteligência artificial, mais conhecida como IA. A IA irá permitir que os pontos inimigos se direcionem para o jogador e procurem o melhor caminho para alcança-lo.
Como sempre, se você tiver algum comentário ou pergunta, deixe eles nos comentários abaixo.
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!