Maintenant que tu sais comment animer un sprite, image par image, en contrôlant l’enchaînement des frames qui le composent, ne perds pas de vue que gb.display
peut aussi prendre en charge l’animation de certains élements de la scène de jeu de façon automatique. Les deux approches peuvent tout à fait coexister simultanément. Pour illustrer cela, je te propose que nous ajoutions deux torches animées au décor pour le rendre un peu plus vivant.
Voici la spritesheet qu’on utilisera pour chaque torche :
Je te propose de la télécharger et de la ranger dans ton dossier artwork
avec les autres assets :
Ensuite, dépose-la sur Image Transcoder pour la convertir en code C++, et colle le code obtenu à la suite du fichier assets/rgb565.h
en nommant le tableau de données TORCH_DATA
. Règle la propriété frame_loop = 2
pour automatiser l’animation des torches.
Sinon télécharge carrément la nouvelle version du fichier à ranger dans ton dossier assets
:
On va avoir besoin d’une nouvelle variable globale torch
, instance de la classe Image
construite sur la base du nouveau tableau de données TORCH_DATA
, que tu peux définir juste avant celle de l’avatar :
Image torch(TORCH_DATA);
Avatar avatar(.5*(SCREEN_WIDTH - AVATAR_WIDTH), Y_GROUND - AVATAR_HEIGHT);
Ensuite on va définir une fonction drawTorches()
chargée d’afficher les deux torches sur la scène de jeu, à des emplacements bien choisis :
void drawTorches() {
gb.display.drawImage(12, 6, torch);
gb.display.drawImage(60, 6, torch);
}
Nul besoin d’en faire plus, gb.display
se chargera de leur animation toute seule, puisque la propriété frame_loop
est fixée à 2
dans le tableau TORCH_DATA
:
assets/rgb565.h
const uint16_t TORCH_DATA[] = {
// metadata
8, // frame width
16, // frame height
10, // frames
2, // frame loop
0xf81f, // transparent color
0, // 16-bits color mode
// colormap
Il ne nous reste plus qu’à invoquer la fonction drawTorches()
, dans la boucle principale, pour afficher les torches sur la scène de jeu :
void loop() {
gb.waitForUpdate();
gb.display.clear();
readUserInput();
avatar.update();
updateGame();
drawTilemap();
drawTorches();
avatar.draw();
}
Et voilà ! Ça ne demandait pas de gros efforts, mais ça donne une toute autre ambiance au décor :
On va s’arrêter là-dessus. Voici le code complet de cette petite touche finale :
examples/example-16.h
#include <Gamebuino-Meta.h>
#include "../assets/rgb565.h"
// ----------------------------------------------------------------------------
// Global constants
// ----------------------------------------------------------------------------
const uint8_t SCREEN_WIDTH = 80;
const uint8_t SCREEN_HEIGHT = 64;
const uint8_t AVATAR_WIDTH = SPRITE_DATA[0];
const uint8_t AVATAR_HEIGHT = SPRITE_DATA[1];
const uint8_t AVATAR_FRAMES = SPRITE_DATA[2];
const uint8_t TILE_WIDTH = TILESET_DATA[0];
const uint8_t TILE_HEIGHT = TILESET_DATA[1];
const uint8_t TILES_WIDE = SCREEN_WIDTH / TILE_WIDTH;
const uint8_t TILES_HIGH = SCREEN_HEIGHT / TILE_HEIGHT;
const uint8_t Y_GROUND = SCREEN_HEIGHT - 2*TILE_HEIGHT;
const uint8_t TILEMAP[] = {
0, 0, 0, 0, 0,
2, 2, 2, 2, 2,
0, 0, 0, 0, 0,
2, 2, 2, 2, 2,
0, 0, 0, 0, 0,
2, 2, 2, 2, 2,
1, 1, 1, 1, 1,
3, 3, 3, 3, 3
};
const int8_t AVATAR_SPEED = 2;
const int8_t AVATAR_JUMP = -5;
const int8_t GRAVITY = 1;
// ----------------------------------------------------------------------------
// Definition of the object-oriented model of the avatar
// ----------------------------------------------------------------------------
struct Avatar {
int16_t x, y;
int8_t vx, vy;
uint8_t frame;
int8_t direction;
bool jumping;
Avatar(int16_t x, int16_t y) : x(x), y(y), vx(0), vy(0), frame(0), direction(1), jumping(false) {}
void moveToLeft() {
vx = - AVATAR_SPEED;
direction = -1;
}
void moveToRight() {
vx = AVATAR_SPEED;
direction = 1;
}
void stop() {
vx = 0;
vy = 0;
frame = 0;
jumping = false;
}
void jump() {
vy = AVATAR_JUMP;
jumping = true;
}
void update() {
x += vx;
y += vy;
if (jumping) {
frame = 3;
} else if (vx && (gb.frameCount & 0x1)) {
++frame %= AVATAR_FRAMES;
}
}
void draw() {
Image sprite(SPRITE_DATA);
sprite.setFrame(frame);
gb.display.drawImage(x, y, sprite, direction * AVATAR_WIDTH, AVATAR_HEIGHT);
}
};
// ----------------------------------------------------------------------------
// Global variables
// ----------------------------------------------------------------------------
Image torch(TORCH_DATA);
Avatar avatar(.5*(SCREEN_WIDTH - AVATAR_WIDTH), Y_GROUND - AVATAR_HEIGHT);
// ----------------------------------------------------------------------------
// Graphics rendering
// ----------------------------------------------------------------------------
void drawTilemap() {
Image tileset(TILESET_DATA);
for (uint8_t j=0; j<TILES_HIGH; ++j) {
for (uint8_t i=0; i<TILES_WIDE; ++i) {
tileset.setFrame(TILEMAP[i + j * TILES_WIDE]);
gb.display.drawImage(
i*TILE_WIDTH, // x
j*TILE_HEIGHT, // y
tileset // image
);
}
}
}
void drawTorches() {
gb.display.drawImage(12, 6, torch);
gb.display.drawImage(60, 6, torch);
}
// ----------------------------------------------------------------------------
// Hnadling user input
// ----------------------------------------------------------------------------
void readUserInput() {
if (gb.buttons.repeat(BUTTON_LEFT, 0)) {
avatar.moveToLeft();
} else if (gb.buttons.repeat(BUTTON_RIGHT, 0)) {
avatar.moveToRight();
} else if (gb.buttons.released(BUTTON_LEFT) || gb.buttons.released(BUTTON_RIGHT)) {
if (!avatar.jumping) avatar.stop();
}
if (gb.buttons.pressed(BUTTON_A) && !avatar.jumping) {
avatar.jump();
}
}
// ----------------------------------------------------------------------------
// Handling physical constraints of the game scene
// ----------------------------------------------------------------------------
void updateGame() {
if (avatar.x < 0) {
avatar.x = 0;
} else if (avatar.x + AVATAR_WIDTH > SCREEN_WIDTH ) {
avatar.x = SCREEN_WIDTH - AVATAR_WIDTH;
}
if (avatar.jumping) {
avatar.vy += GRAVITY;
if (avatar.y + AVATAR_HEIGHT > Y_GROUND) {
avatar.stop();
avatar.y = Y_GROUND - AVATAR_HEIGHT;
}
}
}
// ----------------------------------------------------------------------------
// Initialization
// ----------------------------------------------------------------------------
void setup() {
gb.begin();
gb.setFrameRate(32);
}
// ----------------------------------------------------------------------------
// Main control loop
// ----------------------------------------------------------------------------
void loop() {
gb.waitForUpdate();
gb.display.clear();
readUserInput();
avatar.update();
updateGame();
drawTilemap();
drawTorches();
avatar.draw();
}