Ways of implementing sprite animations

A place for your artistic side. Discuss techniques and tools for pixel art on the NES, GBC, or similar platforms.

Moderator: Moderators

pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Ways of implementing sprite animations

Post by pwnskar »

Banshaku wrote:Since I'm the one that mentioned JSON, I should maybe give more feedback about it ^^;;;
Thanks for the feedback! I agree with the format you suggested, as it makes the output smaller and IMO more human readable as long as you understand what the four values on each row represent. Also, since it better matches the C output of NESST, it might be more familiar to most users and therefore require less learning.

Thanks! :)
niconii
Posts: 219
Joined: Sun Mar 27, 2016 7:56 pm

Re: Ways of implementing sprite animations

Post by niconii »

The only problem is that the JSON standard only allows decimal numbers, so hexadecimal numbers are off-limits.
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: Ways of implementing sprite animations

Post by Banshaku »

@Nicole

I kind of had a feeling that you couln't but wrote it anyway. That's what happens when you have to juggle with so many format/language, you tend to forget ^^;;;

Since the goal is to parse the content into your own format, either we save each value as string that can be formatted for either C/ASM ("$00" or "0x00") or just write decimal values and you just format that decimal in the target format you want later, which is maybe the better choice after all (they are often formater form decimal to -> xxx in many language anyway so it's easier than parsing a string).
User avatar
gauauu
Posts: 779
Joined: Sat Jan 09, 2016 9:21 pm
Location: Central Illinois, USA
Contact:

Re: Ways of implementing sprite animations

Post by gauauu »

Banshaku wrote: Since the goal is to parse the content into your own format ....just write decimal values and you just format that decimal in the target format you want later, which is maybe the better choice after all (they are often formater form decimal to -> xxx in many language anyway so it's easier than parsing a string).
I'm a fan of this method. If a tool will output data into a very standard format (JSON, XML, YAML, CSV, I don't care), then it makes it very easy to write a script to convert it to whatever crazy format I need for my engine. If I need the values written out as hex, I'm happy to convert them myself.
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Ways of implementing sprite animations

Post by pwnskar »

gauauu wrote:
Banshaku wrote: Since the goal is to parse the content into your own format ....just write decimal values and you just format that decimal in the target format you want later, which is maybe the better choice after all (they are often formater form decimal to -> xxx in many language anyway so it's easier than parsing a string).
I'm a fan of this method. If a tool will output data into a very standard format (JSON, XML, YAML, CSV, I don't care), then it makes it very easy to write a script to convert it to whatever crazy format I need for my engine. If I need the values written out as hex, I'm happy to convert them myself.
Yes, I agree as well.

Here's what the JSON looks like atm:

Code: Select all

{
	"metasprites": [
		{
			"label": "playersprite1_idle",
			"oam_entries": [
				57, 32, 91, 3,
				60, 32, 92, 3,
				62, 40, 107, 3,
				56, 40, 106, 3,
				55, 56, 64, 3,
				63, 56, 65, 3,
				52, 31, 62, 1,
				60, 31, 63, 1,
				54, 40, 1, 2,
				67, 40, 2, 2,
				52, 48, 16, 2,
				60, 48, 17, 2,
				67, 48, 18, 2
			]
		},
		{
			"label": "playersprite1_run_03",
			"oam_entries": [
				57, 30, 91, 3,
				60, 30, 92, 3,
				62, 38, 107, 3,
				56, 38, 106, 3,
				50, 53, 72, 3,
				58, 53, 73, 3,
				66, 52, 70, 3,
				52, 29, 62, 1,
				60, 29, 63, 1,
				51, 38, 8, 2,
				66, 38, 10, 2,
				50, 46, 24, 2,
				58, 45, 25, 2,
				66, 46, 26, 2
			]
		},
		{
			"label": "playersprite1_run_01",
			"oam_entries": [
				57, 32, 91, 3,
				60, 32, 92, 3,
				62, 40, 107, 3,
				56, 40, 106, 3,
				52, 56, 66, 3,
				60, 56, 67, 3,
				52, 31, 62, 1,
				59, 29, 89, 129,
				52, 40, 3, 2,
				52, 48, 19, 2,
				60, 48, 20, 2,
				68, 48, 21, 2
			]
		},
		{
			"label": "playersprite1_run_02",
			"oam_entries": [
				56, 38, 6, 2,
				56, 46, 22, 2,
				64, 46, 23, 2,
				57, 30, 91, 3,
				60, 30, 92, 3,
				62, 38, 107, 3,
				56, 38, 106, 3,
				52, 52, 68, 3,
				60, 52, 69, 3,
				68, 52, 70, 3,
				52, 29, 62, 1,
				60, 29, 63, 1
			]
		},
		{
			"label": "playersprite1_holding_weapon_run_01",
			"oam_entries": [
				49, 30, 62, 1,
				55, 28, 89, 129,
				54, 31, 94, 3,
				62, 31, 95, 3,
				54, 39, 110, 3,
				62, 39, 111, 3,
				52, 56, 66, 3,
				60, 56, 67, 3,
				46, 32, 11, 2,
				46, 40, 27, 2,
				54, 40, 12, 2,
				62, 40, 13, 2,
				70, 38, 14, 2,
				54, 48, 28, 2,
				62, 48, 29, 2
			]
		},
		{
			"label": "playersprite1_holding_weapon_run_02",
			"oam_entries": [
				49, 29, 62, 1,
				57, 29, 63, 1,
				54, 30, 94, 3,
				62, 30, 95, 3,
				54, 38, 110, 3,
				62, 38, 111, 3,
				52, 52, 68, 3,
				60, 52, 69, 3,
				68, 52, 70, 3,
				46, 31, 11, 2,
				46, 39, 27, 2,
				54, 39, 12, 2,
				62, 38, 13, 2,
				70, 37, 14, 2,
				54, 46, 28, 2,
				62, 46, 29, 2
			]
		},
		{
			"label": "playersprite1_holding_weapon_run_03",
			"oam_entries": [
				49, 29, 62, 1,
				57, 29, 63, 1,
				54, 30, 94, 3,
				62, 30, 95, 3,
				54, 38, 110, 3,
				62, 38, 111, 3,
				58, 52, 73, 3,
				50, 52, 72, 3,
				66, 51, 70, 3,
				46, 31, 11, 2,
				46, 38, 27, 2,
				54, 39, 12, 2,
				62, 39, 13, 2,
				70, 37, 14, 2,
				54, 46, 28, 2,
				62, 46, 29, 2
			]
		}
	],
	"animations": [
		{
			"label": "running",
			"loop_frame": 0,
			"frames": [
				{
					"delay": 4,
					"metasprite": "playersprite1_run_01"
				},
				{
					"delay": 4,
					"metasprite": "playersprite1_run_02"
				},
				{
					"delay": 4,
					"metasprite": "playersprite1_run_01"
				},
				{
					"delay": 4,
					"metasprite": "playersprite1_run_03"
				}
			]
		},
		{
			"label": "idle",
			"loop_frame": 0,
			"frames": [
				{
					"delay": 4,
					"metasprite": "playersprite1_idle"
				}
			]
		}
	]
}
And here's where the C export is at:

Code: Select all

#ifndef NESAC_MACROS
#define NESAC_MACROS
#define NESAC_LOBYTE(p) (((unsigned int) (p)) & 0xff)
#define NESAC_HIBYTE(p) (((unsigned int) (p)) >> 8)
#endif

const unsigned char playersprite1_idle[]={
	57, 32, 0x5b, 3, 
	60, 32, 0x5c, 3, 
	62, 40, 0x6b, 3, 
	56, 40, 0x6a, 3, 
	55, 56, 0x40, 3, 
	63, 56, 0x41, 3, 
	52, 31, 0x3e, 1, 
	60, 31, 0x3f, 1, 
	54, 40, 0x01, 2, 
	67, 40, 0x02, 2, 
	52, 48, 0x10, 2, 
	60, 48, 0x11, 2, 
	67, 48, 0x12, 2, 
	128
};

const unsigned char playersprite1_run_03[]={
	57, 30, 0x5b, 3, 
	60, 30, 0x5c, 3, 
	62, 38, 0x6b, 3, 
	56, 38, 0x6a, 3, 
	50, 53, 0x48, 3, 
	58, 53, 0x49, 3, 
	66, 52, 0x46, 3, 
	52, 29, 0x3e, 1, 
	60, 29, 0x3f, 1, 
	51, 38, 0x08, 2, 
	66, 38, 0x0a, 2, 
	50, 46, 0x18, 2, 
	58, 45, 0x19, 2, 
	66, 46, 0x1a, 2, 
	128
};

const unsigned char playersprite1_run_01[]={
	57, 32, 0x5b, 3, 
	60, 32, 0x5c, 3, 
	62, 40, 0x6b, 3, 
	56, 40, 0x6a, 3, 
	52, 56, 0x42, 3, 
	60, 56, 0x43, 3, 
	52, 31, 0x3e, 1, 
	59, 29, 0x59, 129, 
	52, 40, 0x03, 2, 
	52, 48, 0x13, 2, 
	60, 48, 0x14, 2, 
	68, 48, 0x15, 2, 
	128
};

const unsigned char playersprite1_run_02[]={
	56, 38, 0x06, 2, 
	56, 46, 0x16, 2, 
	64, 46, 0x17, 2, 
	57, 30, 0x5b, 3, 
	60, 30, 0x5c, 3, 
	62, 38, 0x6b, 3, 
	56, 38, 0x6a, 3, 
	52, 52, 0x44, 3, 
	60, 52, 0x45, 3, 
	68, 52, 0x46, 3, 
	52, 29, 0x3e, 1, 
	60, 29, 0x3f, 1, 
	128
};

const unsigned char* const test_metasprite_list[]={
	playersprite1_idle,
	playersprite1_run_03,
	playersprite1_run_01,
	playersprite1_run_02
};

const unsigned char running[]={
	0,	// index of frame to loop to.
	4,	// time delay for this frame.
	NESAC_LOBYTE(&playersprite1_run_01),
	NESAC_HIBYTE(&playersprite1_run_01),
	4,	// time delay for this frame.
	NESAC_LOBYTE(&playersprite1_run_02),
	NESAC_HIBYTE(&playersprite1_run_02),
	4,	// time delay for this frame.
	NESAC_LOBYTE(&playersprite1_run_01),
	NESAC_HIBYTE(&playersprite1_run_01),
	4,	// time delay for this frame.
	NESAC_LOBYTE(&playersprite1_run_03),
	NESAC_HIBYTE(&playersprite1_run_03)
};

const unsigned char idle[]={
	0,	// index of frame to loop to.
	4,	// time delay for this frame.
	NESAC_LOBYTE(&playersprite1_idle),
	NESAC_HIBYTE(&playersprite1_idle)
};

const unsigned char* const test[]={
	NESAC_LOBYTE(&running),
	NESAC_HIBYTE(&running),
	NESAC_LOBYTE(&idle),
	NESAC_HIBYTE(&idle)
};

So as you can see, I've tried to copy what NESST does but split up the metasprite data in separate arrays. I've still kept the list of pointers though.. don't know if that would come of any use to someone though, so maybe I should just cut it?
But idk, I mean I did add such a list for the animations as well and I'm actually using it (in asm form) in my own project. In the end I guess most people would favor the JSON export though, since it's easier to parse and do whatever you want with.

I've only tried compiling the C export for x86 but from what I can tell the macros seem to work as Nicole intended. One thing that I've cut out from the pasted code is all the unused metasprites. Unused as in not being part of an animation. I'm not sure whether I should check that each metasprite is used or just leave it up to the user. My thinking is there might be a case where you will need some animation that would need to be coded by hand (i have such animations myself) and in such a case you could still draw benefit from having those metasprites exported with their labels. What do you guys think?

:beer:

EDIT: I just discovered that I don't store the metasprite offset from NESST anywhere in the tool. I'll add that to all of the exports ASAP.
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Ways of implementing sprite animations

Post by pwnskar »

I've updated NESAC:
NESAC.zip
(20.5 KiB) Downloaded 496 times
Added features:
+ JSON and C export for animations and metasprites.
+ Ability to load in two tilesets.
+ 8x16 mode for metasprites

For another update I think it might be a good idea to add the ability to export assembly and C with or without metasprites bundled in the same file. Right now it's inconsistent with assembly just outputting animations and C outputting both. I think it would be a good option to let users chose to export them in parts. One such case would be if you're pleased with how the animations are formatted but want to do some RLE packing on your metasprites. Then you could export the animations in asm/C and do whatever you want with the metasprites by parsing the JSON export.

Please let me know if you have any ideas or suggestions on this or anything else!

Cheers! :beer:
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: Ways of implementing sprite animations

Post by Banshaku »

One feature you can add later but is complicated to implement and specific to complicated animation is to allow to change 1k/2k/4k tileset per frame. It may sound unusual but for my current project, when you have boss with complicated frame that are stored in 1k bank slot, you often have to change bank on every frame. This make it quite a pain to test the created sprites set in nesst since you have to manually load the tiles when you change frame.

No rush to add that option but it's an interesting feature when you frames becomes complicated.
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Ways of implementing sprite animations

Post by pwnskar »

That sounds like a good feature. I've not yet tried implementing something like that on the NES myself but I think it should be relatively easy for me to add as a feature in NESAC by adding the ability to load multiple banks and set/read a bank switch command for each animation frame.

Some questions arise, though:

What should be the maximum amount of banks?
Should there be a maximum amount, even?
How should the export for the C and assembly versions of this feature look?

My first thought about number of banks is that they would be limited to a set number but idk what the biggest mapper available today can hold. I'm guessing 256 is a nice big number.
I could perhaps add a listbox of banks somewhere nicely in the gui. Perhaps in an expandable panel that's minimized by default.

About the last question on exports, I'm happy to take any suggestions on how to format the bank switching instructions for C and assembly. How do you store that data in your project, Banshaku?

Thanks for the feedback!
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Ways of implementing sprite animations

Post by tepples »

One game I've been involved with uses an array containing a pointer to each cel's metasprite definition and a second array containing which CHR bank each cel's tiles are in.
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Ways of implementing sprite animations

Post by supercat »

Nicole wrote:The only problem is that the JSON standard only allows decimal numbers, so hexadecimal numbers are off-limits.
Many aspects of the design of Javascript are really terrible, but one very useful aspect of that language is that almost any modern system will have an implementation available which can be used to run many kinds of code by by wrapping the code in some HTML, saving it somewhere with a `.html` extension, and the loading it as a web page which can then place output in a text field. If one a JSON-style object is written to a file in the same directory as such a "web page" preceded by something like "var someName = {objectGoesHere};", the web page will be able to use a "<script src='filename'></script>" tag to read the data in question even if the data includes hex numbers, constants, or other things that aren't allowed in a JSON file.

Note that code running in a web page will, unless the browser is buggy, be limited in what it is allowed to do. While data set by Javascript loaded via "<script>" tag that accesses something in the same directory can be accessed without restriction, scripts will have a limited ability to process graphics fetched for an "<img>" tag unless the path to the IMG has been set using a file-selector or URL-selector object. While having to manually select files is less convenient than having a page simply use them directly, it also makes browsers much safer as script-running tools.

To generate the tile data for Ruby Runner, I write some Javascript, stuck it in a web page, and had it produce a couple of text objects whose contents could then be copied and pasted into include files for ca65; such an approach will allow the scripts to be processed by any system without being bound to Linux, Macintosh, or Windows.
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: Ways of implementing sprite animations

Post by Banshaku »

For now I'm still in the design phase on how data should be managed but it is basically like tepples said: one array contains a list of metasprite and an array of char contains which bank to switch.

As for maximum size, well, a long time ago I was trying to make an editor and trying to define all the maximum that could be used for each in mapper. Now in hindsight, adding all those complication which are just artificial limitations doesn't mean it will make the app better: since the user should know what he's/she's is doing, if I define 20000k of data for a mapper that doesn't exist then it won't be usable. Many programs often put limitations and later those limitation gets in the way.

The only real issue is "how" to define the banks and metasprite data since it affect the indexes of the tiles. For example, when using 1k banks, an engine will decide automatically in which slot the metasprite will be loaded. This means the indexes of tiles will need to be adapted for that slot. In the editor, what is the better way to represent it then: always from slot 0 (first 1k)? Allow any slot? It depends how the game engine manage the slots so this part would be tricky and may be necessary to be flexible enough.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Ways of implementing sprite animations

Post by tepples »

My metasprite routine represents a cel as always in sprite slot 0 (CHR $1000-$13FF). Tile numbers in a metasprite are $00 through $3F, relative to the start of the slot, freeing bits 7 and 6 of the tile number byte to tell whether each individual sprite is flipped vertically or horizontally.

To this tile number the metasprite drawing routine adds the value of the "metasprite base tile" parameter. For games using 1K CHR bank switching (MMC3, VRC2/4, FME-7), this starting value is $00, $40, $80, or $C0, depending on which of the four windows was chosen for a particular actor. For games that stream sprite cels into CHR RAM, this value would be set to the start of the first or second half of a particular actor's sprite cel buffer. I've developed games that use both strategies.*

During a game, the "metasprite base tile" parameter is derived from a property of each actor. In the 1K switching game, sprite sheets can be "small", that is, one 1 KiB or 64-tile page, or "big", comprising multiple pages. Different actors can have the same window value if they use the same small sprite sheet, such as powerups or multiple instances of a simple enemy or even a pair of simple enemies that commonly occur together. But different actors using a big sprite sheet (usually the player character or bosses) must use separate windows. In the game that uses streaming, I hardcoded each actor's space as two 16-tile windows, but if I had to do it over again, I'd probably store the size in tiles of the largest cel along with the sprite sheet and use that to set the size of each actor's space.

Fortunately, during the development of this game, I negotiated permission to reuse low-level code not deemed game-specific in public projects under a free software license. I haven't released everything yet, but I ended up reusing the metasprite code in two projects under the zlib License:
  • From the test harness for a port of the Pently audio driver to ASM6: metasprite.asm
  • From a port of nocash's Magic Floor to Game Boy: metasprite.z80
This released code does not include nontrivial calculation of "metasprite base tile" because I haven't yet made any solo game projects complex enough to need a 1K-switching mapper or sprite cel streaming.


Being careful
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Ways of implementing sprite animations

Post by pwnskar »

I think I'm roughly following what both of you are saying, Bankashu and tepples.

Focusing on keeping the tool ROM based without adding the complexity of RAM based animation, I think it should be possible for me to implement what tepples describes about storing an offset for the tile entries.

It makes sense to me to store the information of which bank to load in which slot along with the metasprite rather than the animation data. That way the metasprite would be correctly displayed when browsing the metasprites and not just during playback. I guess there would be instances where you have a character that actually re-uses the same metasprite data only with switched CHR banks (I think Solstice might be doing this but with CHR-RAM instead of banks). But for the sake of keeping down the complexity of the tool I think it's probably best to start with just having the bank switch data stored with the metasprites, as I'm guessing CHR-ROM is what most devs go for.

I'll have a go at it tonight and see how far I get.

Cheers!
User avatar
Terwilf
Posts: 11
Joined: Tue May 29, 2018 3:23 pm

Re: Ways of implementing sprite animations

Post by Terwilf »

Hi excuse the query, but is it possible to make sprite data imported from JSON, ASM and/or C files? In this way one could convert the data of any game to a format that the application can read :wink:
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: Ways of implementing sprite animations

Post by pubby »

Terwilf wrote: Thu Dec 10, 2020 2:22 am Hi excuse the query, but is it possible to make sprite data imported from JSON, ASM and/or C files? In this way one could convert the data of any game to a format that the application can read :wink:
It's common to represent sprites in JSON or C structs. You have to write your own script to convert that representation to assembly/binary.
Post Reply