Um jogo simples, descomprometido e maroto com vários microgames.
O projeto deve ser entregue como um Pull Request (veja
[1] e [2]) neste repositório.
Ou seja, você deve fazer um fork e, no branch master
, você deve
criar os dois microjogos.
Siga os passos:
- (Um (01) integrante da dupla/grupo) faça um fork deste repositório pela interface do github.com.
- Dê permissão de alteração no fork para o(s) colega(s).
- Clonem o (seu) repositório forked para seu computador.
- Criem um branch cujo nome é o primeiro nome do(s) integrante(s) do grupo,
sem letras maiúsculas e com hífen separando o(s) nome(s).
- Por exemplo,
git branch -b sandy-junior
.
- Por exemplo,
- Trabalhem fazendo commits nesse branch.
- Quando estiver pronto, faça um Pull Request do seu branch (e.g.,
leandro-leonardo
) para o branchmaster
do professor.
Veja alguns detalhes sobre a implementação do jogo a seguir.
O jogo possui algumas telas, como de splash (inicial), menu principal e
"de jogo", e o código referente a cada uma reside em uma classe que herda de
BaseScreen
.
As classes do projeto estão modularizadas nos seguintes pacotes:
br.microgamr
: classes de inicialização e configuração geral do jogo.br.microgamr.graphics
: classes com utilitários gráficos.br.microgamr.logic
: classes de utilidade para a lógica de jogo.br.microgamr.minigames
: classes referentes aos microgames.br.microgamr.minigames.factories
: classes referentes às fábricas abstratas que são responsáveis por instanciar os microgames.br.microgamr.minigames.util
: classes utilitárias aos microgames.br.microgamr.screens
: classes referentes às telas do jogo.
Os assets (recursos gráficos e de áudio) do jogo ficam na pasta core/assets
:
Os assets de cada microgame devem estar dentro de uma pasta cujo nome é
o nome dele, sem maiúsculas e acentuação, com hífen separando as palavras,
caso haja mais de uma (e.g., assets/shoot-the-monsters
).
Para os microgames, estamos usando um gerenciador de assets para pré-carregá-los de forma que, quando da execução da sequência de microgames, o jogo não pára para carregar os recursos e isso proporciona uma experiência de jogo melhor.
Para usar o gerenciador, supondo que você está criando um SuperMicroJogo
,
cada microgame deve ser implementado em 2 passos:
- Declarar de quais assets ele precisa, na classe
SuperMicroJogoFactory
. Por exemplo:package br.microgamr.microgames.factories; public class SuperMicroJogoFactory implements MicroGameFactory { // ... @Override public Map<String, Class> getAssetsToPreload() { return new HashMap<String, Class>() { { // texturas put("super-micro-jogo/personagem.png", Texture.class); // efeitos sonoros put("super-micro-jogo/tiro.wav", Sound.class); // música de fundo put("super-micro-jogo/musica.mp3", Music.class); } }; } }
- Solicitar os assets já carregados ao gerenciador, na classe do
microgame propriamente dito. Por exemplo:
public class SuperMicroJogo extends MicroGame { private Texture texturaPersonagem; private Sound somTiro; private Music musicaFundo; public SuperMicroJogo(BaseScreen screen, GameStateObserver observer, float difficulty) { super( screen, // tela que está executando este microgame. observer, // alguém interessado no estado (eg, HUD). difficulty, // dificuldade [0,1] que este jogo deve ter. 10, // duração em segundos deste microgame. TimeoutBehavior.FAILS_WHEN_MINIGAME_ENDS // o que acontece // qdo o tempo // acaba. ); } @Override protected void onStart() { this.texturaPersonagem = assets.get( "super-micro-jogo/personagem.png", Texture.class); this.somTiro = assets.get( "super-micro-jogo/tiro.wav", Sound.class); this.musicaFundo = assets.get( "super-micro-jogo/musica.mp3", Music.class); // ... } // ... }
Após criar classes que herdam de MicroGame
e MicroGameFactory
, você
deve ir até MenuScreen
e incluir a fábrica do seu microgame na lista de
microgames disponíveis e passar para o construtor de GameSequencer
:
/**
* Navega para a tela de jogo.
*/
private void navigateToMicroGameScreen() {
final float difficultyOfFirstMicrogame = 0;
final float difficultyOfLastMicrogame = 1;
final int numberOfGamesInSequence = 5;
// cria um sequenciador com um número de jogos em sequência e a lista
// de microgames disponíveis para serem sorteados, além de definir a
// dificuldade do microgame inicial e a do final
GameSequencer sequencer = new GameSequencer(numberOfGamesInSequence,
new HashSet<MicroGameFactory>(
Arrays.asList(
// microgames iniciais, de exemplo
// (*não devem ser removidos* ao commitar)
new ShootTheMonstersFactory(),
new ExpelTheMonstersFactory()
// microgames do grupo 1:
// microgames do grupo 2:
// microgames do grupo 3:
// ...
)
),
difficultyOfFirstMicrogame,
difficultyOfLastMicrogame);
game.setScreen(new GameScreen(game, this, sequencer));
}
Nota: para ver como o pré-carregamento está sendo feito, procure na classe
GameSequencer
.
Como muito bem lembramos da queridíssima aula de Computação Gráfica, uma aplicação em OpenGL possui pelo menos 2 sistemas de coordenadas extremamente importantes:
- O sistema de coordenadas do mundo (arbitrário, definido por nós)
- O sistema de coordenadas da janela (definido em pixels)
Em jogos digitais, qual deve ser o comportamento quando um usuário redimensiona a janela? Há pelo menos 3 possibilidades:
- A imagem que estamos renderizando fica espichada ou achatada (não queremos isso)
- Mantemos a razão de aspecto (a imagem não distorce) e fazemos com que toda
a imagem renderizada caiba dentro do espaço disponível
- Isso faz com que surjam barras laterais ou superioes-inferiores "em branco" quando a nova dimensão da janela tem uma razão de aspecto diferente daquela para a qual o jogo foi programado (veja a imagem a seguir)
- Mantemos a razão de aspecto e aumentamos o espaço do mundo que é visível ao jogador
É possível e bem simples fazer qualquer uma dessas formas na LibGDX. Optamos pela forma (2) porque ela permite que usemos valores virtuais para largura e altura da tela... como assim?
Podemos ver 3 constantes importantes em Config.java
:
public class Config {
/**
* A largura do mundo de jogo.
*
* Todos os objetos (sprites, etc.) devem estar contidos em coordenadas x
* que vão de 0 a WORLD_WIDTH para que apareçam na tela.
*/
public static final int WORLD_WIDTH = 1280;
/**
* A altura do mundo de jogo.
*
* Todos os objetos (sprites, etc.) devem estar contidos em coordenadas y
* que vão de 0 a WORLD_HEIGHT para que apareçam na tela.
*/
public static final int WORLD_HEIGHT = 720;
public static final float DESIRED_ASPECT_RATIO
= (float) WORLD_WIDTH / (float) WORLD_HEIGHT;
// ...
}
Repare na figura a seguir. Podendo considerar que o sistema de coordenadas do mundo é sempre x E [0,1280] e y E [0,720], fica fácil posicionar os elementos de forma que eles estarão aonde queremos independente da resolução atual da janela do jogo.
- Pergunta: Por que o código está em inglês?
- Resposta: as linguagens de programação e seus compiladores costumam ter problemas para identificar caracteres com acentuação nos códigos-fonte. Poderíamos escrever em Português sem usar a acentuação, porém se escrevermos em inglês, além de descartar essa possibilidade de problemas, tornamos o código-fonte acessível a leitores estrangeiros que saibam ler em Inglês.
- Pergunta: Fiz meu fork, mas o professor foi lá e fez mais commits...
agora meu fork está desatualizado. Como faço para ressincronizá-lo do
o do professor?
- Resposta: basta atualizar o branch master a partir do fork