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").
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; |
| } |
- 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;
- setLastFrame: Análogo ao anterior, para o ultimo frame;
- getFrameCount: Retorna a quantidade de frames total do Sprite;
- setCurrentFrame: Define o atual frame e reseta o ultimo e primeiro afim de manter a consistência;
- 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;
- 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:
Cadê o link do jogo pronto?
Com qual classe eu posso fazer load de obj no Android?
Postar um comentário