Let’s make an MSX game – Structure and Organization

A couple of weeks ago we started the game concept design and added some basic code that resulted in our sprite being loaded in the graphic mode screen, which was nice and fun. The problem is that our code looks and feels rushed, with lots of hard-coded memory addresses and repeated commands all over the place. Today we are going to organize the whole thing and ensure that the future additions will always respect a minimal structure standard which will become familiar to us in the next articles and make it easier to maintain the program when any update or fix is required.

We gotta be inclusive, always

Most of the assembly compilers support the use of the include statement to add external code into the main program file, and the 8bitworkshop IDE is not different related to this feature. To add an external file to our code we just need to choose where we want to have the statement added to the code, go to the IDE menu, then select File and Add Include File, then enter the file name we want to use, then click in OK. I will create my first included file name System_BIOSCalls.asm and then move the existing BIOS routines to this new file:

Our first include file

We will notice that the include statement is automatically added to the code, and at the left side menu we will find the new file name showing in the list:

New file and include statement added to the project

Clicking on the file name in the menu will show an empty code window, so now we can cust cut the BIOS routine entries from TheMeteorGame.asm file into this new one:

Our BIOS calls have a place of their own now.

Notice that if you leave the code running in the IDE you will get a lot of errors after cutting the code from the main file, but everything is fixed again after pasting the lines in the new file. This is important to observe since we want the code to behave the same way as it was when all code lines were located in the same file.

I also took the opportunity and created the System_SystemVariables.asm file, then moved the variable entries from the main code to this other place, and also the System_CartridgeHeader.asm to remove the cartridge header from the main code and keep things tight. Notice that the org statement still needs to be positioned before the header statement, otherwise the program will not work properly:

Looking much better in my opinion!

Remember that crazy Set16x16Sprites routine we added in the last article to make the VDP understand that16x16 pixels is the sprite size we want to use and not the 8×8 size which is the default for the TMS9918, why not also move these lines to another file specific for graphic routines? I created the Function_SetScreen.asm file to hold any code related to screen settings, then moved the code there.

Work smart, not hard

Moving code around is easy but there is a limit to how useful It can be. I’m aiming to have a reusable code and avoid having to repeat the same commands through the program, and a way to achieve this that I found was to use memory addresses as variables, then call the routines after setting the variable values. Too complicated? Let me demonstrate and make it easier to understand.

I will take the lines that change the values of the foreground, background, and border colors and move this whole section to the Function_SetScreen.asm file, and make it use the registers B,C, and D to set the color values:

; Sets the foreground, background and border colors, calling INITGRP after
InitGraphicMode:
	ld hl,FORCLR		; Loads FORCLR sys variable in HL
        ld (hl),b		; Loads the value from B into the position pointed by HL
        ld hl,BAKCLR		; Loads BAKCLR sys variable in HL
        ld (hl),c		; Loads the value from B into the position pointed by HL
        ld hl,BDRCLR		; Loads BDCLR sys variable in HL
        ld (hl),d		; Loads the value from B into the position pointed by HL
        call INIGRP		; Initializes the VDP graphic mode
        
        ret			; Return to the caller routine

Next, we need to replace the code from the main file with the lines below, which loads the registers with the color values desired then calls the routine InitGraphicMode we created in the Function_SetScreen.asm file:

Init:
	;Let's initialize the screen colors
	ld b,15			; Foreground color
        ld c,13			; Background color
        ld d,13			; Border color
        call InitGraphicMode	; Initializes graphic mode with the color at BCD 

We basically replaced the 7 lines of code from the original code with a total of 14 lines in the new code which doesn’t seem to be an improvement at all, but keep in mind that we will probably change the colors again in the future during the game development, which will justify this change eventually.

Code is a hard thing when things are hard coded.

In the previous article, I used the VRAM addresses to load the patterns and attributes of each sprite in the memory. This required some manual calculation to figure out where the next values should be loaded, and also made the code quite inflexible and hard to read and maintain.

Gladly, the MSX BIOS has two routines that calculate the memory address of the sprite based on their ID loaded in A, and return the value in the HL register, making it easy to understand and change the code:

CALPAT:	   equ 00084h	; Function : Returns the address of the sprite pattern table
          		; Input    : A  - Sprite ID
          		; Output   : HL - For the address
          		; Registers: AF, DE, HL
CALATR:    equ: 00087h	; Function : Returns the address of the sprite attribute table
              		; Input    : A  - Sprite number
              		; Output   : HL - For the address
              		; Registers: AF, DE, HL

CALPAT will load in HL the VRAM position related to the sprite pattern whose ID is loaded in A before calling the routine, and CALATR will do the same but this time for the sprite’s attributes. Notice that there is a little catch here because the next step is to use LDIRVM to move the pattern and attributes data from RAM to VRAM, but the routine requires that the pattern address is loaded in HL and VRAM address in DE, and CALPAT/CALATR will return the target address in HL!

LDIRVM:    equ 0005Ch	; Function : Block transfer to VRAM from memory
    			; Input    : BC - Block length
    			;            DE - Start address of VRAM
    			;            HL - Start address of memory

Since the is no command that loads HL into DE directly, there is a quick way to do this: PUSHing HL content into the stack and then POPing it back in DE. Here is the code snippet that loads the sprite with id 0 into the VRAM:

        ld   a,0		; Load the sprite ID 0 into A
        call CALPAT		; Return the sprite 0 pattern VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        LD   bc,64		; Our sprite pattern uses 64 bytes
        LD   hl,MeteorIdleSprite01 ; Load the pattern address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        
        ld   a,0		; Load the sprite ID 0 into A
        call CALATR		; Return the sprite 0 attribute VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        ld   bc,4		; Our sprite attribute uses 4 bytes
        LD   hl,MeteorIdleSprite01_attrib ; Load the attribute address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        

To load the next 3 sprites all we need to do is duplicate these code lines replacing the value loaded into A and the labels related to the sprite’s patterns and attributes. Once again this change is not actually reducing the code size nor avoiding code repetition, but it’s easier to understand and maintain, and also makes it easier to improve the routine in the future, which we will do in the next article.

One final touch before wrapping up for the day

There is another section of the code that we can move to another file: the sprite patterns and attributes. For that, I’ve created the include file “Asset_MeteorData.asm”, then moved the labels with the hexadecimal values from the main code to this file, which now looks like this:

; The Meteor Game

	include "System_BIOSCalls.asm"
	include "System_SystemVariables.asm"

	org 0x4000
        
	include "System_CartridgeHeader.asm"

; initialize the whole thing here
Init:
	;Let's initialize the screen colors
	ld b,15			; Foreground color
        ld c,13			; Background color
        ld d,13			; Border color
        call InitGraphicMode	; Initializes graphic mode with the color at BCD 
    
        call Set16x16Sprites	; Set the sprite size to 16x16 pixels
        
        ld   a,0		; Load the sprite ID 0 into A
        call CALPAT		; Return the sprite 0 pattern VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        LD   bc,64		; Our sprite pattern uses 64 bytes
        LD   hl,MeteorIdleSprite01 ; Load the pattern address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        
        ld   a,0		; Load the sprite ID 0 into A
        call CALATR		; Return the sprite 0 attribute VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        ld   bc,4		; Our sprite attribute uses 4 bytes
        LD   hl,MeteorIdleSprite01_attrib ; Load the attribute address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address

        ld   a,1		; Load the sprite ID 1 into A
        call CALPAT		; Return the sprite 1 pattern VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        LD   bc,64		; Our sprite pattern uses 64 bytes
        LD   hl,MeteorIdleSprite02 ; Load the pattern address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        
        ld   a,1		; Load the sprite ID 1 into A
        call CALATR		; Return the sprite 1 attribute VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        ld   bc,4		; Our sprite attribute uses 4 bytes
        LD   hl,MeteorIdleSprite02_attrib ; Load the attribute address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        ld   a,2		; Load the sprite ID 2 into A
        call CALPAT		; Return the sprite 2 pattern VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        LD   bc,64		; Our sprite pattern uses 64 bytes
        LD   hl,MeteorIdleSprite03 ; Load the pattern address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        
        ld   a,2		; Load the sprite ID 2 into A
        call CALATR		; Return the sprite 2 attribute VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        ld   bc,4		; Our sprite attribute uses 4 bytes
        LD   hl,MeteorIdleSprite03_attrib ; Load the attribute address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
    

            
ForeverLoop:
    jr ForeverLoop    ; Repeats the loop indefinitely
        

	include "Function_SetScreen.asm"        
	include "Asset_MeteorData.asm"
        
        end Init

That’s all for now

We moved and changed quite a lot of our code, and it may be somehow disappointing that in the end there was no improvement in the actual game and we are still seeing the crazy meteor guy stuck at the same position on the screen, but believe me that this was really an excellent improvement done through this article and it will be really helpful in the future. The complete code is available at https://github.com/oddbitmachine/TheMeteorGame, so feel free to check out the progress done so far. We will be back soon to improve the sprite loading routine, so stay tuned!

A couple of weeks ago we started the game concept design and added some basic code that resulted in our sprite being loaded in the graphic mode screen, which was nice and fun. The problem is that our code looks and feels rushed, with lots of hard-coded memory addresses and repeated commands all over the…

Deixe um comentário

Your email address will not be published. Required fields are marked *

en_USEnglish