Better way to manage animations?

Post Reply
relminator
Posts: 84
Joined: Sun Apr 11, 2010 10:43 am

Better way to manage animations?

Post by relminator » 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:

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!

StevenH
Posts: 133
Joined: Sun Feb 22, 2009 7:59 pm

Re: Better way to manage animations?

Post by StevenH » Thu Apr 22, 2010 12:06 pm

You missed one - ManWoman Hybrid.

This is where you work out your OAM in advance and use the best method for your game.

If you have multiple ships of the same type, all with different animation frames, and the number of ships on screen are more than or equal to the number of frames in the animation then I would put those frame in the oam, if you've only got a couple of ships on screen, or they all have the same animation frame, then use the DMA copy method.

User avatar
Izhido
Posts: 107
Joined: Fri Oct 19, 2007 10:57 pm
Location: Costa Rica
Contact:

Re: Better way to manage animations?

Post by Izhido » Thu Apr 22, 2010 3:33 pm

StevenH: ... that's so gay...

...

(I'm terribly sorry. I simply couldn't resist.)

StevenH
Posts: 133
Joined: Sun Feb 22, 2009 7:59 pm

Re: Better way to manage animations?

Post by StevenH » Thu Apr 22, 2010 6:16 pm

Izhido wrote:StevenH: ... that's so gay...

...

(I'm terribly sorry. I simply couldn't resist.)
*hand hovers over the moderator button*

I know it's a little gray (I'm assuming that you miss typed gray there) but its a solution that to me works very well, as both methods have pro's and cons, and the hybrid scheme takes them and adds more.

I've been playing with a SHEMUP design for a while, and with the right VRAM setup the hybrid option can work very well, you just have to design your levels and waves correctly. In my SHEMUP I have the main ship that the player uses use the Man style, and the enemy ships all use the Woman style.

Once I finnish writing up my ZX Spectrum conversion / basic game tutorial I'll be looking at sprites, so this will be one of the things I will cover, as the Woman scheme can be as easy or as complicated as you want it to be. Also with the new functions that someones writing in the IRC channel your sprites could be located on the SD card, thus you will add another 2 ways to access your data...

relminator
Posts: 84
Joined: Sun Apr 11, 2010 10:43 am

Re: Better way to manage animations?

Post by relminator » Sat Apr 24, 2010 9:55 am

I've tested the "man" scheme and it works well. I'll see about using the "woman" approach for enemies.

Here's the code and binaries I made today. Features animations and an enemy sprite that uses Catmull-rom splines for motion. Catmull-rom spline is using Fixed-point arithmetic (woot!).

http://rel.betterwebber.com/junk.php?id=101


It uses Gritted images now (I had to make a converter for my old sprites (in BASIC) for it to work.)

relminator
Posts: 84
Joined: Sun Apr 11, 2010 10:43 am

Re: Better way to manage animations?

Post by relminator » Tue Apr 27, 2010 3:30 am

Making a "hybrid" system wasn't as hard as I thought it would be. I now have automatic animations called with the same class procedure with the same interface.

This is kind of a stress test demo I made:

http://rel.betterwebber.com/junk.php?id=101

As usual sources and binaries are included for anyone's perusal.

BTW, comments on the code are welcome.

Thanks!!!

*Next would be the BG. :*)
:lol:

StevenH
Posts: 133
Joined: Sun Feb 22, 2009 7:59 pm

Re: Better way to manage animations?

Post by StevenH » Tue Apr 27, 2010 10:46 am

First comment on the code when I downloaded your first offering - WTF?!?!?! C++....

Then it was HOLY MONOLITHIC CODING, in C++!!!!!

Apart from that it looks OK-ish...

Oh why is FP_PI = 16 << 10??

Also have you looked in the tile viewer of DesMuMe at how your OBJ memory has been allocated?

relminator
Posts: 84
Joined: Sun Apr 11, 2010 10:43 am

Re: Better way to manage animations?

Post by relminator » Wed Apr 28, 2010 1:15 am

StevenH wrote:First comment on the code when I downloaded your first offering - WTF?!?!?! C++....

Then it was HOLY MONOLITHIC CODING, in C++!!!!!

Apart from that it looks OK-ish...

Oh why is FP_PI = 16 << 10??

Also have you looked in the tile viewer of DesMuMe at how your OBJ memory has been allocated?
What's wrong with C++?

FP_PI = 32768/2 one-half of the allowed BRAD system in Libnds.


I just used No$gba. I'll try to DL DesMuMe and see if it has an actual debugger.

elhobbs
Posts: 355
Joined: Thu Jul 02, 2009 1:19 pm

Re: Better way to manage animations?

Post by elhobbs » Wed Apr 28, 2010 3:31 am

Nothing is wrong with C++. You used classes like structs so there is no real benefit over C.

Desmume does not have a built in debugger, but it can be used with gdb or insight. There are a few limitations but it works reasonably well.

Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests