Jump to content
IGNORED

cc2600 & news


Recommended Posts

Hi,

I start this new topic to make some announcements about progress on cc2600, my C compiler for Atari 2600 to be found at https://github.com/steux/cc2600.

The news for today is the added cc2600 support for TIATracker. I've made a fork on github with a new option in the menu to generate C code : https://github.com/steux/tiatracker/. You will find in attachment an archive with the win32 executable.

The C code generated is split into 2 headers (song_tiatracker.h and song_trackdata.h, where song is the song name). The first one contains the variables definition and the player code, the second one the song data itself. Note that these functions and data can be into another bank just by enclosing their definition by a "bankn {}" bracket. 2 functions are defined in the header : tia_tracker_init() and tia_tracker_play(), the first to be called once, the second to be called every frame (50Hz or 60Hz depending on the song definition).

Here is an example of code (examples/example_tiatracker.c) that displays a Garfield sprite while playing music :

#include "vcs.h"
#include "vcs_colors.h"

unsigned char X, Y;

#define BLANK 40
#define KERNAL 192
#define OVERSCAN 30

char ypos;
unsigned char *sprite_ptr; // Pointer to the sprite data to be drawn
unsigned char *mask_ptr;   // Pointer to the mask for drawing
unsigned char *color_ptr;  // Pointer to the color table of the sprite

// Generated for sprite size 20 with maskgen.c (in cc2600/misc directory)
const char sprite_mask[364] = {
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

// Drawn with PlayerPal and converted to C code by spritegen.c (in cc2600/misc directory)
const unsigned char sprite0[20] = { 0x66, 0x66, 0x76, 0x3a, 0xbc, 0x84, 0x36, 0x76, 0x6e, 0x5e, 0x3e, 0x48, 0xb6, 0xcb, 0x33, 0x55, 0x33, 0x4a, 0x36, 0x12};
const unsigned char colors0[20] = { 0x12, 0x12, 0x2c, 0x2c, 0x38, 0x38, 0x3c, 0x3c, 0x2c, 0x38, 0x3c, 0x3c, 0x3c, 0x3c, 0x0e, 0x0e, 0x0e, 0x3c, 0x3c, 0x3c};

// Music code
#include "miniblast_tiatracker.h"
#include "miniblast_trackdata.h"

void init()
{
    *COLUBK = VCS_GREEN;
    *COLUPF = VCS_LGREEN;
    ypos = 100;
    // Position sprite horizontally
    strobe(WSYNC);
    X = 6;
    do { X--; } while (X >= 0);
    strobe(RESP0);
    strobe(WSYNC);
}

void main()
{
    tia_tracker_init();
    init();

    while(1) {
        *VBLANK = 2; // Disable VBLANK
        *VSYNC = 2; // Set VSYNC
        strobe(WSYNC); // Hold it for 3 scanlines
        strobe(WSYNC);
        strobe(WSYNC);
        *VSYNC = 0; // Turn VSYNC Off
        
        // Blank
        *TIM64T = ((BLANK - 3) * 76) / 64 + 1;
        // Do some logic here

        // Set up sprite pointer
        sprite_ptr = sprite0 - 1 - ypos; // -1 offset because lower position (ypos = 0) matches sprite_ptr[Y = 1] (line 96)
        mask_ptr = sprite_mask + KERNAL - sizeof(sprite0) - 1 - ypos; // Same offset as speite_ptr
        // Set up color pointer 
        color_ptr = colors0 - ypos; // 0 offset baecause lower position (ypos = 0) matches color_ptr[Y = 0] (lint 103)
       
        // Joystick input 
        if (!(*SWCHA & 0x80)) { *HMP0 = 0xF0; *REFP0 = 0; } // Right
        if (!(*SWCHA & 0x40)) { *HMP0 = 0x10; *REFP0 = 8; } // Left
        if (!(*SWCHA & 0x20) && ypos > 0) ypos--; // Down
        if (!(*SWCHA & 0x10) && ypos < 192 - sizeof(sprite0)) ypos++; // Up
       
        // Apply movement 
        strobe(WSYNC);
        strobe(HMOVE);
        
        // And stop any movement
        strobe(WSYNC);
        strobe(HMCLR);

        Y = KERNAL; // Initialize line counter
        X = sprite_ptr[Y] & mask_ptr[Y]; // Preload sprite data for the first line
        Y--;
        // Load TIA registers for first line
        *GRP0 = X;
        *COLUP0 = color_ptr[Y];
        *PF2 = Y; // Marker. Just to check that what is displayed is correct

        // Do some extra logic
        while (*INTIM);
        strobe(WSYNC);
        *VBLANK = 0;
        load(sprite_ptr[Y] & mask_ptr[Y]);
        Y--;

        // Image drawing
        do {
            strobe(WSYNC);
            store(*GRP0); // Apply preloaded sprite data
            *COLUP0 = color_ptr[Y]; // Load current line sprite color
            *PF2 = Y; // Marker. Just to check that what is displayed is correct
            load(sprite_ptr[Y] & mask_ptr[Y]); // Load next line sprite data
            Y--;
        } while (Y);
        
        // Last line is out of loop and is simpler
        strobe(WSYNC);
        store(*GRP0); // Apply preloaded sprite data
        *COLUP0 = color_ptr[Y]; // Load current line sprite color
        *PF2 = Y; // Marker. Just to check that what is displayed is correct
        
        strobe(WSYNC);
        // Overscan
        *VBLANK = 2; // Enable VBLANK
        *TIM64T = (OVERSCAN * 76) / 64 + 2;
        // Do some logic here
        tia_tracker_play();
        while (*INTIM);
    }
}

 

See you soon for some other news for cc2600.

example_tiatracker.a26 tiatracker_cc_v1.1.zip

  • Like 6
Link to comment
Share on other sites

At last, a Helloworld example is provided in cc2600/examples. It uses 48 pixels wide display provided in minikernel.h header :

#include "vcs.h"
#include "vcs_colors.h"

#include "minikernel.h"

unsigned char X, Y;

#define BLANK 40
#define KERNAL 192
#define OVERSCAN 30

// Helloworld 48 pixels wide generated by tools2600/text2rom2600
aligned(256) const char line0[42] = {
	0x53, 0x54, 0x57, 0x75, 0x52, 0x50, 0x50, 
	0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x66, 
	0x20, 0x50, 0x50, 0x50, 0x20, 0x00, 0x00, 
	0x52, 0x75, 0x75, 0x75, 0x52, 0x50, 0x50, 
	0x47, 0x42, 0x42, 0x52, 0x62, 0x02, 0x06, 
	0x32, 0x50, 0x52, 0x52, 0x32, 0x12, 0x12};

void main()
{
    char i;
    mini_kernel_position_sprites_center();
    *COLUP0 = VCS_WHITE;
    *COLUP1 = VCS_WHITE;

    while(1) {
        *VBLANK = 2; // Disable VBLANK
        *VSYNC = 2; // Set VSYNC
        strobe(WSYNC); // Hold it for 3 scanlines
        strobe(WSYNC);
        strobe(WSYNC);
        *VSYNC = 0; // Turn VSYNC Off
        
        // Blank
        *TIM64T = ((BLANK - 3) * 76) / 64 + 1;
        // Do some logic here

        Y = KERNAL; // Initialize line counter

        // Do some extra logic
        
        while (*INTIM);
        
        // Image drawing
        strobe(WSYNC);
        *VBLANK = 0;
        Y--;
        do {
            strobe(WSYNC);
            Y--;
        } while (Y >= 110);

        i = Y;
        *COLUP0 = VCS_BLUE;
        *COLUP1 = VCS_BLUE;
        mini_kernel_display_text(line0, 7);
        Y = i - 11;

        i = Y;
        *COLUP0 = VCS_WHITE;
        *COLUP1 = VCS_WHITE;
        mini_kernel_display_text(line0, 7);
        Y = i - 11;

        i = Y;
        *COLUP0 = VCS_RED;
        *COLUP1 = VCS_RED;
        mini_kernel_display_text(line0, 7);
        Y = i - 11;

        do {
            strobe(WSYNC);
            Y--;
        } while (Y);

        // Last line is out of loop and is generally simpler
        strobe(WSYNC);
        
        strobe(WSYNC);
        // Overscan
        *VBLANK = 2; // Enable VBLANK
        *TIM64T = (OVERSCAN * 76) / 64;
        // Do some logic here
        while (*INTIM);
    }
}

The "Hello World!" graphics is generated with a new tool called text2rom2600 (https://github.com/steux/tools2600/)

using font.png in attachment.

image.thumb.png.66fc27e96449f3b85b48ed0bea800ceb.png

font.png

helloworld.txt text2rom2600.exe hello_world.a26

Link to comment
Share on other sites

Here is a new example demonstrating the sfx.h header of cc2600 that plays sounds from sound2tia, a utility designed by @RevEng (https://github.com/7800-devtools/sound2tia). It's very simple to use: sfx_schedule schedules a sound to be played, and sfx_play should be called every frame. The mono sounds are scheduled on any empty channel or the channel playing the lowest priority sound (the second byte of the sound array). I've ported all the sounds found in 7800basic (a collection of 117 sounds) on cc2600, yielding a 32KB cart (4 4KB banks are used for the sounds, 2 for the text menu, the remaining being almost empty) ! The menu for choosing the sound to play is constructed using the text2rom2600 utility found in tools2600 on github.

Have fun !

example_sfx.a26 example_sfx.c

  • Like 3
Link to comment
Share on other sites

Today, I introduce sprites2600, a tool (written in Rust) designed to generate C code for sprites to be displayed by cc2600 multisprite engine. You can find this tool at https://github.com/steux/tools2600 (windows executable in attachment).

image.thumb.png.3673ddfcda3bfe4c91755f8c2b32f510.png

sprites2600 processes a YAML file that describes all the sprites to be generated (associated to .png spritesheets). For instance, for the picture above (some shmup sprites - drawn with GIMP), the YAML file is :

sprite_sheets:
  - image: shmup.png
    sprites:
      - name: spaceship 
        top: 0
        left: 0
        height: 16
        color_copy: spaceship_exhaust
      - name: spaceship_exhaust
        top: 0
        left: 0
        height: 24
      - name: fire
        top: 0
        left: 64
        height: 9
        color_copy: spaceship_exhaust
        color_offset: 15
      - name: bullet
        top: 9
        left: 64
        height: 9
      - name: explosion1
        top: 0
        left: 80
        height: 9
        color_copy: explosion3
      - name: explosion2
        top: 0
        left: 88
        height: 11 
        color_copy: explosion3
      - name: explosion3
        top: 0
        left: 96 
        height: 12 
      - name: explosion4
        top: 0
        left: 104
        height: 12 
        color_copy: explosion3
      - name: explosion5
        top: 0
        left: 112
        height: 12 
        color_copy: explosion3
      - name: enemy1 
        top: 0
        left: 72
        height: 16
      - name: bigboss
        top: 24
        left: 0
        height: 32
        pixel_width: 2
      - name: letter_g
        top: 0
        left: 8
        height: 16
        color_copy: spaceship_exhaust
      - name: letter_a
        top: 0
        left: 16 
        height: 16
        color_copy: spaceship_exhaust
      - name: letter_m
        top: 0
        left: 24
        height: 16
        color_copy: spaceship_exhaust
      - name: letter_e
        top: 0
        left: 32
        height: 16
        color_copy: spaceship_exhaust
      - name: letter_o
        top: 0
        left: 40
        height: 16
        color_copy: spaceship_exhaust
      - name: letter_v
        top: 0
        left: 48
        height: 16
        color_copy: spaceship_exhaust
      - name: letter_r
        top: 0
        left: 56
        height: 16
        color_copy: spaceship_exhaust

 

The generated C code for the command "sprites2600 shmup.yaml" (examples/example_shmup_gfx.c in cc2600 repositorty) will be :

MS_KERNEL_BANK const char spaceship_gfx[20] = {0, 0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x18, 0x18, 0x3c, 0xbd, 0xff, 0xdb, 0xdb, 0xdb, 0x66, 0x66, 0, 0};
MS_KERNEL_BANK const char spaceship_exhaust_gfx[28] = {0, 0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x18, 0x18, 0x3c, 0xbd, 0xff, 0xdb, 0xdb, 0xdb, 0x66, 0x66, 0xf6, 0xff, 0xff, 0x6f, 0x76, 0x6e, 0x66, 0x22, 0, 0};
#ifdef PAL
MS_KERNEL_BANK const char spaceship_exhaust_colors[26] = {0, 0, 0x04, 0x04, 0xd4, 0xd0, 0xd0, 0x06, 0x08, 0x08, 0x0a, 0x0a, 0x0a, 0x0c, 0x0c, 0x0e, 0x0e, 0x40, 0x42, 0x44, 0x46, 0x48, 0x2a, 0x2a, 0x2c, 0x1c};
#else
MS_KERNEL_BANK const char spaceship_exhaust_colors[26] = {0, 0, 0x04, 0x04, 0x84, 0x80, 0x80, 0x06, 0x08, 0x08, 0x0a, 0x0a, 0x0a, 0x0c, 0x0c, 0x0e, 0x0e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x1a, 0x1a, 0x1c, 0x1c};
#endif
MS_KERNEL_BANK const char fire_gfx[13] = {0, 0, 0x42, 0xe7, 0xe7, 0xe7, 0x42, 0x42, 0x42, 0x42, 0x42, 0, 0};
MS_KERNEL_BANK const char bullet_gfx[13] = {0, 0, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, 0x18, 0, 0};
#ifdef PAL
MS_KERNEL_BANK const char bullet_colors[11] = {0, 0, 0x2c, 0x2c, 0x2a, 0x2a, 0x48, 0x46, 0x44, 0x42, 0x30};
#else
MS_KERNEL_BANK const char bullet_colors[11] = {0, 0, 0x1c, 0x1c, 0x1a, 0x1a, 0x38, 0x36, 0x34, 0x32, 0x30};
#endif
MS_KERNEL_BANK const char explosion1_gfx[13] = {0, 0, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0c, 0x24, 0x34, 0x08, 0, 0};
MS_KERNEL_BANK const char explosion2_gfx[15] = {0, 0, 0x00, 0x00, 0x08, 0x3c, 0x12, 0x26, 0x64, 0x2c, 0x2e, 0x30, 0x08, 0, 0};
MS_KERNEL_BANK const char explosion3_gfx[16] = {0, 0, 0x06, 0x68, 0x77, 0xcf, 0xa3, 0x93, 0xd3, 0x41, 0xf5, 0x48, 0x6a, 0x1e, 0, 0};
#ifdef PAL
MS_KERNEL_BANK const char explosion3_colors[14] = {0, 0, 0x2e, 0x2c, 0x2a, 0x48, 0x64, 0x60, 0x60, 0x64, 0x48, 0x2a, 0x2c, 0x1e};
#else
MS_KERNEL_BANK const char explosion3_colors[14] = {0, 0, 0x1e, 0x1c, 0x1a, 0x38, 0x44, 0x40, 0x40, 0x44, 0x38, 0x1a, 0x1c, 0x1e};
#endif
MS_KERNEL_BANK const char explosion4_gfx[16] = {0, 0, 0x23, 0xe5, 0xaa, 0x84, 0x47, 0x40, 0x00, 0x86, 0xa3, 0xf7, 0x76, 0x52, 0, 0};
MS_KERNEL_BANK const char explosion5_gfx[16] = {0, 0, 0x25, 0xc2, 0x41, 0x81, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x82, 0x64, 0x43, 0, 0};
MS_KERNEL_BANK const char enemy1_gfx[20] = {0, 0, 0x22, 0x44, 0x6e, 0xf7, 0x66, 0x5a, 0xdb, 0xdb, 0x5a, 0x7e, 0x3c, 0x18, 0x58, 0x58, 0x58, 0x18, 0, 0};
#ifdef PAL
MS_KERNEL_BANK const char enemy1_colors[18] = {0, 0, 0x2e, 0x2a, 0x48, 0x64, 0x60, 0x28, 0x58, 0x28, 0x28, 0x28, 0x26, 0xd0, 0x26, 0x58, 0x64, 0x40};
#else
MS_KERNEL_BANK const char enemy1_colors[18] = {0, 0, 0x1e, 0x1a, 0x38, 0x44, 0x40, 0xe8, 0xc8, 0xe8, 0xe8, 0xe8, 0xe6, 0x80, 0xe6, 0xc8, 0x44, 0x40};
#endif
MS_KERNEL_BANK const char bigboss_gfx[36] = {0, 0, 0x04, 0x0c, 0x0c, 0x0e, 0x1e, 0x5e, 0x5e, 0x5e, 0x5f, 0x6f, 0xeb, 0xeb, 0xeb, 0xe7, 0xe7, 0xf7, 0xf7, 0x77, 0x73, 0x73, 0xf7, 0xff, 0xff, 0xff, 0x7f, 0x7f, 0x3f, 0x3f, 0x1e, 0x0c, 0x1e, 0x0c, 0, 0};
#ifdef PAL
MS_KERNEL_BANK const char bigboss_colors[34] = {0, 0, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xac, 0xa2, 0xac, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa6, 0xa2, 0xa6, 0xa6, 0xa2, 0xa2, 0xa2, 0xa4, 0xa4, 0xa2, 0xa4, 0xa2, 0xa2, 0x40, 0x46, 0x1c};
#else
MS_KERNEL_BANK const char bigboss_colors[34] = {0, 0, 0x62, 0x62, 0x62, 0x62, 0x62, 0x6c, 0x62, 0x6c, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x66, 0x62, 0x66, 0x66, 0x62, 0x62, 0x62, 0x64, 0x64, 0x62, 0x64, 0x62, 0x62, 0x30, 0x36, 0x1c};
#endif
MS_KERNEL_BANK const char letter_g_gfx[20] = {0, 0, 0x3c, 0x3e, 0x76, 0x60, 0xe0, 0xc0, 0xc0, 0xcf, 0xc3, 0xc3, 0xc3, 0xc7, 0x66, 0x7e, 0x3e, 0x3c, 0, 0};
MS_KERNEL_BANK const char letter_a_gfx[20] = {0, 0, 0x18, 0x18, 0x1c, 0x3e, 0x36, 0x36, 0x36, 0x62, 0x63, 0x63, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0, 0};
MS_KERNEL_BANK const char letter_m_gfx[20] = {0, 0, 0xfc, 0xde, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0, 0};
MS_KERNEL_BANK const char letter_e_gfx[20] = {0, 0, 0x3f, 0x3f, 0x70, 0x60, 0xe0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xe0, 0x7f, 0x7f, 0x3f, 0x3f, 0, 0};
MS_KERNEL_BANK const char letter_o_gfx[20] = {0, 0, 0x3c, 0x3e, 0x76, 0x63, 0xe3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0x66, 0x7e, 0x3c, 0x3c, 0, 0};
MS_KERNEL_BANK const char letter_v_gfx[20] = {0, 0, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xe6, 0x66, 0x6c, 0x6c, 0x3c, 0x38, 0x18, 0x18, 0, 0};
MS_KERNEL_BANK const char letter_r_gfx[20] = {0, 0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xfc, 0xcc, 0xc6, 0xc6, 0xc3, 0xc3, 0, 0};
#define MS_NB_SPRITES_DEF 18
MS_KERNEL_BANK const char *ms_grptr[MS_NB_SPRITES_DEF] = {spaceship_gfx, spaceship_exhaust_gfx, fire_gfx, bullet_gfx, explosion1_gfx, explosion2_gfx, explosion3_gfx, explosion4_gfx, explosion5_gfx, enemy1_gfx, bigboss_gfx, letter_g_gfx, letter_a_gfx, letter_m_gfx, letter_e_gfx, letter_o_gfx, letter_v_gfx, letter_r_gfx};
MS_KERNEL_BANK const char *ms_coluptr[MS_NB_SPRITES_DEF] = {spaceship_exhaust_colors, spaceship_exhaust_colors, spaceship_exhaust_colors + 15, bullet_colors, explosion3_colors, explosion3_colors, explosion3_colors, explosion3_colors, explosion3_colors, enemy1_colors, bigboss_colors, spaceship_exhaust_colors, spaceship_exhaust_colors, spaceship_exhaust_colors, spaceship_exhaust_colors, spaceship_exhaust_colors, spaceship_exhaust_colors, spaceship_exhaust_colors};
MS_KERNEL_BANK const char ms_height[MS_NB_SPRITES_DEF] = {19, 27, 12, 12, 12, 14, 15, 15, 15, 19, 35, 19, 19, 19, 19, 19, 19, 19};
#define SPRITE_SPACESHIP 0
#define SPRITE_SPACESHIP_EXHAUST 1
#define SPRITE_FIRE 2
#define SPRITE_BULLET 3
#define SPRITE_EXPLOSION1 4
#define SPRITE_EXPLOSION2 5
#define SPRITE_EXPLOSION3 6
#define SPRITE_EXPLOSION4 7
#define SPRITE_EXPLOSION5 8
#define SPRITE_ENEMY1 9
#define SPRITE_BIGBOSS 10
#define SPRITE_LETTER_G 11
#define SPRITE_LETTER_A 12
#define SPRITE_LETTER_M 13
#define SPRITE_LETTER_E 14
#define SPRITE_LETTER_O 15
#define SPRITE_LETTER_V 16
#define SPRITE_LETTER_R 17

which is a code ready to be used with the cc2600 multisprite code.

 

Node that drawing the picture with GIMP requires a few steps :

- The NTSC Atari 2600 Palette should be used in GIMP. Only these colors will be processed by sprites2600 (logically). The .gpl GIMP palette file is in attachment and is shown in the GIMP picture above.

- Only one color per sprite line. This is an Atari 2600...

- You can reuse others sprites color, using the color_copy option

 

Note that sprites2600 generates the NTSC and PAL colors (activated by #define PAL or -DPAL on the cc2600 compilation command line).

Note also that GIMP is perfectly suited to draw Atari 2600 sprites, since it can be configured to use "fat pixels".

For this, just change the Image/Print size... to set a non square DPI, like in the picture below :

image.png.4cc19b36d3b2630f8dd5f34b62deb478.png

And click in View/Dot for Dot to disable the square display. You will then be able to edit pictures with flat pixels.

image.png.8811b4ec211f04535acd3b551529e8e0.png

Now that we have the music (tiatracker support), the sound effects (sfx support), the sprites tool (sprites2600), the multisprite library, we have everything to make 60fps vertical scrolling shmup on the venerable Atari VCS. Do you think it's possible to turn the grandma into a vertical shmup machine ?

shmup.png

sprites2600.exe shmup.yaml HW-Atari-2600-NTSC.gpl

  • Like 1
Link to comment
Share on other sites

On 2/17/2024 at 7:33 PM, bsteux said:

Do you think it's possible to turn the grandma into a vertical shmup machine ?

With your track record in the cc7800... I would not answer no. I won't be able to do it :( 

  • Like 1
Link to comment
Share on other sites

Shmup, shmup, shmup !

 

Here is a full fledged vertical scrolling shmup. What's inside ? I've tried to put enemies, bullets, spawning, scrolling, game over, scoring, music (tiatracker), sound effects all together. And well, surprinsingly, it fits well inside old' grandma. No ARM code, no assembler. Pure cc2600.

The only compromise I had to do was not to move all enemies at each frame. Well, I move them virtually (i.e. I update their coordinates every frame), but the sprite itself is moved / updated on screen... when there is still time left. And the result is rather convincing.

What is quite satisfying is the not-so-blinking behaviour of the multisprite library, and the resulting relative simplicity of the code. It served as a good debugging example for cc2600 (latest version in attachment).

 

Note that I'm using here a 16Kb setup (4 banks) with Superchip (i.e. 128 additional RAM bytes, eating 256 bytes in each bank...) :

Bank 0 contains the main loop (3131 bytes left).

Bank 1 contains the multisprite kernel code, the sprites and the playfield (here very simplistic) (411 bytes still left)

Bank 2 contains the game logic (spawning, enemy moves, shooting detection, etc) (1129 bytes still left)

Bank 3 contains the bottom minikernel (for score display), the music data & player (TIATracker), and the sound effects (948 bytes left).

Still plenty of room for many improvements (pow, other weapons, many enemies). Why not a 256KB cart with many levels ?

 

Note the use of NUSIZ to make rows of enemies (like in Space Invaders) and bullets. A nifty feature of the Atari 2600... And the "big boss" sprites made of 2 sprites. And the 3d effect with the spaceship going inside the circles. No need for a Nvidia RTX ! Why reinvent the wheel when you have the TIA ?

 

Here is the full source code (examples/example_shmup.c) :

 

#include "vcs.h"
#include "vcs_colors.h"

#define EXTRA_RAM superchip
#define MS_OFFSCREEN_BANK bank2
#define MS_OFFSCREEN2_BANK bank3
#define MS_KERNEL_BANK bank1
#define MS_MAX_NB_SPRITES 10 

// Music code
bank3 {
#include "miniblast_tiatracker.h"
#include "miniblast_trackdata.h"
#include "sfx.h"

const char sfx_pewpew[66] = {
	0x10, 0, 0x00, 0x1c, 0x04, 0x0f, 0x1c, 0x04, 0x0f, 0x09, 0x04, 0x0b, 0x03, 0x0c, 0x0a, 0x04,
	0x0c, 0x0e, 0x12, 0x04, 0x0c, 0x19, 0x04, 0x0f, 0x1c, 0x04, 0x0f, 0x07, 0x04, 0x05, 0x09, 0x04,
	0x05, 0x0d, 0x04, 0x06, 0x0c, 0x04, 0x05, 0x18, 0x04, 0x06, 0x1c, 0x04, 0x05, 0x1e, 0x04, 0x03,
	0x07, 0x04, 0x03, 0x09, 0x04, 0x03, 0x0c, 0x04, 0x02, 0x04, 0x0c, 0x02, 0x06, 0x0c, 0x01, 0x00,
	0x00, 0x00
};

const char sfx_bigboom[261] = {
	0x10, 1, 0x00, 0x1d, 0x07, 0x0f, 0x1e, 0x06, 0x0f, 0x00, 0x06, 0x0f, 0x14, 0x07, 0x0f, 0x13,
	0x0f, 0x0f, 0x1b, 0x07, 0x0f, 0x0e, 0x07, 0x0f, 0x1b, 0x07, 0x0f, 0x0f, 0x07, 0x0f, 0x10, 0x07,
	0x0f, 0x10, 0x06, 0x0f, 0x16, 0x07, 0x0f, 0x0d, 0x0f, 0x0f, 0x1e, 0x0c, 0x0f, 0x16, 0x01, 0x0f,
	0x17, 0x01, 0x0f, 0x10, 0x07, 0x0f, 0x10, 0x0f, 0x0f, 0x15, 0x07, 0x0d, 0x1a, 0x07, 0x0f, 0x1a,
	0x01, 0x0f, 0x1a, 0x07, 0x0f, 0x14, 0x0f, 0x0f, 0x16, 0x07, 0x0f, 0x16, 0x07, 0x0f, 0x15, 0x07,
	0x0f, 0x17, 0x07, 0x0f, 0x13, 0x0f, 0x0f, 0x13, 0x0f, 0x0f, 0x19, 0x0f, 0x0f, 0x18, 0x07, 0x0c,
	0x0b, 0x06, 0x0c, 0x1e, 0x01, 0x0d, 0x10, 0x01, 0x0d, 0x14, 0x07, 0x0f, 0x16, 0x06, 0x0c, 0x17,
	0x07, 0x0c, 0x1a, 0x01, 0x0c, 0x12, 0x06, 0x0d, 0x17, 0x07, 0x0c, 0x0b, 0x0f, 0x0c, 0x19, 0x07,
	0x09, 0x19, 0x07, 0x0b, 0x0b, 0x0f, 0x09, 0x0d, 0x0e, 0x0b, 0x0d, 0x0e, 0x0b, 0x19, 0x0f, 0x09,
	0x0e, 0x0f, 0x06, 0x1b, 0x0c, 0x08, 0x18, 0x0f, 0x08, 0x13, 0x07, 0x05, 0x1a, 0x01, 0x05, 0x17,
	0x0f, 0x08, 0x16, 0x06, 0x08, 0x0c, 0x06, 0x05, 0x1c, 0x0f, 0x06, 0x16, 0x06, 0x08, 0x0b, 0x06,
	0x06, 0x12, 0x06, 0x04, 0x0f, 0x0f, 0x05, 0x11, 0x07, 0x06, 0x09, 0x06, 0x05, 0x10, 0x06, 0x05,
	0x10, 0x06, 0x05, 0x10, 0x06, 0x05, 0x11, 0x0f, 0x04, 0x15, 0x0f, 0x04, 0x1e, 0x07, 0x05, 0x16,
	0x01, 0x04, 0x16, 0x01, 0x04, 0x1a, 0x0f, 0x04, 0x19, 0x0f, 0x02, 0x1e, 0x0f, 0x02, 0x1b, 0x0f,
	0x02, 0x1e, 0x0f, 0x02, 0x1c, 0x0f, 0x02, 0x0d, 0x0f, 0x01, 0x0f, 0x06, 0x02, 0x0e, 0x06, 0x01,
	0x18, 0x0f, 0x01, 0x0b, 0x06, 0x02, 0x16, 0x0f, 0x01, 0x17, 0x0f, 0x01, 0x13, 0x06, 0x01, 0x0f,
	0x0e, 0x01, 0x00, 0x00, 0x00
};
}

// Sprites data (generated by sprite2600 shmup.yaml > example_shmup_gfx.c)
#include "example_shmup_gfx.c"

#define BLANK 40
#define OVERSCAN 30

#define BULLET_L    0
#define BULLET_BL   1
#define BULLET_B    2
#define BULLET_BR   3
#define BULLET_R    4
#define BULLET_TR   5
#define BULLET_T    6
#define BULLET_TL   7

MS_OFFSCREEN_BANK {
    const char sprite_width[8] = {8, 24, 40, 40, 72, 16, 72, 32}; 
    const char sprite_is_one_invader[8] = {1, 0, 0, 0, 0, 1, 0, 1}; 
    const char invader_score[2] = {100, 10}; 
    const char sprite_new_nusiz_remove_left[7] = {0, 0, 0, 1, 0, 0, 0}; 
    const char sprite_offset_remove_left[7] = {0, 16, 32, 16, 64, 0, 32}; 
    const char sprite_new_nusiz_remove_right[7] = {0, 0, 0, 1, 0, 0, 2}; 
    const signed char bullet_dx[8] = {-2, -1, 0, 1, 2, 1, 0, -1}; 
    const signed char bullet_dy[8] = {0, 2, 3, 2, 0, -2, -3, -2};
    const char bullet_start_direction[8] = {BULLET_BL, BULLET_B, BULLET_BR, BULLET_B, BULLET_L, BULLET_R, BULLET_TL, BULLET_TR};
}

#define REG_COLUPF  0x08
#define REG_COLUBK  0x09
#define REG_CTRLPF  0x0a // LSB: Playfield priority / Score mode / Reflective playfield
#define REG_PF0     0x0d
#define REG_PF1     0x0e
#define REG_PF2     0x0f

MS_KERNEL_BANK const char playfield[] = {
    5, REG_CTRLPF, 0, REG_PF0, 0, REG_PF1, 0, REG_PF2, VCS_BLUE, REG_COLUPF,
    0xfc, REG_PF2, 0xff, REG_PF2, 0x03, REG_PF1, 0x0f, REG_PF1, 0x1f, REG_PF1, 0x3f, REG_PF1, 0x7f, REG_PF1, 0xff, REG_PF1,
    0x80, REG_PF0, 0, REG_PF2, 0xc0, REG_PF0, 0xf0, REG_PF1, 0xe0, REG_PF0, 0xe0, REG_PF1, 0xc0, REG_PF1,
    0xf0, REG_PF0, 0x80, REG_PF1, VCS_WHITE, REG_COLUPF, 0x00, REG_PF1, 1, REG_CTRLPF, 0x70, REG_PF0, VCS_LGREY, REG_COLUPF,
    0xf0, REG_PF0, 0xe0, REG_PF0, 0xc0, REG_PF1, VCS_GREY, REG_COLUPF, 0xf0, REG_PF1, 0xc0, REG_PF0, 0xff, REG_PF1, 0x0, REG_PF0, 0xff, REG_PF2,
    0x0f, REG_PF1, 0x04, REG_COLUPF, 0, REG_PF1, 0, REG_PF2,

    5, REG_CTRLPF, 0, REG_PF0, 0, REG_PF1, 0, REG_PF2, VCS_BLUE, REG_COLUPF,
    0xfc, REG_PF2, 0xff, REG_PF2, 0x03, REG_PF1, 0x0f, REG_PF1, 0x1f, REG_PF1, 0x3f, REG_PF1, 0x7f, REG_PF1, 0xff, REG_PF1,
    0x80, REG_PF0, 0, REG_PF2, 0xc0, REG_PF0, 0xf0, REG_PF1, 0xe0, REG_PF0, 0xe0, REG_PF1, 0xc0, REG_PF1,
    0xf0, REG_PF0, 0x80, REG_PF1, VCS_WHITE, REG_COLUPF, 0x00, REG_PF1, 1, REG_CTRLPF, 0x70, REG_PF0, VCS_LGREY, REG_COLUPF,
    0xf0, REG_PF0, 0xe0, REG_PF0, 0xc0, REG_PF1, VCS_GREY, REG_COLUPF, 0xf0, REG_PF1, 0xc0, REG_PF0, 0xff, REG_PF1, 0x0, REG_PF0, 0xff, REG_PF2,
    0x0f, REG_PF1, 0x04, REG_COLUPF, 0, REG_PF1, 0, REG_PF2,

    5, REG_CTRLPF, 0, REG_PF0, 0, REG_PF1, 0, REG_PF2, VCS_BLUE, REG_COLUPF,
    0xfc, REG_PF2, 0xff, REG_PF2, 0x03, REG_PF1, 0x0f, REG_PF1, 0x1f, REG_PF1, 0x3f, REG_PF1, 0x7f, REG_PF1, 0xff, REG_PF1,
    0x80, REG_PF0, 0, REG_PF2, 0xc0, REG_PF0, 0xf0, REG_PF1, 0xe0, REG_PF0, 0xe0, REG_PF1, 0xc0, REG_PF1,
    0xf0, REG_PF0, 0x80, REG_PF1, VCS_WHITE, REG_COLUPF, 0x00, REG_PF1, 1, REG_CTRLPF, 0x70, REG_PF0, VCS_LGREY, REG_COLUPF,
    0xf0, REG_PF0, 0xe0, REG_PF0, 0xc0, REG_PF1, VCS_GREY, REG_COLUPF, 0xf0, REG_PF1, 0xc0, REG_PF0, 0xff, REG_PF1, 0x0, REG_PF0, 0xff, REG_PF2,
    0x0f, REG_PF1, 0x04, REG_COLUPF, 0, REG_PF1, 0, REG_PF2,

    5, REG_CTRLPF, 0, REG_PF0, 0, REG_PF1, 0, REG_PF2, VCS_BLUE, REG_COLUPF,
    0xfc, REG_PF2, 0xff, REG_PF2, 0x03, REG_PF1, 0x0f, REG_PF1, 0x1f, REG_PF1, 0x3f, REG_PF1, 0x7f, REG_PF1, 0xff, REG_PF1,
    0x80, REG_PF0, 0, REG_PF2, 0xc0, REG_PF0, 0xf0, REG_PF1, 0xe0, REG_PF0, 0xe0, REG_PF1, 0xc0, REG_PF1,
    0xf0, REG_PF0, 0x80, REG_PF1, VCS_WHITE, REG_COLUPF, 0x00, REG_PF1, 1, REG_CTRLPF, 0x70, REG_PF0, VCS_LGREY, REG_COLUPF,
    0xf0, REG_PF0, 0xe0, REG_PF0, 0xc0, REG_PF1, VCS_GREY, REG_COLUPF, 0xf0, REG_PF1, 0xc0, REG_PF0, 0xff, REG_PF1, 0x0, REG_PF0, 0xff, REG_PF2,
    0x0f, REG_PF1, 0x04, REG_COLUPF, 0, REG_PF1, 0, REG_PF2
};

#define MS_PLAYFIELD_HEIGHT (192 - 12)
#define MS_SELECT_FAST
#include "multisprite.h"

#define MK_ARMY_FONT
#define MK_BANK bank3
#include "minikernel.h"
MK_BANK const char lives31[7] = {0x02, 0x07, 0x57, 0xf9, 0xf9, 0x20, 0x20};
MK_BANK const char lives32[7] = {0x80, 0xc0, 0xd4, 0x3e, 0x3e, 0x08, 0x08};
MK_BANK const char lives22[7] = {0x80, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00};
MK_BANK const char livesdummy[1] = {0};
MK_BANK const char lives11[7] = {0x00, 0x00, 0x50, 0xf8, 0xf8, 0x20, 0x20};
MK_BANK const char *livesleft[4] = { lives22 + 3, lives11, lives31, lives31 };
MK_BANK const char *livesright[4] = { lives22 + 3, lives22 + 3, lives22, lives32 };

EXTRA_RAM char player_xpos, player_ypos, player_state, player_state2, player_timer, player_rank, nb_lives;
EXTRA_RAM char button_pressed; 
EXTRA_RAM char missile_sprite, missile_rank;
EXTRA_RAM int score;
EXTRA_RAM char update_score;
EXTRA_RAM char game_counter;
EXTRA_RAM char game_state;
EXTRA_RAM char background_color;
EXTRA_RAM char moving_enemy_counter;
EXTRA_RAM char do_update_live_display;
EXTRA_RAM char do_schedule_sfx;
#define GAME_STARTED    0
#define GAME_OVER       1
#define MAX_NB_ENEMIES  3 
EXTRA_RAM char enemy_sprite[MAX_NB_ENEMIES], enemy_type[MAX_NB_ENEMIES], enemy_state[MAX_NB_ENEMIES], enemy_counter[MAX_NB_ENEMIES], enemy_rank[MAX_NB_ENEMIES], enemy_x[MAX_NB_ENEMIES], enemy_y[MAX_NB_ENEMIES];

#define MAX_NB_BULLETS  3 
EXTRA_RAM char bullet_sprite[MAX_NB_BULLETS], bullet_direction[MAX_NB_BULLETS], bullet_rank[MAX_NB_BULLETS];
  
#ifdef DEBUG
char min_timer_vblank;
char min_timer_overscan;
#endif

MS_KERNEL_BANK prepare_background(char scrolling)
{
    char j, start = 0;
    scrolling += 12;
    if (scrolling >= 30) start = scrolling - 30;
    *PF2 = 0;
    *COLUBK = background_color;
    // Replay background to put the correct colors/regs
    for (Y = start; Y != scrolling;) {
        j = playfield[Y++];
        X = playfield[Y++];
        VSYNC[X] = j;
    }
}

MK_BANK update_lives_display()
{
    X = nb_lives; 
    mk_s0 = livesleft[X];
    mk_s1 = livesright[X];
}

void game_init()
{
    multisprite_init(playfield);
    score = 0;
    update_score = 1;
    player_xpos = 76;
    player_ypos = 192;
    player_state = 0;
    moving_enemy_counter = 0;
    do_update_live_display = 0;
    do_schedule_sfx = 0;
    missile_sprite = MS_UNALLOCATED;
    button_pressed = 1;
    player_timer = 1;
    nb_lives = 3;
    for (X = MAX_NB_ENEMIES - 1; X >= 0; X--) {
        enemy_type[X] = 0;
    }
    for (X = MAX_NB_BULLETS - 1; X >= 0; X--) {
        bullet_sprite[X] = 0;
    }
    update_lives_display();
    game_counter = 0;
    game_state = GAME_STARTED;
    multisprite_new(SPRITE_SPACESHIP, player_xpos, player_ypos, 0);
    player_rank = X;
    background_color = VCS_BLACK;
}

MS_OFFSCREEN_BANK void spawn_new_enemy(char type, char spec)
{
    char i, r, s;
    for (X = MAX_NB_ENEMIES - 1; X >= 0; X--) {
        if (!enemy_type[X]) break;
    }
    if (X == -1) return; // No room for this enemy
    
    enemy_type[X] = type;
    i = X;
    if (type == 1) {
        enemy_state[X] = 0;
        enemy_counter[X] = 0;
        r = multisprite_new(SPRITE_ENEMY1, 60, 22, 3);
        s = X;
        X = i;
        if (r == -1) {
            enemy_type[X] = 0; // No room left for this enemy
        } else {
            enemy_sprite[X] = r;
            enemy_state[X] = spec;
            enemy_rank[X] = s;
            enemy_x[X] = 60;
            enemy_y[X] = 22;
        }
    } else if (type == 128) {
        enemy_counter[X] = 3;
        r = multisprite_new(SPRITE_BIGBOSS, spec, 2, 5);
        Y = X;
        X = i;
        enemy_rank[X] = Y;
        s = multisprite_new(SPRITE_BIGBOSS, spec + 16, 2, 5 | MS_REFLECTED);
        X = i;
        if (r == -1) {
            enemy_type[X] = 0; // No room left for this enemy
        } else {
            enemy_sprite[X] = r;
            enemy_state[X] = s;
            enemy_x[X] = spec;
            enemy_y[X] = 2;
        }
    }
}

MS_OFFSCREEN_BANK void fire_new_bullet(char x, char y, char direction, char nusiz)
{
    char i, r, s;
    for (X = MAX_NB_BULLETS - 1; X >= 0; X--) {
        if (!bullet_sprite[X]) break;
    }
    if (X == -1) return; // No room for this bullet 
    
    i = X;
    r = multisprite_new(SPRITE_BULLET, x, y, nusiz);
    s = X;
    X = i;
    if (r != -1) {
        bullet_sprite[X] = r;
        bullet_direction[X] = direction;
        bullet_rank[X] = s;
    }
}

MS_OFFSCREEN_BANK void check_shot_at_enemy()
{
    char i, j, my = ms_sprite_y[X = missile_sprite];
    char my2 = my - 12;
    char mx = ms_sprite_x[X];
    char mx2 = mx + 7;
    char hit = 0;
    for (X = MAX_NB_ENEMIES - 1; X >= 0; X--) {
        if (enemy_type[X]) {
            j = enemy_type[X] & 128;
            Y = enemy_sprite[X];
            if (ms_sprite_y[Y] < my && ms_sprite_y[Y] >= my2) {
                // We are at the right height
                i = X;
                char l = sprite_width[X = ms_sprite_nusiz[Y] & 7];
                if (j) l <<= 1;
                if (mx2 >= ms_sprite_x[Y] && mx < ms_sprite_x[Y] + l) {
                    // Let's see if we hit one of these invaders
                    if (sprite_is_one_invader[X]) {
                        hit = 1;
                        X = i;
                        if (j) {
                            enemy_counter[X]--;
                            if (enemy_counter[X] == 0) { 
                                j = Y;
                                multisprite_delete_with_rank(enemy_state[X], enemy_rank[X] + 1);
                                Y = j;
                                j = 0;
                                X = i;
                            }
                        }
                        if (!j) { 
                            X = enemy_type[X] & 0x7f; // Remove the boss byte
                            score += invader_score[X];
                            X = i;
                            enemy_type[X] = 0;
                            multisprite_delete_with_rank(Y, enemy_rank[X]);
                        }
                    } else {
                        // Let's see if we hit the left side
                        if (mx < ms_sprite_x[Y] + 8) {
                            // Yes !
                            hit = 1;
                            // Let's reduce the size of this invader
                            ms_sprite_nusiz[Y] = sprite_new_nusiz_remove_left[X];
                            ms_sprite_x[Y] += sprite_offset_remove_left[X];
                        } else if (mx2 >= ms_sprite_x[Y] + l - 8) {
                            // Yes. Wi hit the right side
                            hit = 1;
                            // Let's reduce the size of this invader
                            ms_sprite_nusiz[Y] = sprite_new_nusiz_remove_right[X];
                        } else if (X == 3 && mx2 >= ms_sprite_x[Y] + 16 && mx < ms_sprite_x[Y] + 24) {
                            // Yes. Wi hit the right side
                            hit = 1;
                            // Let's reduce the size of this invader
                            ms_sprite_nusiz[Y] = 2;
                        } else if (X == 6 && mx2 >= ms_sprite_x[Y] + 32 && mx < ms_sprite_x[Y] + 40) {
                            // Yes. Wi hit the right side
                            hit = 1;
                            // Let's reduce the size of this invader
                            ms_sprite_nusiz[Y] = 4;
                        }
                    }
                    if (hit) {
                        do_schedule_sfx = 2; 
                        multisprite_delete_with_rank(missile_sprite, missile_rank);
                        missile_sprite = MS_UNALLOCATED;
                        score += 1;
                        update_score = 1;
                        break;
                    }
                }
                X = i;
            }
        }
    }
}

MS_OFFSCREEN_BANK void game_move_enemies()
{
    char i, j;
    // "cheap" part of movement first
    for (X = MAX_NB_ENEMIES - 1; X >= 0; X--) {
        if (enemy_type[X] == 1) {
            Y = enemy_sprite[X];
            if (enemy_counter[X] & 1) {
                ms_sprite_nusiz[Y] |= MS_REFLECTED;
            } else {
                ms_sprite_nusiz[Y] &= ~MS_REFLECTED;
            }
            enemy_counter[X]++;
            if (enemy_counter[X] < 100) {
                enemy_y[X]++;
                if (enemy_counter[X] >= 60) {
                    enemy_x[X]++;
                    if (enemy_counter[X] == 60) {
                        j = ms_sprite_nusiz[Y];
                        fire_new_bullet(enemy_x[X], enemy_y[X] + 12, bullet_start_direction[Y = enemy_state[X]], j);
                    }
                }
            } else {
                enemy_y[X]--;
                if (enemy_y[X] == 21) {
                    i = X;
                    multisprite_delete_with_rank(Y, enemy_rank[X]);
                    X = i;
                    enemy_type[X] = 0;
                }    
            }
        } else if (enemy_type[X] == 128) {
            enemy_y[X]++;
            if (enemy_y[X] == 70) {
                fire_new_bullet(enemy_x[X] + 6, enemy_y[X] + 24, BULLET_B, 1);
            } else if (enemy_y[X] == 200) {
                i = X;
                multisprite_delete_with_rank(enemy_sprite[X], enemy_rank[X]);
                X = i;
                multisprite_delete_with_rank(enemy_state[X], enemy_rank[X]);
                X = i;
                enemy_type[X] = 0;
            }
        }
    }
    
    // And actually move the sprites if there is still some resources available
    while (*INTIM >= 23) {
        X = moving_enemy_counter;
        if (X == 0) {
            X = MAX_NB_ENEMIES;
        }
        X--;
        moving_enemy_counter = X;

        if (enemy_type[X] == 1) {
            multisprite_move_with_rank(enemy_sprite[X], enemy_x[X], enemy_y[X], enemy_rank[X]); 
            X = moving_enemy_counter;
            enemy_rank[X] = Y;
        } else if (enemy_type[X] == 128) {
            // Move the right one before to save the order
            multisprite_move_with_rank(enemy_state[X], -1, enemy_y[X], enemy_rank[X] + 1); 
            X = moving_enemy_counter;
            multisprite_move_with_rank(enemy_sprite[X], -1, enemy_y[X], enemy_rank[X]); 
            X = moving_enemy_counter;
            enemy_rank[X] = Y;
        }
    }
}

MS_OFFSCREEN_BANK void game_move_bullets()
{
    char i, nx, ny, destroy;
    for (X = MAX_NB_BULLETS - 1; X >= 0; X--) {
        Y = bullet_sprite[X];
        if (Y) {
            destroy = 0;
            i = X;
            X = bullet_direction[X];
            nx = ms_sprite_x[Y] + bullet_dx[X];
            if (nx < 3) destroy = 1;
            if (nx >= 150) destroy = 1;
            ny = ms_sprite_y[Y] + bullet_dy[X];
            if (ny < MS_OFFSET) destroy = 1;
            if (ny >= MS_PLAYFIELD_HEIGHT + MS_OFFSET - 2) destroy = 1;
            if (destroy) {
                X = i;
                multisprite_delete_with_rank(Y, bullet_rank[X]);
                X = i;
                bullet_sprite[X] = 0;
            } else {
                X = i;
                multisprite_move_with_rank(Y, nx, ny, bullet_rank[X]);
                X = i;
                bullet_rank[X] = Y;
            }    
        }
    }
}

void game_scenario()
{
    if (!(game_counter & 1)) {
        if ((game_counter & 7) == 0) {
            spawn_new_enemy(128, 60);
        } else {
            spawn_new_enemy(1, (game_counter >> 2) & 3);
        }
    }
}

MS_OFFSCREEN_BANK game_over()
{
    char i;
    // Destroy all enemies & bullets
    for (X = MAX_NB_ENEMIES - 1; X >= 0; X--) {
        enemy_type[X] = 0;
    }
    for (X = MAX_NB_BULLETS - 1; X >= 0; X--) {
        bullet_sprite[X] = 0;
    }
    // Destroy missile
    missile_sprite = MS_UNALLOCATED;

    multisprite_clear();
    multisprite_new(SPRITE_LETTER_G, 80 - 19, 80, 0);
    multisprite_new(SPRITE_LETTER_A, 80 - 9, 80, 0);
    multisprite_new(SPRITE_LETTER_M, 80 + 1, 80, 0);
    multisprite_new(SPRITE_LETTER_E, 80 + 11, 80, 0);
    multisprite_new(SPRITE_LETTER_O, 80 - 19, 130, 0);
    multisprite_new(SPRITE_LETTER_V, 80 - 9, 130, 0);
    multisprite_new(SPRITE_LETTER_E, 80 + 1, 130, 0);
    multisprite_new(SPRITE_LETTER_R, 80 + 11, 130, 0);

    game_state = GAME_OVER;
    game_counter = 0;
    background_color = VCS_RED;
}

MS_OFFSCREEN_BANK lose_one_life()
{
    do_schedule_sfx = 2; 
    player_state = 1;
    player_state2 = 0;
    player_timer = 10;
    nb_lives--;
    do_update_live_display = 1; 
}

MS_OFFSCREEN_BANK game_logic()
{
    X = 0; Y = 0;
    if (!(*SWCHA & 0x80) && player_xpos < 153) { player_xpos++; Y = 1; } // Right
    else if (!(*SWCHA & 0x40) && player_xpos >= 1) { player_xpos--; Y = 1; } // Left
    if (!(*SWCHA & 0x20) && player_ypos < 200) { player_ypos++; Y = 1;} // Down
    else if (!(*SWCHA & 0x10) && player_ypos >= 32) { player_ypos--; ms_sprite_model[X] = SPRITE_SPACESHIP_EXHAUST; Y = 1;} // Up
    else { ms_sprite_model[X] = SPRITE_SPACESHIP; } 
    if (Y) player_rank = multisprite_move_with_rank(0, player_xpos, player_ypos, player_rank);

    // Missile management
    if (missile_sprite != MS_UNALLOCATED) {
        X = missile_sprite;
        // Check for collision 
        if (ms_sprite_nusiz[X] & MS_PF_COLLISION) {
            if (ms_sprite_x[X] < 12 || ms_sprite_x[X] >= 153 - 12) {
                multisprite_delete_with_rank(missile_sprite, missile_rank);
                missile_sprite = MS_UNALLOCATED;
            }
        }
        // Check if an enemy was destroyed
        check_shot_at_enemy();
    }
    if (missile_sprite != MS_UNALLOCATED) {
        X = missile_sprite;
        char y = ms_sprite_y[X] - 6;
        if (y < 32) {
            multisprite_delete_with_rank(missile_sprite, missile_rank);
            missile_sprite = MS_UNALLOCATED;
        } else {
            missile_rank = multisprite_move_with_rank(missile_sprite, -1 /* Go straight */, y, missile_rank);
        }
    }

    if (!(*INPT4 & 0x80)) {
        if (!button_pressed) {
            button_pressed = 1;
            if (missile_sprite == MS_UNALLOCATED) {
                do_schedule_sfx = 1; 
                missile_sprite = multisprite_new(SPRITE_FIRE, player_xpos, player_ypos - 8, 0);
                missile_rank = X;
            }
        }
    } else button_pressed = 0;
    
    // Player management
    if (player_state == 0) {
        // Check collision with playfield
        if (ms_sprite_nusiz[X = 0] & MS_PF_COLLISION) {
            if (ms_sprite_x[X] < 12 || ms_sprite_x[X] >= 153 - 12) {
                lose_one_life();
            }
        }
        if (ms_sprite_nusiz[X = 0] & MS_COLLISION) {
            lose_one_life();
        }
    }
    if (player_state == 0) {
        if (player_timer >= 1) {
            player_timer = 0;
            ms_sprite_nusiz[X] = 0;
        } else {
            player_timer = 1;
            ms_sprite_nusiz[X] = MS_REFLECTED;
        }
    } else {
        // Player explosion
        if (player_state == 1) {
            ms_sprite_model[X = 0] = SPRITE_EXPLOSION1 + player_state2;
            player_timer--;
            if (player_timer == 0) {
                player_timer = 10;
                player_state2++;
                if (player_state2 == 5) {
                    if (nb_lives == 0) game_over();
                    else {
                        player_state = 0;
                        ms_sprite_model[X] = SPRITE_SPACESHIP;
                    }
                }
            } 
        }
    }
}

void game_wait_for_restart()
{
    if (game_counter >= 3) {
        game_counter = 3;
        if (!(*INPT4 & 0x80)) {
            if (!button_pressed) {
                game_init();
            }
        } else button_pressed = 0;
    }
}

void main()
{
    tia_tracker_init();

#ifdef DEBUG
    min_timer_overscan = 255;
    min_timer_vblank = 255;
#endif

    char scrolling = 0;
    game_init();

    do {
        *VBLANK = 2; // Enable VBLANK
        *VSYNC = 2; // Set VSYNC
        strobe(WSYNC); // Hold it for 3 scanlines
        strobe(WSYNC);
        strobe(WSYNC);
        *VSYNC = 0; // Turn VSYNC Off
        
        // Blank
        *TIM64T = ((BLANK - 3) * 76) / 64 - 3;
        // Do some logic here
        if (game_state == GAME_STARTED) 
            game_logic();
        else game_wait_for_restart();
        game_move_enemies();

        ms_scenery = playfield - MS_OFFSET + 12;
        ms_scenery += scrolling;

        multisprite_kernel_prep();
       
#ifdef DEBUG
        if (*INTIM < min_timer_vblank) min_timer_vblank = *INTIM;
#endif 

        while (*INTIM); // Wait for end of blank
 
        multisprite_kernel();
        
        // Overscan
        strobe(WSYNC);
        *COLUBK = VCS_RED;
        *GRP0 = 0; *GRP1 = 0;
        *PF0 = 0; *PF1 = 0; *PF2 = 0;
        *COLUP0 = VCS_WHITE; *COLUP1 = VCS_WHITE;
         
        mini_kernel_6_sprites();
        strobe(WSYNC);
        *COLUBK = VCS_RED;
        strobe(WSYNC);
        *VBLANK = 2; // Enable VBLANK
        *TIM64T = ((OVERSCAN) * 76) / 64 + 2;
        // Do some logic here
        multisprite_kernel_post();
        
        prepare_background(scrolling);
        scrolling -= 2;
        if (scrolling < 0) {
            scrolling = 80;
            game_counter++;
            if (game_state == GAME_STARTED) 
                game_scenario();
        } else {
            // This is executed only if the game scenario is not evaluated
            // So that balances the execution time 
            game_move_bullets();
            if (do_schedule_sfx) {
                if (do_schedule_sfx == 1) {
                    sfx_schedule(sfx_pewpew);
                } else {
                    sfx_schedule(sfx_bigboom);
                }
                do_schedule_sfx = 0;
            }
            if (do_update_live_display) {
                do_update_live_display = 0;
                update_lives_display();
            }
            if (update_score) {
                mini_kernel_update_score_4_digits(score);
                update_score = 0;
            }
        }

        tia_tracker_play();
        sfx_play();
#ifdef DEBUG
        if (*INTIM < min_timer_overscan) min_timer_overscan = *INTIM;
#endif 
        while (*INTIM); // Wait for end of overscan
    } while(1);
}

 

Have you played Atari today ?

example_shmup_NTSC.a26 example_shmup_PAL.a26 cc2600-0.4.4-x86_64.msi

  • Like 1
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...