Jump to content
VincentF

Devlog: Vixx (bullet hell game)

Recommended Posts

Hello Everybody !

I wanted to make a post to share a project I started several days ago. I wanted to create a "Bullet Hell" type of game (it's a sub-genre of shoot'em ups games) for the X16, following a concept idea I got to control several sprites on screen.

This post will act as a devlog for this project. For the history, I discovered the genre two weeks ago and wanted to try making one for fun.

So here we go with the first issue of the devlog ! (it'll be long since I got a lot of code done already) Note that I'm not this good at programming in assembly, My only knowledge comes from some youtube videos (Game Mechanics Explained, 8-bit Show And Tell, ...) so I may not be aware of most best practices. (I would like to hear from you if you got some ! 😉 )

#1 26/01/2021: Game framework and first contact

So, before I start with the thing, here's some info on my working environment :
I work with ACME Assembler, because it's the first one I learned and I like the way it works so that's what I'm using.
I have some "libraries" I wrote for myself to declare every Kernal addresses, petscii special characters, some vera addresses, masks, macros, a "basic_startup" macro, some math macros (adc16, inc16, multiply, divide, ...) etc...
I usually start a project by creating a folder and some files in it, I like to start with "main.asm" and "paper.asm". The "main" file will contain the actual program, and the "paper" will contain the variables, constants and some comments on how the game will be structured.

 

Once I got my working environment ready, I started to think of how I'll handle the bullets on-screen. So at first I needed to design the bullet's datas.
To keep things simple I wanted to classify every moving object on screen as an "object". Each objects has 4 properties: a type, x and y coordinates and a parameter. The type will determine which code will handle the movements. The parameter will allow the code to know how to behave and also modify it if needed. The x and y coordinates are ... used as coordinates ¯\_(ツ)_/¯.

Okay, I got a structure for my objects. that's 6 bytes per objects counting the coordinates as 16 bits values. Let's see how I'll configure the VERA ... 640x480 is quite overkill so I downgrade it to 320x240. Playing with the coordinates I see that the low byte of x and y coordinates takes around 3/4 of the screen horizontally and the entire screen vertically. So I can cut off 2 bytes per objects. VERA allows up to 128 sprites on screen, so I note a hard limit of 128 objects. 128 objects means I'll take 512 bytes if the screen is full of bullets.

 

I drew a small 8x8 bullet sprite and on startup I initialize all VERA's sprites as this one. Now, how to handle the movements ?
I said earlier that I started this project with an idea in mind, so here it is : I have an array of addresses, and using JMP ($ABCD, X), I can access all of them. So that mean I can only store 128 types of objects in my game. That's enough for what I want.

So I have 512 bytes for the objects, and 256 bytes for the movement table. I can place them at the end of the fixed RAM ($9C00 to $9FFF). I also use one more byte ($9BFF) to store a count of how much bullets I got in my list (so I don't need to parse it in whole each time I need to).

I came with two loops to handle the objects : one that will move them, and one that'll refresh their position on screen. I wanted to move the objects on every frame but didn't want to bloat the VSYNC interrupt with it. So I created a wait_frame variable that'll be set once the movement has been handled, and will be reset when the VSYNC occured.

Here's the pseudo-code for the "move objects" loop :

Quote

    Load Object structure into a fixed RAM location
    Shift the type to the right once (to align with the 16bit addresses)
    Place the type into the X register
    Jump (movement_table, X) ; <- we will assume the movement routine selected will always return to movement_ended
movement_ended:
    Save Object structure changes from the fixed RAM location back into the Object Array
    Repeat until object_count

For the "display object" loop, it's even simpler : we take the coordinates and output them directly into VERA.

 

I implemented some simple movement functions : the first one is a "Null" movement, nothing moves. The second one is a "reset" movement, we force the position out of the screen and set its type as "Null".
The third one is a player controlled movement. We get the joystick buttons and move accordingly.
The next ones are basic constant movements. Since the parameter is only one byte, and the bullets does not need to move like a blue hedgehog, I assumed the left hex part will be the x movement and the right hex part will be the y movement. I firstly assumed that the value is pixels per frames, but I decided later to take the lowest bit of each hexadecimal number to make half-pixel movements (we move the bullet every odd frames).

You can't imagine how much I had to debug the thing and the weird behaviors I got because of some not-properly-set flag before a critical operation (I knew the carry was used for ADC and SBC, but not for ROL and ROR). Also the hard-to-reverse crashes because you exited a loop without pulling away a value you pushed on the stack. But the best reward was the system working properly at the end, and was even very generic at the end !

 

The next thing I decided to work with was the collisions. Bullet Hell games are easy to make on this side because pretty much everything has small round hitboxes. So after moving the bullets the next thing I do is checking for collisions against the player (I assumed the player will always be the first object on the array). So I take the player's coordinates, and loop through every other objects.
I first subtract the player's x coordinate with the current object's, check the carry to know if I have to check < 3 or > 252. If I match, I do the same check for the y coordinates and if there is a second match, the player has been hit ! (I still need to implement lives)

I later decided to award the player some points if he takes risks and go near bullets. So I added a little scoreboard on the right (discovered by the way the wonderful decimal mode of the 6502) and added a check for larger distances in my collision code to give the player points for each frame passed near a bullet.

 

On the way, I started to create some basic subroutines to add objects to the array (this will check for empty spaces and place the object in it, with a limit of 128 objects (objects inserted beyond the limit will be skipped), optimize the object's array by setting the count to the lowest value possible (freeing the processor as much as possible). I also added some boundary check for the movement functions that'll reset the bullets when they get offscreen. I also imported a RNG subroutine found on the web.

 

While playing with the insert routine, I went through a new problem : how to handle the bullets ? I'll need to create some on the fly, but I can't just use some random code for this. I need some sort of conductor to keep things clean and ease the creation of levels.
So I started to create the "Choreography" module. It'll live on the game loop (not the vsync loop), and instead of being run once per frame (as the movements), it'll be run as much as possible, but leaving the priority to movement's and collision's code.

The Choreography code will be an interpreter, with its own Program Counter, X and Y position, two A and B registers and a "mode" variable that'll just sit here doing nothing but will eventually get a meaning later.
The program counter will start at a fixed location in the code ($9000) and will travel it reading each bytes of opcodes and their parameters. The X and Y refers to a "cursor", it's the position where objects will be inserted next. it can move to absolute values as well as relative ones. It has two registers that'll be used mainly for looping and doing conditional jumps.
I created some opcodes : SLEEP to skip some frames, INSERT to insert object into the game, SETPOS to set the position of the cursor, MOVPOS to move it in a relative way, LOADA to load some value to the "A" register, LOADB for the "B" register, INCA and INCB to increment them, JMP to jump, JAZ "Jump when A is Zero", JAN "Jump when A is Not zero", and so on ...

I wrote some of this custom code and got a very nice result. I wanted to share this with you :

bullet_hell_1.gif.d29d2a94ffd62f18f863a7d79bfe6166.gif

 

And that's All for this first loooooong devlog ! I'm a bit tired by writing all of this ^^' (it was around 1am when this was finished) so I'm not sure I'll double-check now if I got mistakes on this. I also wanted to share this with you in case you can get some ideas for your own projects. Another reason is I tend to easily drop projects by not being motivated in them anymore. I'm considering making this project open-source so you'll be able to review (if you're courageous) and continue it (for the courageous++) just in case. Tell me what you're thinking of this project, I'll be happy to talk with you further about it ^^

Edited by VincentF
add devlog tag
  • Like 5

Share this post


Link to post
Share on other sites

Hello Everybody !

Here's the second issue of my devlog, I made some progress and thanks to x16-edit's source code, I was able to setup my file saving/loading code ^^

#2 - 28/01/2021: Gamemodes and High-scores

These last few days I worked mainly on creating a gamemode system to handle menus and different game states (initializing game, game loop, game over screen ...). When the gamemode is changed, we need to jump the code to a "change_gamemode" routine that acts as an intersection between every gamemodes. Its purpose is just to jump to the right routine based on the gamemode. Then, the routine manages itself (calling subroutines, changing stuff, waiting some frames, looping around...) until the gamemode is changed again.

I then created a "title screen" and "game over screen" gamemode, a "init_game" mode that changes directly to the last one called "game_loop".
- "Title_screen" just displays a "Press Enter" and waits for an input. It then switch to "init_game".
- "Init_game", sets up the variables for a new game and switch to "game_loop".
- "Game_loop" is the main loop, with all the bullets and collisions and choreography, etc. When the player loses it switch to "game_over"
- Finally, "Game_over" just displays a "Game Over" text for a few seconds and return back to "title_screen".

 

By the way, I implemented some lives for the player, with an invincibility cooldown (with no visual effects for now) and decided to start implementing a high-score system.

This system was quite hard to create, because I have never saved anything with the X16. So starting from scratch, I looked on the Ultimate C64 Reference how to use the Kernal functions to save things but wasn't able to do much. After looking on the IO errors, I found out that we need to provide a SD card to the emulator in order to save files. Okay, so let's add an SDcard ! Getting more troubles trying to OPEN anything, I finally given up and searched some example code of a saving routine. I knew the existence of X16-edit so I decided to read some assembly code (kinda "exotic", due to an assembler language that differs from ACME). Taking examples from this code (and resetting the sdcard file multiple times because the emulator has corrupted it many times probably because I didn't have closed the file properly), I finally got my score file saved on the card ! It took me hours to get it to work properly but now that this part is done, I'm happy with it.

 

Since last devlog, I also improved the Choreography Interpreter, making it able to print text on the screen, execute external code, and other opcodes to use when composing levels.
 b_hell_chor_opcodes.png.5b90198ddba79498576ff18176814e35.png b_hell_chor_loop.png.c5e76b5380a6bba8e6d9cf7ad34d5f60.png
(The first image shows the different Choreography opcodes, and the second image shows an example of choreography code, the demo gif below shows the result of it)

 

Here's a gif of the progress so far, the choreography demonstrates almost the maximum amount of bullets that can run simultaneously on screen (there are ~120 object displayed over the maximum 128)

recording.gif.ca5165b09c69af342bdf29883e7cb90a.gif

 

And that wraps up my progress of these last two days. The progress is satisfying and I can't wait to have some alpha version to share with you all. Thanks for reading ! ^^

Edited by VincentF
  • Thanks 1

Share this post


Link to post
Share on other sites

Hello Everybody !

This time I'm not writing a devlog (or kinda) but I want some feedback from you.

First, I came with some lore for the game and a name !

Vixx16

Your Commander X16 got a virus ! Thankfully you have an antivirus called Vix that'll get rid of the evil software.
You play as the Vix Antivirus, scanning the computer trying to locate and destroy the virus.

 

I got some gameplay footage with more visual things and also a demo for you to test (the boss is a placeholder that loops until you lose) :
WARNING: you must use an EMPTY sdcard to store the high scores, for some reason the program sometimes corrupts the file ! (If by the way you know why the file corrupts so easily I would be grateful)

vixx16.prg

recording.gif.97eb58108301bdfbcb729f050bfe782f.gif

  • Like 2

Share this post


Link to post
Share on other sites

With the virus theme combined with "bullet hell" style gameplay I get strong Nier Automata vibes, which is a good thing (I adore that game). Were you inspired by that?

Not a big fan though of the game name to be honest.  Repeating "x16" in everything is not my preference.  "Vixx" is good enough to me 🙂

  • Like 1

Share this post


Link to post
Share on other sites
7 minutes ago, desertfish said:

With the virus theme combined with "bullet hell" style gameplay I get strong Nier Automata vibes, which is a good thing (I adore that game). Were you inspired by that?

Hmm no, I love nier automata as well but the idea does not comes from it... maybe indirectly ? 🤔

7 minutes ago, desertfish said:

Not a big fan though of the game name to be honest.  Repeating "x16" in everything is not my preference.  "Vixx" is good enough to me 🙂

The original name was "Vix", but when drawing the title screen the "x16" came by itself. It made sense to me since in the game story we are playing in a X16 ... Maybe I need to rethink this part.

Share this post


Link to post
Share on other sites

It looks nice, all simultaneously moving sprites.

I have experienced SD card corruption if the SD card is mounted in the local file system at the same time it's used by the emulator. There's nothing stopping you from doing that, but you should avoid it.

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, Stefan said:

I have experienced SD card corruption if the SD card is mounted in the local file system at the same time it's used by the emulator. There's nothing stopping you from doing that, but you should avoid it.

I didn't have mounted the file before he corrupted itself. Playing around with it I found it's related to the fact I tried to "OPEN" a file that doesn't exist. For some reason the "LED indicator" keeps blinking even after I "CLOSE" the file (that didn't exist in the first place). Is that an emulator bug maybe ? 🤷‍♂️

Share this post


Link to post
Share on other sites

I haven't had that problem myself in my program X16 Edit.

My program always reads the disk status after accessing a file, the equivalent of entering DOS at the BASIC prompt. Maybe reading the disk status is necessary, clearing the error state before accessing other files.

It's not likely an emulator bug. More likely a "feature" of the Kernal that you stumbled upon.

  • Thanks 1

Share this post


Link to post
Share on other sites

Thank you for your advice ! I'll try to add some check for the status this evening and see if it works better this way.
Just in case, here's the loading code :

loading_code.png.0b129673d63a3526b6041a10003cbcfa.png

Edited by VincentF
fixed issue with code formatting :/

Share this post


Link to post
Share on other sites

OK. Please let us know if it solves your problem. Interesting issue after all.

Share this post


Link to post
Share on other sites

You need to add ",s,r" or so to the file name to communicate to the disk driver that you are opening a sequential file (basically every file that isn't a BASIC or .PRG file IIRC) for reading.

When I tried around with files I had the same issue with blinking LED and couldn't figure out what was happening. The documentation isn't very clear about this.

For more info about this, I suggest reading https://en.m.wikipedia.org/wiki/Commodore_DOS

  • Thanks 1

Share this post


Link to post
Share on other sites
30 minutes ago, kliepatsch said:

You need to add ",s,r" or so to the file name to communicate to the disk driver that you are opening a sequential file (basically every file that isn't a BASIC or .PRG file IIRC) for reading.

I know this already ^^ here's the filenames I use (when writing I do a len+2 to include the overwrite element) :

file_hiscore_len = 11
file_hiscore_w !pet "@:bhellsc,s,w"
file_hiscore_r !pet "bhellsc,s,r"

By the way, thank you for the link 🙂

12 hours ago, Stefan said:

OK. Please let us know if it solves your problem. Interesting issue after all.

Okay so I got some updates on why the sdcard get corrupted. It seems that a software on my computer had locked the file for reading and I suspect that was causing the issue (so this joins the problem when the sdcard is mounted on the local filesystem).

However, this does not prevent the sdcard activity light from blinking continuously only when we attempt to load a non-existing file. I need to investigate this further.

Share this post


Link to post
Share on other sites

The blinking pixels at top right corner goes away after you read the disk status.

In assembly, you need to open yet another file for reading with secondary address 15 (=command channel) to do this.

  • Thanks 1

Share this post


Link to post
Share on other sites

There are two bugs in your code:

  • It must call CLRCHN before calling CLOSE.
  • If the file doesn't exist, then a time-out status bit, not the end-of-file bit, is set.

Share this post


Link to post
Share on other sites

Thanks for the information 🙂 I'll fix all of this asap

3 hours ago, Greg King said:

It must call CLRCHN before calling CLOSE.

I had previously switched CLRCHN and CLOSE but when trying to debug the file corruption + led activity light I noticed it was this way on x16-edit :

753575085_Screenshot_2021-02-02stefan-b-jakobssonx16-edit.png.3658f3fae899238a8fc003e6c450faf8.png

3 hours ago, Greg King said:

If the file doesn't exist, then a time-out status bit, not the end-of-file bit, is set.

Strangely, when opening the non existent file, here's what I got after OPEN and after READST :

  before_readst.png.bf9d60b0ab4b7bd97153134f49b95d3f.png  after_readst.png.11b37601bb5e50b3585970247cd5e44a.png

According to the documentation, READST should set bit 1 of the accumulator (so value = $02) but on my debugger it's just sending $00.
🤔 If you got an idea of what I should look at... I'm checking for an EOF because that was how it was done on x16-edit (link) and it does work as intended.

The only remaining thing is to stop the SD card activity led from blinking.

On 1/31/2021 at 12:29 PM, Stefan said:

In assembly, you need to open yet another file for reading with secondary address 15 (=command channel) to do this.

I quickly tried to do that :

open15.png.afe7f72f22a6a154e89820f6ee8a15b9.png

(didn't set the name since it was done earlier)
But the blinking is still here 🤔

One thing I'll do is create a blank file, it should fix this issue once and for all.

Thank you for helping me figure this out ! 🙂 It's really appreciated !

EDIT:  creating the file effectively fixed the blinking issue ^^

Edited by VincentF

Share this post


Link to post
Share on other sites
5 hours ago, VincentF said:

The only remaining thing is to stop the SD card activity led from blinking.
I quickly tried to do that :
open15.png.afe7f72f22a6a154e89820f6ee8a15b9.png

(didn't set the name since it was done earlier)
But the blinking is still here 🤔

To what was the name set earlier?  You must call SETNAM with the accumulator set to zero (the other two registers don't matter when the "name" has zero length)!  Otherwise, the "name" string is sent as a DOS command (which might cause a syntax error).  Also, you actually must read the status message, in order to cancel the error.

Share this post


Link to post
Share on other sites
31 minutes ago, Greg King said:

To what was the name set earlier?

The file is  "bhellsc,s,r", and I called SETNAM with the accumulator set at value #11 (the length of the string)

Reading the documentation, I think I understand why I need to call with accumulator set at #0, that's because we don't want any file.
I find the OPEN command (and others file management commands) quite obscure because wherever I try to search information for it there is always some missing info. (for example secondary addresses)

Edited by VincentF

Share this post


Link to post
Share on other sites

Hi,

The read loop in my program stops if READST returns anything else than 0.

I have tested what return value you get from READST on file not found. And it is $42. That is $2 (time out read) + $40 (end of identity, sort of end of file). As your code tests for $40 that doesn't seem to explain your problem.

To read the disk status, you need to open the command channel with a zero length file name. Otherwise you are effectively sending the file name to the drive which will try to interpret it as a DOS command. In most cases this will cause a syntax error, and the blinking continues.

To see what I mean, try entering DOS "<anyfilename>" at the BASIC prompt. The blinking light turns on. Then read the status by entering just "DOS". You can see that the last error was syntax error.

I haven't tested it, but you might actually also need to read the byte stream from the command channel before closing it in order to make the blinking light go away. Only by reading the stream, you get to know what disk error occurred, if that's of any interest to you.

Finally, Greg and I discussed the order in which you should call CLOSE and CLRCHN some time ago. It's common to find C64 code examples online that first calls CLOSE and then CLRCHN. When testing this thourougly, I found that it doesn't matter in which order you call these functions. Having used X16 Edit for several months now, I have not had any file related problems with calling CLOSE first and CLRCHN second. I think you may rule out that this has anything to do with your problem.

  • Like 1

Share this post


Link to post
Share on other sites

I think I misunderstood the DOS commands issued by the FS calls, I thought that by "reading the status" you meant calling READST but it was just calling OPEN with a zero-length file ! 😮

I'm not fully aware of all the features of the C64 / X16, so I'm still learning things everyday ^^

Sorry about the past answers >_< was dumb

Edited by VincentF

Share this post


Link to post
Share on other sites

OK.

READST just tells you that you have reached end of file or that there is some error, but not which one.

By opening another file with secondary address 15 you may read the disk status text stream. The text stream is read with the same functions as a normal text file(call CHKIN, and then call CHRIN and READST in a loop until you get a non-zero status value).

"00, OK, 00,00" is returned if there was no error.

"62, FILE NOT FOUND,00,00" is returned  on, you guessed it, file not found.

There are, of coarse, many other possible disk errors. Read the complete list in the 1541 user manual page 42 found here: http://www.commodore.ca/wp-content/uploads/2018/11/commodore_vic_1541_floppy_drive_users_manual.pdf

This is not entirely intuitive, being a Commodore legacy, when disk drives where almost computers of their own that you communicated with over the serial bus.

 

Edited by Stefan
  • Thanks 1

Share this post


Link to post
Share on other sites

Looks promising. You should probably use READST to check for errors/EOI instead of return value 0 from CHRIN.

What happens when you run this?

Share this post


Link to post
Share on other sites

Right now it's returning carry set (debugged the output and it made sense) but still no luck on the blinking, i'll try with the READST. I tried to check for zeroes but I need to CMP #$30 instead. (petscii zeroes)

Share this post


Link to post
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.

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.


×
×
  • Create New...

Important Information

Please review our Terms of Use