Como construir um jogo SpriteKit no Swift 3 (Parte 3)

Publicados: 2022-03-10
Resumo rápido ↬ Você já se perguntou o que é preciso para criar um jogo SpriteKit? Os botões parecem uma tarefa maior do que deveriam ser? Você já se perguntou como persistir as configurações em um jogo? A criação de jogos nunca foi tão fácil no iOS desde a introdução do SpriteKit. Na terceira parte desta série de três partes, terminaremos nosso jogo RainCat e completaremos nossa introdução ao SpriteKit. Se você perdeu a lição anterior, pode recuperar o atraso obtendo o código no GitHub. Lembre-se que este tutorial requer Xcode 8 e Swift 3.

Você já se perguntou o que é preciso para criar um jogo SpriteKit? Os botões parecem uma tarefa maior do que deveriam ser? Você já se perguntou como persistir as configurações em um jogo? A criação de jogos nunca foi tão fácil no iOS desde a introdução do SpriteKit. Na terceira parte desta série de três partes, terminaremos nosso jogo RainCat e completaremos nossa introdução ao SpriteKit.

Se você perdeu a lição anterior, pode recuperar o atraso obtendo o código no GitHub. Lembre-se que este tutorial requer Xcode 8 e Swift 3.

Leitura adicional no SmashingMag: Link

  • Gamificação e UX: onde os usuários ganham ou perdem
  • Design UX lúdico: construindo um jogo melhor
  • Combinando UX Design e Psicologia para Mudar o Comportamento do Usuário
Raincat, lição 3
RainCat, lição 3
Mais depois do salto! Continue lendo abaixo ↓

Esta é a lição três em nossa jornada RainCat. Na lição anterior, tivemos um longo dia passando por algumas animações simples, comportamentos de gatos, efeitos sonoros rápidos e música de fundo.

Hoje vamos nos concentrar no seguinte:

  • heads-up display (HUD) para pontuação;
  • menu principal — com botões;
  • opções para silenciar sons;
  • opção de sair do jogo.

Ainda mais ativos

Os recursos da lição final estão disponíveis no GitHub. Arraste as imagens para Assets.xcassets novamente, como fizemos nas lições anteriores.

Atenção!

Precisamos de uma maneira de manter a pontuação. Para fazer isso, podemos criar um display heads-up (HUD). Isso será bem simples; será um SKNode que contém a pontuação e um botão para sair do jogo. Por enquanto, vamos nos concentrar apenas na pontuação. A fonte que usaremos é a Pixel Digivolve, que você pode obter em Dafont.com. Assim como no uso de imagens ou sons que não são seus, leia a licença da fonte antes de usá-la. Este afirma que é gratuito para uso pessoal, mas se você realmente gosta da fonte, pode doar ao autor da página. Você nem sempre pode fazer tudo sozinho, então retribuir àqueles que o ajudaram ao longo do caminho é bom.

Em seguida, precisamos adicionar a fonte personalizada ao projeto. Este processo pode ser complicado na primeira vez.

Baixe e mova a fonte para a pasta do projeto, em uma pasta “Fontes”. Já fizemos isso algumas vezes nas lições anteriores, então passaremos por esse processo um pouco mais rapidamente. Adicione um grupo chamado Fonts ao projeto e adicione o arquivo Pixel digivolve.otf .

Agora vem a parte complicada. Se você perder esta parte, provavelmente não poderá usar a fonte. Precisamos adicioná-lo ao nosso arquivo Info.plist . Este arquivo está no painel esquerdo do Xcode. Clique nele e você verá a lista de propriedades (ou plist ). Clique com o botão direito do mouse na lista e clique em "Adicionar linha".

Adicionar linha
Adicione uma linha à plist .

Quando a nova linha aparecer, digite o seguinte:

 Fonts provided by application

Então, no Item 0 , precisamos adicionar o nome da nossa fonte. A plist deve ter a seguinte aparência:

Pixel digivolve.otf
Fonte adicionada ao plist com sucesso!

A fonte deve estar pronta para uso! Devemos fazer um teste rápido para garantir que funcione como pretendido. Vá para GameScene.swift e em sceneDidLoad adicione o seguinte código na parte superior da função:

 let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)

Funciona?

Olá Mundo!
Testando nosso SKLabelNode . Ah não! O rótulo "Hello World" está de volta.

Se funcionar, então você fez tudo corretamente. Se não, então algo está errado. Code With Chris tem um guia de solução de problemas mais aprofundado, mas observe que é para uma versão mais antiga do Swift, então você terá que fazer pequenos ajustes para trazê-lo para o Swift 3.

Agora que podemos carregar fontes personalizadas, podemos começar em nosso HUD. Exclua o rótulo “Hello World”, porque o usamos apenas para garantir que nossa fonte seja carregada. O HUD será um SKNode , agindo como um contêiner para nossos elementos HUD. Este é o mesmo processo que seguimos ao criar o nó em segundo plano na lição um.

Crie o arquivo HudNode.swift usando os métodos usuais e insira o seguinte código:

 import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }

Antes de fazer qualquer outra coisa, abra Constants.swift e adicione a seguinte linha ao final do arquivo — nós a usaremos para recuperar e persistir a pontuação mais alta:

 let ScoreKey = "RAINCAT_HIGHSCORE"

No código, temos cinco variáveis ​​que pertencem ao placar. A primeira variável é o SKLabelNode real, que usamos para apresentar o rótulo. A seguir está nossa variável para manter a pontuação atual; em seguida, a variável que detém a melhor pontuação. A última variável é um booleano que nos informa se estamos apresentando a pontuação mais alta no momento (usamos isso para estabelecer se precisamos executar uma SKAction para aumentar a escala do placar e colori-lo para o amarelo do piso).

A primeira função, setup(size:) , existe apenas para configurar tudo. Configuramos o SKLabelNode da mesma forma que fizemos anteriormente. A classe SKNode não possui nenhuma propriedade de tamanho por padrão, portanto, precisamos criar uma maneira de definir um tamanho para posicionar nosso rótulo scoreNode . Também estamos buscando a pontuação mais alta atual de UserDefaults . Essa é uma maneira rápida e fácil de salvar pequenos blocos de dados, mas não é segura. Como não estamos preocupados com a segurança neste exemplo, UserDefaults está perfeitamente bem.

Em nosso addPoint() , estamos incrementando a variável score atual e verificando se o usuário obteve uma pontuação alta. Se eles tiverem uma pontuação alta, salvamos essa pontuação em UserDefaults e verificamos se estamos mostrando a melhor pontuação no momento. Se o usuário obteve uma pontuação alta, podemos animar o tamanho e a cor de scoreNode .

Na função resetPoints() , definimos a pontuação atual como 0 . Em seguida, precisamos verificar se estávamos mostrando a pontuação mais alta e redefinir o tamanho e a cor para os valores padrão, se necessário.

Por fim, temos uma pequena função chamada updateScoreboard . Esta é uma função interna para definir a pontuação para o texto de scoreNode . Isso é chamado em addPoint() e resetPoints() .

Conectando o HUD

Precisamos testar se nosso HUD está funcionando corretamente. Vá para GameScene.swift e adicione a seguinte linha abaixo da variável foodNode na parte superior do arquivo:

 private let hudNode = HudNode()

Adicione as duas linhas a seguir na função sceneDidLoad() , próximo ao topo:

 hudNode.setup(size: size) addChild(hudNode)

Então, na função spawnCat() , redefina os pontos caso o gato tenha caído da tela. Adicione a seguinte linha após adicionar o sprite do gato à cena:

 hudNode.resetPoints()

Em seguida, na função handleCatCollision(contact:) , precisamos redefinir a pontuação novamente quando o gato for atingido pela chuva. Na instrução switch no final da função — quando o outro corpo é RainDropCategory — adicione a seguinte linha:

 hudNode.resetPoints()

Por fim, precisamos informar ao placar quando o usuário ganhou pontos. No final do arquivo em handleFoodHit(contact:) , encontre as seguintes linhas até aqui:

 //TODO increment points print("fed cat")

E substitua-os por isso:

 hudNode.addPoint()

Voilá!

HUD desbloqueado!
HUD desbloqueado!

Você deve ver o HUD em ação. Corra e colete alguns alimentos. A primeira vez que você coletar comida, você verá a pontuação ficar amarela e crescer em escala. Quando você vir isso acontecer, deixe o gato ser atingido. Se a pontuação for redefinida, você saberá que está no caminho certo!

Pontuação máxima!
Maior pontuação de todos os tempos (… no momento da redação)!

A próxima cena

Isso mesmo, estamos passando para outra cena! Na verdade, quando concluído, esta será a primeira tela do nosso aplicativo. Antes de fazer qualquer outra coisa, abra Constants.swift e adicione a seguinte linha ao final do arquivo — nós a usaremos para recuperar e persistir a pontuação mais alta:

 let ScoreKey = "RAINCAT_HIGHSCORE"

Crie a nova cena, coloque-a na pasta “Scenes” e chame-a de MenuScene.swift . Digite o seguinte código no arquivo MenuScene.swift :

 import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }

Como essa cena é relativamente simples, não criaremos nenhuma classe especial. Nossa cena consistirá em dois botões. Estes podem ser (e possivelmente merecem ser) sua própria classe de SKSpriteNodes , mas por serem diferentes o suficiente, não precisaremos criar novas classes para eles. Esta é uma dica importante para quando você constrói seu próprio jogo: você precisa ser capaz de determinar onde parar e refatorar o código quando as coisas ficarem complexas. Depois de adicionar mais de três ou quatro botões a um jogo, talvez seja hora de parar e refatorar o código do botão de menu em sua própria classe.

O código acima não está fazendo nada de especial; ele está definindo as posições de quatro sprites. Também estamos definindo a cor de fundo da cena, para que todo o fundo seja o valor correto. Uma boa ferramenta para gerar códigos de cores a partir de strings HEX para o Xcode é o UI Color. O código acima também está definindo as texturas para nossos estados de botão. O botão para iniciar o jogo tem um estado normal e um estado pressionado, enquanto o botão de som é uma alternância. Para simplificar as coisas para a alternância, estaremos alterando o valor alfa do botão de som ao pressionar o usuário. Também estamos puxando e definindo o SKLabelNode de alta pontuação.

Nosso MenuScene está parecendo muito bom. Agora precisamos mostrar a cena quando o aplicativo é carregado. Vá para GameViewController.swift e encontre a seguinte linha:

 let sceneNode = GameScene(size: view.frame.size)

Substitua por isso:

 let sceneNode = MenuScene(size: view.frame.size)

Essa pequena alteração carregará o MenuScene por padrão, em vez do GameScene .

Nossa nova cena!
Nossa nova cena! Observe os 1,0 quadros por segundo: Nada está se movendo, então não há necessidade de atualizar nada.

Estados do botão

Os botões podem ser complicados no SpriteKit. Muitas opções de terceiros estão disponíveis (eu mesmo fiz uma), mas em teoria você só precisa conhecer os três métodos de toque:

  • touchesBegan(_ touches: with event:)
  • touchesMoved(_ touches: with event:)
  • touchesEnded(_ touches: with event:)

Cobrimos isso brevemente ao atualizar o guarda-chuva, mas agora precisamos saber o seguinte: qual botão foi tocado, se o usuário soltou o toque ou clicou nesse botão e se o usuário ainda está tocando nele. É aqui que nossa variável selectedButton entra em ação. Quando um toque começa, podemos capturar o botão que o usuário começou a clicar com essa variável. Se eles arrastarem para fora do botão, podemos lidar com isso e dar a textura apropriada a ele. Quando eles soltam o toque, podemos ver se eles ainda estão tocando dentro do botão. Se estiverem, podemos aplicar a ação associada a ela. Adicione as seguintes linhas na parte inferior de MenuScene.swift :

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }

Este é um simples manuseio de botões para nossos dois botões. Em touchesBegan(_ touches: with events:) , começamos verificando se temos algum botão selecionado no momento. Se o fizermos, precisamos redefinir o estado do botão para não pressionado. Em seguida, precisamos verificar se algum botão foi pressionado. Se um for pressionado, ele mostrará o estado destacado para o botão. Em seguida, definimos selectedButton como o botão para uso nos outros dois métodos.

Em touchesMoved(_ touches: with events:) , verificamos qual botão foi tocado originalmente. Em seguida, verificamos se o toque atual ainda está dentro dos limites de selectedButton e atualizamos o estado destacado a partir daí. O estado destacado do startButton altera a textura para a textura do estado pressionado, onde o estado destacado do soundButton tem o valor alfa do sprite definido como 50%.

Por fim, em touchesEnded(_ touches: with event:) , verificamos novamente qual botão está selecionado, se houver, e depois se o toque ainda está dentro dos limites do botão. Se todos os casos forem satisfeitos, chamamos handleStartButtonClick() ou handleSoundButtonClick() para o botão correto.

Um tempo para ação

Agora que temos o comportamento básico do botão desativado, precisamos de um evento para acionar quando eles são clicados. O botão mais fácil de implementar é startButton . Ao clicar, precisamos apenas apresentar o GameScene . Atualize handleStartButtonClick() na função MenuScene.swift para o seguinte código:

 func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }

Se você executar o aplicativo agora e pressionar o botão, o jogo começará!

Agora precisamos implementar a alternância mudo. Já temos um gerenciador de som, mas precisamos saber se o silenciamento está ativado ou desativado. Em Constants.swift , precisamos adicionar uma chave para persistir quando o muting estiver ativado. Adicione a seguinte linha:

 let MuteKey = "RAINCAT_MUTED"

Usaremos isso para salvar um valor booleano em UserDefaults . Agora que isso está configurado, podemos passar para SoundManager.swift . É aqui que verificaremos e definiremos UserDefaults para ver se o muting está ativado ou desativado. Na parte superior do arquivo, na variável trackPosition , adicione a seguinte linha:

 private(set) var isMuted = false

Esta é a variável que o menu principal (e qualquer outra coisa que reproduza som) verifica para determinar se o som é permitido. Inicializamos como false , mas agora precisamos verificar UserDefaults para ver o que o usuário deseja. Substitua a função init() pelo seguinte:

 private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }

Agora que temos um valor padrão para isMuted , precisamos da capacidade de alterá-lo. Adicione o seguinte código à parte inferior de SoundManager.swift :

 func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }

Esse método alternará nossa variável silenciada, bem como atualizará UserDefaults . Se o novo valor não for silenciado, a reprodução da música começará; se o novo valor for silenciado, a reprodução não começará. Caso contrário, interromperemos a reprodução da faixa atual. Depois disso, precisamos editar a instrução if em startPlaying() .

Encontre a seguinte linha:

 if audioPlayer == nil || audioPlayer?.isPlaying == false {

E substitua por isso:

 if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {

Agora, se o silenciamento estiver desativado e o reprodutor de áudio não estiver definido ou o reprodutor de áudio atual não estiver mais tocando, tocaremos a próxima faixa.

A partir daqui, podemos voltar para MenuScene.swift para finalizar nosso botão mudo. Substitua handleSoundbuttonClick() pelo seguinte código:

 func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }

Isso alterna o som no SoundManager , verifica o resultado e define adequadamente a textura para mostrar ao usuário se o som está silenciado ou não. Estamos quase terminando! Só precisamos definir a textura inicial do botão no lançamento. Em sceneDidLoad() , encontre a seguinte linha:

 soundButton = SKSpriteNode(texture: soundButtonTexture)

E substitua por isso:

 soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)

O exemplo acima usa um operador ternário para definir a textura correta.

Agora que a música está ligada, podemos ir para CatSprite.swift para desativar o miado do gato quando o mudo está ativado. No hitByRain() , podemos adicionar a seguinte instrução if após remover a ação de caminhada:

 if SoundManager.sharedInstance.isMuted { return }

Essa instrução retornará se o usuário silenciou o aplicativo. Por causa disso, vamos ignorar completamente nossos efeitos sonoros currentRainHits , maxRainHits e miados.

Depois de tudo isso, agora é hora de experimentar nosso botão mudo. Execute o aplicativo e verifique se ele está tocando e silenciando os sons adequadamente. Silencie o som, feche o aplicativo e reabra-o. Certifique-se de que a configuração de mudo persiste. Observe que, se você apenas silenciar e executar novamente o aplicativo do Xcode, talvez não tenha dado tempo suficiente para que UserDefaults salvos. Jogue o jogo e certifique-se de que o gato nunca mia quando você está mudo.

Testando a funcionalidade do botão.

Saindo do jogo

Agora que temos o primeiro tipo de botão para o menu principal, podemos entrar em alguns negócios complicados adicionando o botão Sair à nossa cena de jogo. Algumas interações interessantes podem surgir com nosso estilo de jogo; atualmente, o guarda-chuva se moverá para onde o usuário tocar ou mover seu toque. Obviamente, o guarda-chuva movendo-se para o botão sair quando o usuário está tentando sair do jogo é uma experiência de usuário muito ruim, então tentaremos impedir que isso aconteça.

O botão de sair que estamos implementando imitará o botão de início do jogo que adicionamos anteriormente, com grande parte do processo permanecendo o mesmo. A mudança será na forma como lidamos com os toques. Obtenha seus ativos quit_button e quit_button_pressed no arquivo Assets.xcassets e adicione o seguinte código ao arquivo HudNode.swift :

 private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")

Isso irá lidar com nossa referência quitButton , juntamente com as texturas que definiremos para os estados do botão. Para garantir que não atualizamos inadvertidamente o guarda-chuva enquanto tentamos sair, precisamos de uma variável que informe ao HUD (e à cena do jogo) que estamos interagindo com o botão sair e não com o guarda-chuva. Adicione o seguinte código abaixo da variável booleana showingHighScore :

 private(set) var quitButtonPressed = false

Novamente, esta é uma variável que apenas o HudNode pode definir, mas que outras classes podem verificar. Agora que nossas variáveis ​​estão configuradas, podemos adicionar o botão ao HUD. Adicione o seguinte código à função setup(size:) :

 quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)

O código acima irá definir o botão sair com a textura do nosso estado não pressionado. Também estamos definindo a posição para o canto superior direito e definindo zPosition como um número alto para forçá-lo a sempre desenhar no topo. Se você executar o jogo agora, ele aparecerá no GameScene , mas ainda não poderá ser clicado.

Botão Sair
Observe o novo botão sair em nosso HUD.

Agora que o botão foi posicionado, precisamos ser capazes de interagir com ele. No momento, o único lugar onde temos interação no GameScene é quando estamos interagindo com o umbrellaSprite -chuvaSprite . Em nosso exemplo, o HUD terá prioridade sobre o guarda-chuva, para que os usuários não precisem tirar o guarda-chuva do caminho para sair. Podemos criar as mesmas funções em HudNode.swift para imitar a funcionalidade de toque em GameScene.swift . Adicione o seguinte código ao HudNode.swift :

 func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }

O código acima é muito parecido com o código que criamos para MenuScene . A diferença é que há apenas um botão para acompanhar, para que possamos lidar com tudo dentro desses métodos de toque. Além disso, como saberemos a localização do toque no GameScene , podemos apenas verificar se nosso botão contém o ponto de toque.

Vá para GameScene.swift e substitua os touchesBegan(_ touches with event:) e touchesMoved(_ touches: with event:) pelo seguinte código:

 override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }

Aqui, cada método lida com tudo praticamente da mesma maneira. Estamos dizendo ao HUD que o usuário interagiu com a cena. Em seguida, verificamos se o botão sair está capturando os toques no momento. Se não for, então movemos o guarda-chuva. Também adicionamos a touchesEnded(_ touches: with event:) para lidar com o final do clique para o botão sair, mas ainda não a estamos usando para umbrellaSprite -chuvaSprite .

Clicar no botão sair não move o guarda-chuva, mas clicar em outro lugar sim.

Agora que temos um botão, precisamos de uma maneira de fazer com que ele afete a GameScene . Adicione a seguinte linha ao topo de HudeNode.swift :

 var quitButtonAction : (() -> ())?

Este é um fechamento genérico que não tem entrada nem saída. Vamos definir isso com o código no arquivo GameScene.swift e chamá-lo quando clicarmos no botão em HudNode.swift . Então, podemos substituir o TODO no código que criamos anteriormente na função touchEndedAtPoint(point:) por isso:

 if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }

Agora, se definirmos o encerramento quitButtonAction , ele será chamado a partir deste ponto.

Para configurar o encerramento quitButtonAction , precisamos passar para GameScene.swift . Em sceneDidLoad() , podemos substituir nossa configuração de HUD pelo seguinte código:

 hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)

Execute o aplicativo, pressione play e, em seguida, pressione sair. Se você estiver de volta ao menu principal, seu botão sair está funcionando conforme o esperado. No encerramento que criamos, inicializamos uma transição para o MenuScene . E definimos esse encerramento para que o nó HUD seja executado quando o botão sair for clicado. Outra linha importante aqui é quando definimos o quitButtonAction como nil . A razão para isso é que um ciclo de retenção está ocorrendo. A cena está mantendo uma referência ao HUD onde o HUD está mantendo uma referência à cena. Como há uma referência a ambos os objetos, nenhum deles será descartado quando chegar a hora da coleta de lixo. Nesse caso, toda vez que entrarmos e sairmos do GameScene , outra instância dele será criada e nunca liberada. Isso é ruim para o desempenho e o aplicativo acabará ficando sem memória. Existem várias maneiras de evitar isso, mas no nosso caso podemos apenas remover a referência a GameScene do HUD, e a cena e o HUD serão encerrados assim que voltarmos ao MenuScene . Krakendev tem uma explicação mais profunda dos tipos de referência e como evitar esses ciclos.

Agora, vá para GameViewController.swift e remova ou comente as três linhas de código a seguir:

 view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true

Com os dados de depuração fora do caminho, o jogo parece muito bom! Parabéns: Estamos atualmente em beta! Confira o código final de hoje no GitHub.

Pensamentos finais

Esta é a lição final de um tutorial de três partes, e se você chegou até aqui, você fez muito trabalho em seu jogo. Neste tutorial, você passou de uma cena que não tinha absolutamente nada para um jogo completo. Parabéns! Na lição um, adicionamos o chão, pingos de chuva, fundo e sprites de guarda-chuva. Também brincamos com a física e garantimos que nossas gotas de chuva não se acumulassem. Começamos com a detecção de colisão e trabalhamos na seleção de nós para que não ficássemos sem memória. Também adicionamos alguma interação do usuário, permitindo que o guarda-chuva se mova para onde o usuário toca na tela.

Na lição dois, adicionamos o gato e a comida, além de métodos de desova personalizados para cada um deles. Atualizamos nossa detecção de colisão para permitir sprites de gato e comida. Também trabalhamos no movimento do gato. O gato ganhou um propósito: comer cada pedaço de comida disponível. Adicionamos uma animação simples para o gato e adicionamos interações personalizadas entre o gato e a chuva. Por fim, adicionamos efeitos sonoros e música para que pareça um jogo completo.

Nesta última lição, criamos um display heads-up para manter nosso rótulo de pontuação, bem como nosso botão de saída. Lidamos com ações em nós e permitimos que o usuário saísse com um retorno de chamada do nó HUD. Também adicionamos outra cena na qual o usuário pode iniciar e voltar após clicar no botão sair. Lidamos com o processo para iniciar o jogo e para controlar o som no jogo.

Para onde ir a partir daqui

Colocamos muito tempo para chegar até aqui, mas ainda há muito trabalho que pode ser feito neste jogo. RainCat continua o desenvolvimento ainda, e está disponível na App Store. Abaixo está uma lista de desejos e necessidades a serem adicionados. Alguns dos itens foram adicionados, enquanto outros ainda estão pendentes:

  • Adicione ícones e uma tela inicial.
  • Finalize o menu principal (simplificado para o tutorial).
  • Corrija bugs, incluindo gotas de chuva desonestas e desova de vários alimentos.
  • Refatore e otimize o código.
  • Altere a paleta de cores do jogo com base na pontuação.
  • Atualize a dificuldade com base na pontuação.
  • Anime o gato quando a comida estiver logo acima dele.
  • Integrar o Game Center.
  • Dê crédito (incluindo o devido crédito para faixas de música).

Acompanhe o GitHub porque essas alterações serão feitas no futuro. Se você tiver alguma dúvida sobre o código, sinta-se à vontade para nos enviar uma mensagem para [email protected] e podemos discutir isso. Se certos tópicos receberem atenção suficiente, talvez possamos escrever outro artigo discutindo o tópico.

Obrigada!

Quero agradecer a todas as pessoas que ajudaram no processo de criação do jogo e no desenvolvimento dos artigos que o acompanham.

  • Cathryn Rowe Pela arte inicial, design e edição, e pela publicação dos artigos em nossa Garagem.
  • Morgan Wheaton Para o design final do menu e as paletas de cores (que ficarão incríveis quando eu realmente implementar esses recursos - fique atento).
  • Nikki Clark Pelos cabeçalhos e divisores incríveis nos artigos e pela ajuda na edição dos artigos.
  • Laura Levisay Por todos os GIFs incríveis nos artigos e por me enviar GIFs de gatos fofos para apoio moral.
  • Tom Hudson Pela ajuda na edição dos artigos e sem os quais esta série não teria sido feita.
  • Lani DeGuire Pela ajuda na edição dos artigos, o que deu muito trabalho.
  • Jeff Moon Para ajudar a editar a lição três e o pingue-pongue. Muito pingue-pongue.
  • Tom Nelson Por ajudar a garantir que o tutorial funcione como deveria.

Sério, levou uma tonelada de pessoas para preparar tudo para este artigo e liberá-lo para a loja.

Obrigado a todos que lêem esta frase, também.