Jump to content

VS Squad (Early WIP) (Atari 2600)

Recommended Posts

Update: Check my comment below for more details, but I've added the current version as vsshooter.bin on this post



-- Original post --


Hello, I've been working on my first major 2600 project, and decided to post about it here just to see what people think about it.


It's a vertically scrolling shooter for the 2600 running on a stock 4k cart. I don't have a harmony cart, so I've only had the opportunity to test it in an emulator, so apologies if it's not stable on real hardware.


The kernel should theoretically support ~30 enemies on screen at a time. The current version technically supports this, but it can cause some bad artifacts with scrolling enemies. Currently it's more of a tech demonstration than a game, since I need to rewrite the kernel to allow for collision detection (I was planning on doing collision detection separately, but after calculating the timing it would take 30+ scanlines for all the math) but I figured before I gutted my code I would record the state things are in right now. Luckily, I won't have to remove any functionality shown in the current demo, so it should be representative of the game if I get the time to finish it






Edited by triggthediscovery
update to show progress
  • Like 11
Link to comment
Share on other sites

I like your use of the ball to free up both players for all the enemy.


Picture rolls on real hardware.




Take a look at Step 1 of my tutorial and compare how it updates VSYNC and VBLANK, especially in relation to WSYNC. As one example look at this bit of code:

        lda #2      ; LoaD Accumulator with 2 so D1=1
        sta WSYNC   ; Wait for SYNC (halts CPU until end of scanline)
        sta VSYNC   ; Accumulator D1=1, turns on Vertical Sync signal
        sta WSYNC   ; Wait for Sync - halts CPU until end of 1st scanline of VSYNC
        sta WSYNC   ; wait until end of 2nd scanline of VSYNC
        lda #0      ; LoaD Accumulator with 0 so D1=0
        sta WSYNC   ; wait until end of 3rd scanline of VSYNC
        sta VSYNC   ; Accumulator D1=0, turns off Vertical Sync signal
        rts         ; ReTurn from Subroutine

vs. what you're doing:




The VSYNC should happen after WSYNC, not before.

  • Like 2
Link to comment
Share on other sites

I hacked the ROM using Stella - besides the VSYNC WSYNC swap I also needed to move the lda #$00 sta VBLANK. That should be done just before you start the kernel (drawing the visible screen), not during the Vertical Sync. Reason being is that will turn on video output, and the colors will interfere with the display's ability to detect the VSYCN signal.



changed the sta VBLANK to NOP NOP to remove it, swapped 2 occurrences of VSYNC and WSYCN




Added sta VBLANK just before the screen gets drawn




After that, the other thing I notice is the enemy only shows up on the right side of the screen. It was like that in the video, so it's not due to the hacks I did. For debugging purposes you should make each player a different color, which'll make it easier to figure out if the position problem is with player0 or player1.




I also suggest you read at least the first few parts of my tutorial, you'll want to use the timers (as shown in Step 2) rather than a bunch of writes to WSYNC (as shown in Step 1).


  • Like 2
Link to comment
Share on other sites

I apologize, I accidentally uploaded an outdated bin file!


Thanks SpiceWare, I'm realizing I'm going to need a flashcart if I want to make sure it works properly on a real system. In regards to all my WSYNCs I might use the timer in the final version, but right now they were just the easiest way to get a stable game in Stella, I intend to hopefully use all that free CPU time for something fun.


But here's my current beta, and I know it works as a demo in Stella, but it doesn't have any added features, again sorry for the outdated bin




  • Like 3
Link to comment
Share on other sites

There's no question that you will use the timers, without them it's too difficult to implement game logic while also maintaining a stable display.


Nicht sehr gut on hardware.






While it works in Stella, it doesn't work if you have Stella set for developer mode:






Note the Developer text just after 4K. This is part of the frame stats, which is toggled using a Developer Key in Stella (ALT-L for Linux or Windows, and COMMAND-L for Mac). Do note that due to the randomize options of Developer mode, the game will sometimes start up differently:

and sometimes work correctly:
To turn on Developer Mode hit TAB to bring up the in-game menu:
Click Developer... which is the bottom left button, then select the Developer Settings radio button at the top.





As a developer you really want to turn that on, it will expose bugs in your code that will show up on some, but not all, Atari consoles. One of the most common bugs is to leave off a # in an immediate mode instruction, aka typing in LDA $02 when you really meant to type LDA #$02. Same for LDX and LDY immediate mode instructions.





  • Like 3
Link to comment
Share on other sites

Flash cart options are:


The Harmony - I have both versions, the original and the Encore that supports larger games. One thing I like is games written to take advantage of the original Harmony's hardware (ie: utilize the ARM as a coprocessor) can be produced as stand along games by using the Melody board. Recent examples of what can be down with that are my Draconian and John's Mappy (check the videos). While you're not yet ready to create games like those, you will be in the future.


The UnoCart supports larger games as well. My understanding is they're working on a version for standalone games, but I don't know when that'll be ready. Follow along with this topic if you'd like to keep up with UnoCart development, as well as where to buy one.


For smaller projects you could also use a Starpath Supercharger, it'll support 4K games; however, it's around the same cost as the Harmony or UnoCart and not as easy to work with. You'll have to turn your BIN into an audio file and figure out how to play it into the Supercharger - games for it were sold on cassette tapes back in the 80s. I started out with one of these, but haven't used it in over a decade.

  • Like 1
Link to comment
Share on other sites

  • 6 months later...
  • 11 months later...

Well, it's been **checks calendar** a while, but I have been working on this in fits and starts for a bit now. I had some issues, namely losing the source code, but I've managed a more game like demonstration of what this is supposed to be.


This version adds collision detection, which ended up requiring a lot of the kernel to be rewritten. When in P0 difficulty B it only applies collision detection to the enemies, with difficulty A applying it to them and the player. The player collision is rough (it causes image scrolling issues on real hardware.) It's there because I had already done the work before I realized it would take some work to get right, and figured putting it behind an option would be possible.

This version also adds color to the enemies, and gives each set different graphics. I realized I could add this more or less for free to the kernel, and figured "why not?"


I also added an enemies killed counter at the bottom. For the purposes of this it only goes up to 99, but that will serve as the basis of the score counter once that gets in.


I've tried testing in emulator and real hardware, but there might still be bad boot states remaining. The collision itself is also kinda funky, there's definitely room for improvement there, but I'm planning on leaving it until I get further into development.


But that's more or less it. Fingers crossed, I can get this to a satisfactory place without too much more work, but honestly I didn't think I would still be on this 18 months after starting.


  • Like 5
Link to comment
Share on other sites

Is that nusiz copies you're shooting?

when I did that in a test, I only checked the hitboxes of the enemy that was in the same vertical region as the missile, so max one check per frame, maybe you're allready doing that but otherwise this is how I did it, some is batari basic but I'm sure you can read it.


player0 was my missile and the player1 array the enemies


 ldx #number of enemies - 1


  LDA player0y


  adc player0height

  CMP player1y,x

  BCC .noOverlap

  LDA player1y,x


  ADC player1height,x

  CMP player0y

  BCC .noOverlap


  STX trick   ; store x to temporary trick variable before exiting

 jmp .exitLoop



 bpl .verticalCheckLoop

 jmp .skipCollisionCheck  ;if no match was found during the loop


end  ;assembly end

  ;prepack some calculations

  temp4=player1x[trick]+8   ;RIGHT EDGE OF FIRST COPY

  temp5=player1x[trick]+32   ;LEFT EDGE OF SECOND COPY (RIGHT = 40)

  temp6=player1x[trick]+64   ;LEFT EDGE OF THIRD COPY (RIGHT = 72)



 ; go to the correct horizontal hitbox checking based on how many nusiz copies

 if temp2= $06 then goto which7w3 ;$06

 if temp2= $02 then goto  which7w2l ;02

 if temp2= $04 then goto which7w2w ;04

 if temp2= $00 then goto  which7w1 ;00



   ;  If sprite on the left was hit, switches to 2 medium copies

   ;  and moves copies to the right side.


   if temp3 >= player1x[trick] && player0x <= temp4 then  _NUSIZ1[trick]=$02: player1x[trick]=temp5 : goto done


   ;  If sprite on the right was hit, switches to 2 medium copies


   if temp3 >= temp6 && player0x <= player1x[trick]+72 then _NUSIZ1[trick]=$02  : goto done

   ;  If sprite in the middle was hit, switches to 2 wide copies.


   if temp3 >= temp5 && player0x <= player1x[trick]+40 then _NUSIZ1[trick]=$04   : goto done

   goto skipCollisionCheck   ;if no copy was hit



   ;  Last copy on current row is removed off screen and nusiz copies is reset to three.


   if temp3 >= player1x[trick] && player0x <= temp4 then player1y[trick]=189 : _NUSIZ1[trick]=$06  : goto done

  goto skipCollisionCheck   ;if no copy was hit


   if temp3 >= player1x[trick] && player0x <= temp4 then player1x[trick]=temp6 : goto lastCopy

   if temp3 >= temp6 && player0x <= player1x[trick]+72 then  goto lastCopy

  goto skipCollisionCheck   ;if no copy was hit


   if temp3 >= player1x[trick] && player0x <= temp4 then player1x[trick]=temp5 : goto lastCopy

   if temp3 >= temp5 && player0x <= player1x[trick]+40 then  goto lastCopy

  goto skipCollisionCheck   ;if no copy was hit




 ; code for what happens when you hit an enemy goes here


 ; if no copy was hit you land here


Link to comment
Share on other sites

I'm not exactly sure what the limitations of Batari basic are (I've heard it leverages the ARM), but I'm writing this for a stock 4k cart in assembly, so I was very particular (read:overcomplicated) with how I handled it.


Apologies if this is a little hard to follow, please let me know if I need to clarify something.


Background info:

The enemies are drawn using a rotating 16 byte buffer, each byte applying to a 4 kernel line subroutine (move, move+4, draw, ect.) and NUSIZ instructions (1 copy, two close, ect.) which is updated every 4 kernel lines (the subroutine is in the top 4 bits, NUZIO in the bottom 3). The play area is 64 kernel lines tall, so the 16 byte buffer covers the screen.

The player is drawn with the ball in a rotating 64 byte buffer, updated every kernel line.

I make sure to track the player and enemy positions every kernel line so I know their absolute positions on screen.


For collision:

In the enemy update cycle (once every 4 scanlines), there is a collision check. This uses the (player position - enemy position) (which ever one you collided with) and references a 256-bit array (covering all possible relative positions) to determine which enemy was hit (#%10000000 is the leftmost, #%01000000 is the middle, #%00100000 is the rightmost). This is then XORed with the 16 byte enemy buffer pointer (I only need the lowest 4 bits, as it's only 16 bytes long) to store which enemy the player collided with. This is then stored in memory to be referenced later (as this was already too much logic in the kernel)

Outside the kernel, I used the enemy pointer to determine which byte in the enemy table the player collided with, and the surrounding bytes to determine which enemies were present on the line, updating the enemy buffer to "remove" that enemy so it won't appear next frame.


So, I'm actually only doing one collision check every 4 kernel lines (once for each set on enemies).

Edited by triggthediscovery
submitted too early
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.

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.

  • Recently Browsing   0 members

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