Skip to content

Instantly share code, notes, and snippets.

@murilogteixeira
Last active April 1, 2020 21:34
Show Gist options
  • Save murilogteixeira/d51fc1568467f6418b0b0d14f412c44d to your computer and use it in GitHub Desktop.
Save murilogteixeira/d51fc1568467f6418b0b0d14f412c44d to your computer and use it in GitHub Desktop.
Modelo inicial de uma Máquina de Estados usando GKStateMachine

GKStateMachine

Clique aqui para ver um exemplo de projeto para iOS e MacOS no GitHub

O que é?

É uma máquina de estados que contém uma coleção de objetos de estados que definem a lógica específica do jogo e as regras para transição de estados.

No GamePlayKit podemos usar o GKState para definir cada estado do seu jogo com as regras específicas de cada estado. Usando uma instância de GKStateMachine podemos gerenciar os estados do jogo. Com isso é possível organizar o código do jogo de forma a controlar quando entramos e saímos em um estado definindo todas as ações e comportamentos de cada estado em específico.

Formas de uso

Com a máquina de estado podemos controlar vários aspectos do jogo. Como por exemplo:

  • Um personagem pode usar a máquina de estados para controlar os estados de Correr, Fugir, Morrer, Reaparecer, no qual podemos definir os comportamentos a serem executados em cada estado.
  • Uma torre de disparo pode controlar os estados de Pronto, Carregar, Disparar controlando quando procura alvos próximos e a frequência do disparo.
  • Uma interface de usuário do jogo pode usar os estados Menu, Jogar, GameOver, Pause para definir elementos específicos de cada tela e elementos que estão em execução.

Como construir uma Máquina de Estados

Primeiro criamos uma subclasse de GKState distinta para cada estado possível do jogo. Em cada classe de estado, o método isValidNextState(_:) determina para quais estados pode ocorrer a transição a partir da dela. Em seguida crie um objeto de máquina de estado e construa instâncias das classes de estados e passe para o contrutor da máquina de estados. Exemplo:

let states = [
  MenuState(),
  PlayState(),
  GameOverState()
]

let gameState = GKStateMachine(states: states)

Então, coloque a máquina de estados para funcionar entrando no primeiro estado chamando a função enter(:_) e passando a classe do estado que deseja entrar:

gameState(enter: MenuState.self)

Definindo o comportamento de cada estado

Para definir o comportamento específico de cada estado, na classe do estado, sobrescreva os métodos didEnter(from:), update(deltaTima:) e willExit(to:).

A máquina de estados notifica cada estado sempre que uma mudança ocorre. Nos métodos didEnter(from:) e willExit(to:) podemos executar ações em resposta a essas mudanças, como por exemplo, alterar a aparencia de um personagem quando ele está no estado de Fuga sinalizando que está vulnerável ao ataque inimigo.

O método update(deltaTime:) funciona para definir ações a serem executadas por quadro, por exemplo alterar a posição do seu personagem.

Esqueleto de uma máquina de estado

Aqui há dois arquivos como um modelo inicial para guiar na criação da sua máquina de estados que eu costumo utilizar em meus projetos. Nesse caso, cada estado será uma tela, mas isso não impede de usar o mesmo princípio para as demais situações.

O arquivo GameScene.swift contém a base para a máquina de estados, é onde iniciamos a nossa máquina definindo o objeto que vai gerenciar nossos estados.

O arquivo State.swift é o arquivo base para cada estado, nele contém a implementação dos métodos principais para o funcionamento da máquina de estados e você pode replicar para arquivo inicial de todos os estados.

Existem comentários em todo o código para explicar a funcionalidade de cada linha. Caso necessite, pode criar da forma que desejar utilizando os conceitos apresentados acima. Esses documentos servem para mostrar uma aplicação real da máquina de estados para um jogo utilizando GKStateMachine.

Espero ter ajudado você a entender e a criar a sua máquina de estados e utilizar de forma a deixar o seu código e seu jogo mais eficiente.

Fonte: GKStateMachine - GamePlayKit | Apple Developer Documentation

import SpriteKit
// MARK: GameScene
class GameScene: SKScene {
// gameState: controla todos os estados do jogo
lazy var gameState = GKStateMachine(states: self.states)
// states: estados do jogo
lazy var states = [
// Substitua State pela suas classes de estado
State(gameScene: self),
State(gameScene: self)
]
// controlNode: nó de controle (nó pai) que contém os filhos usados em todas as cenas como por exemplo os áudios que seu jogo irá executar
lazy var controlNode: SKNode = {
let node = SKNode()
node.position = CGPoint.zero
node.name = "controlNode"
node.zPosition = 0
return node
}()
// MARK: didMove
override func didMove(to view: SKView) {
// Entra no estado
// Substitua IntroState pela sua classe de estado
gameState.enter(IntroState.self)
// Adiciona o nó controle
addChild(controlNode)
// Configura o anchorPoint para o centro da cena
anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
// Chama o update do seu estado atual
override func update(_ currentTime: TimeInterval) {
gameState.currentState?.update(deltaTime: currentTime)
}
override init(size: CGSize) {
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
import SpriteKit
import GameplayKit
// MARK: State
class State: GKState {
// gameScene do jogo
unowned let gameScene: GameScene
// nó de controle da gameScene
var controlNode: SKNode!
// cena desse estado
var scene: SKSpriteNode!
// Personagem da cena
// Substitua Hero pela sua classe de herói
lazy var hero: Hero = {
let hero = Hero()
// configuração inicial do hero
return hero
}()
// Enemy da cena
// Substitua Enemy pela sua classe de inimigo
lazy var enemy: Enemy = {
let enemy = Enemy()
// configuração inicial do enemy
return enemy
}()
// Outro elemento da cena
// Substitua Other pela sua classe de qualquer outro elemento
lazy var other: Other = {
let other = Other()
// configuração inicial do other
return other
}()
// Valida para qual estado esse pode ir
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
// Substitua State1 pela sua classe de estado
case is State1.Type:
return true
// Substitua State2 pela sua classe de estado
case is State2.Type:
return true
default:
return false
}
}
// Entrou nesse estado
override func didEnter(from previousState: GKState?) {
// Inicializa a referencia para o controlNode da gameScene
controlNode = gameScene.controlNode
// Inicializa a cena desse estado
scene = buildScene()
// Adiciona a cena desse estado no nó de controle
controlNode?.addChild(scene)
// Adicionar todos os nós que compõem essa cena
scene.addChild(hero)
scene.addChild(enemy)
scene.addChild(other)
}
// Vai sair desse estado
override func willExit(to nextState: GKState) {
self.scene.removeAllChildren()
self.scene.removeFromParent()
self.controlNode = nil
self.scene = nil
}
// Update realiza as ações que sao executadas por frame
override func update(deltaTime seconds: TimeInterval) {
}
// Construir cena
func buildScene() -> SKSpriteNode {
let node = SKSpriteNode()
node.color = .white
node.size = gameScene.size
node.zPosition = 1
node.name = "sceneName"
return node
}
// MARK: init
init(gameScene: GameScene) {
self.gameScene = gameScene
super.init()
}
}
@jppsantos
Copy link

Muito bom, ótimo material!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment