Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - PW's untitled SMS homebrew game

Reply to topic
Author Message
  • Joined: 10 Aug 2023
  • Posts: 33
Reply with quote
PW's untitled SMS homebrew game
Post Posted: Sat Feb 03, 2024 10:36 pm
Hello!

Since last year I've been working on a Sega Master System homebrew game. I used to call it "ninja girl", but then I changed it to a girl holding a laser rifle so I have no idea anymore. I'm intending to make an action game somewhere in the vein of Megaman and Contra. No idea if I can get there, but I'm enjoying the ups and downs so far.

I often post updates, screenshots and clips on Twitter/X under @pw_32x (https://twitter.com/pw_32x). I also lurk on the SMS Power Discord. I thought I'd finally post here in case I wanted to post longer form stuff.

As for the project itself, it uses devkitSMS. I've also got a in-progress engine and a suite of converters and tools I've developed over the years. Everything is open source and up at https://github.com/pw32x/ninjagirl with increasingly outdated documentation. It's not really meant for human consumption, but it's there. The repository also includes a daily versions folder with in-progress roms.

Thanks!

Random in-development shots:

  View user's profile Send private message
  • Joined: 28 Jan 2017
  • Posts: 556
  • Location: Málaga, Spain
Reply with quote
Post Posted: Sun Feb 04, 2024 8:28 am
"Psychic world 2 - carnage in sonic land" would be great!!! (based on images)

Jokes apart:

1. Hey, nice graphics!!!!
2. Why so many historical rom files? git can handle these files also!!!
3. BuildMaster.dll seems to have a virus!!!! (at least, that is what Windows defender) says.
4. Ok, the platform code is working, but now is when the hardest part comes (enemies, collisions, powerups, more screens, a history, etc.) do not hesitate to ask for specific "platformer in ansi c using devkitsms" questions... there are so many complex things to fulfill!!!!

Regards
  View user's profile Send private message
  • Joined: 15 Jul 2022
  • Posts: 29
Reply with quote
Post Posted: Sun Feb 04, 2024 12:17 pm
I've been watching your progress on Twitter/X for some time now.
It's very promising!

Thanks for making the code repository available to the public. It's always good to have it available and learn how you do it.
  View user's profile Send private message
  • Joined: 10 Aug 2023
  • Posts: 33
Reply with quote
Post Posted: Sun Feb 04, 2024 1:53 pm
eruiz00 wrote

1. Hey, nice graphics!!!!


Thanks! It's a hodge-podge of background graphics from older unreleased projects and new sprite graphics. The background graphics will need to be revisited someday. I'm the slowest pixel artist ever so who knows when that will happen.

eruiz00 wrote

2. Why so many historical rom files? git can handle these files also!!!


I like the idea of just picking a version randomly in a folder and see what the progress looked like then. Do I need dozens of rom files in a folder? Probably not. Is it fun to have them around? Definitely.

eruiz00 wrote

3. BuildMaster.dll seems to have a virus!!!! (at least, that is what Windows defender) says.


Strange. A local scan doesn't reveal anything.

I'm still iffy about submitting prebuilt binaries. People would probably expect a proper release of some kind. I might just remove them all to eliminate confusion/hassle.

KingQuack wrote

Thanks for making the code repository available to the public. It's always good to have it available and learn how you do it.


Sure thing! It's in no way, shape or form meant to be some kind of Unity for SMS games, but better to have something out there as an example or a base to build upon.
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14744
  • Location: London
Reply with quote
Post Posted: Sun Feb 04, 2024 7:58 pm
It’s kind of a big no-no to put binaries in Git source control… but it’s also kind of common. It just means you retain all that data in the repo history forever.
  View user's profile Send private message Visit poster's website
  • Joined: 15 Aug 2019
  • Posts: 260
  • Location: Lancashire UK
Reply with quote
Post Posted: Fri Feb 09, 2024 2:00 pm
Hey buddy, been following your posts on Twitter with interest. Glad to see more details about this one going on the forum. In terms of title suggestions you could always open it up as a bit of a competition on social media to drive some engagement but I have a couple I'd throw in the hat.

It's a girl with a laser so how about Laser-Girl. I know its an obvious one but catchy. Alternatively Laser-Bird, Laser-Wife, you get the gist. Good luck bud
  View user's profile Send private message Visit poster's website
  • Joined: 01 Feb 2014
  • Posts: 877
Reply with quote
Post Posted: Sat Feb 10, 2024 6:44 am
Very nice graphics. That cartoony style plays well to the natural strengths of the Master System. You could even emphasize it by using slightly more saturated colors on the sprites. At the moment they appear more muted than the backgrounds which seems a bit backwards, as you normally want the characters to stand out.

Regarding the name: If this had been released in the SMS's homeland back in the day, Laser Rifle Ninja Girl no Daibōken would have been considered a perfectly reasonable name, but when bringing it to the west, that would probably have been shortened to just Laser Girl, which I think has a very nice ring to it.
  View user's profile Send private message
  • Joined: 10 Aug 2023
  • Posts: 33
Reply with quote
Post Posted: Mon Mar 18, 2024 3:38 am
Continuing from https://www.smspower.org/forums/15228-DevkitSMSDevelopYourHomebrewInC?start=750#...
discussing performance improvements to my sprite drawing routine. I didn't want to pollute the main devkitSMS thread, so I'm moving the discussion here.

I've tried new variations on the DrawUtils_DrawBatched function. Unfortunately, none of them have been faster. I'll list them out below.

Breaking out the xtile param into more explicit steps:


void DrawUtils_DrawBatched(void)
{
   const u8* runner = (const u8*)DrawUtils_currentSpriteStrips;

DrawUtils_DrawBatched_loop:

        u16 y = DrawUtils_screenY + (s8)*(runner);

        u16 xtileParam = (DrawUtils_screenX + (s8)*(runner + 1));
        xtileParam <<= 8;
        xtileParam |= *(runner + 2) + DrawUtils_vdpTileIndex;


        switch (*(runner + 3))
        {
        case 0: return;
        case 1:     DrawUtils_addSprite(y, xtileParam);
            break;
        case 2:     DrawUtils_addTwoAdjoiningSprites(y, xtileParam);
            break; 
        case 3:     DrawUtils_addThreeAdjoiningSprites(y, xtileParam);
            break;
        case 4:     DrawUtils_addFourAdjoiningSprites(y, xtileParam);
            break;
        }

        runner += 4;

    goto DrawUtils_DrawBatched_loop;
}



Using a pointer to array to run through the values.


#define PARAM_COMBINER(x, tile) ((x<<8)|tile)

void DrawUtils_DrawBatched(void)
{
   const u8* runner = (const u8*)DrawUtils_currentSpriteStrips;

DrawUtils_DrawBatched_loop:

        u16 y = DrawUtils_screenY + (s16)*(runner++);

        u16 xtileParam = (DrawUtils_screenX + (s16)*(runner++)) << 8;
        xtileParam |= *(runner++) + DrawUtils_vdpTileIndex;


        switch (*(runner++))
        {
        case 0: return;
        case 1:     DrawUtils_addSprite(y, xtileParam);
            break;
        case 2:     DrawUtils_addTwoAdjoiningSprites(y, xtileParam);
            break; 
        case 3:     DrawUtils_addThreeAdjoiningSprites(y, xtileParam);
            break;
        case 4:     DrawUtils_addFourAdjoiningSprites(y, xtileParam);
            break;
        }

    goto DrawUtils_DrawBatched_loop;
}





An array of function calls indexed by the number of sprites to draw


void (*drawSprite[]) (unsigned int y, unsigned int x_tile) __naked __preserves_regs(d,e,iyh,iyl) __sdcccall(1) =
{
    NULL, // do nothing for 0 sprites
    DrawUtils_addSprite,
    DrawUtils_addTwoAdjoiningSprites,
    DrawUtils_addThreeAdjoiningSprites,
    DrawUtils_addFourAdjoiningSprites,
};



#define PARAM_COMBINER(x, tile) ((x<<8)|tile)

void DrawUtils_DrawBatched(void)
{
   const BatchedAnimationSpriteStrip* runner = DrawUtils_currentSpriteStrips;

DrawUtils_DrawBatched_loop:

        if (!runner->count)
            return;

        u16 xtileParam = PARAM_COMBINER(DrawUtils_screenX + runner->xOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        u16 y = DrawUtils_screenY + runner->yOffset;

        drawSprite[runner->count](y, xtileParam);

        runner++;

    goto DrawUtils_DrawBatched_loop;
}




Working with four byte arrays, one for each strip parameter.


void DrawUtils_DrawBatched(void)
{
    const s8* yOffsets = ((const s8*)DrawUtils_currentSpriteStrips);
    const s8* xOffsets = ((const s8*)DrawUtils_currentSpriteStrips) + 1;
    const u8* tileIndexes = ((const s8*)DrawUtils_currentSpriteStrips) + 2;
    const u8* spriteCounts = ((const s8*)DrawUtils_currentSpriteStrips) + 3;

DrawUtils_DrawBatched_loop:

        u16 xtileParam = PARAM_COMBINER(DrawUtils_screenX + *xOffsets, *tileIndexes + DrawUtils_vdpTileIndex);
        u16 y = DrawUtils_screenY + *yOffsets;

        switch (*spriteCounts)
        {
        case 1:     DrawUtils_addSprite(y, xtileParam);
            break;
        case 2:     DrawUtils_addTwoAdjoiningSprites(y, xtileParam);
            break; 
        case 3:     DrawUtils_addThreeAdjoiningSprites(y, xtileParam);
            break;
        case 4:     DrawUtils_addFourAdjoiningSprites(y, xtileParam);
            break;
        }

        yOffsets += 4;
        xOffsets += 4;
        tileIndexes += 4;
        spriteCounts += 4;

        if (!*spriteCounts)
            return;

    goto DrawUtils_DrawBatched_loop;
}



Combination of 4 byte arrays and an array of function pointers.



void (*drawSprite[]) (unsigned int y, unsigned int x_tile) __naked __preserves_regs(d,e,iyh,iyl) __sdcccall(1) =
{
    NULL, // do nothing for 0 sprites
    DrawUtils_addSprite,
    DrawUtils_addTwoAdjoiningSprites,
    DrawUtils_addThreeAdjoiningSprites,
    DrawUtils_addFourAdjoiningSprites,
};



#define PARAM_COMBINER(x, tile) ((x<<8)|tile)

void DrawUtils_DrawBatched(void)
{
    const s8* yOffsets = ((const s8*)DrawUtils_currentSpriteStrips);
    const s8* xOffsets = ((const s8*)DrawUtils_currentSpriteStrips) + 1;
    const u8* tileIndexes = ((const s8*)DrawUtils_currentSpriteStrips) + 2;
    const u8* spriteCounts = ((const s8*)DrawUtils_currentSpriteStrips) + 3;

DrawUtils_DrawBatched_loop:

        if (!*spriteCounts)
            return;

        drawSprite[*spriteCounts](DrawUtils_screenY + *yOffsets, PARAM_COMBINER(DrawUtils_screenX + *xOffsets, *tileIndexes + DrawUtils_vdpTileIndex));

        yOffsets += 4;
        xOffsets += 4;
        tileIndexes += 4;
        spriteCounts += 4;

    goto DrawUtils_DrawBatched_loop;
}


It's late so I haven't collected the generated assembly. I'll do that tomorrow if it's necessary.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3827
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Mar 18, 2024 9:18 am
in general, SDCC performs better when the functions are small and the local variables are kept to a minimum - often one or max two chars or one int. Sometimes using a single local variable with a short lifespan is the best option as it doesn't even get into RAM - SDCC optimizes this using a register.

example:
void foo (void) {
  unsigned char i;
  for (i=0;i<4;i++)
    do_something(i, other_param);
}


I often use a small test.c source file to test how SDCC behaves with different approaches, for instance what would change if in the example above I declare the i variable static? Sure, you need to be able to read the generated Z80 code to exactly see what's going on, but you could anyway measure the different performances using Emulicious' profiler - you're sort of 'blind' testing but that's better than nothing at all.

Regarding using the switch statement - yes, sometimes a block of IF-ELSE-IF performs better, especially when there are just a few different possible values and if they're spread.

For instance here:
switch (k) {
  case 0: do_something(); break;
  case 12: do_something_else(); break;
  case 45: do_something_else_2(); break;
}


I would expect either SDCC turning this into the same code that a block of IF-ELSE-IF would generate or generate sub-optimal code. I'd bet on the former, but it's better to try and see what comes out.
  View user's profile Send private message Visit poster's website
  • Joined: 10 Aug 2023
  • Posts: 33
Reply with quote
Post Posted: Wed Mar 20, 2024 1:10 am
More adventures in the project's optimization journey.

_OUTI

So last night I updated devkitSMS and it went smoothly, except for the custom parts I made that gave me a slight head scratching.

I had added new _OUTI routines for 192 and 256 bytes in crt0_sms.s.


_OUTI256::                              ; _OUTI256 label points to a block of 192 OUTI and a RET
        .rept 64
        outi
        .endm
_OUTI192::                              ; _OUTI192 label points to a block of 192 OUTI and a RET
        .rept 64
        outi
        .endm
_OUTI128::                              ; _OUTI128 label points to a block of 128 OUTI and a RET
        .rept 64
        outi
        .endm
_OUTI64::                               ; _OUTI64 label points to a block of 64 OUTI and a RET
        .rept 32
        outi
        .endm
_OUTI32::                               ; _OUTI32 label points to a block of 32 OUTI and a RET
        .rept 32
        outi
        .endm
_outi_block::                           ; _outi_block label points to END of OUTI block
        ret


But strangely trying to use them wouldn't work correctly. I had made corresponding UNSAFE_SMS_VRAMmemcpy functions. Everything looked correct but trying to use them in my sprite-tiles-to-vdp update code it would be off by one sprite. It would upload 2 sprites instead of 3, 1 instead of 2. I thought maybe the linker was somehow off or maybe the _OUTI256 routine was pushing against some other region of rom. In the end I used toxa's idea to declare the OUTI functions as void* and used nested OUTI128 calls. I didn't need the new 192/256 versions anymore so I saved that much in rom space. Everything started working again.

SDCC

This morning I upgraded my version of SDCC from 4.3 to 4.4. The time spent in DrawUtils_DrawBatched more than doubled, which was shocking. I didn't have the patience to start investigating so I just reverted back to 4.3.

Adjoining Sprites

Before I wanted to try out the devkitSMS metasprite routines, I thought I'd try the new versions of the adjoining sprite functions. Here as well I was really surprised at the performance drop, which DrawUtils_DrawBatched went from just 5% self time to 15%. With the new devkitSMS, its adjoining sprites functions and my modified versions are basically identical, so having a big difference was really odd.

The difference was how the functions were called. Previously I was calling the My_add*AdjoiningSprites_f() functions directly, where I had switched to the SMS_add*AdjoiningSprites macros. For some reason calling the macros tripled the self time of DrawUtils_DrawBatched. When I changed it to use the SMS_addTwoAdjoiningSprites_f functions directly, the performance reverted back to normal.

I've included the C/Asm listings of the DrawUtils_DrawBatched for functions and macros in case someone wanted to take a look.

DrawUtils using functions
https://www.dropbox.com/scl/fi/j0uecrwe8bn12xvkr0w60/DrawUtils_functions.asm?rlk...

DrawUtils using macros
https://www.dropbox.com/scl/fi/kle66zjm0x97w6xcfjh8p/DrawUtils_macros.asm?rlkey=...

I'm not sure what to make of it. You'd think there shouldn't be a difference, but there is.

The adventure continues!
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3827
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Mar 20, 2024 8:02 am
pw wrote
I had added new _OUTI routines for 192 and 256 bytes in crt0_sms.s.

But strangely trying to use them wouldn't work correctly.


I suspect it's because there's only 512 bytes reserved to crt0 by default, unless you tell the linker to reserve more space.
Good you solved that in some other way :)

pw wrote
I've included the C/Asm listings of the DrawUtils_DrawBatched for functions and macros in case someone wanted to take a look.

DrawUtils using functions
https://www.dropbox.com/scl/fi/j0uecrwe8bn12xvkr0w60/DrawUtils_functions.asm?rlk...

DrawUtils using macros
https://www.dropbox.com/scl/fi/kle66zjm0x97w6xcfjh8p/DrawUtils_macros.asm?rlkey=...

I'm not sure what to make of it. You'd think there shouldn't be a difference, but there is.


I've never seen SDCC generate such bad code before. It's full of useless shuffling... and honestly it's hard for me to tell why it's doing so.
Lacking any better idea, I would suggest you try removing the goto and the label. So use
for (;;) {
  <your code here>
}
instead, see if it improves.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3827
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Mar 20, 2024 8:20 am
also I noticed your local variables x,y,tile don't really help because you're recalculating them at each loop, so you could try this:

void DrawUtils_DrawBatched(void)
{
   const BatchedAnimationSpriteStrip* runner = DrawUtils_currentSpriteStrips;

  for (;;) {   // endless loop

    switch (runner->count)
    {
    case 1:     SMS_addSprite(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break;
    case 2:     SMS_addTwoAdjoiningSprites(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break; 
    case 3:     SMS_addThreeAdjoiningSprites(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break;
    case 4:     SMS_addFourAdjoiningSprites(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break;
    }

    runner++;

    if (!runner->count)
        return;

   }  // endless loop terminator
}
  View user's profile Send private message Visit poster's website
  • Joined: 10 Aug 2023
  • Posts: 33
Reply with quote
Post Posted: Wed Apr 17, 2024 3:47 am
Current status.

https://twitter.com/pw_32x/status/1780419857537765868

I've spent the last weeks (months?) optimizing as much as possible and I'm about at the limit of what my meagre skill set do in pure C. The scene above has the amount of objects/projectiles I set as a goal, but there are no special effects animating, no sound fx are being played, and the background isn't getting scrolled. The frame time is still too much.

I'm at a frustration point. My current thinking is like:

I could save myself a lot of trouble if the player had a melee weapon like a sword. I'd only need to perform 3 collision checks instead of 9. Collisions are expensive. Player bullet updates are expensive.

I could also save myself a lot of trouble if I changed genre like doing a simple racing game. But I'd sure like to make an action game.

I could also save myself a lot of trouble and move away from SMS development. I don't mind the hardware/video limitations at all. But the CPU has definitely been a bottleneck for me. My struggles come from not being a low-level programmer. I'm a tools guy.

But let's say for the moment I wanted to integrate inline assembly into the current project. There are tons of z80 materials so I don't think I'll have any problem with its general use. (I've done a smidgen of 6502 a long while ago) The thing that's always mystified me is how to integrate C values/structs into inline assembly.

Here's an update function that handles a bullet going right. Emulicious says takes over a thousand cycles. Does that sound normal? I'm not sure if I can rely on SDCC is doing the best thing.

Anyway,

void Bullet_UpdateRight(GameObject* object)
{
   // 1040/1182/1042.9 min/max/avg cycles

   object->x += object->speedx;
   object->y += object->speedy;

   // world to screen transformation
   object->screenx = object->x - ScrollManager_horizontalScroll;
   object->screeny = object->y - ScrollManager_verticalScroll;

   if (object->screenx > SCREEN_RIGHT)
   {
      ObjectManager_DestroyObject(object);
   }
}


In this case, how would I use the GameObject in inline asm? Where can I find documentation specifically about it? Would I even do it this way? How do I get the correct mindset?

Going back to C, are there other compiler options out there? Some that may be more optimized for the z80?
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14744
  • Location: London
Reply with quote
Post Posted: Wed Apr 17, 2024 7:26 am
One immediate option is to target 30fps to double your frame time.

It’s hard to judge the code you posted in isolation, I guess it will be using the index registers quite heavily which could add up to 1000 cycles. The GameObject structures could be optimised to avoid this, by putting certain fields next to each other and using pointer increments to walk through, but you can only really do that for one code path and the rest need random access.
  View user's profile Send private message Visit poster's website
  • Joined: 19 Oct 2023
  • Posts: 131
Reply with quote
Post Posted: Wed Apr 17, 2024 8:29 am
pw wrote

I could save myself a lot of trouble if the player had a melee weapon like a sword. I'd only need to perform 3 collision checks instead of 9. Collisions are expensive. Player bullet updates are expensive.


I mentioned it on twitter but you could timeout the bullets and ignore terrain. I think Psychic World does something like this?

Then optimise player bullet collisions by only checking the column that an enemy is on.

So player bullet updates and puts itself into the array of columns (so only 1 bullet per col, time your bullets accordingly). Then while the enemy is updating it only does full (or just vertical) collision checks with the bullet in the columns the enemy is overlapping (if any).

pw wrote

I could also save myself a lot of trouble if I changed genre like doing a simple racing game. But I'd sure like to make an action game.


I hope you can stick at it, but know how frustrating it can be. If you need help with any art I'm happy to help but understand if it's a solo thing.
  View user's profile Send private message Visit poster's website
  • Joined: 06 Mar 2022
  • Posts: 670
  • Location: London, UK
Reply with quote
Post Posted: Wed Apr 17, 2024 10:31 am
Further to the above solid advice, to try and address some of your specific questions (and I'm sure sverx will be able to add a lot more)

pw wrote
Emulicious says takes over a thousand cycles. Does that sound normal?

It sounds like a lot, I'd guess there is room for optimisation somewhere.

EDIT: back of envelope - it's about 5 lines isn't it? Per bullet move sounds like a lot.

pw wrote
In this case, how would I use the GameObject in inline asm?

There are a few ways. If you want to keep passing it into your function as a pointer then you'll find that the address of GameObject should be in register HL, as per the default SDCC Z80 calling convention. That's because all pointers on the Z80 are 16 bits and SDCC passes a single 16 bit parameter in HL.

See section 4.3.3 of the SDCC manual.

pw wrote
Where can I find documentation specifically about it?

See above.
Also, there are many places in SMSLib, like this for just one example where you can see asm optimised code for the SMS so that's as good a place as any to get tips.

pw wrote
Would I even do it this way?

Possibly. But you might be better trying to optimise in C first, before resorting to assembler.

pw wrote
How do I get the correct mindset?

Try and get your C code as lean as possible. I think it's been said before here, but start by trying to analyse the code that SDCC generates, which might give you clues to why it's taking the number of cycles that it is. If it points towards potential optimisations at the C level, do those first. If it points to the fact that SDCC is generating inherently inefficient solutions to your specific problem, consider writing the minimal asm to get it back on track.

pw wrote
Going back to C, are there other compiler options out there? Some that may be more optimized for the z80?

SDCC's more or less the only game in town for this particular use case. It's mature and well supported by the community, but C (and other high level compiled languages) work much better when they target processors that have been designed to support its features, like wider data types and hardware floating point operations, etc.

Many times when compilers don't generate what you want them to it's because you are expecting them to make assumptions about your intentions that they cannot, so to get optimal code you may need to refactor what you've written to explicitly force the compiler to go down the track you need it to.

For what it's worth, C to Z80 is generally better than C to 6502!

Stick with it. This community has produced some incredible games written in C so it's definitely possible.

willbritton wrote
If you wanted to share a github project you might find some people with some spare time to have a look and advise more specifically on performance bottlenecks.

EDIT: sorry, I see you already did that in your first post!
  View user's profile Send private message Visit poster's website
  • Joined: 29 Mar 2012
  • Posts: 886
  • Location: Spain
Reply with quote
Post Posted: Wed Apr 17, 2024 11:45 am
@badcomputer's advice about how to check collisions is very good advice. Pure spatial splitting is too much...
  View user's profile Send private message
  • Joined: 09 Aug 2021
  • Posts: 131
Reply with quote
Post Posted: Wed Apr 17, 2024 11:50 am
i started answering about SDCC, but my reply got a bit large, so i thought it will be better idea to put it into the separate topic, here it is: https://www.smspower.org/forums/20104-SDCCBestPracticesForDesigningTheGameInC
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3827
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Apr 17, 2024 1:07 pm
Honestly 1040 cycles minimum for some code that performs 4 additions and checks one 'more than' condition (and performs another function only in case, but that is not counted in those 1040 cycles as that's likely when it does 1082 cycles) is a bit too much in my opinion too.

I tried to compile that presuming they're all 16 bit additions.
Using signed ints for everything I get 927 cycles, using unsigned ints instead down to 919.
Turning the > into a >= it goes down to 900.

Rearranging the code as follows:
void Bullet_UpdateRight(GameObject* object)
{
   object->x += object->speedx;
   object->screenx = object->x - ScrollManager_horizontalScroll;

   if (object->screenx >= SCREEN_RIGHT)
   {
      ObjectManager_DestroyObject(object);
   }
   else
   {
      object->y += object->speedy;
      object->screeny = object->y - ScrollManager_verticalScroll;
   }
}
got it down to 677 cycles.

So one suggestion I can surely give is: process only the data you need to process and make sure you do all processing on some data before you process other data.
I mean: see how object->x depends on object->speedx and then object->screenx depends on object->x - then you check its value before processing the Y transformations, IF they're still needed.

Now I'll see why the generated code requires 677 cycles anyway, it still seems a bit too much in my opinion.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3827
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Apr 17, 2024 1:33 pm
Maxim wrote
I guess it will be using the index registers quite heavily which could add up to 1000 cycles.


Instead, it seems to use them very rarely - but it sure shuffles all the other registers a lot, I wonder if there's really not a better way...

;   ---------------------------------
; Function Bullet_UpdateRight
; ---------------------------------
_Bullet_UpdateRight::
   push   ix
   ld   ix,#0
   add   ix,sp
   push   af
;main.c:26: object->x += object->speedx;
   ld   c,l
   ld   b,h
   ld   e, (hl)
   inc   hl
   ld   d, (hl)
   push   bc
   pop   iy
   ld   l, 4 (iy)
;   spillPairReg hl
   ld   h, 5 (iy)
;   spillPairReg hl
   add   hl, de
   ex   de, hl
   ld   l, c
   ld   h, b
   ld   (hl), e
   inc   hl
   ld   (hl), d
;main.c:27: object->screenx = object->x - ScrollManager_horizontalScroll;
   ld   hl, #0x0008
   add   hl, bc
   ex   (sp), hl
   ld   hl, #_ScrollManager_horizontalScroll
   ld   a, e
   sub   a, (hl)
   inc   hl
   ld   e, a
   ld   a, d
   sbc   a, (hl)
   ld   d, a
   pop   hl
   push   hl
   ld   (hl), e
   inc   hl
;main.c:29: if (object->screenx >= SCREEN_RIGHT)
   ld   a,d
   ld   (hl),a
   sub   a, #0x01
   jr   C, 00102$
;main.c:31: ObjectManager_DestroyObject(object);
   ld   l, c
;   spillPairReg hl
;   spillPairReg hl
   ld   h, b
;   spillPairReg hl
;   spillPairReg hl
   call   _ObjectManager_DestroyObject
   jr   00104$
00102$:
;main.c:35: object->y += object->speedy;
   ld   hl, #0x0002
   add   hl, bc
   ex   (sp), hl
   pop   hl
   push   hl
   ld   e, (hl)
   inc   hl
   ld   d, (hl)
   ld   l, c
;   spillPairReg hl
;   spillPairReg hl
   ld   h, b
;   spillPairReg hl
;   spillPairReg hl
   push   bc
   ld   bc, #0x0006
   add   hl, bc
   pop   bc
   ld   a, (hl)
   inc   hl
   ld   h, (hl)
;   spillPairReg hl
   ld   l, a
;   spillPairReg hl
;   spillPairReg hl
   add   hl, de
   ex   de, hl
   pop   hl
   push   hl
   ld   (hl), e
   inc   hl
   ld   (hl), d
;main.c:36: object->screeny = object->y - ScrollManager_verticalScroll;
   ld   hl, #0x000a
   add   hl, bc
   ld   c, l
   ld   b, h
   ld   hl, #_ScrollManager_verticalScroll
   ld   a, e
   sub   a, (hl)
   inc   hl
   ld   e, a
   ld   a, d
   sbc   a, (hl)
   ld   d, a
   ld   a, e
   ld   (bc), a
   inc   bc
   ld   a, d
   ld   (bc), a
00104$:
;main.c:38: }
   ld   sp, ix
   pop   ix
   ret
  View user's profile Send private message Visit poster's website
  • Joined: 06 Mar 2022
  • Posts: 670
  • Location: London, UK
Reply with quote
Post Posted: Wed Apr 17, 2024 1:46 pm
If ScrollManager_horizontalScroll and ScrollManager_verticalScroll change relatively infrequently, you might be able to take a different approach to converting between world and screen coordinates (since you don't appear to have any scaling to do):

   // when horizontal scroll changes
   object->screenx += horizontal_scroll_delta;
   // when vertical scroll changes
   object->screeny += vertical_scroll_delta;

   // in Bullet_UpdateRight
   object->x += object->speedx;
   object->y += object->speedy;
   object->screenx += object->speedx;
   object->screeny += object->speedy;
  View user's profile Send private message Visit poster's website
  • Joined: 19 Oct 2023
  • Posts: 131
Reply with quote
Post Posted: Wed Apr 17, 2024 3:24 pm
kusfo wrote
@badcomputer's advice about how to check collisions is very good advice. Pure spatial splitting is too much...


I actually got it from your reply here (9 years ago!): https://www.smspower.org/forums/post87614#87614
  View user's profile Send private message Visit poster's website
  • Joined: 29 Mar 2012
  • Posts: 886
  • Location: Spain
Reply with quote
Post Posted: Wed Apr 17, 2024 4:15 pm
badcomputer wrote
kusfo wrote
@badcomputer's advice about how to check collisions is very good advice. Pure spatial splitting is too much...


I actually got it from your reply here (9 years ago!): https://www.smspower.org/forums/post87614#87614


Time flies :_)
  View user's profile Send private message
  • Joined: 10 Aug 2023
  • Posts: 33
Reply with quote
Post Posted: Thu Apr 18, 2024 1:18 am
Thanks for the advice everybody! I appreciate it a lot.

I've decided to take a break on this. It's gotten to a point where it needs a lot more effort than what I'm willing to provide. I just don't have the low-level skillset to get it at an acceptable performance level and the amount of work I foresee to get there does not spark joy.

I've learned from burning out on the 32X that I need to stop before going crazy.

So I went too ambitious on the first attempt at an SMS game, oh well! But I did enjoy working on it. The Master System hardware has its quirks but it has some definite advantages over the NES. And the project did give me an opportunity to work on new tools, new techniques, and new graphics. I loved working on the graphics. Those sprites, animations, and backgrounds will definitely not be wasted.

Anyway, the code and data are always available on the Github, https://github.com/pw32x/ninjagirl. I won't be taking that down or anything. The daily built ROM files are there too for archeology's sakes.

If anybody wants to see how NOT to write a Master System game, I'll be glad to be an example!
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3827
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Apr 18, 2024 9:36 am
pw wrote
I've decided to take a break on this. [...]
I've learned from burning out on the 32X that I need to stop before going crazy.


Take care, and come back later - the tools are constantly improving and we all become more knowledgeable so what you wanted to do won't be out of reach forever.
  View user's profile Send private message Visit poster's website
  • Joined: 23 Jan 2010
  • Posts: 437
Reply with quote
Post Posted: Thu Apr 18, 2024 9:51 am
Quote
I've decided to take a break on this. It's gotten to a point where it needs a lot more effort than what I'm willing to provide. I just don't have the low-level skillset to get it at an acceptable performance level and the amount of work I foresee to get there does not spark joy.

Why give a break? If is because you is tired i will understand but if code is nibbling at the nerves you dont have members in smspower but friends that could help you in any way possible. Try ask...
  View user's profile Send private message
  • Joined: 06 Mar 2022
  • Posts: 670
  • Location: London, UK
Reply with quote
Post Posted: Thu Apr 18, 2024 10:10 am
segarule wrote
Why give a break? If is because you is tired i will understand but if code is nibbling at the nerves you dont have members in smspower but friends that could help you in any way possible. Try ask...

Sounds like pw has made a sensible decision for right now based on some self-knowledge and previous experience and that's something worth respecting.

I wish I was better at taking breaks. It's not a good idea to work so hard at something until you start dreading it.

@pw wishing you the best, we'll still be here :)
  View user's profile Send private message Visit poster's website
  • Joined: 19 Oct 2023
  • Posts: 131
Reply with quote
Post Posted: Thu Apr 18, 2024 10:43 am
As I mentioned on twitter, sorry to hear this but I understand your decision.

I quit my platformer for similar reasons. I couldn't get the performance I needed (particularly with scrolling+collisions) and at that point had run of out energy to keep refactoring. Starting from scratch (at a later date) made more sense.

That's also why I dialed back the technical requirements on my current game. I felt like I needed to learn to walk more before I could run!
  View user's profile Send private message Visit poster's website
  • Joined: 01 Feb 2014
  • Posts: 877
Reply with quote
Post Posted: Thu Apr 18, 2024 12:26 pm
Totally understandable decision. This is a hobby for us all and that should be fun, first and foremost. It’s important to know when to take a step back and recharge.

In the past I have prided myself on finishing a project before starting a new one, but then I nearly burned myself out on not one but two consecutive projects. Now I usually have two in parallel, so I can work on the other if I hit a wall in one.

That said, I loved how your game was shaping up and I’m sad to see it go. However, you’re always welcome to return, either to this or with a new project. We'll be here and try to help you the best we can.
  View user's profile Send private message
  • Joined: 19 Oct 2023
  • Posts: 131
Reply with quote
Post Posted: Thu Apr 18, 2024 6:08 pm
Kagesan wrote

In the past I have prided myself on finishing a project before starting a new one, but then I nearly burned myself out on not one but two consecutive projects. Now I usually have two in parallel, so I can work on the other if I hit a wall in one.


Kagesan convincing me to start another project.
  View user's profile Send private message Visit poster's website
Reply to topic



Back to the top of this page

Back to SMS Power!