Advertisement
  1. Code
  2. Mobile Development
  3. iOS Development

SpriteKit From Scratch: Físicas e Colisões

Scroll to top
Read Time: 11 min
This post is part of a series called SpriteKit From Scratch.
SpriteKit From Scratch: Constraints and Actions
SpriteKit From Scratch: Visual and Audio Effects

() translation by (you can also view the original English article)

Introdução

Neste tutorial, a terceira parte da série SpriteKit From Scratch, vamos dar uma olhada detalhada na funcionalidade de simulação de física do SpriteKit e como ela pode ser utilizada em seus jogos 2D.

Este tutorial requer que você esteja rodando o Xcode 7.3 ou superior, que inclui o Swift 2.2 e os SDKS do iOS 9.3, tvOS 9.2 e OS X 10.11.4.

Para me seguir, você pode usar o projeto criado no tutorial anterior ou baixar uma nova cópia no GitHub.

As imagens usadas pra o jogo desta série podem ser encontradas no GraphicRiver. O GraphicRiver é uma ótima fonte para encontrar trabalhos artísticos e imagens para seus jogos.

1. Mundos Físicos

A primeira parte de qualquer simulação de física no SpriteKit é a propriedade physicsWorld da cena atual. Esta propriedade é um objeto SKPhysicsWorld que define propriedades como a gravidade, velocidade da simulação da física e representante de contato da cena. Os mundos físicos também pode definir articulações entre dois objetos para fixar de forma eficaz vários nós em pontos específicos.

Gravidade

Para o jogo no estilo de vista superior como estamos criamos nesta série, queremos mudar o valor padrão de gravidade fornecido pelo SpriteKit. A gravidade padrão é planejada para um jogo de vista frontal com um valor de (0, -9.8) que simula a gravidade da Terra, que é, aceleração horizontal 0 e uma aceleração vertical de 9.8m/s². Pra nosso jogo, precisamos de uma gravidade vertical de 0, assim o carro não vai começar a acelerar para baixo uma vez que atribuirmos suas propriedades físicas

Abra o MainScene.sks e clique no fundo cinza para selecionar a cena. Em seguida, abra o Attributes Inspector e altere o Gravity em ambos os componentes, X e Y, para 0.

Zero Scene GravityZero Scene GravityZero Scene Gravity

É importante observar que apenas a gravidade da propriedade mundo físico pode ser alterada usando o editor de cena do Xcode. Qualquer outra propriedade precisa ser alterada via programação.

Representante do contato

Para que o jogo possa detectar colisões entre dois objetos, precisamos atribuir a propriedade contactDelegate do mundo físico da cena. Este representando pode ser qualquer objeto que esteja em conformidade com o protocolo SKPhysicsContactDelegate. O protocolo define dois métodos, didBeginContact(_:) e didEndContact(_:). Você pode usar estes método para executar ações baseadas nos objetos que estão colidindo na cena.

Para manter o nosso código em um único lugar, vamos tornar a instância da MainScene o seu próprio representante de contato. Abra o MainScene.swift e edite a definição da classe MainScene para ficar em conformidade com o protocolo SKPhysicsContactDelegate.

1
import UIKit
2
import SpriteKit
3
4
class MainScene: SKScene, SKPhysicsContactDelegate {
5
6
    ...
7
8
}

No didMoveToView(_:), definimos a instância MainScene como o representante de contato da propriedade physicsWorld.

1
override func didMoveToView(view: SKView) {
2
3
    ...
4
    
5
    physicsWorld.contactDelegate = self
6
}

Iremos implementar os métodos do protocolo SKPhysicsContactDelegate mais tarde. Primeiro precisamos configurar as propriedades de física dos nós na cena.

2. Corpo físico

Qualquer nó no SpriteKit que você queira simular a física de alguma forma deve ser atribuído um único objeto SKPhysicsBody. Os corpos físicos contém várias propriedades, incluindo:

  • massa
  • densidade
  • área
  • atrito
  • velocidade

Em seus jogos, você também pode atribuir 32 categorias únicas e um corpo físico pode ser atribuído para qualquer uma dessas categorias. As categorías são muito úteis para determinar quais nós em sua cena podem interagir com outros em termos de colisões.

Nos corpos físicos, estas categorias são representadas pelas propriedades categoryBitMask e collisionBitMask, que ambas contem o valor 0xFFFFFFFF por padrão. Isso significa que todos os nós pertencem a todas as categorias. É importante notar que neste valor, cada dígito hexadecimal F é uma forma de taquigrafia e representa o número 15 em dígitos binários (1111) cada, dos quais correspondem a uma das 32 categorias que você pode usar.

Quando dois nós se colidem, uma operação lógica AND é processada entre a colissionBitMask e a categoryBitMask. Se o resultado for um valor diferente de zero, então o SpriteKit executa sua simulação entre o corpo dos dois nós.

Note que este cálculo AND é executado duas vezes com os dois corpos trocados. Por exemplo.

  • Calculo 1. bodyA.collisionBitMask & bodyB.categoryBitMask
  • Calculo 2: bodyB.collisionBitMask & bodyA.categoryBitMask

Se você não sabe como um operador AND funciona, então aqui esta um exemplo simples escrito em Swift.

1
let mask1 = 0x000000FF
2
let mask2 = 0x000000F0
3
4
let result = mask1 & mask2 // result = 0x000000F0

O operador AND determina quais partes das máscaras de bits são os mesmos e retorna um novo valor de máscara de bits que contém as partes correspondentes.

Uma coisa importante é que estas mascaras de bits afetam apenas o lado da simulação de física do SpriteKit e você não será notificado de colisões detectadas dessa maneira. Isso significa que os corpos podem interagir entre si, mas nenhum dos métodos do representante de contato serão chamados.

Para que estes métodos sejam executados, você deve especificar um contactTestBitMask para cada corpo, que gera um valor diferente de zero quando um operador AND atuar neles. Para todos os corpos físicos, esta mascara de bits tem um valor padrão de 0x00000000, o que significa que você não pode ser notificado de qualquer colisão que os corpos físicos façam parte.

Copos físicos, incluindo suas várias mascaras de bits, podem ser configuradas no editor de cena. Abra o MainScene.sks, selecione o carro e abra o Attributes Inspector na direita. Desça até a seção Physics Definition.

Physics Definition SectionPhysics Definition SectionPhysics Definition Section

Como o Body Type está definido como None, nenhuma das propriedades relacionadas a física são visíveis. Para alterar isso, precisamos atribuir um valor diferente de None ao Body Type. Três tipos de corpos estão disponíveis:

  • bounding rectangle
  • bounding circle
  • alpha mask
Physics Body TypesPhysics Body TypesPhysics Body TypesTh

Estes três tipos de corpos físicos são os mais comuns no SpriteKit. O Bounding rectangle e o Bounding circle funcionam criando uma barreira em torno do sprite para ser usado na simulação da física. Isso significa que o sprite colide com outro nó sempre que o limite da sua forma delimitadora atingir o corpo físico de outro nó.

A borda de um retângulo delimitador é exatamente do mesmo tamanho do nó mostrado no editor de cena. Se você selecionar Bounding circule, entretanto, você verá um fino circulo azul representando a forma do circulo delimitador.

Bounding Circle Body TypeBounding Circle Body TypeBounding Circle Body Type

Alpha mask funciona um pouco diferente e verifica a textura real da imagem do sprite para determinar as bordas do corpo físico. Este tipo de corpo é de longe o mais preciso no SpriteKit, mas ele tem um grande impacto na performance do seu jogo, em particular quando se está usando sprite com formas complexas.

Para nosso jogo, já que usamos apenas o sprite de um carro e nossa cena não é particularmente complexa, iremos usar o corpo do tipo Alpha mask. Não é recomendando usar este tipo de corpo para todos os sprites em sua cena, mesmo sendo mais preciso. Quando você seleciona esta opção, você poderá ver uma linha azul aparecendo ao redor das bordas do carro.

Alpha Mask Body TypeAlpha Mask Body TypeAlpha Mask Body Type

É importante notar que outros tipos de corpos físicos podem ser criados via programação, tais como corpos de objetos CGPath, bem como círculos e retângulos de tamanhos personalizados.

Ainda no Attributes Inspector, você poderá ver agora mais opções disponíveis para você na seção Physics Definition. A única propriedade que precisamos mudar é o Contact Mask. Altere seu valor para 1.

Contact Mask of 1Contact Mask of 1Contact Mask of 1

Com o corpo físico do carro configurado, podemos começar colocando alguns obstáculos no jogo para colidir com o carro.

3. Detectando colisões

Antes de implementarmos os métodos do protocolo SKPhysicsContactDelegate, precisamos adicionar alguns obstáculos para o carro evitar. Para fazer isso, iremos colocar na frente do carro um novo obstáculo a cada três segundos e posicionar os obstáculos em uma pista aleatória.

Abra o MainsScene.swift e adicione uma instrução de import para o framework GameplayKit, assim podemos usar o gerador de números aleatórios fornecido pelo GameplayKit.

1
import GameplayKit

Em seguida, adicione o seguinte método na classe MainScene:

1
func spawnObstacle(timer: NSTimer) {
2
    if player.hidden {
3
        timer.invalidate()
4
        return
5
    }
6
    
7
    let spriteGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 2)
8
    let obstacle = SKSpriteNode(imageNamed: "Obstacle \(spriteGenerator.nextInt())")
9
    obstacle.xScale = 0.3
10
    obstacle.yScale = 0.3
11
    
12
    let physicsBody = SKPhysicsBody(circleOfRadius: 15)
13
    physicsBody.contactTestBitMask = 0x00000001
14
    physicsBody.pinned = true
15
    physicsBody.allowsRotation = false
16
    obstacle.physicsBody = physicsBody
17
    
18
    let center = size.width/2.0, difference = CGFloat(85.0)
19
    var x: CGFloat = 0
20
    
21
    let laneGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 3)
22
    switch laneGenerator.nextInt() {
23
    case 1:
24
        x = center - difference
25
    case 2:
26
        x = center
27
    case 3:
28
        x = center + difference
29
    default:
30
        fatalError("Number outside of [1, 3] generated")
31
    }
32
    
33
    obstacle.position = CGPoint(x: x, y: (player.position.y + 800))
34
    addChild(obstacle)
35
}

Este método é chamado a cada três segundos. Vamos destrincha-lo abaixo para ver o que esta acontecendo. Se o nó do jogador estiver invisível, o que acontecerá quando o carro atingir um obstáculo, então invalidamos o timer e paramos a criação dos obstáculos.

Obtemos um número aleatorio entre 1 e 2, e o usamos para criar um nó de sprite com um dos dois sprites de obstáculos disponíveis no projeto. Então alteramos a escala do obstáculos, para que eles sejam pequenos o suficiente para que o carro possa desviar.

Em seguida, criamos um corpo físico para este novo obstáculo com um circulo com um raio de 15 e uma máscara de contato 0x00000001. Atribuímos true para a pinned e false para a allowsRotation, assim o obstáculo ficará no lugar e não se moverá. O corpo físico é então atribuído ao obstáculo.

Geramos outro número aleatorio entre 1 e 3, para determinar em qual pista o obstáculo irá aparecer e damos ao obstáculo sua posição calculada e o adicionamos a cena.

Perceba que a diferença na horizontal, 85, usado no spawnObstacle(_:) é diferente da que usamos quando movemos o carro, 70. Fazemos isso para dar um pouco mais de espaço para o carro se mover entre os obstáculos.

Com a lógica de aparição dos obstáculos no lugar, podemos adicionar o seguinte código no final do método didMoveToView(_:).

1
override func didMoveToView(view: SKView) {
2
    
3
    ...
4
5
    let timer = NSTimer(timeInterval: 3.0, target: self, selector: #selector(spawnInObstacle(_:)), userInfo: nil, repeats: true)
6
    NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
7
    
8
    let camera = SKCameraNode()
9
    self.camera = camera
10
    camera.position = CGPoint(x: center, y: player.position.y + 200)
11
    let moveForward = SKAction.moveBy(CGVectorMake(0, 100), duration: 1.0)
12
    camera.runAction(SKAction.repeatActionForever(moveForward))
13
    addChild(camera)
14
    
15
    player.xScale = 0.4; player.yScale = 0.4 // Makes car smaller to fit better between obstacles

16
}

Criamos um timer para executar o spawObstacle(_:) a cada três segundos e adicionamos ele ao loop principal de execução. Também criamos um SKCameraNode que atua como a câmera para a cena e a atribuímos para a propriedade camera da cena. Isso faz com que a cena seja renderizada a partir do ponto de vista do nó da câmera. Veja que a câmera é centralizada na horizontal e ligeiramente acima do carro.

Adicionamos também um ação idêntica para a câmera, para que ela fique alinhada acima do carro. Adicionamos a câmera como um nó filho da cena como qualquer outro nó regular. Por último mas não menos importante, reduzimos a escala do carro para que ele possa se encaixar um pouco melhor entre os obstáculos.

A última parte para a detecção de colisão é implementar um dos métodos do protocolo SKPhysicsContactDelegate. No nosso caso, como queremos ser notificados assim que o carro acerta um obstáculo vamos implementar o método didBeginContact(_:). Adicione o seguinte método à classe MainScene.

1
func didBeginContact(contact: SKPhysicsContact) {
2
    if contact.bodyA.node == player || contact.bodyB.node == player {
3
        player.hidden = true
4
        player.removeAllActions()
5
        camera?.removeAllActions()
6
    }
7
}

Você pode ver que este método recebe um parâmetro SKPhysicsContact. Este método contém as principais informações sobre a colisão, incluindo sua direção, impulso e os objetos envolvidos.

Neste código, estamos preocupados apenas sobre quais nós estão envolvidos na colisão. Verificamos se um deles é o carro, se for, tornamos o carro invisível e paramos o movimento do carro e da câmera.

Compile e execute o seu app para jogar. Você verá que, quando o jogador acerta um obstáculo o carro desaparece e a cena para de se mover.

Scene After CollisionScene After CollisionScene After Collision

Conclusão

Agora você deve saber como configurar os corpos físicos para os nós em sua cena e usar-los para simular físicas realistas. Você também deve estar confortável para configurar a detecção de colisão em sua cena, definindo um representante de contato e atribuir máscaras de bits para o teste de contato dos nós que você espera que se colidem entre si.

No próximo tutorial de SpritKit From Scratch, iremos olhar a funcionalidade visual mais avançado em SpriteKit , incluindo sistema de partículas, luzes e filtros.

Como sempre, deixa seus comentários e feedback nos comentários abaixo.

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

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
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.