Skip to content

Instantly share code, notes, and snippets.

@GitLeandroHub
Last active October 13, 2020 13:16
Show Gist options
  • Save GitLeandroHub/b4621519d2f6e6c929c61a47031f8735 to your computer and use it in GitHub Desktop.
Save GitLeandroHub/b4621519d2f6e6c929c61a47031f8735 to your computer and use it in GitHub Desktop.
Simple Space-shooter game in Flutter using *Flame* game engine from Scratch. (All code in main.dart, no good habits and modularization)
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/time.dart';
//util é async, entao alterar o main para async
import 'package:flame/flame.dart';
//utilizar FlameAnimation como Alias para não dar conflito com animation do Dart
import 'package:flame/animation.dart' as FlameAnimation;
import 'dart:math';
import 'package:flame/spritesheet.dart';
//scrcpy pra fazer o display do aparelho android caso não tenha emulador virtual https://github.com/Genymobile/scrcpy
//Todo jodo do flame "vira" um widget do flutter
void main() async {
//await Flame.util.setPortrait();
//await Flame.util.fullScreen();
Size size = await Flame.util.initialDimensions();
//passar size para widget para repassar para o game
runApp(GameWidget(size));
}
class GameWidget extends StatelessWidget {
final Size size;
GameWidget(this.size);
@override
Widget build(BuildContext context) {
//inicializar o game numa variável
final game = SpaceShooterGame(size);
return GestureDetector(
onPanStart: (_) {
game.beginFire();
},
onPanEnd: (_) {
game.stopFire();
},
onPanCancel: () {
game.stopFire();
},
//registrar eventos
onPanUpdate: (DragUpdateDetails details) {
//delta vai retornar um offset de quanto mudou nessa tragetória do update dele
game.onPlayerMove(details.delta);
},
child: Container(color: Color(0xFFFFFFFF), child: game.widget),
);
}
}
//para adicionar uma lista chamada "collidingObjects" de objetos que estão colidindo
class AnimationCollidableGameObject extends AnimationGameObject {
List<AnimationCollidableGameObject> collidingObjects = [];
}
class AnimationGameObject {
Rect position;
FlameAnimation.Animation animation;
void render(Canvas canvas) {
//verificar se o arquivo está carreagado em background
if (animation.loaded()) {
//verificado, esse método retorna o sprite/frame atual e chama um renderRect e passa canvas e position
animation.getSprite().renderRect(canvas, position);
}
}
//animação muda de frame baseado no tempo, entao adicionar o método update
void update(double deltaTime) {
animation.update(deltaTime);
}
}
//Game é a classe mais basica do flame, ela dá o game loop
class SpaceShooterGame extends Game {
final Size screenSize;
Random random = Random();
//criar uma constante que delimita quanto o NPC se move por segundo
static const enemy_speed = 150;
static const shoot_speed = -500;
static const star_speed = 10;
AnimationGameObject player;
//classe importada utilitaria do Flame para criar algo que vai executar periodicamente
//instanciar timer:
Timer enemyCreator;
Timer shootCreator;
Timer starCreator;
//Criar os NPCs e instanciar
//atualizar os objetos para serem do tipo "Collidable"
List<AnimationCollidableGameObject> enemies = [];
List<AnimationCollidableGameObject> shoots = [];
List<AnimationCollidableGameObject> explosions = [];
List<AnimationCollidableGameObject> stars = [];
SpriteSheet starsSpritesheet;
//construtor
SpaceShooterGame(this.screenSize) {
player = AnimationGameObject()
..position = Rect.fromLTWH(175, 600, 72, 60)
..animation = FlameAnimation.Animation.sequenced("player.png", 4,
textureWidth: 24, textureHeight: 20);
//sequence é um dos construtores da animação do Flame que cria uma animação em função dos frames
//instanciar o enemy creator
//Timer recebe obrigatoriamente o tempo e depois adicionar booleano falando se ele irá se repetir (true, pois será periódico) e uma funcao callback para cada vez que houver um intervalo ele executa
enemyCreator = Timer(1.0, repeat: true, callback: () {
//nextDouble torna o número de zero a um
enemies.add(AnimationCollidableGameObject()
..animation = FlameAnimation.Animation.sequenced("enemy.png", 1,
textureWidth: 128, textureHeight: 128)
..position = Rect.fromLTWH(
(screenSize.width - 50) * random.nextDouble(), 0, 50, 50));
});
//pois irá executar desde o começo do jogo
enemyCreator.start();
//instanciar/inicializar o shoot tambem
shootCreator = Timer(0.5, repeat: true, callback: () {
shoots.add(AnimationCollidableGameObject()
..animation = FlameAnimation.Animation.sequenced("bullet.png", 1,
textureWidth: 24, textureHeight: 20)
..position = Rect.fromLTWH(
player.position.left + 20, player.position.top - 20, 20, 20));
});
final starGapTime = (screenSize.height / 10) / star_speed;
starCreator = Timer(starGapTime, repeat: true, callback: () {
createRowOfStars(0);
});
starCreator.start();
//inicializar Spritesheet no construtor
starsSpritesheet = SpriteSheet(
imageName: "stars.png",
textureWidth: 38,
textureHeight: 38,
columns: 4,
rows: 4);
createInitialStars();
}
//criar um "mestre" no game que recebe o quanto ele mudou
//alterar a posição do player de acordo com o offset
void onPlayerMove(Offset delta) {
player.position = player.position.translate(delta.dx, delta.dy);
}
//criar dois metodos: beginFire e stopFire
void beginFire() {
shootCreator.start();
}
void stopFire() {
shootCreator.stop();
}
//criar os métodos para as explosões
void createExplosionAt(double x, double y) {
final animation = FlameAnimation.Animation.sequenced("explosion.png", 6,
textureWidth: 32, textureHeight: 32, stepTime: 0.05)
..loop = false;
explosions.add(AnimationCollidableGameObject()
..animation = animation
..position = Rect.fromLTWH(x - 25, y - 25, 20, 20));
}
void createStarAt(double x, double y) {
final animation = starsSpritesheet.createAnimation(random.nextInt(3), to: 4)
..variableStepTimes = [max(20, 100 * random.nextDouble()), 0.1, 0.1, 0.1];
stars.add(AnimationCollidableGameObject()
..position = Rect.fromLTWH(x, y, 10, 10)
..animation = animation);
}
void createRowOfStars(double y) {
final gapSize = 6;
double starGap = screenSize.height / gapSize;
for (var i = 0; i < gapSize; i++) {
createStarAt(starGap * i + (random.nextDouble() * starGap),
y + (random.nextDouble() * 20));
}
}
void createInitialStars() {
final gapSize = 10;
double rows = screenSize.height / gapSize;
for (var i = 0; i < gapSize; i++) {
createRowOfStars(i * rows);
}
}
@override
//game loop, processa a logica do jogo: movimento, colisão...
//delta time (em segundos) = quanto tempo se passou desde a ultima atualização
//flutter almeja 60 frames por segundo. Portanto esse update será chamado 60 vezes por segundo com delta time representando muito proximo desse
void update(double deltaTime) {
// e como o Timer é vinculado ao tempo do jogo precisa chamar o enemyCreator.update passando o deltatime para ficar vinculado ao fps do jogo
enemyCreator.update(deltaTime);
shootCreator.update(deltaTime);
starCreator.update(deltaTime);
player.update(deltaTime);
enemies.forEach((enemy) {
//atualizar o método update, pois agora o enemy também tem animação
enemy.update(deltaTime);
//alterar a posição [translate (x, y)] do NPC de acordo com a Velocidade e multiplicar pelo delta para que ele tenha a velocidade relativa para esse frame
enemy.position = enemy.position.translate(0, enemy_speed * deltaTime);
});
shoots.forEach((shoot) {
shoot.update(deltaTime);
shoot.position = shoot.position.translate(0, shoot_speed * deltaTime);
});
explosions.forEach((explosion) {
explosion.update(deltaTime);
});
stars.forEach((star) {
star.update(deltaTime);
star.position = star.position.translate(0, star_speed * deltaTime);
});
//para todos os tiros /passar para todos os enimigos e verificar se houve colisao
shoots.forEach((shoot) {
enemies.forEach((enemy) {
//pelo proprio método do Rect que recebe um proprio rect e retorna um booleano para identificar se houve colisão ou não
if (shoot.position.overlaps(enemy.position)) {
//se houve colisão
createExplosionAt(shoot.position.left, shoot.position.top);
shoot.collidingObjects.add(enemy);
enemy.collidingObjects.add(shoot);
}
});
});
//limpar o estado para não manter tanto objeto na memória e não crashar o jogo
//remover quando sair da tela ou || quando colidir com alguma coisa
enemies.removeWhere((enemy) {
return enemy.position.top >= screenSize.height ||
enemy.collidingObjects.isNotEmpty;
});
shoots.removeWhere((shoot) {
return shoot.position.bottom <= 0 || shoot.collidingObjects.isNotEmpty;
});
explosions.removeWhere((explosion) => explosion.animation.isLastFrame);
stars.removeWhere((star) => star.position.top >= screenSize.height);
}
@override
//renderiza o estado atual do frame. Recebe o Canvas provido pela propria API do flutter
//chama o player.render para ser o canvas
void render(Canvas canvas) {
player.render(canvas);
//renderizar os NPCs
enemies.forEach((enemy) {
enemy.render(canvas);
});
shoots.forEach((shoot) {
shoot.render(canvas);
});
explosions.forEach((explosion) {
explosion.render(canvas);
});
stars.forEach((star) {
star.render(canvas);
});
}
}
name: flame_primeiro
description: A new Flame Flutter application.
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flame: ^0.26.0
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.0
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
//Necessário para o Flutter encontrar as imagens. O Flame por padrão busca as imagens em /assets
# The following section is specific to Flutter.
flutter:
assets:
- assets/images/player.png
- assets/images/enemy.png
- assets/images/explosion.png
- assets/images/bullet.png
- assets/images/stars.png
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment