It is currently Mon Sep 16, 2019 7:35 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 43 posts ]  Go to page Previous  1, 2, 3
Author Message
PostPosted: Sun Apr 28, 2019 12:51 pm 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 98
Location: Gothenburg, Sweden
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! :)


Top
 Profile  
 
PostPosted: Sun Apr 28, 2019 6:58 pm 
Offline

Joined: Sun Mar 27, 2016 7:56 pm
Posts: 217
The only problem is that the JSON standard only allows decimal numbers, so hexadecimal numbers are off-limits.


Top
 Profile  
 
PostPosted: Sun Apr 28, 2019 7:31 pm 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 2312
Location: Fukuoka, Japan
@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).


Top
 Profile  
 
PostPosted: Mon Apr 29, 2019 9:06 am 
Offline
User avatar

Joined: Sat Jan 09, 2016 9:21 pm
Posts: 624
Location: Central Illinois, USA
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.

_________________
My games: http://www.bitethechili.com


Top
 Profile  
 
PostPosted: Mon Apr 29, 2019 10:51 am 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 98
Location: Gothenburg, Sweden
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:
{
   "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:
#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.


Top
 Profile  
 
PostPosted: Sun May 05, 2019 10:50 am 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 98
Location: Gothenburg, Sweden
I've updated NESAC:
Attachment:
NESAC.zip [20.5 KiB]
Downloaded 95 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:


Top
 Profile  
 
PostPosted: Sun May 05, 2019 8:54 pm 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 2312
Location: Fukuoka, Japan
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.


Top
 Profile  
 
PostPosted: Mon May 06, 2019 1:52 am 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 98
Location: Gothenburg, Sweden
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!


Top
 Profile  
 
PostPosted: Mon May 06, 2019 7:26 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21591
Location: NE Indiana, USA (NTSC)
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.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Mon May 06, 2019 7:52 am 
Offline

Joined: Thu Apr 18, 2019 9:13 am
Posts: 161
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.


Top
 Profile  
 
PostPosted: Mon May 06, 2019 8:26 am 
Offline
User avatar

Joined: Tue Jun 24, 2008 8:38 pm
Posts: 2312
Location: Fukuoka, Japan
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.


Top
 Profile  
 
PostPosted: Mon May 06, 2019 9:25 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21591
Location: NE Indiana, USA (NTSC)
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

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Mon May 06, 2019 10:10 am 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 98
Location: Gothenburg, Sweden
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!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 43 posts ]  Go to page Previous  1, 2, 3

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group