terça-feira, 20 de março de 2012

Game Engine - Parte 3 - Sprites

Seguindo a sequencia dos tutoriais sobre a Game Engine (ultima parte aqui), hoje vamos introduzir a classe Sprite. Acho que já deu para perceber que não estamos falando do refrigerante de limão e sim a sequencia de imagens 2D que formam a animação de algum elemento no jogo. Ou seja, todo elemento gráfico cuja imagem venha de um arquivo (.PNG, .JPG) será tratado pela classe Sprite (você pode escolher não fazer isso, e tratar a posição, desenho e animação "na mão").

Página do projeto na versão do tutorial: http://code.google.com/p/gdacarv-android-game-engine/source/browse/trunk/+gdacarv-android-game-engine/?r=6
Revisão: 6

Veja e acompanhe a classe Sprite completa aqui.

No seu jogo haverá elementos gráficos estáticos (elementos do cenário, de interface, etc..) e dinâmicos, com animações (personagens, inimigos, alguns objetos...). Para os gráficos estáticos basta usar uma imagem simples com o gráfico dele e pronto. Para os dinâmicos, não seria nada prático ter muitas imagens, cada uma com um frame da animação, e nem gif é suportado nativamente no Android, então a solução aqui (e em grande parte dos game engines 2D) é usar um único arquivo contendo toda a animação de um personagem/elemento. Por exemplo:
Sprite de Link em algumas situações (parado, andando..)

Note que cada imagem individualmente ocupa um retângulo de tamanho fixo para todas as imagens. Esses frames serão passados de um a um, dando a sensação de movimento. Vamos então criar a classe Sprite, e definir os seguintes campos:

public static final int ANIM_STOP = 0; //Constantes que definem a animação
    public static final int ANIM_GO = 1;
    public static final int ANIM_GOBACK = 2;
       
    protected int animation = ANIM_GOBACK; //Atual tipo de movimentação
       
    public int x = 0, y = 0; //Posição da Sprite
    private Bitmap mBitmap; //Imagem
    public boolean visible = true; //Visivel
   
    private final int BMP_ROWS; //Quantidade de linhas da imagem
    private final int BMP_COLUMNS; //Quantidade de colunas da imagem
   
    private int currentFrame = 0; //Atual frame da animação
    public int width,height; //Largura e altura de um frame (não do bitmap todo)
    private int firstFrame = 0, lastFrame = 1; //Primeiro e ultimo frame da animação
   
    private boolean animationControl = false; //Flag de controle da animação (no caso de ANIM_GOBACK)
    public Paint mPaint; //Paint usado para desenho

Agora vamos construir o construtor (o que contém o código, o outro é apenas uma versão simplificada para imagens estáticas que apenas chama esse construtor com parâmetros default):

    public Sprite(Bitmap bmp, int bmp_rows, int bmp_columns) {
          this.mBitmap = bmp;
          this.BMP_ROWS = bmp_rows;
          this.BMP_COLUMNS = bmp_columns;
          this.width = bmp.getWidth() / BMP_COLUMNS;
          this.height = bmp.getHeight() / BMP_ROWS;
          lastFrame = BMP_COLUMNS*BMP_ROWS;
          if(getFrameCount() == 1)
                  animation = ANIM_STOP;
    }
Recebe como parâmetros a imagem em Bitmap, a quantidade de linhas (de frames) e de colunas, define a largura e altura do frame, o ultimo frame, e se tiver apenas um frame define a animação como ANIM_STOP (parada).

Perceba que o parâmetro da imagem é da classe Bitmap, ou seja, você tem que carregar o Bitmap no seu GameView e passar já pronto. Caso a imagem venha de um recurso (ao invés de asset, ou arquivo) você pode carrega-lo assim:
Resources res = getResources();
Bitmap imagem = BitmapFactory.decodeResource(res, R.drawable.personagem);
A seguir vamos definir a função update() que é executada a cada frame. Ela vai cuidar de definir qual frame será desenho, e pode ser sobre-escrito por um filho para execução de outras tarefas, como, por exemplo, execução de IA:

    public void update() {
        switch (animation) {
                case ANIM_GO:
                        currentFrame = ((currentFrame+1-firstFrame) % (lastFrame-firstFrame)) + firstFrame;
                        break;
                case ANIM_GOBACK:
                        if(currentFrame+1 == lastFrame)
                                animationControl = true;
                        else if(currentFrame == firstFrame)
                                animationControl = false;
                        currentFrame = currentFrame+(animationControl ? -1 : 1);
                        break;
                }
    }

E a função onDraw responsável pelo desenho na tela:

    public void onDraw(Canvas canvas) {
        if(visible){
                int srcX = (currentFrame % BMP_COLUMNS) * width;
                int srcY = (currentFrame / BMP_COLUMNS) * height;
                canvas.drawBitmap(mBitmap,
                                new Rect(srcX, srcY, srcX + width, srcY + height),
                                new Rect(x, y, x + width, y + height),
                                mPaint);
        }
    }
Apenas verifica se o Sprite está visível, e desenha o atual frame na tela nas coordenas definidas por x e y, usando o Paint salvo no Sprite.

Temos algumas funções para auxiliar a animação:

    public void setFirstFrame(int frame){
        firstFrame = frame;
        if(firstFrame >= lastFrame){
                lastFrame = firstFrame + 1;
                currentFrame = firstFrame;
                animationControl = false;
        }else
        if(currentFrame < firstFrame){
                currentFrame = firstFrame;
                animationControl = false;
        }
    }
   
    public void setLastFrame(int frame){
        lastFrame = frame;
        if(lastFrame <= firstFrame){
                firstFrame = lastFrame - 1;
                currentFrame = firstFrame;
                animationControl = false;
        }else
        if(currentFrame > frame){
                currentFrame = firstFrame;
                animationControl = false;
        }
    }
   
    public int getFrameCount(){
        return BMP_COLUMNS*BMP_ROWS;
    }
   
    public void setCurrentFrame(int frame){
        currentFrame = frame;
        firstFrame = frame;
        lastFrame = frame+1;
    }
   
    public boolean setAnimation(int frame, int iframe, int lframe, int type){
        if(frame < iframe || frame >= lframe || iframe >= lframe || type < 0 || type > 2)
                return false;
        currentFrame = frame;
        firstFrame = iframe;
        lastFrame = lframe;
        if(getFrameCount() > 1)
                animation = type;
        return true;
    }
   
    public boolean setAnimation(int type){
        if(getFrameCount() > 1){
                animation = type;
                return true;
        }
                else
                        return false;
    }
  1. setFirstFrame: Define o primeiro frame da animação e verifica de é maior que o ultimo ou maior que o atual e faz as modificações necessárias para manter a consistência;
  2. setLastFrame: Análogo ao anterior, para o ultimo frame;
  3. getFrameCount: Retorna a quantidade de frames total do Sprite;
  4. setCurrentFrame: Define o atual frame e reseta o ultimo e primeiro afim de manter a consistência;
  5. setAnimation: Método mais indicado para troca de animação, define o atual, o primeiro e o ultimo frame e o tipo de animação. Retorna true se tudo estiver correto ou false caso contrário;
  6. setAnimation(type): Sobrecarga do método anterior mudando apenas o tipo de animação.
Pronto, nossa classe Sprite está criada. Agora basta utiliza-la no jogo apenas instanciando-a, se quiser apenas mostrar uma imagem ou animação, ou estendendo-a se for um caso mais complexo, como o héroi do seu jogo, por exemplo.

Próxima semana pretendo começar a fazer um tutorial de como fazer um jogo usando essa engine, passo a passo. Então, até lá!

4 comentários:

Anônimo at 9 de junho de 2015 às 12:49 disse...

Cadê o link do jogo pronto?

Anônimo at 9 de junho de 2015 às 13:13 disse...

Com qual classe eu posso fazer load de obj no Android?

Anônimo at 9 de junho de 2015 às 15:15 disse...
Este comentário foi removido pelo autor.
Mafia666 at 24 de agosto de 2015 às 11:13 disse...
Este comentário foi removido pelo autor.

Postar um comentário

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