sexta-feira, 20 de janeiro de 2012

Game Engine - Parte 1 - Criando o loop principal

Olá pessoal! Nessa postagem irei começar a construir uma Game Engine para Android. O que ser isso? Será um pacote base que pode ser utilizado em diversos projetos de games 2D para Android. Assim poderei explicar conceitos básicos da programação de jogos e do Android.

Meu primeiro jogo desenvolvido para Android foi o Hit Fallux!, mas como ainda estava iniciando no Android acabei fazendo ele com umas gambiarras, usando como base outros exemplos de jogos. Com isso acabei não pegando muito bem alguns conceitos, e agora que surgiu outro projeto de jogo decidi fazer da forma certa. Primeiramente pesquisei game engines já disponíveis e encontrei, entre outras, a AndEngine, que aparentemente é muito boa mas achei um pouco difícil de aprender. Decidi então ao invés de usar meu tempo aprendendo a usar a AndEngine, criar a minha própria engine, podendo assim entender melhor os conceitos, e criar uma engine que atenda as minhas necessidades.

Estarei hospedando o projeto aqui: http://code.google.com/p/gdacarv-android-game-engine/
API utilizada: 7 (2.1)
Revisão utilizada no tutorial: r6

Todo jogo tem um loop principal que é onde acontece basicamente 3 coisas:
  1. Captura de entrada;
  2. Calculo da física, atualização de IA, etc;
  3. Saída (desenhar tela, produzir som...).

Então é isso que vamos fazer utilizando a classe GameLoopThread, que obviamente estende a classe Thread (mais informações sobre Threads aqui).

package com.gdacarv.engine.androidgame;

import android.graphics.Canvas;

public class GameLoopThread extends Thread {
 
    private GameView mGameView;
    private boolean mRunning = false;
   
    public GameLoopThread(GameView view) {
          this.mGameView = view;
    }

    public void setRunning(boolean run) {
          mRunning = run;
    }

    @Override
    public void run() {
    while (mRunning) {
           Canvas c = null;
           try {
                  c = mGameView.getHolder().lockCanvas();
                  synchronized (mGameView.getHolder()) {
                      mGameView.update();
                      mGameView.onDraw(c);
                  }
           } finally {
                  if (c != null) {
                         mGameView.getHolder().unlockCanvasAndPost(c);
                  }
           }
    }
    }
}  

Explicando algumas coisas:
  1. GameView será a View principal do jogo, onde tudo ocorrerá. Mostrarei e explicarei ela em breve;
  2. mRunning é uma variável de controle para parar o loop;
  3. mGameView.getHolder() retorna o SurfaceHolder da GameView (que é uma extensão da SurfaceView) que controla o acesso a área de desenho da View, e o .lockCanvas() trava e retorna a superfície a ser editada apenas por este processo;
  4. A seguir é feito o update do jogo e o desenho na superfície, que serão detalhados em breve;
  5. .unlockCanvasAndPost(c) para destravar e desenhar a área de desenho da View na tela.
Tudo certo né? Acontece que ao deixar a loop desse jeito sua taxa de atualização não será constante, ou seja, se tiver cpu disponível ele rodará mais vezes, se tiver pouca, rodará menos vezes. Sem contar que como Android roda em diversos dispositivos seu jogo teria velocidade diferente para cada velocidade de processamento diferente. Para impedir isso vem o conceito de FPS (frames por segundos), que busca criar um limite superior de quantas vezes o loop será executado em um segundo. Para implementa-lo basta mudar um pouco a função run():

    @Override
    public void run() {
     long ticksPS = 1000 / mGameView.FPS;
        long startTime, sleepTime;
    while (mRunning) {
           Canvas c = null;
           startTime = System.currentTimeMillis();
           try {
                  c = mGameView.getHolder().lockCanvas();
                  synchronized (mGameView.getHolder()) {
                   mGameView.update();
                   mGameView.onDraw(c);
                  }
           } finally {
                  if (c != null) {
                         mGameView.getHolder().unlockCanvasAndPost(c);
                  }
           }
           sleepTime = ticksPS-(System.currentTimeMillis() - startTime);
                 try {
                        if (sleepTime > 0)
                               sleep(sleepTime);
                 } catch (Exception e) {}
    }
    }

mGameView.FPS será um campo definido no GameView. Assim você pode altera-lo para a necessidade do seu jogo.

A diferença agora é que o progama calcula quanto tempo cada frame tem para ser executado, subtrai do que foi gasto no update() e no onDraw() e dorme o resto.

Veja o GameLoopThread completo aqui.

Pronto! O loop principal está pronto. Se você sentiu falta da captura de entrada então parabéns, você é esperto... Mas não se preocupe, por enquanto ela será lidada no GameView.

Parte 2 - Criando o GameView >>

4 comentários:

Anônimo at 24 de abril de 2013 às 07:03 disse...

Olá, gostaria de aprender convosco.
E quero começar pelo principio.
Será que poderiam dizer-me onde eu coloco este código?

Se quiserem poderão responder para o meu mail:
andre.sousa79@gmail.com

LNI at 13 de maio de 2013 às 02:16 disse...

Também concordo com o andré se ideia a ensinar já começou mal esse tutorial eu conheço pouco java e pouco android também não sei aonde colocar o código deviam começar do zero explicando

Rafael Peres Cirilo at 21 de janeiro de 2014 às 17:28 disse...

pow irmão, então antes de pensar em criar um jogo, aprenda mais sobre java e sobre android, que você vai saber onde inserir o código sem precisar de figurinhas explicativas.

Jair Humberto at 15 de fevereiro de 2014 às 22:33 disse...

Eh, sem saber o básico sobre Java e Android, não dá pra pensar em criar um jogo. A não ser que você use o GameMaker, que custa 799 dólares

Postar um comentário

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