Esta é a última parte da minha série de 4 partes, onde aprendo como construir um jogo de plataforma simples com o motor Flame. Já sabemos como adicionar um personagem de jogador animado que chamei de The Boy, como criar um nível de jogo rolável usando o editor Tiled e como adicionar gravidade e pular com a ajuda da detecção de colisão (partes , , ). 1 2 3 Nesta parte, vamos adicionar moedas, que podem ser coletadas pelo personagem, HUD para mostrar quantas moedas o jogador tem, e uma tela de vitória, que mostraremos quando todas as moedas forem coletadas. Adicionando moedas Precisamos dizer ao jogo onde gerar moedas (ou qualquer outro objeto do jogo). Como você deve ter adivinhado, vamos usar o editor Tiled para adicionar outra camada de objetos, de maneira semelhante à que adicionamos Plataformas, mas com duas diferenças: Com as plataformas, inserimos imagens sprite nos dados de nível. Este método não é muito adequado para moedas, porque queremos remover o objeto assim que o jogador o coletar. Então vamos apenas adicionar pontos de spawn, para saber onde as moedas devem aparecer no jogo, e a renderização será feita usando os componentes do Flame. Para as plataformas, usamos retângulos de qualquer tamanho, mas para as moedas, vamos adicionar pontos de spawn do tamanho de 1 ladrilho. No entanto, se você quiser adicionar uma linha de moedas uma após a outra (oi Mario), poderá fazê-lo facilmente modificando o código do jogo e considerando o tamanho de um ponto de geração ao adicionar objetos de moeda. Mas, para os propósitos desta série, assumimos que os pontos de geração de moedas são 1x1. Abra nosso nível no editor Tiled e crie uma nova camada de objeto chamada Coins. Em seguida, usando a ferramenta Retangular, adicione vários pontos de spawn no mapa para adicionarmos componentes de moeda usando o mecanismo de jogo. Meu nível agora está assim: Devo acrescentar que nosso jogo é bastante simples e sabemos que esses retângulos vazios se transformarão em moedas durante a execução do jogo. Mas se quisermos adicionar mais tipos de objetos, será difícil distingui-los. Felizmente, o Tiled tem uma ferramenta para isso, chamada “Insert Tile”, que pode adicionar dicas visuais para cada objeto, mas essas imagens não serão renderizadas no jogo. Tudo bem, salve o nível e retorne ao IDE. Vamos adicionar nossa classe à pasta . Coin /objects/ class Coin extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { late final SpriteAnimation spinAnimation; late final SpriteAnimation collectAnimation; Coin(Vector2 position) : super(position: position, size: Vector2.all(48)); @override Future<void> onLoad() async { spinAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(16), stepTime: 0.12, ), ); collectAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.range( start: 4, end: 7, amount: 8, textureSize: Vector2.all(16), stepTimes: List.filled(4, 0.12), loop: false ), ); animation = spinAnimation; final hitbox = RectangleHitbox() ..collisionType = CollisionType.passive; add(hitbox); return super.onLoad(); } } Temos 2 animações diferentes para girar e coletar e um , para verificar colisões com o jogador posteriormente. RectangleHitbox o método Em seguida, retorne ao game.dart e modifique spawnObjects para gerar nossas moedas: final coins = tileMap.getLayer<ObjectGroup>("Coins"); for (final coin in coins!.objects) { add(Coin(Vector2(coin.x, coin.y))); } Execute o jogo e veja as moedas adicionadas: Agora vamos fazê-los desaparecer quando o jogador os coletar. o método Volte para coin.dart e adicione collect : void collect() { animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; } Quando esse método for chamado, vamos mudar a animação giratória para a de coleta e, quando terminar, vamos remover esse componente do jogo. a classe o método Em seguida, vá para theboy.dart e sobrescreva onCollisionStart : @override void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) { if (other is Coin) { other.collect(); } super.onCollisionStart(intersectionPoints, other); } A razão pela qual usamos em vez de é que queremos que o retorno de chamada de colisão seja acionado apenas uma vez. onCollisionStart onCollision As moedas agora estão desaparecendo em colisão com o menino. Vamos adicionar a interface do usuário para rastrear o número de moedas coletadas. Adicionando o HUD HUD, ou heads-up display, é simplesmente uma barra de status que exibe qualquer informação sobre o jogo: pontos de vida, munição, etc. Vamos exibir um ícone de moeda para cada moeda coletada. Para simplificar, vou armazenar o número de moedas em uma variável, mas para interfaces mais complexas, considere usar um pacote , que permite atualizar e observar o estado do jogo de maneira conveniente. flame_bloc Adicione uma nova classe que conterá a lógica do HUD: lib/hud.dart class Hud extends PositionComponent with HasGameRef<PlatformerGame> { Hud() { positionType = PositionType.viewport; } void onCoinsNumberUpdated(int total) { final coin = SpriteComponent.fromImage( game.images.fromCache(Assets.HUD), position: Vector2((50 * total).toDouble(), 50), size: Vector2.all(48)); add(coin); } } Duas coisas interessantes aqui: Definimos como para fixar nosso HUD no canto da tela. Se não fizermos isso, devido ao movimento da câmera, o HUD estará se movendo com o nível. positionType PositionType.viewport O método será chamado toda vez que o jogador coletar a moeda. Ele usa o parâmetro para calcular o deslocamento do próximo ícone de moeda e, em seguida, adiciona um novo sprite de moeda à posição calculada. onCoinsNumberUpdated total arquivo Em seguida, retorne ao game.dart e adicione novas variáveis de classe: int _coins = 0; // Keeps track of collected coins late final Hud hud; // Reference to the HUD, to update it when the player collects a coin componente : Em seguida, adicione o Hud na parte inferior do método onLoad hud = Hud(); add(hud); E adicione um novo método: void onCoinCollected() { _coins++; hud.onCoinsNumberUpdated(_coins); } de Por fim, chame-o do método collect Coin : void collect() { game.onCoinCollected(); animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; } Brilhante, nosso HUD agora mostra quantas moedas coletamos! tela de vitória A última coisa que quero adicionar é a tela de vitória, que será exibida assim que o jogador coletar todas as moedas. classe Adicione uma nova const à PlatformerGame : late int _totalCoins; método E atribua a ele o número de moedas que temos no nível. Adicione esta linha ao final do spawnObjects : _totalCoins = coins.objects.length; E adicione isso ao final do método . onCoinCollected Observe que pode ser necessário adicionar manualmente. import 'package:flutter/material.dart'; if (_coins == _totalCoins) { final text = TextComponent( text: 'U WIN!', textRenderer: TextPaint( style: TextStyle( fontSize: 200, fontWeight: FontWeight.bold, color: Colors.white, ), ), anchor: Anchor.center, position: camera.viewport.effectiveSize / 2, )..positionType = PositionType.viewport; add(text); Future.delayed(Duration(milliseconds: 200), () => { pauseEngine() }); } Aqui, verifico se o contador de moedas é igual ao número de moedas no nível e, se for, adiciono um rótulo de vitória no topo da tela do jogo. Em seguida, pause o jogo para interromper o movimento do jogador. Também adicionei um atraso de 200 ms, para que o rótulo seja renderizado antes da pausa. Deve ficar assim: E esse é o jogo! Claro, não parece um jogo acabado agora, mas com tudo o que expliquei, deve ser bastante fácil adicionar mais níveis, inimigos ou outros itens colecionáveis. Resumo Esta parte conclui a série. O mecanismo Flame tem muito mais a oferecer que eu não mencionei, incluindo o mecanismo de física Forge2D, partículas, efeitos, menus de jogo, áudio etc. ter uma compreensão de como construir jogos mais complexos. O Flame é uma ferramenta poderosa, mas fácil de usar e aprender. É modular, o que permite trazer outras coisas legais como Box2D e é mantido ativamente. Mas uma das maiores vantagens do Flame é que ele é construído sobre o Flutter, o que significa que oferece suporte multiplataforma com um pouco de trabalho adicional. No entanto, ser uma extensão do Flutter significa que todos os problemas do Flutter também persistem no Flame. Por exemplo, está aberto há vários anos sem resolução, e você também pode notá-lo no jogo que construímos. Mas, no geral, é uma ótima ferramenta para criar jogos que vale a pena experimentar. o bug de antialiasing do Flutter Outras histórias da série: Ensinando seu personagem a correr em chamas Projetando seu nível no Flame Usando a detecção de colisão para fazer seu personagem pular O código completo deste tutorial você encontra no meu github Recursos No final de cada parte, adicionarei uma lista de criadores e recursos incríveis com os quais aprendi. Recursos do jogo de plataforma GrafxKid's Arcade Documentação do editor lado a lado Série de desenvolvimento de jogos Flame da DevKage: https://youtu.be/mSPalRqZQS8 Canal de Craig Oda https://youtu.be/hwQpBuZoV9s Tutorial do jogo Ember Quest Documentação do motor de chama