Olá! Seguido da Parte 1 do tutorial sobre Game Engine, chegou a hora de criar nossa GameView, que será responsável por praticamente tudo dentro do jogo. Ela que irá preparar e desenhar a tela, lidará com o update() geral e com o onDraw().
Revisão do projeto usada no tutorial: r6.
Como já comentei na Parte 1, o GameView irá estender o SurfaceView, que é uma View que permite manipulação direta do que irá desenhar, pixel a pixel. Vamos começar com o básico:
package com.gdacarv.engine.androidgame;
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public abstract class GameView extends SurfaceView{
private SurfaceHolder mHolder;
protected GameLoopThread gameLoopThread;
public long FPS = 30;
public GameView(Context context) {
super(context);
gameLoopThread = new GameLoopThread(this);
mHolder = getHolder();
mHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if(gameLoopThread.getState() == Thread.State.TERMINATED)
gameLoopThread = new GameLoopThread(GameView.this);
else
onLoad();
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry) {
try {
gameLoopThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
}
protected abstract void onLoad();
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRGB(0, 0, 0);
}
public void update() {
}
}
Toda SurfaceView está ligada a uma SurfaceHolder e essa implementa alguns métodos para indicar se a SurfaceView está criada, se foi mudada ou destruída. Em surfaceCreated verificamos se a GameLoopThread já foi executada (jogo estava executando e outra aplicação tomou o palco, assim como ir pra home do Android) e se for o caso temos que iniciar outra Thread pois a antiga está morta e não pode ser iniciada de novo, caso contrário a SurfaceView está sendo criada pela primeira vez então não há necessidade de criar uma nova Thread e sim executar o onLoad, que deve ser implementado pela GameView do seu jogo para carregar o estado inicial do seu jogo.
Em surfaceDestroyed nos precisamos parar o GameLoopThread, para isso setamos o estado com gameLoopThread.setRunning(false) e usamos um loop que junto com o gameLoopThread.join() vai esperar nossa GameLoopThread terminar. Isso é necessário pois depois da execução da surfaceDestroyed, nada pode ser desenhado na surface (obviamente), e se não pararmos a GameLoopThread ele tentará fazer isso gerando um crash no aplicativo.
Ainda não faremos nada com surfaceChanged e criamos os métodos onLoad, onDraw (que por enquanto só limpa a tela, pintando tudo de preto. Precisamos fazer isso pois não temos garantia do que será preservado na tela entre um .unlockCanvasAndPost(c) e .lockCanvas()) e uptade.
Agora vamos adicionar a entrada no jogo. Apesar de existir diversos tipos de entrada como acelerômetro, gps, câmera, botoes, teclados físicos, vamos apenas usar a tela sensível ao toque por enquanto. Para isso vamos adicionar um método e a assinatura de outro, que deve ser implementado pelo filho do GameView.
@Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (getHolder()) {
TouchEvents(event);
}
return true;
}
public abstract void TouchEvents(MotionEvent event);
Simples não? Pra que o synchronized (getHolder())? Para impedir que o TouchEvents seja executado ao mesmo tempo que o update ou o onDraw. Pronto, agora basta o seu GameView implementar o TouchEvents e usar o argumento recebido para tirar as informações e fazer o processamento.
Atenção: Pode have mais de uma chamada ao onTouchEvent por frame.
Pronto, agora temos a estrutura básica para um jogo. Entretanto vou introduzir uma classe Sprites para facilitar o desenho de imagens na tela. Primeiramente vamos criar uma lista com as sprites presentes no jogo (nem todas as sprites precisam pertencer a essa lista, mas todas que pertencerem serão automaticamente atualizadas e desenhadas):
protected ArrayList<Sprite> mSprites;
Iniciaremos ela no construtor da GameView:
public GameView(Context context) {
super(context);
mSprites = new ArrayList<Sprite>();
...
Mudaremos o método onDraw:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRGB(0, 0, 0);
for (Sprite sprite : mSprites)
sprite.onDraw(canvas);
}
Assim ele desenha todos os sprites automaticamente. Note que o índice do Sprite na lista está ligado a sua ordem no desenho, ou seja, Sprites de posição x+1 serão desenhados na frente dos Sprites de posição x, caso eles se sobreponham.
Faremos também uma alteração no update():
public void update() {
for (Sprite sprite : mSprites)
sprite.update();
}
Assim cada sprite da lista executará o seu próprio método update(), que servirá para atualizar sua imagem, posição, reação de IA, etc...
Como a classe Sprite é um pouco extensa, deixarei ela para o próximo post.
Veja o GameView completo aqui.
1 comentários:
massa
Postar um comentário