Better way to manage animations?
Posted: Thu Apr 22, 2010 2:48 am
Okay I've made a little tech demo of a SHMUP with options and wave shots but as of now sprites are made of boxes. One big reason is that I'm not sure what animation scheme I would use.
Here's the binary and source:
http://rel.betterwebber.com/junk.php?id=100
Here's the code:
I've read the example files from the devkit package and I'm leaning on using the "man" animation scheme instead of the "woman" animation scheme.
Pros of the man animation scheme:
1. GFX are stored in main mem
2. Animations are easier to manage since the frames if a single sprite would share the same oamID.
3. The createsprite function would automatically set the oamID for each sprite (128 at most)
Con:
1. DMA every frame for every sprite that is active. (would that be a big overhead?) You would at most only use 128 DMAcopies.
Pros of the woman scheme:
1. all GFX are in Vram, so it should be faster
Con:
1. oamID would be really cumbersome to manage(I just tried it.)
ie. Allocate gfx for each sprite and some of them share the same oamID so you need to manually set oamIDs for each sprite.
Thanks in advance!
Here's the binary and source:
http://rel.betterwebber.com/junk.php?id=100
Here's the code:
Code: Select all
/*
* SHMUP test
* relminator
* http://rel.betterwebber.com
*
*/
#include <nds.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#define MAX_SPRITES 128
#define MAX_VIPER_BULLETS 24
#define MAX_VIPER_SHOT_DELAY 6
#define VIPER_SHOT_SPEED 8
#define MAX_OPTIONS 2
#define MAX_OPTION_BULLETS 8
#define MAX_OPTION_SHOT_DELAY 10
#define FP_PI 16384
enum E_shot_ID
{
E_SHOT_NORMAL = 0,
E_SHOT_WAVE
};
class Csprite
{
public:
//static u8 current_oamID;
u8 oamID;
s16 x;
s16 y;
u8 width;
u8 height;
bool active;
u16 *gfx;
SpriteColorFormat format;
SpriteSize size;
};
class Cbullet
{
public:
s32 x;
s32 y;
s32 dx;
s32 dy;
s32 angle; // 0-32767
s32 radius; // FP 1.19.12
E_shot_ID shot_ID;
Csprite sprite;
};
class Coption
{
public:
s32 x;
s32 y;
s32 dx;
s32 dy;
s32 angle; //0-32767
s32 radius_x;
s32 radius_y;
Cbullet shots[MAX_OPTION_BULLETS];
Csprite sprite;
};
class Cviper
{
public:
s32 x;
s32 y;
s32 dx;
s32 dy;
u8 shot_level;
Csprite sprite;
};
void start_shots(Cbullet bullet[], s32 start_x, s32 start_y, u8 max);
void start_sine_shots(Cbullet bullet[], s32 start_x, s32 start_y, u8 max);
void do_shots(Cbullet bullet[], u8 max);
void do_options(Coption option[], s32 cx, s32 cy);
Cviper viper;
Cbullet bullets[MAX_VIPER_BULLETS];
Coption options[MAX_OPTIONS];
OamState *oam = &oamMain;
//a sprite constructor
void create_sprite (Csprite &spr,s32 width, s32 height, SpriteSize size, SpriteColorFormat format)
{
static u8 oamID = 0;
spr.oamID = oamID;
spr.active = true;
spr.width = width;
spr.height = height;
spr.size = size;
spr.format = format;
//api: allocate a chunk of sprite graphics memory
spr.gfx = oamAllocateGfx(oam, size, format);
oamID++;
}
//sprite deconstructor
void destroy_sprite(Csprite &spr)
{
spr.active = false;
//api: free the graphics
if(spr.gfx)
{
oamFreeGfx(oam, spr.gfx);
}
spr.gfx = 0;
}
//map our sprite to oam entries
void update_sprite(Csprite &s)
{
//oamSet (OamState *oam, int id, int x, int y, int priority,
// int palette_alpha, SpriteSize size, SpriteColorFormat format,
// const void *gfxOffset, int affineIndex, bool sizeDouble,
// bool hide, bool hflip, bool vflip, bool mosaic)
oamSet(oam,
s.oamID, // id
s.x, s.y, // x,y
0, // priority
0, // palette alpha
s.size, // size
s.format, // color format
s.gfx, // gfx offset
-1, // affine index
false, // double if rotating?
!s.active, // hidden if true
false, // hflip
false, // vflip
false); // apply mosaic?
}
int main(void)
{
videoSetMode(MODE_0_2D);
videoSetModeSub(MODE_0_2D);
vramSetBankA(VRAM_A_MAIN_SPRITE);
vramSetBankB(VRAM_B_MAIN_SPRITE);
vramSetBankD(VRAM_D_SUB_SPRITE);
consoleDemoInit();
iprintf("\x1b[1;1HSHMUP Test alpha");
iprintf("\x1b[2;1HRelminator");
iprintf("\x1b[3;1Hhttp://rel.betterwebber.com");
iprintf("\x1b[5;1HControls:");
iprintf("\x1b[6;1HArrows->move");
iprintf("\x1b[7;1HKey A->Fire weapons");
//api: initialize OAM to 1D mapping with XX byte offsets and no external palette
oamInit(oam, SpriteMapping_1D_128, false);
// let's make a 32x16 ship
int width = 32;
int height = 16;
SpriteSize size = SpriteSize_32x16;
create_sprite (viper.sprite,width, height, size, SpriteColorFormat_256Color);
// allocate gfx ok fill with garbage
if(viper.sprite.gfx)
{
//fill the 256 color sprite with index 1 (2 pixels at a time)
dmaFillHalfWords((1<<8)|1, viper.sprite.gfx, width*height);
}
// let's some a 16x8 bullets
width = 16;
height = 8;
size = SpriteSize_16x8;
for (int i=0; i<MAX_VIPER_BULLETS; i++)
{
create_sprite (bullets[i].sprite, width, height, size, SpriteColorFormat_256Color);
// allocate gfx ok fill with garbage
if(bullets[i].sprite.gfx)
{
//fill the 256 color sprite with random index (2 pixels at a time)
u8 c = rand() % 256;
dmaFillHalfWords((c<<8)|c, bullets[i].sprite.gfx, width*height);
}
bullets[i].sprite.active = false;
}
// let's some a 16x16 options
width = 16;
height = 16;
size = SpriteSize_16x16;
for (int i=0; i<MAX_OPTIONS; i++)
{
create_sprite (options[i].sprite, width, height, size, SpriteColorFormat_256Color);
// allocate gfx ok fill with garbage
if(options[i].sprite.gfx)
{
//fill the 256 color sprite with random index (2 pixels at a time)
u8 c = rand() % 256;
dmaFillHalfWords((c<<8)|c, options[i].sprite.gfx, width*height);
}
//set radius and angle
options[i].angle = i * FP_PI;
options[i].radius_x = 50;
options[i].radius_y = 40;
// setup option bullets
for (int j=0; j<MAX_OPTION_BULLETS; j++)
{
int s_width = 16;
int s_height = 8;
SpriteSize s_size = SpriteSize_16x8;
create_sprite (options[i].shots[j].sprite, s_width, s_height, s_size, SpriteColorFormat_256Color);
// allocate gfx ok fill with garbage
if(options[i].shots[j].sprite.gfx)
{
//fill the 256 color sprite with random index (2 pixels at a time)
u8 c = rand() % 256;
dmaFillHalfWords((c<<8)|c, options[i].shots[j].sprite.gfx, s_width*s_height);
}
options[i].shots[j].sprite.active = false;
}
}
//load a randomly colored palette
for(int i = 0; i < 256; i++)
{
SPRITE_PALETTE[i] = rand();
SPRITE_PALETTE_SUB[i] = rand();
}
u8 viper_shot_delay = MAX_VIPER_SHOT_DELAY;
u8 option_shot_delay = MAX_OPTION_SHOT_DELAY;
viper.x = 10;
viper.y = 96-16;
while(1)
{
int keys = 0;
scanKeys();
keys = keysHeld();
if (keys & KEY_LEFT)
if (viper.x > 0) viper.x -= 3;
if (keys & KEY_RIGHT)
if (viper.x < SCREEN_WIDTH-viper.sprite.width) viper.x += 3;
if (keys & KEY_UP)
if (viper.y > 0) viper.y -= 2;
if (keys & KEY_DOWN)
if (viper.y < SCREEN_HEIGHT-viper.sprite.height) viper.y += 2;
if (keys & KEY_A)
{
viper_shot_delay--;
if(viper_shot_delay == 0)
{
viper_shot_delay = MAX_VIPER_SHOT_DELAY;
start_shots(bullets,viper.x+32,viper.y+4,MAX_VIPER_BULLETS);
start_sine_shots(bullets,viper.x+32,viper.y+4,MAX_VIPER_BULLETS);
}
// options
option_shot_delay--;
if(option_shot_delay == 0)
{
option_shot_delay = MAX_OPTION_SHOT_DELAY;
for (int i=0; i<MAX_OPTIONS; i++)
start_shots(options[i].shots,options[i].x+16,options[i].y+4,MAX_OPTION_BULLETS);
}
}
// copy bullet pos to oam compatible sprite struct
viper.sprite.x = viper.x;
viper.sprite.y = viper.y;
do_shots(bullets,MAX_VIPER_BULLETS);
do_options(options,viper.x+8,viper.y);
// update to oam
// bullets
for (int i=0; i<MAX_VIPER_BULLETS; i++)
{
update_sprite(bullets[i].sprite);
}
// options and option shots
for (int i=0; i<MAX_OPTIONS; i++)
{
update_sprite(options[i].sprite);
for (int j=0; j<MAX_OPTION_BULLETS; j++)
update_sprite(options[i].shots[j].sprite);
}
// viper
update_sprite(viper.sprite);
// wait retrace
swiWaitForVBlank();
//api: updates real oam memory
oamUpdate(oam);
//consoleClear();
//printf("temp= %i \n", Gtemp);
}
return 0;
}
void start_shots(Cbullet bullet[], s32 start_x, s32 start_y,u8 max)
{
for(int i=0; i<max; i++)
{
if (!bullet[i].sprite.active)
{
bullet[i].sprite.active = true;
bullet[i].x = start_x;
bullet[i].y = start_y;
bullet[i].dx = VIPER_SHOT_SPEED;
bullet[i].dy = 0;
bullet[i].shot_ID = E_SHOT_NORMAL;
break;
}
}
}
void start_sine_shots(Cbullet bullet[], s32 start_x, s32 start_y, u8 max)
{
static s16 sine_off = 0;
s16 angle;
sine_off += 512;
for (int j=0; j<2; j++)
{
if (j==0)
angle = FP_PI+sine_off;
else
angle = sine_off;
for (int i=0; i<max; i++)
{
if (!bullet[i].sprite.active)
{
bullet[i].sprite.active = true;
bullet[i].x = start_x;
bullet[i].y = start_y;
bullet[i].dx = VIPER_SHOT_SPEED-2;
bullet[i].dy = start_y;
bullet[i].angle = angle;
bullet[i].radius = 0;
bullet[i].shot_ID = E_SHOT_WAVE;
break;
}
}
}
}
void do_shots(Cbullet bullet[], u8 max)
{
for(int i=0; i<max; i++)
{
if (bullet[i].shot_ID == E_SHOT_NORMAL)
{
bullet[i].x += bullet[i].dx;
bullet[i].y += bullet[i].dy;
}
else
{
bullet[i].angle += 800;
bullet[i].radius += 3000;
s32 dy = (sinLerp(bullet[i].angle)) * (bullet[i].radius);
bullet[i].x += bullet[i].dx;
bullet[i].y = bullet[i].dy + (dy >> 24);
}
if (bullet[i].x>SCREEN_WIDTH)
{
bullet[i].sprite.active = false;
}
// copy bullet pos to oam compatible sprite struct
bullet[i].sprite.x = bullet[i].x;
bullet[i].sprite.y = bullet[i].y;
}
}
void do_options(Coption option[], s32 cx, s32 cy)
{
for(int i=0; i<MAX_OPTIONS; i++)
{
option[i].angle += 256;
option[i].x = cx + ( (cosLerp(option[i].angle) * option[i].radius_x) >> 12);
option[i].y = cy + ( (sinLerp(option[i].angle) * option[i].radius_y) >> 12);
// copy bullet pos to oam compatible sprite struct
option[i].sprite.x = option[i].x;
option[i].sprite.y = option[i].y;
do_shots(option[i].shots, MAX_OPTION_BULLETS);
}
}
I've read the example files from the devkit package and I'm leaning on using the "man" animation scheme instead of the "woman" animation scheme.
Pros of the man animation scheme:
1. GFX are stored in main mem
2. Animations are easier to manage since the frames if a single sprite would share the same oamID.
3. The createsprite function would automatically set the oamID for each sprite (128 at most)
Con:
1. DMA every frame for every sprite that is active. (would that be a big overhead?) You would at most only use 128 DMAcopies.
Pros of the woman scheme:
1. all GFX are in Vram, so it should be faster
Con:
1. oamID would be really cumbersome to manage(I just tried it.)
ie. Allocate gfx for each sprite and some of them share the same oamID so you need to manually set oamIDs for each sprite.
Thanks in advance!