domingo, 22 de janeiro de 2012

Game Engine - Parte 2 - Criando o GameView

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:

Anônimo at 18 de maio de 2014 às 22:48 disse...

massa

Postar um comentário

 
© 2011 Tutoriandroid | Recode by Ardhiansyam | Based on Android Developers Blog