Discussion about software development for the old-school Gameboys, ranging from the "Gray brick" to Gameboy Color
(Launched in 2008)
You are not logged in.
Hi! I was wondering whether it would be possible to receive some feedback on a ROM I've been working on?
In particular, I'm interested in how the structure of my code can be improved, as well as any other things in general.
Thank you
tileMap.asm (main file):
INCLUDE "gbhw.inc"
INCLUDE "memory.asm"
INCLUDE "tileSetExp.z80"
INCLUDE "tileMapTest.z80"
INCLUDE "TitleScreen.z80"
INCLUDE "Sprite.z80"
INCLUDE "npc.z80"
SECTION "Copy_DMA", ROM0[$28]
COPY_DATA:
pop hl
push bc
ld a, [hli] ; will trash OAM
ld b, a
ld a, [hli] ; will trash sprite OAM
ld c, a
.copy_loop:
ld a, [hli]
ld [de], a
inc de
dec bc
; check counter is zero
ld a, b
or a, c
jr nz, .copy_loop
; finish
pop bc
jp hl
reti
SECTION "ROM_entry", ROM0[$100]
di
jp Start
REPT $150 - $104
db 0
ENDR
SECTION "Game Code", ROM0
; VARIABLES
framesLeft EQU 6
spriteArrayLength EQU 4
; VARIABLES
; ARRAYS
spriteArray:
DB $1E, $14, $00, $00 ; YPos, XPos, tile #, attrib
; ARRAYS
; COLLISION TABLE
collisionMap:
DB $00, $01, $02, $01, $01, $01, $01, $01, $01, $01, $01
;COLLISION TABLE
Start:
nop
di
ld sp, $ffff
call OAM_Clear
ld b, spriteArrayLength
ld de, spriteArray
ld hl, sprite
call loadSpriteData
call DMA_Copy
call waitVBlank
call $ff80
call waitVBlank ; wait for VBlank before turning the screen off
ld hl, $ff40
res 7, [hl]
ld hl, 0
ld hl, $8800 ; destination
ld de, BTile
ld bc, BTileEnd - BTile
call memCopyVRAM
call splash_screen
call waitVBlank
ld hl, $ff40
res 7, [hl]
ld hl, 0
call drawMap
ld hl, $8000
ld de, Sprite
ld bc, SpriteEnd - Sprite
call memCopyVRAM
call counterSet
ld a, %10001011 ; turn screen on
ld [$ff40], a
ld b, framesLeft ; frame number we want to copy to OAM
ld hl, frames
call updateSprite
updateSprite:
.FrameArray:
; pop af
; loop through array starting at [0]
ld a, [hl] ; load the contents of frames into a
ld [sprite+2], a ; load into OAM location
call waitVBlank ; wait for VBlank
call $ff80 ; call DMA routine from HRAM
inc hl ; increment the frame number
dec b ; decrement the number of frames left
jr nz, .FrameArray
;call movement ; read the joypad after looping through the animation
push hl
call waitVBlank
call movement ; read joypad and move
pop hl
; if the currentFrame is zero, then load 6 and loop to FrameArray
ld b, framesLeft
ld hl, frames
jr .FrameArray
loadSpriteData:
.SpriteData
; loop through array
ld a, [de] ; take one byte from array
ld [hli], a ; increment byte in reserved memory
; YPos, XPos, tile number, attribute
inc de
dec b
jr nz, .SpriteData
ret
splash_screen:
call clear_bg
; perform manually for now
ld hl, $9c00
ld de, titleScreen
ld bc, titleScreenEnd - titleScreen
call memCopyVRAM
ld a, %10001001 ; turn screen on
ld [$ff40], a
.waitButtonPress
ld a, %00010000
ld [$ff00], a
ld a, [$ff00]
ld a, [$ff00]
and $01
cp 1
jr z, .waitButtonPress
ret
clear_bg:
; clear background screen
ld hl, $9c00
ld de, $8800
ld bc, 32*32
call memCopyVRAM
; clear sprite data
ld hl, $ff40
res 1, [hl]
ld hl, 0
ret
read_joypad:
; read_joypad
; We need to read the D-Pad to check whether we have any D-Pad input
ld a, %00100000 ; to get directional keys on D-Pad
ld [$ff00], a ; load to memory location to perform the read operation
ld a, [$ff00] ; load results of read operation into accumulator
ld a, [$ff00] ; do more than once due to bouncing
cpl
and %00001111
ld b, a
; read buttons
ld a, %00100000
ld [$ff00], a
ld a, [$ff00]
ld a, [$ff00]
ld a, [$ff00]
ld a, [$ff00]
ld a, [$ff00]
ld a, [$ff00]
cpl
and %00001111
swap a
or b
ld b, a
ld a, $30
ld [$ff00], a
ld a, b
;ld [_PadData], a
ret
movement:
; move sprite depending on the buttons
; Right
; read joypad
call read_joypad
and %00000001
call nz, moveRight
; Left
call read_joypad
and %00000010
call nz, moveLeft
; up
call read_joypad
and %00000100
call nz, moveUp
; down
call read_joypad
and %00001000
call nz, moveDown
ret
moveRight:
call getSpritePos
inc e
ld b, d
ld c, e
call collisionDetection
cp a, $01
ret z
ld hl, sprite ; load sprite
inc hl ; [sprite+1] - XPos
inc [hl] ; XPos + 1
call waitVBlank
call $ff80
ret
moveLeft:
call getSpritePos
dec e
ld b, d
ld c, e
call collisionDetection
cp a, $01
ret z
ld hl, sprite
inc hl
dec [hl]
call waitVBlank
call $ff80
ret
moveUp:
call getSpritePos
ld a, e
sbc a, $20
ld e, a
ld b, d
ld c, e
jr c, .carryRegSub
call collisionDetection
cp a, $01 ; look at self modifying code in assembly in book for possible changes - increment instead of decrement
call nz, moveUpOnce
ret
.carryRegSub:
;ld a, d
dec b
;ld d, a
ccf
cp a, $01
call z, moveUpOnce
moveUpOnce
ld hl, sprite
dec [hl]
call waitVBlank
call $ff80
ret
moveDown:
call getSpritePos
ld a, e
adc a, $20
ld e, a
ld b, d
ld c, e
jr c, .carryRegAdd
call collisionDetection
cp a, $01
call nz, moveDownOnce
ret
.carryRegAdd:
inc b
ccf
call collisionDetection
cp a, $01
call nz, moveDownOnce
ret
moveDownOnce:
ld hl, sprite
; ld a, [hl]
inc [hl]
; ld [sprite], a
call waitVBlank
call $ff80
ret
counterSet:
; need to change tile at $8810 to number 0
ld hl, $9c05 ; destination
ld [hl], $88
ret
counterIncrease:
call waitVBlank
ld hl, $9c05
ld a, [hl]
cp a, $91 ; if accumulator contents equal to $91, reset to zero
jr z, .reset
inc [hl] ; increment to next number tile from byte pointed to by hl
; increase coin # variable
ld a, [Coins]
add a, 1
ld [Coins], a
; check if coin is equal to 3
cp a, $03
call z, Start
ret
.reset
ld [hl], $88
ret
collide_coin:
call soundData
call counterIncrease
call deleteCoin
ret
deleteCoin:
; find the address of the coin to be deleted
; current pos will be in de
; de is in bc
ld h, b ; load the position of the coin
ld l, c ; hl should now contain the position of the coin
call waitVBlank
ld a, [hl]
sub a, $02
ld [hl], a
ret
collisionDetection:
ld a, [de]
ld de, collisionMap
sub a, $80
add a, e
ld e, a
ld a, 0
adc a, d
ld d, a
ld a, [de]
cp a, $02
call z, collide_coin ; if not equal to $02, collide and return
ret
getSpritePos:
ld hl, sprite
ld a, [hl] ; YPos
cp a, $10
jr nc, .moreThanEight
ld a, $10
.moreThanEight
sub a, $10
srl a
srl a
srl a
ld de, $0000 ; offset
ld e, a
; multiply de by 32
sla e
rl d
sla e
rl d
sla e
rl d
sla e
rl d
sla e
rl d
ld hl, sprite
inc hl
ld a, [hl]
cp a, 8
jr nc, .moreThanEight_2
.moreThanEight_2
sub a, 8
srl a
srl a
srl a
add a, e
ld e, a
ld a, d
add a, $9c
ld d, a
ret
soundData:
; activate sound system
ld a, %10000000
ld [$ff16], a
ld a, %11110001
ld [$ff17], a
ld a, 255
ld [$ff10], a
ld a, %11000111
ld [$ff19], a
ret
SECTION "OAM vars", WRAM0[$C100]
clear: DS 4*36 ; reserve 144 bytes
sprite: DS 4*1 ; reserve 4 bytes
npc: DS 4*1 ; reserve 4 bytes
Coins: DS 1 ; reserve 1 bytes
SECTION "SpriteAnim", ROMX[$5D00]
; sprite array
frames:
DB $00, $00, $00, $01, $01, $01
memory.asm:
SECTION "Utility", ROM0
memCopyVRAM:
ld a, [de]
ld [hli], a
inc de
dec bc
; check we are at zero
ld a, c
or b ; 0 1 = 1 unless 0 0 = 0
jr nz, memCopyVRAM
ret
drawMap:
ld hl, $9c00 ; destination
ld de, Map ; load map
ld bc, 32*32 ; MapWidth
.innerloop
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, c
or b
jr nz, .innerloop
ret
DMA_Copy:
ld de, $FF80 ; HRAM dest
rst $28 ; call VBLANK interrupt vector
DB $00, $0D ; DMA code is 13 bytes
DB $F5, $3E, $C1, $the Big N, $46, $FF, $3E, $28, $3D, $20, $FD, $F1, $D9
; 13 bytes
ret
OAM_Clear:
xor a, a ; clear a register
ld b, 40*4 ; load 160 bytes into b
ld hl, clear ; load 160 bytes into hl from shadow OAM
.clear_OAM
ld [hli], a ; load 0 into a and increment hl
dec b ; decrement counter until 160 bytes filled
jp nz, .clear_OAM
ret
waitVBlank:
ld a, [$FF44] ; LCDC y-coord
cp 144
jr c, waitVBlank
ret
delay:
.loop
dec bc
ld a, b
or c
jr nz, .loop
ret
Offline
Sure, some quick notes:
- I'd recommend using HW definitions instead of raw registers. It's easier to memorize and to read.
- You don't need nop after Start.
- Stack in HRAM is perfectly fine but I'd use WRAM instead. HRAM is better for crucial variables because you can use one byte shorter and faster instructions (bytes/cycles):
LD A,(C) - 2/8 LD A,(a16) - 3/16
- I can't see any palette setup?
- You can avoid turning screen on/off by fading all the colors to black or white and do VRAM copy with waiting for access. It's slower but looks proper.
- Use interrupts, for vblank especially. There's no reason not to do it. You can place call to SFF80 there.
- Read joypad once per frame and keep results in (H/W)RAM for further processing.
- Try checking macros for 16 bit stuff, multiplying, etc.
I have couple of assembly examples here. You might find something interesting.
Keep up!
Last edited by tmk (2021-10-07 15:24:40)
Offline