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