Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. SpriteKit
Code

Criar Space Invaders com Swift e SpriteKit: Implementando o gameplay

by
Difficulty:BeginnerLength:LongLanguages:
This post is part of a series called Create Space Invaders with Swift and Sprite Kit.
Create Space Invaders with Swift and Sprite Kit: Implementing Classes
Create Space Invaders with Swift and Sprite Kit: Finishing Gameplay

Portuguese (Português) translation by David Batista (you can also view the original English article)

Final product image
What You'll Be Creating

Na parte anterior desta série, nós implementamos o esboço das classes principais do jogo. Neste tutorial, nós iremos criar o movimento para os invasores, disparos ambos, invasores e jogador, e implementaremos a detecção de colisão. Vamos começar.

1. Movendo os invasores

Iremos usar o método update do cenário para mover os invasores. Sempre que você quiser mover algo manualmente, o método update geralmente será onde você fará isso.

Antes de fizermos isso, nós precisamos atualizar a propriedade rightBounds. Ele foi inicialmente definido como 0, porque precisamos usar o size do cenário para definir a variável. Fomos capazes de fazer isso fora de qualquer um dos métodos da classe, então vamos atualizar essa propriedade no método didMoveToView(_:).

Em seguida, implemente o método moveInvaders abaixo do método setupPlayer que você criou no tutorial anterior.

Nós declaramos uma variável, changeDirection, para controlar quando os invasores precisam mudar de direção, movendo para esquerda ou para direita. Em seguida, nós usamos o método enumerateChildNodesWithName(usingBlock:), que procura um nó filho e chama a closure uma vez para cada nó encontrado com o nome correspondente a "invader". A closure recebe dois parâmetros, node é o nó que corresponde ao name e stop é um ponteiro para uma variável booleana para finalizar a enumeração. Nós não iremos usar o stop aqui, mas é bom saber para que ele é usado.

Nós fazemos o downcast do node para uma instância SKSpriteNode a qual o invader é uma subclasse, pegamos a metade de sua largura, invaderHaldWidth, e atualizamos sua posição. Em seguida, verificamos se sua posição esta dentro dos limites das bordas, leftBounds e rightBounds, e, se não, definimos changeDirection para true.

Se changeDirection for igual a true, nós negativamos invaderSpeed, que irá mudar a direção que o invasor se move. Em seguida, enumeramos os invasores e atualizamos sua posição y. Por ultimo, nós definimos changeDirection de volta para false.

O método moveInvaders é chamado no método update(_:).

Se você testar seu aplicativo agora, você verá os invasores movendo-se para esquerda, direita e então para baixo se ele alcançar a borda que estabelecemos para cada lado.

2. Disparando tiros do invasor

Passo 1: fireBullet

Às vezes queremos que um dos invasores dispare um tiro. Da forma que está agora, os invasores na linha de baixo estão definidos para disparar, porque estão no array invadersWhoCanFire.

Quando um invasor recebe um tiro do jogador, então o invasor uma linha acima na mesma coluna será adicionado no array invadersWhoCanFire, enquanto o invasor que recebeu o tiro será removido. Desta forma, apenas o invasor na extremidade de baixo de cada coluna pode disparar.

Adicione o método fireBullet para a classe InvaderBullet no arquivo InvaderBullet.swift.

No método fireBullet, nós instanciamos uma instância InvaderBullet, passamos "laser" para o imageName e como nós não queremos um som nós passamos nil para o bulletSound.  Nós definimos a position para ser a mesma do invasor, com um pequeno deslocamento na posição y, e adicionamos ela ao cenário.

Nós criamos duas instâncias SKAction, moveBulletActionremoveBulletAction. A ação moveBulletAction move a bala para determinado ponto por uma determinada duração, enquanto a ação removeBulletAction remove ela do cenário.  Chamando o método sequence(_:) com estas ações, elas são executadas em sequência. É por isso que eu mencionei o método waitForDuration ao reproduzir um som na parte anterior desta série. Se você criar um objeto SKAction e chamar playSoundFileNamed(_:waitForCompletion:) e definir waitForCompletion como true, a duração da ação será enquanto durar o som, caso contrario ela pulará imediatamente para a próxima ação da sequência.

Passo 2: invokeInvaderFire

Adicione o método invokeInvaderFire abaixo dos outros métodos que você criou no arquivo GameScene.swift.

O método runBlock(_:) da classe SKAction cria uma instância SKAction e imediatamente chama a closure passando o método runBlock(_:). Na closure, nós chamamos o método fireInvaderBullet. Como chamamos este método na closure, nós temos que usar self para chama-lo.

Então, criamos uma instância SKAction chamada waitToFireInvaderBullet chamando waitForDuration(_:), passando o número de segundos de espera antes de seguir. Em seguida, nós criamos uma instância SKAction, invaderFire, chamando o método sequence(_:). Este método recebe uma coleção de ações que são chamadas pela ação invaderFire. Nós queremos que essa sequência repita para sempre, por isso criamos uma ação chamada repeatForeverAction, passando o objeto SKAction para repetir, e chamamos runAction, passando a ação repeatForeverAction. O método runAction é declarado na classe SKNode.

Passo 3: fireInvaderBullet

Adicione o método fireInvaderBullet abaixo do método invokeInvaderFire que você criou no passo anterior.

Neste método, chamamos um método chamado randomElement que parece retornar um elemento aleatório da matriz de invadersWhoCanFire e em seguida, chamamos seu método fireBullet.  Mas infelizmente não existe um método randomElement na estrutura Array. No entanto, podemos criar uma extensão do Array para fornecer essa funcionalidade.

Passo 4: Implemente randomElement

Vá em File > New > File... e escolha Swift File. Estamos fazendo algo diferente de antes, por isso certifique que você está escolhendo Swift File e não Cocoa Touch Class. Pressione Next e chame o arquivo de Utilities. Adicione o seguinte no arquivo Utilities.swift.

Nós estendemos a estrutura Array para que tenha um método chamado randomElement. A função arc4random_uniform retorna um número entre 0 e qualquer um número passado. Como o Swift não converte implicitamente tipos numéricos, nós teremos que converter nós mesmos. Finalmente, nós retornamos o elemento do array do índice index.

Este exemplo ilustra como é fácil adicionar funcionalidade em estruturas e classes. Você pode ler mais sobre criar extensões no The Swift Programming Language.

Passo 5: Disparando a bala

Com tudo isso feito, nós podemos disparar agora. Adicione o seguinte método didMoveToView(_:).

Se você testar o aplicativo agora, a cada segundo, você poderá ver os invasores da primeira linha disparando.

3. Disparando tiros do jogador

Passo 1: fireBullet(scene:)

Adicione a seguinte propriedade na classe Player no arquivo Player.swift.

Nós temos que limitar com que frequência o jogador pode disparar. A propriedade canFire será usada para regular isto. Em seguida, adicione o seguinte método, fireBullet(scene:), na classe Player.

Nós primeiro nos certificamos que o jogador poderá disparar verificando se canFire está definido com true. Se não estiver, nós imediatamente saímos do método.

Se o jogador puder disparar, nós definimos a propriedade canFire como false, de modo que ele não possa fazer outro disparo imediatamente. Nós então instanciamos uma instância de PlayerBullet, passando "laser" para o parâmetro imageNamed. Como queremos tocar um som quando o jogador disparar, nós passamos "laser.mp3" para o parâmetro bulletSound.

Nós então definimos a posição da bala e a adicionamos na tela. As linhas seguintes são iguais ao método fireBullet da classe Invader, em que nós movemos as balas e removemos elas do cenário. Em seguida, nós criamos uma instância SKAction, waitToEnableFire, chamando o método de classe waitForDuration(_:). Por ultimo, nós chamamos o método runAction, passando waitToEnableFire e para completar voltamos a propriedade canFire para true.

Passo 2: Disparando a bala do jogador

Sempre que o usuário tocar na tela, nós queremos disparar uma bala. Isto é muito simples, basta chamar o método fireBullet do objeto player no método touchesBegan(_:withEvent:) da classe GameScene.

Se você testar seu aplicativo agora, você deverá ser capaz de disparar quando tocar na tela. Você também poderá ouvir o som de laser sempre que um tiro for disparado.

4. Categorias de Colisão

Para detectar quando os nós se colidem ou fazem contato entre si, nós usaremos o motor de fisica do Sprite Kit. Contudo, o comportamento padrão do motor de física é que tudo colida com tudo quando um corpo físico for adicionado a eles. Nós precisamos de uma forma de separar qual nó queremos que interaja com qual e podemos fazer isso criando categorias que especifique os corpos físicos.

Você define essas categorias usando um mascara bit que usa uma flag interger de 32-bit que pode ser ativada ou desativada. Isso também significa que você pode ter apenas 32 categorias em seu jogo. Isto não apresenta um problemas para a maioria dos jogos, mas isto é algo que você deve ter em mente.

Adiciona a definição da estrutura a seguir na classe GameScene, abaixo da declaração da propriedade invaderNum no arquivo gameScene.swift.

Nós usamos a estrutura, CollisionCategories, para criar categorias para as classes Invader, Player, InvaderBullet e PlayerBullet. Nós estamos usando o deslocamento de bit para ligar os bits.

5. Colisão entre Player e InvaderBullet

Passo 1: Configurando InvaderBullet para colisão

Adicione o bloco de código abaixo, o método init(imageName:bulletSound:), no arquivo InvaderBullet.swift.

Existem várias maneiras de criar um corpo físico. Neste exemplo, usamos o inicializador init(texture:size:), que fará com que a detecção de colisão use a forma da textura que passarmos. Existem vários outros inicializadores disponíveis, que você pode ver na referência da classe SKPhysicsBody.

Nós poderíamos facilmente usar o inicializador init(rectangleOfSize:), já que as balas tem um formato retangular. Em um jogo deste tamanho isto não fará diferença. No entanto, esteja ciente que usar o método init(texture:size:) pode ser computacionalmente caro já que é necessário calcular a forma exata da textura. Se você tem objetos que tem o formato retangulares ou circulares, então você deve usar esses tipos de inicializadores, se o desempenho do jogo estiver se tornando um problema.

Para a detecção de colisão funcionar, pelo menos um dos corpos que está testando tem de ser marcado como dinâmico. Definindo a propriedade usesPreciseCollisionDetection como true, o Sprite Kit utilizará uma detecção de colisão mais precisa. Defina esta propriedade como true em corpos pequenos e de movimento rápido como os nossos tiros.

Cada corpo pertence a uma categoria e você define isso definindo sua categoryBitMask. Já que estamos na classe de InvaderBullet, podemos configurá-la para CollisionCategories.InvaderBullet.

Para saber quando este corpo faz contato com outro corpo que você tem interesse, você define o contactBitMask. Aqui nós queremos saber quando o invaderBullet faz contato com o jogador, então usamos CollisionCategories.Player. Já que uma colisão não deve provocar qualquer força física, definimos a collisionBitMask com 0x0.

Passo 2: Configurando o Player para colisão

Adicione o método init abaixo, no arquivo Player.swift.

Muito disto será parecido com o passo anterior, por isso não irei explicar novamente aqui. Existem duas diferenças a se notar no entanto. Uma é que o usesPreciseCollsionDetection foi definida com false, que é o padrão. Isto é importante para perceber que apenas um dos corpos em contato precisa desta propriedade definida com true (que será a bala). A outra diferença é que nós também queremos saber quando o jogador entra em contato com o invasor. Você pode ter mais de uma categoria em contactBitMask separando elas com o operador or (|). Fora isso, você notará que é basicamente o posto do InvaderBullet.

6. Colisão entre Invader e PlayerBullet

Passo 1: Configurando o Invader para colisão

Adicione o método init abaixo, no arquivo Invader.swift.

Isso deve fazer sentido se você estiver seguindo todo o tutorial. Nós definimos a physicsBodycategoryBitMask e contactBitMask.

Passo 2: Configurando PlayerBullet para colisão

Adicione o método init(imageName:bulletSound:) abaixo, no arquivo PlayerBullet.swift. Novamente, a implementação deve ser familiar.

7. Configurando a física para GameScene

Passo 1: Configurando o mundo fisico

Nós devemos configurar a classe GameScene para implementar o SKPhysicsContactDelegate, então nós poderemos responder quando dois corpos se colidirem. Adicione o seguinte para fazer a classe de GameScene ficar em conformidade com o protocolo SKPhysicsContactDelegate.

Em seguida, temos de configurar algumas propriedades na physicsWorld do cenário. Insira o seguinte código na parte superior do método didMoveToView(_:) do arquivo GameScene.swift.

Nós configuramos a propriedade gravity da physicsWorld com 0, de modo que nenhum corpo físico no cenário seja afetado com a gravidade. Você também pode fazer isso corpo por corpo ao invés de definir para o mundo inteiro não ter gravidade, definindo a propriedade affectedByGravity. Nós também definimos a propriedade contactDelegate do mundo físico para self, a instância de GameScene.

Passo 2: Implementando o protocolo SKPhysicsContactDelegate

Para a classe GameScene ficar em conformidade com o protocolo SKPhysicsContactDelegate, nós precisamos implementar o método didBeginContact(_:). Este método é chamada quando dois corpos entram em contato. A implementação do método didBeginContact(_:) ficará da seguinte forma.

Nós primeiro declaramos duas variáveis firstBody e secondBody. Quando dois corpos entrarem em contato, nós não saberemos qual corpo é qual. Isto significa que primeiro é preciso fazer algumas verificações para certificar-se de que firstBody é aquele com a menor categoryBitMask.

Em seguida, passamos por cada cenário possível usando o operador & e as categorias de colisão que definimos anteriormente para verificar quem está fazendo contato. Nós registraremos o resultado no console para certificar-se de que tudo está funcionando como deveria. Se você testar o aplicativo, todos os contatos deveram estar trabalhando corretamente.

Conclusão

Este foi um tutorial um pouco longo, mas agora temos os invasores se movendo, balas sendo disparadas do jogador e dos invasores e a detecção de contato trabalhando usando as máscaras de bits. Estamos na reta final do jogo. Na próxima, e final, parte desta serie, nós teremos um jogo completo.





Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.