Jump to content
IGNORED

cc7800, a C compiler dedicated to the Atari 7800


bsteux

Recommended Posts

On 8/4/2023 at 11:57 AM, Defender_2600 said:

 

Although 320B has 2 fewer colors than 320C, with 320B you can use 4 colors (out of 6 total) without any pixel placement restrictions (or all 6 colors without restrictions but in this case you have to give up transparency), instead with 320C all 8 colors have restrictions on pixel placement, in fact each pair of pixels must have the same color or a color + background color.

 

So they are different modes with different strengths that must be evaluated for the graphics you want to create.

Really interesting. I noticed that some high profile NES games like SMB3 only uses 9 or 10 colors throughout the game. Could SMB3 be recreated in 320C mode? or would another mode be required?

 

Also whats different about 320D and A modes?

Edited by KrunchyTC
Link to comment
Share on other sites

On 8/4/2023 at 11:57 AM, Defender_2600 said:

 

Although 320B has 2 fewer colors than 320C, with 320B you can use 4 colors (out of 6 total) without any pixel placement restrictions (or all 6 colors without restrictions but in this case you have to give up transparency), instead with 320C all 8 colors have restrictions on pixel placement, in fact each pair of pixels must have the same color or a color + background color.

 

So they are different modes with different strengths that must be evaluated for the graphics you want to create.

I guess a better question is, what is the point of that limitation? what causes that? Why do pixels have to be doubled up like that? or have to share a BG color?

Edited by KrunchyTC
Link to comment
Share on other sites

1 hour ago, KrunchyTC said:

I guess a better question is, what is the point of that limitation? what causes that? Why do pixels have to be doubled up like that? or have to share a BG color?

Maria was engineered for a particular cost target, and they already had to drop the on-board sound-chip to keep within the silicon budget. The 320 modes economically use the same underlying circuitry as the 160 modes, including the palettes and transparency. I'm pretty sure the design goal was the 160A, 160B, and 320A modes, which are all normal enough, with the other 320 modes being bonus/hack modes.

 

In other words, nothing is free. Having quirk-free-high color 320 modes would have required a much more complicated design.

  • Like 4
  • Thanks 2
Link to comment
Share on other sites

15 hours ago, RevEng said:

Maria was engineered for a particular cost target, and they already had to drop the on-board sound-chip to keep within the silicon budget. The 320 modes economically use the same underlying circuitry as the 160 modes, including the palettes and transparency. I'm pretty sure the design goal was the 160A, 160B, and 320A modes, which are all normal enough, with the other 320 modes being bonus/hack modes.

 

In other words, nothing is free. Having quirk-free-high color 320 modes would have required a much more complicated design.

Yeah, that does make sense. I personally would just ditch the hi res modes for anything other than super old arcade ports, like Frenzy. Although, Rikki & Vikki does look great, as does the Mario bros arcade remake. But for side scrollers, and shmups, I'd gladly take the hi color 160 modes, as competent programmers can make them look just as good as NES, and SMS.

  • Like 3
Link to comment
Share on other sites

Hi,

Here is the example for 320B/D sparse tiling. Frankly, using 320B mode is painful (I discovered the C1 issue). By the way, it's supposed to be a 6 colors + background mode, but in my case, it's more a 3+ colors mode (I have to put P4C3 to black in order to draw the black scrolling border at the bottom of the screen. Is there any other known simple way to implement vertical scrolling (saving 1 color, but not requiring more computation for the last display line) ?, and P0C1 and P4C1 are barely usable due to 320B C1 restriction). The guys from Vikky & Rikky did a great job managing to use it (the rocks nicely get over the restrictions - used in my example as a "tribute" to their work. No intention to steal anything). 320D is also a pain in the ass. The multicolor modes (P1 != 0 or P0 != 0), though economical, are barely usable (I've used it for the waterfall in my example), and the 320A-like mode (P0 = 0 and P1 =0) is restricted to 2 colors P0C2 and P4C2... (red and white in my iteration. I think it's the same in Rikky & Vikky, at least on the screenshot I've used as an example).

 

#include "stdlib.h"
#include "string.h"
#include "prosystem.h"
#include "joystick.h"

#define MODE_320BD

// Generated from tiles7800 --sparse 320BD_tiles.yaml 320BD_sparse.tmx
const char tilemap_0_0[2] = {84, 86};
const char tilemap_0_1[1] = {44};
const char tilemap_0_2[4] = {76, 78, 80, 82};
const char tilemap_0_3[1] = {44};
const char tilemap_0_4[2] = {80, 82};
const char tilemap_0_5[2] = {84, 86};
const char tilemap_0_6[4] = {52, 54, 56, 58};
const char tilemap_0_7[2] = {88, 90};
const char tilemap_0_data[] = {0, 0, tilemap_0_0, 0xe0, tilemap_0_0 >> 8, (0 << 5) | ((-2) & 0x1f), 15, 1, 1, tilemap_0_1, 0x60, tilemap_0_1 >> 8, (5 << 5) | ((-1) & 0x1f), 11, 8, 7, tilemap_0_2, 0xe0, tilemap_0_2 >> 8, (0 << 5) | ((-4) & 0x1f), 24, 9, 9, tilemap_0_3, 0x60, tilemap_0_3 >> 8, (5 << 5) | ((-1) & 0x1f), 11, 10, 10, tilemap_0_4, 0xe0, tilemap_0_4 >> 8, (0 << 5) | ((-2) & 0x1f), 15, 24, 24, tilemap_0_5, 0xe0, tilemap_0_5 >> 8, (0 << 5) | ((-2) & 0x1f), 15, 28, 27, tilemap_0_6, 0xe0, tilemap_0_6 >> 8, (0 << 5) | ((-4) & 0x1f), 24, 31, 31, tilemap_0_7, 0xe0, tilemap_0_7 >> 8, (0 << 5) | ((-2) & 0x1f), 15, 96, 0xff};
..
const char tilemap_data[64] = {tilemap_0_data & 0xff, tilemap_0_data >> 8, tilemap_1_data & 0xff, tilemap_1_data >> 8, tilemap_2_data & 0xff, ...
...
tilemap_30_data & 0xff, tilemap_30_data >> 8, tilemap_31_data & 0xff, tilemap_31_data >> 8};

#define TILING_HEIGHT 32
#define TILING_WIDTH 32
#include "sparse_tiling.h"

// Generated from sprites7800 320BD_tiles.yaml
reversed scattered(16,24) char white_tubes[384] = {
	0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc,
	0x30, 0x00, 0x00, 0x18, 0x7f, 0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x00, 0xc0,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0xb6, 0x00, 0x00, 0xda, 0x7f, 0xfe, 0x3f, 0xfc,
	0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfb, 0xfe, 0xdf, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xdf, 0xfc,
	0xb6, 0xff, 0xfe, 0xda, 0x05, 0xa0, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xf2, 0xff,
...
  
ramchip int x, y;

void main()
{
    x = 0; y = 0;

    multisprite_init();
    multisprite_set_charbase(white_tubes);
    tiling_init(tilemap_data);
    joystick_init();
    
    tiling_goto(x, y);
    *P0C1 = multisprite_color(0xbc); // Light turquoie
    *P0C2 = multisprite_color(0x33); // Red
    *P0C3 = multisprite_color(0xa5); // Dark turquoise
    *P4C1 = multisprite_color(0x83); // Dark Blue
    *P4C2 = 0x0f; // White
    // P4C3 is black due to bottom scrolling hiding scheme

    // Main loop
    do {
        multisprite_flip();
        joystick_update();
        if (joystick[0] & JOYSTICK_LEFT) x--; 
        else if (joystick[0] & JOYSTICK_RIGHT) x++;
        if (joystick[0] & JOYSTICK_UP) y--; 
        else if (joystick[0] & JOYSTICK_DOWN) y++;
        tiling_goto(x, y);
    } while(1);
}

 

I've attached to this post the first windows release of sprites7800 and tiles7800, so that Windows user can use these tools without using Rust and Cargo. All the examples are in the examples directory in my Github cc7800 repository.

Best regards !

320BD_tiles.png

tiled_320BD_sparse.png

sprites7800.exe tiles7800.exe 320BD_sparse.tsx 320BD_sparse.tmx 320BD_tiles.yaml

  • Like 9
Link to comment
Share on other sites

  • 2 weeks later...

I've updated multisprite.h in cc7800 to implement DMA Masking vertical scrolling, an idea by @RevEng. It's working like a charm. Previously, my vertical scrolling feature was implemented by drawing a black curtain on the bottom of the screen (consuming DMA, ROM memory of the curtain and a palette color for the black). Now, vertical scrolling consumes less DMA where sprites are displayed in the bottom (46 DMA cycles in that zone vs 10 + 32 * 3 + (8 + 31 * 3) = 409 cycles in the DMA Masking zone). This leaves more DMA cycles for displaying actual sprites, and it uses no ROM memory neither palette color. Now my sparse tiling example in 320BD mode can use the P4C3 color (orange), and now uses all the available colors like in Rikki & Vikki !

  • Like 10
Link to comment
Share on other sites

Your cc7800 is fast becoming the most interestin tool for 7800 games. I have to confess that I don't even get all the goodies you have implemented yet. But at some point in time I hope to read through this carefully. Thanks for sharing!

 

  • Like 1
Link to comment
Share on other sites

2 hours ago, karri said:

Your cc7800 is fast becoming the most interestin tool for 7800 games. I have to confess that I don't even get all the goodies you have implemented yet. But at some point in time I hope to read through this carefully. Thanks for sharing!

 

Thanks, but unfortunately I think cc7800 still requires a lot of documentation to explain all the features and macros already available. An expert in Maria and C will probably find it straightforward, but as of today it's far from reach from the majority of game designers. A best starting point certainly is 7800Basic, which is wonderfully documented. But at least it's good to have a choice, especially for people who have 20+ years of experience in C. Volunteers for documentation are welcome ! I'll do everything possible to provide all the necessary explanations and help.

  • Like 5
Link to comment
Share on other sites

7 hours ago, SlidellMan said:

[...] However, judging from the lack of proper documentation, it still has a ways to go.

I know theres no ill will intended, but don't think "proper documentation" is entirely accurate . cc7800 just expects the programmer to be savvy about the 7800 (same as dasm) and C type languages. (which are legion)

  • Like 1
Link to comment
Share on other sites

14 hours ago, SlidellMan said:

Wow, you may have to show what you have made to Pigsy's Retro Game Development. However, judging from the lack of proper documentation, it still has a ways to go.

Yes, still ways to go. Indeed, cc7800 is fourfold :

- It's a C compiler for 6502 that is efficient enough to produce Atari console game code (i.e. DASM quality-like. It's not possible to use cc65 for instance to make an "action" game on 2600 or 7800, since the code would be too big and too slow. I've tried and this was totally unsucessful). This is cc6502 (written from scratch in Rust), which is I think operational and well tested. It doesn't support the full C language (no structures in particular, but structure support is so badly handled by the 6502...), but is really optimized 6502 code oriented through the possible use of "special" X and Y indexing variables. cc6502 itself requires some documentation to explicit all the limitations and to show how to write code in order to get the most of the 6502. Frankly, given all the tests and programs I've already written with it, it's quite mature today.

- It's a mapper for Atari 7800, to support the specific Atari 7800 memory layout and bankswitching. This is actually cc7800, adding keywords to the C language : bankX, scattered, holeydma, ramchip... This is working but may be not complete. Requires also some explanations.

- It's a set of tools (tools7800) to generate C code for the sprites, tiles...

- And to make it usable to create games, a set of headers (conio.h, multisprite.h, sparse_tiling.h, etc) and examples, with many macros that I'm still currently developing. This also requires documentation before being fully useful...

It's going on. My goal is to have a complete package for the 4th of april 2024, the date of anniversary of the first release.

This will never be a package for beginners. This will always require prior C embedded development experience.

  • Like 4
Link to comment
Share on other sites

@bsteux any new tools are greatly appreciated and welcomed. This is a great option for something that needs to be more performant on the hardware where speed and memory are optimized. 7800basic is a great tool and can allow some wonderful development possibilities, but I am always looking for ways to expand capabilities and performance. I haven't coded in C in quite some time, but I will have to brush up on the old skills.

 

Thank for you creating a pathway for more great coding on our little system. It's great to see guys like you empowering the 7800 to do what it was meant to do back in the mid-1980s.

  • Like 2
Link to comment
Share on other sites

A new macro was added to multisprite.h : multisprite_display_small_sprite, in order to support the optimized display of sprites of small height (8 lines) with 16 lines high display zones. When the y position of a small sprite is below 8 in a height 16 display zone, it's possible to avoid the second display list entry (generally, a 16 lines sprite requires 2 display list entries, except when it's aligned with the display zone), yielding an average gain of 25% of DMA/CPU/DLL memory. With this optimization, we can get 64 missiles running around at 60fps and 128 missiles at 30fps, and less DMA glitches.

Here is the full example :

 

#include "prosystem.h"
#include "multisprite.h"

char i, xpos, ypos;

#define NB_SMALL_SPRITES 128
ramchip short sp_xpos[NB_SMALL_SPRITES], sp_ypos[NB_SMALL_SPRITES];
ramchip char sp_direction[NB_SMALL_SPRITES];

const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };

// Generated with sprites7800 missile.yaml
holeydma reversed scattered(16,1) char missile[16] = {
	0x18, 0x96, 0x7a, 0x7e, 0x7e, 0x6e, 0x9a, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

void main()
{
    multisprite_init();
    
    *P0C1 = multisprite_color(0x1c); // Yellow 
    *P0C2 = multisprite_color(0x37); // Orange
    *P0C3 = multisprite_color(0x43); // Red
   
    // Initialize small sprites
    for (ypos = 0, xpos = 0, i = 0, X = 0; X != NB_SMALL_SPRITES; xpos++, ypos++, X++) {
        sp_xpos[X] = xpos << 8;
        sp_ypos[X] = ypos << 8;
        sp_direction[X] = i++;
        if (i == 24) i = 0;
    }

    // Main loop
    do {
        multisprite_flip();
        for (i = 0; i != NB_SMALL_SPRITES; i++) {
            X = i;
            Y = sp_direction[X];
            sp_xpos[X] += dx[Y];
            sp_ypos[X] += dy[Y];
            xpos = sp_xpos[X] >> 8;
            ypos = sp_ypos[X] >> 8;
            if ((xpos < 5 && (dx[Y] >> 8) < 0) || 
                (xpos >= 150 && (dx[Y] >> 8) >= 0)) {
                sp_direction[X] = horizontal_pingpong[Y];
            }
            if ((ypos < 5 && (dy[Y] >> 8) < 0) || 
                (ypos >= MS_YMAX - 20 && (dy[Y] >> 8) >= 0)) {
                sp_direction[X] = vertical_pingpong[Y];
            }
            multisprite_display_small_sprite(xpos, ypos, missile, 1, 0); 
        }
    } while(1);
}

You will also find in this post the latest installer for cc7800 and a new sprites7800 window executable that supports small sprites and big sprites. Next example will be a big sprite display using the macro multisprite_display_big_sprite.

missile.png

missile.yaml cc7800-0.2.12-x86_64.msi sprites7800.exe

  • Like 3
Link to comment
Share on other sites

No shmup without a good explosion, which requires big sprite display. And big displays is one of the nice features of Maria !

So here are the explosions (examples/example_explosions.c) using the new multisprite_display_big_sprite macro (which optimizes the big sprite display by chaining the display list entries) :

 

#include "prosystem.h"
#include "multisprite.h"

#define NB_EXPLOSIONS 5 
ramchip char xpos[NB_EXPLOSIONS], ypos[NB_EXPLOSIONS];

// Generated with sprites7800 explosion.yaml
holeydma reversed scattered(16,6) char explosion1[96] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

void main()
{
    char i, j = 0, k, l = 0, x, y;
    multisprite_init();
    
    *P0C1 = multisprite_color(0x1c); // Yellow 
    *P0C2 = multisprite_color(0x37); // Orange
    *P0C3 = multisprite_color(0x43); // Red
   
    // Initialize explosions 
    x = 0; y = 0;
    for (X = 0; X != NB_EXPLOSIONS; X++) {
        xpos[X] = x;
        ypos[X] = y;
        x += 160 / NB_EXPLOSIONS;
        y += 224 / NB_EXPLOSIONS;
    }

    // Main loop
    do {
        multisprite_flip();
        char *gfx = explosion1;
        for (X = j; X != 0; X--) gfx += 18;
        k = j;
        for (i = 0; i != NB_EXPLOSIONS; i++) {
            x = xpos[i];
            y = ypos[i];
            multisprite_display_big_sprite(x, y, gfx, 6, 0, 3, 0);
            gfx += 18;
            k++;
            if (k == NB_EXPLOSIONS) {
                k = 0;
                gfx = explosion1;
            }
        }
        l++;
        if (l == 5) {
            j++;
            if (j == NB_EXPLOSIONS) j = 0;
            l = 0;
        }
    } while(1);
}

 

Next will come the integration of sparse tiling with vertical scrolling, in order to make nice shmups and run & guns. And then pixel-perfect collision computation, which is also required for a good shmup...

Maybe (if I've got enough time) will I turn the sparse tiling into bitmapped display (immediate mode) in RAM - at the moment it only uses ROM -, in order to save DMA/CPU for more bullets on screen... Let's see...

 

explosion.png

explosion.yaml

  • Like 6
Link to comment
Share on other sites

On 9/14/2023 at 7:01 AM, KrunchyTC said:

I'm curious if anyone here has ever delved into the code of double dragon on 7800, and could see why its such a bad port :P 

From what I've read on the web (I've neither tested the game nor looked at the code), it's not considered a bad port. Double dragon is even cited as one of the best games on the Atari 7800 (well, there aren't many to be honest) https://neovideohunter.wordpress.com/2018/01/13/atari-7800-review-double-dragon-activision-1989/. From what I've seen, it looks like it was programmed by seasoned guys (given the specificity of the Atari 7800 and the lack of tools at this time). The upper part of the screen is in 320 mode, and it's using 160A sparse tiling mode for the background (with many palettes). Not so bad. The characters are ugly. They should have been using 160B mode, but they probably couldn't afford it (probably need to reuse the same graphics with different palettes due to the limited ROM available). And in contrary to the NES, a 2 players game is available ! From a technical point of view, it makes sense. Is it really that bad at playing ?

Link to comment
Share on other sites

Here is the next contribution to cc7800 : sparse tiling integrated with vertical scrolling. The idea is that you can easily design some nice levels with Tiled (Usually 20 tiles large and max 256 in height), generate the code for the tiles with sprites7800, generate the sparse tiles data with tiles7800, and integrate it into your scrolling code. This yields a quite efficient way of making backgrounds, not too expensive (it's sparse tiling, so you can use 160A with holes and as many palettes as you want), and using only ROM (as this version uses the indirect mode of Maria). In my example, the 256 high shmup level consumes 4kb for the tiles graphics, and around 4kb for the sparse tiles data. You can imagine making a "super game" with 7 levels, each occupying a 16kb ROM bank (4kb for the sprites, 4kb for the sparse tiles (level definition), 4kb for the tiles graphics data (backdoung), and 4kb for the music, the specific code and the enemy spawning tables).

The code for this example (examples/example_vertical_scrolling_sparse.c), using only 2 palettes (blue and grey) :

#include "prosystem.h"
#define VERTICAL_SCROLLING
#include "multisprite.h"

// Generated by sprites7800 shmup_tiles.png
reversed scattered(16,32) char blue_objects1[512] = {
	0x00, 0x50, 0x74, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x55,
	0x40, 0x00, 0x74, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x54, 0xf8, 0x00, 0x00, 0xa5,
	0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x00, 0x00, 0x15, 0x55, 0x55,
....
	0x0e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x0f, 0x50, 0x0f, 0x50,
	0x0e, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x09, 0x20, 0x09, 0x20,
	0x0d, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x10, 0x0a, 0x10, 0x0a, 0x10,
	0x0e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0x09, 0x10, 0x09, 0x20
};

// Generated by tiles7800 --sparse shmup_tiles.yaml shmup.tmx
#include "example_shmup_tiles.c"

void main()
{
    char counter = 255;
    char done = 0;

    multisprite_init();
    multisprite_set_charbase(blue_objects1);
    multisprite_vscroll_init_sparse_tiles(tilemap_data_ptrs);

    // Grey palette
    *P0C1 = 0x04;
    *P0C2 = 0x08;
    *P0C3 = 0x0c;

    // Blue palette
    *P1C1 = multisprite_color(0x84); // Dark blue 
    *P1C2 = multisprite_color(0x88); // Light blue
    *P1C3 = multisprite_color(0xac); // Turquoise 

    // Main loop
    do {
        // Prepare scrolling data
        if (multisprite_vscroll_buffer_empty()) {
            if (!done) {
                multisprite_vscroll_buffer_sparse_tiles(counter);
                if (counter) counter--; else done = 1; 
            } else if (done == 1) {
                multisprite_vscroll_buffer_sparse_tiles(255);
                done = 2;
            } else done = 3;
        }

        while (*MSTAT & 0x80); 
        multisprite_flip();
        if (done != 3) multisprite_vertical_scrolling(1);

    } while(1);
}

You will find in attachment the latest versions of sprites7800 (with collision map generation, for the next demo), tiles7800 (with 256 high sparse tiling code generation), the shmup project, and the compiled 32kb ROM of the example.

Next will be a demo for pixel perfect collision, and next next a vertical scrolling using RAM and immediate mode (instead of the ROM and Maria indirect mode here).

And maybe next next next a "kinda" shmup game with everything together (vertical scrolling, a controllable spaceship, some bullets and a bad guy to get shot at).

tiled_shmup.png

shmup_tiles.png

sprites7800.exe tiles7800.exe shmup.tiled-project shmup_tiles.yaml shmup.tsx shmup.tmx example_vertical_scrolling_sparse_tiling.a78

  • Like 6
Link to comment
Share on other sites

8 hours ago, bsteux said:

From what I've read on the web (I've neither tested the game nor looked at the code), it's not considered a bad port. Double dragon is even cited as one of the best games on the Atari 7800 (well, there aren't many to be honest) https://neovideohunter.wordpress.com/2018/01/13/atari-7800-review-double-dragon-activision-1989/. From what I've seen, it looks like it was programmed by seasoned guys (given the specificity of the Atari 7800 and the lack of tools at this time). The upper part of the screen is in 320 mode, and it's using 160A sparse tiling mode for the background (with many palettes). Not so bad. The characters are ugly. They should have been using 160B mode, but they probably couldn't afford it (probably need to reuse the same graphics with different palettes due to the limited ROM available). And in contrary to the NES, a 2 players game is available ! From a technical point of view, it makes sense. Is it really that bad at playing ?

Yeah I mean, it's an ok port for sure. it could have been better, like the fact there is no actual scrolling (slow and very coarse). Could have looked miles better, but as you said, they probably couldn't afford it (Jack Tramiel).

Edited by KrunchyTC
  • Like 1
Link to comment
Share on other sites

Hi,

Today is about collision detection and more precisely fast pixel accurate collision detection. Collision detection is something very important in video games, but unfortunately the Atari 7800 doesn't provide any hardware support for it. We'll have to deal with the 6502 for that... I've added 2 macros to headers/multisprite.h : multisprite_compute_box_collision, and multisprite_compute_collision. The first one is a simple macro that (as its name implies) checks for a box collision between 2 objects - the classical way on Atari 7800, fast but not accurate -. The second one is pixel accurate collision detection that relies on a pre-computed collision map between sprites. 

Example : I want to compute the collision between a bullet and a spaceship. In shmup.yaml, I define :

sprite_sheets:
  - image: shmup.png
    holeydma: 16
    sprites:
      - name: bullet
        top: 48 
        left: 0
        height: 8
        width: 8
        palette: fire
      - name: spaceship 
        top: 48 
        left: 8
        height: 32
        width: 24
        mode: 160B
        palette: all
    collisions:
      - sprite1: bullet 
        sprite2: spaceship

Note the new collisions keyword, that will make sprites7800 generate the collision map between the 2 sprites.

When we execute sprites7800 shmup.yaml, it generates a collision map, which is a simple C array :

const char collision_bullet_spaceship[78] = {0x03, 0x80, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x0f, 0xe0, 0x0f, 0xe0, 0x0f, 0xe0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x3f, 0xf8, 0x3f, 0xf8, 0x1b, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

We can use it in our game code to detect a collision by :

 multisprite_compute_collision(xbullet, ybullet, 4, 8, spaceship_x, spaceship_y, 12, 24, collision_bullet_spaceship);
 if (multisprite_collision_detected) {
     // Explosion
 }

where we provide the position of the 2 possibly colliding objects, their size and the collision map computed by sprites7800.

The macro is defined in headers/multisprite.h as :

// ~100 cycles max pixel accurate collision detection (60us)
#define multisprite_compute_collision(x1, y1, w1, h1, x2, y2, w2, h2, collision_map) {\
    _ms_tmp3 = 0; \
    _ms_tmp2 = (y1) + ((h1) - 1) - (y2); \
    if (_ms_tmp2 >= 0) { \
        if ((x1) <= (x2) + ((w2) - 1)) { \
            if ((y1) <= (y2) + ((h2) - 1)) { \
                _ms_tmp = (x1) + ((w1) - 1) - (x2); \
                if (_ms_tmp >= 0) { \
                    Y = _ms_tmp2 << ((w1 + w2 - 1) / 8); \
                    while (_ms_tmp >= 8) { \
                        Y++; \
                        _ms_tmp -= 8; \
                    } \
                    _ms_tmp3 = collision_map[Y] & _ms_bit_extract[X = _ms_tmp]; \
                } \
            } \
        } \
    } \
}

It's basically 4 if/then (box collision test), followed by a bit test on the collision map. This generates the following assembler code in our example :

; { _ms_tmp3 = 0; _libc_tmp2 = ( ybullet) + (( 8) - 1) - ( spaceship_y); if (_libc_tmp2 >= 0) { if ((xbullet) <= ( spaceship_x) + (( 12) - 1)) { if (( ybullet) <= ( spaceship_y) + (( 24) - 1)) { _libc_tmp = (xbullet) + (( 4) - 1) - ( spaceship_x); if (_libc...
	LDA #0                 	; 2
	STA _ms_tmp3           	; 3
	LDA main_3_ybullet     	; 3
	CLC	; 2
	ADC #7                 	; 2
	SEC	; 2
	SBC spaceship_y        	; 4
	STA _libc_tmp2         	; 3
	BCC .ifend98           	; 2/3
	LDA spaceship_x        	; 4
	CLC	; 2
	ADC #11                	; 2
	CMP main_3_xbullet     	; 3
	BCC .ifend99           	; 2/3
	LDA spaceship_y        	; 4
	CLC	; 2
	ADC #23                	; 2
	CMP main_3_ybullet     	; 3
	BCC .ifend100          	; 2/3
	LDA main_3_xbullet     	; 3
	CLC	; 2
	ADC #3                 	; 2
	SEC	; 2
	SBC spaceship_x        	; 4
	STA _libc_tmp          	; 3
	BCC .ifend101          	; 2/3
	LDA _libc_tmp2         	; 3
	ASL	; 2
	TAY	; 2
.while12
	LDA _libc_tmp          	; 3
	CMP #8                 	; 2
	BCC .whileend12        	; 2/3
	INY	; 2
	LDA _libc_tmp          	; 3
	SEC	; 2
	SBC #8                 	; 2
	STA _libc_tmp          	; 3
	JMP .while12           	; 3
.whileend12
	LDX _libc_tmp          	; 3
	LDA collision_bullet_spaceship,Y	; 4/5
	AND _ms_bit_extract,X  	; 4/5
	STA _ms_tmp3           	; 3
.ifend101
.ifend100
.ifend99
.ifend98

which is not much more than simple box collision. Its max execution time is 60us, but most of the time it will be less (we start by testing the y position of the 2 objects, and most of the time the bullets are above the spaceship). Note that we have defined a macro and not used a C function in order to benefit from constant parameter optimization (as width and height are constants, the code is simpler than when using variables).

 

Here is the source code for example_collisions, which is almost a game (try to survive this 32 sprites assault) :

#include "prosystem.h"
#include "multisprite.h"
#include "joystick.h"

const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };

// Generated by sprites7800 shmup.yaml

holeydma reversed scattered(16,6) char explosion1[96] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x2a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x68, 0x00, 0x00, 0x00, 0x62, 0x96, 0xaa, 0xa8, 0x00,
...
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
holeydma reversed scattered(16,1) char bullet[16] = {
	0x18, 0x96, 0x7a, 0x7e, 0x7e, 0x6e, 0x9a, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
holeydma reversed scattered(16,6) char spaceship[96] = {
	0x00, 0x00, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x30, 0x80,
	0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00,
	0xe1, 0x94, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x94, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x94, 0x00, 0x00,
	0x00, 0x00, 0xb9, 0xd6, 0x00, 0x00, 0x10, 0x00, 0xba, 0xd6, 0x00, 0x40, 0x13, 0x30, 0xba, 0xd6,
	0x80, 0x4c, 0x20, 0x30, 0xa9, 0x96, 0x80, 0x80, 0x20, 0xd2, 0xa9, 0x96, 0x68, 0x80, 0x20, 0xd2,
	0xa9, 0x96, 0x68, 0x80, 0x00, 0x5a, 0xaa, 0x5a, 0x5a, 0x00, 0x32, 0x5a, 0xaa, 0x5a, 0x5a, 0x80
};
holeydma reversed scattered(16,6) char spaceship_1[96] = {
	0xda, 0x5a, 0x66, 0x59, 0x5a, 0x68, 0xda, 0x9e, 0x6a, 0x5a, 0x6b, 0x68, 0xda, 0x9a, 0x68, 0x52,
	0x6a, 0x68, 0xda, 0x9a, 0xa0, 0x50, 0x6a, 0x68, 0xfb, 0x98, 0xe0, 0x60, 0x62, 0xec, 0x00, 0x98,
	0x30, 0x80, 0x62, 0x00, 0x00, 0x98, 0x20, 0x40, 0x62, 0x00, 0x00, 0x48, 0x20, 0x40, 0x12, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
holeydma reversed scattered(16,1) char exhaust1[16] = {
	0x3c, 0xbe, 0xaa, 0xaa, 0x98, 0x24, 0x18, 0x10, 0x18, 0x14, 0x04, 0x24, 0x14, 0x10, 0x14, 0x04
};
holeydma reversed scattered(16,1) char exhaust2[16] = {
	0x3c, 0xbe, 0xaa, 0xaa, 0x1a, 0x24, 0x08, 0x24, 0x18, 0x10, 0x18, 0x14, 0x04, 0x24, 0x14, 0x10
};
holeydma reversed scattered(16,1) char exhaust3[16] = {
	0x3c, 0xbe, 0xaa, 0xaa, 0x9a, 0x20, 0x18, 0x24, 0x08, 0x24, 0x18, 0x10, 0x18, 0x14, 0x04, 0x24
};
holeydma reversed scattered(16,2) char exhauststart[32] = {
	0x03, 0xc0, 0x3f, 0xfc, 0xae, 0xee, 0x26, 0x64, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

const char collision_bullet_spaceship[78] = {0x03, 0x80, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x0f, 0xe0, 0x0f, 0xe0, 0x0f, 0xe0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x3f, 0xf8, 0x3f, 0xf8, 0x1b, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

#define NB_SMALL_SPRITES 32 
ramchip short sp_xpos[NB_SMALL_SPRITES], sp_ypos[NB_SMALL_SPRITES];
ramchip char sp_direction[NB_SMALL_SPRITES];
ramchip char exhaust_state;
ramchip char spaceship_x, spaceship_y, spaceship_state, spaceship_state_counter; 

void main()
{
    char i;
    multisprite_init();
    
    // Grey palette
    *P0C1 = 0x04;
    *P0C2 = 0x08;
    *P0C3 = 0x0b;

    // Blue palette
    *P1C1 = multisprite_color(0x84); // Dark blue 
    *P1C2 = multisprite_color(0x87); // Light blue
    *P1C3 = multisprite_color(0xac); // Turquoise 

    // Green palette
    *P2C1 = multisprite_color(0xd4); // Dark green 
    *P2C2 = multisprite_color(0xd8); // Light green
    *P2C3 = 0x0f; 
    
    // Fire palette
    *P3C1 = multisprite_color(0x1c); // Yellow 
    *P3C2 = multisprite_color(0x37); // Orange
    *P3C3 = multisprite_color(0x43); // Red

    // Initialize spaceship state
    spaceship_x = 80 - 6;
    spaceship_y = 180;
    exhaust_state = 0;
    spaceship_state = 0;
     
    // Initialize small sprites
    { 
        char xpos, ypos; 
        for (ypos = 0, xpos = 0, i = 0, X = 0; X != NB_SMALL_SPRITES; xpos++, ypos++, X++) {
            sp_xpos[X] = xpos << 8;
            sp_ypos[X] = ypos << 8;
            sp_direction[X] = i++;
            if (i == 24) i = 0;
        }
    }

    // Main loop
    do {
        multisprite_flip();
       
        joystick_update();
        if (spaceship_state < 2) {
            if (joystick[0] & JOYSTICK_LEFT) {
                if (spaceship_x) spaceship_x--;
            } else if (joystick[0] & JOYSTICK_RIGHT) {
                if (spaceship_x < 160 - 13) spaceship_x++;
            }
            if (joystick[0] & JOYSTICK_UP) {
                if (spaceship_y) spaceship_y--;
                exhaust_state++;
                if (exhaust_state == 13) exhaust_state = 10; 
            } else {
                exhaust_state = 0;
                if (joystick[0] & JOYSTICK_DOWN) {
                    if (spaceship_y < 224 - 33) spaceship_y++;
                }
            }
        }

        char draw_spaceship;
        if (spaceship_state == 0) {
            draw_spaceship = 1;
        } else if (spaceship_state == 1) {
            // Blinking returning spaceship
            draw_spaceship = spaceship_state_counter & 8;
            spaceship_state_counter--;
            if (spaceship_state_counter == 0) {
                spaceship_state = 0;
            }
        } else if (spaceship_state == 2) {
            char *gfx, x, y;
            if (spaceship_state_counter >= 16) {
                gfx = explosion1;
            } else if (spaceship_state_counter >= 12) {
                gfx = explosion2;
            } else if (spaceship_state_counter >= 8) {
                gfx = explosion3;
            } else if (spaceship_state_counter >= 4) {
                gfx = explosion4;
            } else gfx = explosion5;

            x = spaceship_x - 6;
            y = spaceship_y - 12;
            if (y < 0) y = 0; else if (y >= 224 - 48) y = 224 - 49;
            multisprite_display_big_sprite(x, y, gfx, 6, 3, 3, 0);
            spaceship_state_counter--;
            if (spaceship_state_counter == 0) {
                spaceship_state = 1;
                spaceship_state_counter = 100;
            }
            draw_spaceship = 0;
        }
        
        if (draw_spaceship) {
            multisprite_display_big_sprite(spaceship_x, spaceship_y, spaceship, 6, 0, 2, 1);
            // Draw exhaust
            if (exhaust_state > 0 && spaceship_y < 224 - 25 - 16) {
                char x, y;
                y = spaceship_y + 24;
                if (exhaust_state < 10) {
                    x = spaceship_x + 2;
                    multisprite_display_small_sprite_ex(x, y, exhauststart, 2, 3, 0);
                } else {
                    char *gfxptr;
                    i = exhaust_state - 10;
                    gfxptr = exhaust1 + i;
                    x = spaceship_x + 4;
                    multisprite_display_sprite_ex(x, y, gfxptr, 1, 3, 0);
                }
            }
        }

        for (i = 0; i != NB_SMALL_SPRITES; i++) {
            char xbullet, ybullet;
            X = i;
            Y = sp_direction[X];
            sp_xpos[X] += dx[Y];
            sp_ypos[X] += dy[Y];
            xbullet = sp_xpos[X] >> 8;
            ybullet = sp_ypos[X] >> 8;
            if ((xbullet < 5 && (dx[Y] >> 8) < 0) || 
                (xbullet >= 150 && (dx[Y] >> 8) >= 0)) {
                sp_direction[X] = horizontal_pingpong[Y];
            }
            if ((ybullet < 5 && (dy[Y] >> 8) < 0) || 
                (ybullet >= MS_YMAX - 20 && (dy[Y] >> 8) >= 0)) {
                sp_direction[X] = vertical_pingpong[Y];
            }
            multisprite_display_small_sprite_ex(xbullet, ybullet, bullet, 1, 3, 0); 
   
            if (spaceship_state == 0) {
                multisprite_compute_collision(xbullet, ybullet, 4, 8, spaceship_x, spaceship_y, 12, 24, collision_bullet_spaceship);
                if (multisprite_collision_detected) {
                    // Explosion
                    spaceship_state = 2;
                    spaceship_state_counter = 20;
                }
            } 
        }
    } while(1);
}

 

Next will be an example of using RAM to make a faster scrolling than using ROM, paving the way to 60fps shmups...

 

example_collisions.a78

  • Like 6
  • Thanks 3
Link to comment
Share on other sites

Hi,

Today, a new vertical scrolling setup, very optimized :

- It uses sparse tiling, and thus can mix empty spaces and 160A with multiple palettes (i.e. it's a 25 colors background scrolling. Well, in my example, I'm using only 2 palettes out of 8 )

- It uses 12kb of RAM at 0x4000 as video memory, copying on the fly the tiles gfx to RAM, which enables the use of immediate mode of Maria. Tiles are copied step by step, in order to smooth the processing time over frames.

- It uses DMA Masking as suggested by @RevEng, adapted to double buffering.

- Display lists are rolling over, i.e. there is no copy when scrolling (only when putting data into the scrolling buffer)

- As such, it consumes the minimum of CPU / DMA during display, possibly enabling 60fps vertical scrolling games (shmups!)

- Without a single line of assembly code because it's cc7800 (but you can take the resulting code in your ASM or 7800Basic program if you want).

 

Here is the source code for the example (examples/example_vertical_scrolling_sparse_vmem.c) :

#include "prosystem.h"
#define VERTICAL_SCROLLING
#define MULTISPRITE_USE_VIDEO_MEMORY
#include "multisprite.h"

// Generated by sprites7800 shmup_tiles.png
#include "example_shmup_tiles.c"

// Generated by tiles7800 --sparse shmup_tiles.yaml shump.tmx
#include "example_shmup_tilesets.c"

void main()
{
    char counter = 255;
    char done = 0;

    multisprite_init();
    multisprite_vscroll_init_sparse_tiles_vmem(tilemap_data_ptrs, blue_objects1);

    // Grey palette
    *P0C1 = 0x04;
    *P0C2 = 0x08;
    *P0C3 = 0x0c;

    // Blue palette
    *P1C1 = multisprite_color(0x84); // Dark blue 
    *P1C2 = multisprite_color(0x88); // Light blue
    *P1C3 = multisprite_color(0xac); // Turquoise 

    // Main loop
    do {
        multisprite_vscroll_buffer_sparse_tiles_vmem_step();
        multisprite_vscroll_buffer_sparse_tiles_vmem_step();
        // Prepare scrolling data
        if (multisprite_vscroll_buffer_empty()) {
            if (!done) {
                multisprite_vscroll_buffer_sparse_tiles_vmem(counter);
                if (counter) counter--; else done = 1; 
            } else if (done == 1) {
                multisprite_vscroll_buffer_sparse_tiles_vmem(255);
                done = 2;
            } else done = 3;
        }

        while (*MSTAT & 0x80); 
        multisprite_flip();
        if (done != 3) multisprite_vertical_scrolling(2);

    } while(1);
}

Note the multisprite_vscroll_buffer_sparse_tiles_vmem_step() function, which is called twice, in order to allow for a faster scrolling (2 pixels by 2 pixels). One step copies 1/16th of the tiles gfx from ROM to RAM, so if you want to scroll 2 lines per 2 lines, and as tiles are 16 pixels high, you have to call it twice per frame to smooth the computing/ROM to RAM transfer over the frames.

 

Next week, big time : a (almost) fully functional shmup example, with scrolling, sound & music, explosions, a big boss and a gameover., in 60fps.. This to check that it can fit in a Atari 7800... The target: Summer Carnival '92: Recca on the NES... without the infamous NES blinking !

example_vertical_scrolling_sparse_vmem.a78

  • Like 8
Link to comment
Share on other sites

Have you played Atari today ? Here is a 60 fps shmup for the Atari 7800 (well, not really, it's just the beginning of it, but well... it runs at full speed !).

Nothing really new this week, as this is just an integration of the previous developments in order to check that everything fits in...  In the end, it's a rather complete basis for developing a shmup : the code manages the FIFOs for bullets, enemies, explosions, the collisions, the music (Pokey & TIA for sound effects) and the scrolling background. And the gameover (I'm proud of this one with the big skull). It just lacks the enemy spawning (I just spawn a single boss) and the level design (it's YOUR work as game developers).

The result is that all the stuff takes approximately 70% of CPU available (in NTSC and 60fps), so there is still room for completing the game. On the other hand, it terms of ROM space, it's already very tight.

Indeed, this is the first time I'm using cc7800 to make a true supergame (128KB ROM with bankswitching). The memory layout is the following (each bank is 16kB) :

- Bank0 (or default bank) is the one that is fixed at 0xC000 => 0xFFF (in the ROM layout, it's indeed the last one. cc7800 manages this for you). It contains most of the code (including RMTPlayer for the Pokey Music) and 4kb of sprites that will be used throughout the game (spaceship, gameover, the bullets, digits for scoring...). In the end, Bank0 is almost full (there is still ~2kb of ROM available, 4kb if you remove POKEY RTMPlayer support).

- Bank1 to Bank6 contain the level definitions and are mapped from 0x8000 to 0xbffff on demand (when you jump into a banked function, there is automatic bankswitching). I've only included the definition for 1 level, but the bank is almost full (4kb for the tiles definition, 4kb for the sparse tileset definition (the actual layout of the background), 4kb for the enemy sprites (here only 1 160B big boss sprite), 2.7Kb for the Pokey music and 1.3kb for the enemy management code). There is no room anymore for the spawning of enemies. Solution ? Reduce the music size - nice music but too fat - and reduce the tileset definition by reusing parts of the layout. 

- Bank7 contains the initialization code (palettes definitions, multisprite library init, etc), which helps to reduce bank0 usage. A splash screen would perfectly fit there...

 

In attachment, you will find :

- The latest version of cc7800, with several bankswitching related bug corrected.

- 2 tested ROMS :

         . one with RAM at 0x4000 (with a vertical scrolling in immediate mode), no pokey and a lot of CPU available

         . the other with Pokey at 0x4000 (with music, but background drawing in indirect mode). Less CPU available, but not that bad indeed.

- The C source code of the Shmup game (let's call it Power Shmup 7800)

- The yaml and png to be used with sprites7800 and tiles7800 to generate the graphics code.

 

In order to compile the full thing, type in cc7800 directory (from the github repository) :

cc7800 -g -v -Iheaders -Iexamples -DMULTISPRITE_USE_VIDEO_MEMORY examples/example_shmup.c

to get the the immediate mode vertical scrolling, and

cc7800 -g -v -Iheaders -Iexamples -DPOKEY_MUSIC examples/example_shmup.c

to get the one with some Music. The 2 options are not compatible yet, since both of the them use the 0x4000 precious memory bank (Concerto doesn't support 0x450 Pokey at the moment).

(the -g flag generates the .asm and .lst files, the -v generates a verbose output with the full memory layout. Very very instructive. You need to understand this if you want to go further).

 

You can also compile with -DDEBUG flag to see the CPU available on screen.

 

Next time ? The use of sparse tiling to draw the background of a single screen game.

 

gameover.png

shmup.png

shmup_enemies.png

cc7800-0.2.15-x86_64.msi example_shmup_pokey.a78 example_shmup_vmem.a78 shmup.yaml example_shmup.c

  • Like 7
Link to comment
Share on other sites

I like the simplicity of defining functions to reside in a certain bank like bank1. I do have a PAL 7800 with a Dragonfly cart. So I am a bit tempted to compile this for my console :) 

 

The linker config in cc65 is so much more complicated to work with.

Link to comment
Share on other sites

Pretty amazing tool. Installing everything, compiling examples. And everything just works!
I will later test it on the real 7800. The screenshot is from a7800.

Nyttkuva2023-10-09201104.thumb.png.3bd0d9f5e57f32303d8fafa99f82ba76.png

I will share apartment with two French developers @Fadest, @LordKraken at eJagfest two weeks from now. I am sure we will discuss cc7800 and try it out. Thanks for sharing it.

  • Like 3
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...