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 guys, I'm not sure if this is the right place to put it.
I am learning to develop homebrew games for gameboy, it looks very interesting.
I have some experience programming in Assembly for the MSX computer, I even made a game called Pacific (https://andrebaptista.com.br/?page=PacificMsx).
I already made a simple Arkanoid clone for Gameboy as POC and learning exercise (is there any way to put a .gb ROM file here?)
Cheers
Offline
Just put it on github:
https://github.com/albs-br/paranoid
Pretty simple game, I will be adding things as I learn.
Offline
nice start! you rom has at least two issues:
1. rom size does not match the header, and
2. you are trying to access VRAM when it is not readable.
both exceptions are shown in BGB emulator - i suggest to use it for debugging.
Offline
Thank you for the observations.
1 - I've already seen this one.There is no ROM_SIZE_16KBYTE constant in gbhw,inc. The smallest is ROM_SIZE_32KBYTE. Also, I have no idea on how to set the ROM to 32Kb (is it an assembler directive?)
2- Where can I find this info? I'm using BGB emulator.
Offline
The usual way to handle the ROM size is using rgbfix. Add the flag "-p 0xff" to rgbfix to pad the ROM up to the closest valid size and update the ROM size field in the header.
Offline
Thanks @nitro2k01, the ROM size issue is solved.
But about the invalid access to VRAM, I haven't seen it. I do have the "break on access inaccessible VRAM" checked on BGB options, but it's never reached...
Offline
Hi guys, this is my main game loop:
GameLoop: call ReadInput call GameLogic .waitVBlank: ld a, [rLY] cp 145 jr nz, .waitVBlank call UpdateVram call UpdateOAMRam jp GameLoop
My question: Is this the right/recommended way of doing this? Good practice? Is this wasting CPU cycles/battery energy? Should I use HALT and interruption for updating VRAM at VBlank? Can someone provide an example code?
Last edited by albs_br (2020-12-08 16:09:54)
Offline
That isn't good practice. Normally you want to have a IRQ handler at address 0x0040 (VBL handler), you want to set bit 0 of IE to 1 to enable the VBL interrupt, and you want to call update oam from there. You can call UpdateVram from the game loop, though.
If you really want to talk to people in real time, there is a link to the gbdev discord here: https://gbdev.io/ You may find more help there, as most tutorials are quite lacking...
Offline
Thanks for helping.
I managed to implement the sugestions, following an example found on a pdf online:
GameLoop: halt ; stop system clock ; return from halt when ; interrupted nop ; (See WARNING on http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf, page 20) ld a, [VblankFlag] or a ; V-Blank interrupt ? jr z, GameLoop ; No, some other interrupt xor a ld [VblankFlag], a ; Clear V-Blank flag call ReadInput call GameLogic call UpdateVram jp GameLoop
And:
VblankInt: push af push bc push de push hl ; call SpriteDma ; Do sprite updates call UpdateOAMRam ld a, 1 ld [VblankFlag], a pop hl pop de pop bc pop af reti
Far better now.
Offline
That's the only game loop example I found so far. What's the best documentation?
Offline
Now I'm stuck in a difference in behavior between BGB emulator and Emulicious. The issue is enabling/disabling the Window mid frame, using LYC=LY int.
LCDCInt: push af push bc push de push hl ; Read STAT (LCD Status Register) ; Bit 6 - LYC=LY Coincidence Interrupt (1=Enable) (Read/Write) ; Bit 5 - Mode 2 OAM Interrupt (1=Enable) (Read/Write) ; Bit 4 - Mode 1 V-Blank Interrupt (1=Enable) (Read/Write) ; Bit 3 - Mode 0 H-Blank Interrupt (1=Enable) (Read/Write) ; Bit 2 - Coincidence Flag (0:LYC<>LY, 1:LYC=LY) (Read Only) ; Bit 1-0 - Mode Flag (Mode 0-3, see below) (Read Only) ld a, [rSTAT] and STATF_LYCF jp nz, .LYCequalLY jp .reti .LYCequalLY: ld a, [rLY] cp 80 ; jp z, .ly80 ; cp 40 ; jp z, .ly40 ; .ly40: ; Enable window ld a, [rLCDC] ; load current value set 5, a ; enable window ld [rLCDC], a ; save ld a, 80 ; line to trigger the next interrupt ld [rLYC], a jp .reti .ly80: ; Disable window ld a, [rLCDC] ; load current value res 5, a ; enable window ld [rLCDC], a ; save ld a, 40 ; line to trigger the next interrupt ld [rLYC], a .reti: pop hl pop de pop bc pop af reti
Both source and compiled ROM can be found here: https://github.com/albs-br/paranoid
Thanks
Last edited by albs_br (2020-12-12 23:08:24)
Offline
I forgot to explain, It's working on Emulicious and not working on BGB.
On Emulicious you can see the window midscreen (between lines 40 and 80) and nothing is displayed on BGB.
With the Window at a fixed position it works like a charm on both... So it has to be something with the way BGB handle the LCDC int (I suppose it's the right behavior, as BGB is considered the most accurate emulator).
Offline
The background is not a "layer" like the BG layers on SNES etc, but more a hardware hack to change the data source for the background at a given pixel and to the end of the scanline. The way the window works (roughly speaking) is that if the window is activated in LCDC while the pixel corresponding to WX,WY is being drawn, the window starts drawing. When the window has started drawing for the first time, it starts drawing on every new scanline at WX. Since in your case, WY=0 and the window is only activated in LCDC between scanlines $40 and $60, the starting condition is never true and the window never starts drawing.
What do you want to achieve? Show the window between scanlines $40 and $60? Do this:
* Set WY permanently to $40.
* Enable the window in VBlank. It will now start drawing at scanline $40 because of the WY setting.
* Use the LCD interrupt to disable the window at scanline $60.
Last edited by nitro2k01 (2020-12-13 13:06:00)
Offline
nitro2k01 wrote:
Since in your case, WY=0 and the window is only activated in LCDC between scanlines $40 and $60, the starting condition is never true and the window never starts drawing.
In other words the emulicious has a wrong implementation of the bg/window drawing.
Offline
albs_br wrote:
nitro2k01 wrote:
Since in your case, WY=0 and the window is only activated in LCDC between scanlines $40 and $60, the starting condition is never true and the window never starts drawing.
In other words the emulicious has a wrong implementation of the bg/window drawing.
Indeed. I tried it on hardware as well to be sure, and the window section is not drawn.
Offline
New question: how is the best way to pass parameters to a subroutine? On Z80 I am used to the IX + n instructions, but on the GB CPU they are missing.
Ex:
MoveEnemy_1: ld a, [enemy_1_x] inc a ld [enemy_1_x], a ; same for other properties of enemy ret
One possible approach whould be to pass a pointer for the first property address on HL and "navigate" up and down to the others
ld hl, enemy_1_x ; addr of first property of enemy 1, ie. addr of enemy 1 call MoveEnemy ld hl, enemy_2_x call MoveEnemy MoveEnemy: ld a, [hl] ; load x of enemy inc a ld [hl], a inc hl ld a, [hl] ; load y of enemy ; do something with the second property ld [hl], a ret
This approach has some seriuos disadvantages, besides being "ugly": if I have to add properties to the enemy, they should be at the end, or a great refactory on the sub will be needed.
Offline
Essentially, you're right. The Gameboy CPU is not Z80 but a Sharp architecture which by all evidence is called SM83. There are no index registers, no shadow registers and what you show in your example is what you have to do.
There are a couple of potential small improvements to your example. Loading to/from HL, you can get a free increment/decrement of HL. I believe the same exists on Z80.
ld [hl+], a ; Store a to [hl] and post-increment hl.
Also, if you're applying the same operation on all actors (such as applying movement) you may just want to ditch the call and put it in a loop to save on a call/ret for every iteration. Then you would have to make sure that HL points to the first member of the actor's struct at the end of the loop.
This is basic stuff but can be worth pointing out.
In asm you obviously choose whatever calling convention you want. In C, arguments are pushed to the stack and you would use the ld HL,SP+xx opcode to get a pointer to an argument. The return value is stored in DE. Also note that if you ever need to interface C code, the BC register needs to be preserved through the call, whereas other registers can destroyed.
Offline
Only today I remembered to put a video of the "final" version on Youtube:
https://www.youtube.com/watch?v=5ks1Bf9HxOY
Nothing special, just some tests to check the GB capabilities, maybe someday I make a complete game.
Last edited by albs_br (2021-04-21 19:55:37)
Offline