diff --git a/galactic-armada/Makefile b/galactic-armada/Makefile index e93df79b..d0d8fd04 100644 --- a/galactic-armada/Makefile +++ b/galactic-armada/Makefile @@ -1,120 +1,120 @@ -# You can set the name of the .gb ROM file here -PROJECTNAME = GalacticArmada -SRCDIR = src -LIBDIR = libs -OBJDIR = obj -DSTDIR = dist -RESDIR = $(SRCDIR)/resources -ASMDIR = $(SRCDIR)/main -RESSPRITES = $(RESDIR)/sprites -RESBACKGROUNDS = $(RESDIR)/backgrounds -GENDIR = $(SRCDIR)/generated -GENSPRITES = $(GENDIR)/sprites -GENBACKGROUNDS = $(GENDIR)/backgrounds -BINS = $(DSTDIR)/$(PROJECTNAME).gb - -# Tools -RGBDS ?= -ASM := $(RGBDS)rgbasm -GFX := $(RGBDS)rgbgfx -LINK := $(RGBDS)rgblink -FIX := $(RGBDS)rgbfix - -# Tool flags -ASMFLAGS := -L -FIXFLAGS := -v -p 0xFF - -# https://stackoverflow.com/a/18258352 -# Make does not offer a recursive wild card function, so here's one: -rwildcard = $(foreach d,\ - $(wildcard $(1:=/*)), \ - $(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d) \ - ) - -# https://stackoverflow.com/a/16151140 -# This makes it so every entry in a space-delimited list appears only once -unique = $(if $1,\ - $(firstword $1) $(call unique,$(filter-out $(firstword $1),$1)) \ - ) - -# Collect ASM sources from ASMDIR and LIBDIR. -ASMSOURCES_COLLECTED = \ - $(call rwildcard,$(ASMDIR),*.asm) $(call rwildcard,$(LIBDIR),*.asm) - -OBJS = $(patsubst %.asm,$(OBJDIR)/%.o,$(notdir $(ASMSOURCES_COLLECTED))) - -all: $(BINS) - -# ANCHOR: generate-graphics -NEEDED_GRAPHICS = \ - $(GENSPRITES)/player-ship.2bpp \ - $(GENSPRITES)/enemy-ship.2bpp \ - $(GENSPRITES)/bullet.2bpp \ - $(GENBACKGROUNDS)/text-font.2bpp \ - $(GENBACKGROUNDS)/star-field.tilemap \ - $(GENBACKGROUNDS)/title-screen.tilemap - -# Generate sprites, ensuring the containing directories have been created. -$(GENSPRITES)/%.2bpp: $(RESSPRITES)/%.png | $(GENSPRITES) - $(GFX) -c "#FFFFFF,#cfcfcf,#686868,#000000;" --columns -o $@ $< - -# Generate background tile set, ensuring the containing directories have been created. -$(GENBACKGROUNDS)/%.2bpp: $(RESBACKGROUNDS)/%.png | $(GENBACKGROUNDS) - $(GFX) -c "#FFFFFF,#cbcbcb,#414141,#000000;" -o $@ $< - -# Generate background tile map *and* tile set, ensuring the containing directories -# have been created. -$(GENBACKGROUNDS)/%.tilemap: $(RESBACKGROUNDS)/%.png | $(GENBACKGROUNDS) - $(GFX) -c "#FFFFFF,#cbcbcb,#414141,#000000;" \ - --tilemap $@ \ - --unique-tiles \ - -o $(GENBACKGROUNDS)/$*.2bpp \ - $< -# ANCHOR_END: generate-graphics - -compile.bat: Makefile - @echo "REM Automatically generated from Makefile" > compile.bat - @make -sn | sed y/\\/\\\\/\\\\\\\^/ | grep -v make >> compile.bat - - -# ANCHOR: generate-objects -# Extract directories from collected ASM sources and append "%.asm" to each one, -# creating a wildcard-rule. -ASMSOURCES_DIRS = $(patsubst %,%%.asm,\ - $(call unique,$(dir $(ASMSOURCES_COLLECTED))) \ - ) - -# This is a Makefile "macro". -# It defines a %.o target from a corresponding %.asm, ensuring the -# "prepare" step has ran and the graphics are already generated. -define object-from-asm -$(OBJDIR)/%.o: $1 | $(OBJDIR) $(NEEDED_GRAPHICS) - $$(ASM) $$(ASMFLAGS) -o $$@ $$< -endef - -# Run the macro for each directory listed in ASMSOURCES_DIRS, thereby -# creating the appropriate targets. -$(foreach i, $(ASMSOURCES_DIRS), $(eval $(call object-from-asm,$i))) -# ANCHOR_END: generate-objects - -# Link and build the final ROM. -$(BINS): $(OBJS) | $(DSTDIR) - $(LINK) -n dist/GalacticArmada.sym -m dist/GalacticArmada.map -o $@ $^ - $(FIX) $(FIXFLAGS) $@ -# Ensure directories for generated files exist. -define ensure-directory -$1: - mkdir -p $$@ -endef - -PREPARE_DIRECTORIES = \ - $(OBJDIR) $(GENSPRITES) $(GENBACKGROUNDS) $(DSTDIR) - -$(foreach i, $(PREPARE_DIRECTORIES), $(eval $(call ensure-directory,$i))) - -# Clean up generated directories. -clean: - rm -rfv $(PREPARE_DIRECTORIES) -# Declare these targets as "not actually files". -.PHONY: clean all - +# You can set the name of the .gb ROM file here +PROJECTNAME = GalacticArmada +SRCDIR = src +LIBDIR = libs +OBJDIR = obj +DSTDIR = dist +RESDIR = $(SRCDIR)/resources +ASMDIR = $(SRCDIR)/main +RESSPRITES = $(RESDIR)/sprites +RESBACKGROUNDS = $(RESDIR)/backgrounds +GENDIR = $(SRCDIR)/generated +GENSPRITES = $(GENDIR)/sprites +GENBACKGROUNDS = $(GENDIR)/backgrounds +BINS = $(DSTDIR)/$(PROJECTNAME).gb + +# Tools +RGBDS ?= +ASM := $(RGBDS)rgbasm +GFX := $(RGBDS)rgbgfx +LINK := $(RGBDS)rgblink +FIX := $(RGBDS)rgbfix + +# Tool flags +ASMFLAGS := -L +FIXFLAGS := -v -p 0xFF + +# https://stackoverflow.com/a/18258352 +# Make does not offer a recursive wild card function, so here's one: +rwildcard = $(foreach d,\ + $(wildcard $(1:=/*)), \ + $(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d) \ + ) + +# https://stackoverflow.com/a/16151140 +# This makes it so every entry in a space-delimited list appears only once +unique = $(if $1,\ + $(firstword $1) $(call unique,$(filter-out $(firstword $1),$1)) \ + ) + +# Collect ASM sources from ASMDIR and LIBDIR. +ASMSOURCES_COLLECTED = \ + $(call rwildcard,$(ASMDIR),*.asm) $(call rwildcard,$(LIBDIR),*.asm) + +OBJS = $(patsubst %.asm,$(OBJDIR)/%.o,$(notdir $(ASMSOURCES_COLLECTED))) + +all: $(BINS) + +# ANCHOR: generate-graphics +NEEDED_GRAPHICS = \ + $(GENSPRITES)/player-ship.2bpp \ + $(GENSPRITES)/enemy-ship.2bpp \ + $(GENSPRITES)/bullet.2bpp \ + $(GENBACKGROUNDS)/text-font.2bpp \ + $(GENBACKGROUNDS)/star-field.tilemap \ + $(GENBACKGROUNDS)/title-screen.tilemap + +# Generate sprites, ensuring the containing directories have been created. +$(GENSPRITES)/%.2bpp: $(RESSPRITES)/%.png | $(GENSPRITES) + $(GFX) -c "#FFFFFF,#cfcfcf,#686868,#000000;" --columns -o $@ $< + +# Generate background tile set, ensuring the containing directories have been created. +$(GENBACKGROUNDS)/%.2bpp: $(RESBACKGROUNDS)/%.png | $(GENBACKGROUNDS) + $(GFX) -c "#FFFFFF,#cbcbcb,#414141,#000000;" -o $@ $< + +# Generate background tile map *and* tile set, ensuring the containing directories +# have been created. +$(GENBACKGROUNDS)/%.tilemap: $(RESBACKGROUNDS)/%.png | $(GENBACKGROUNDS) + $(GFX) -c "#FFFFFF,#cbcbcb,#414141,#000000;" \ + --tilemap $@ \ + --unique-tiles \ + -o $(GENBACKGROUNDS)/$*.2bpp \ + $< +# ANCHOR_END: generate-graphics + +compile.bat: Makefile + @echo "REM Automatically generated from Makefile" > compile.bat + @make -sn | sed y/\\/\\\\/\\\\\\\^/ | grep -v make >> compile.bat + + +# ANCHOR: generate-objects +# Extract directories from collected ASM sources and append "%.asm" to each one, +# creating a wildcard-rule. +ASMSOURCES_DIRS = $(patsubst %,%%.asm,\ + $(call unique,$(dir $(ASMSOURCES_COLLECTED))) \ + ) + +# This is a Makefile "macro". +# It defines a %.o target from a corresponding %.asm, ensuring the +# "prepare" step has ran and the graphics are already generated. +define object-from-asm +$(OBJDIR)/%.o: $1 | $(OBJDIR) $(NEEDED_GRAPHICS) + $$(ASM) $$(ASMFLAGS) -o $$@ $$< +endef + +# Run the macro for each directory listed in ASMSOURCES_DIRS, thereby +# creating the appropriate targets. +$(foreach i, $(ASMSOURCES_DIRS), $(eval $(call object-from-asm,$i))) +# ANCHOR_END: generate-objects + +# Link and build the final ROM. +$(BINS): $(OBJS) | $(DSTDIR) + $(LINK) -n dist/GalacticArmada.sym -m dist/GalacticArmada.map -o $@ $^ + $(FIX) $(FIXFLAGS) $@ +# Ensure directories for generated files exist. +define ensure-directory +$1: + mkdir -p $$@ +endef + +PREPARE_DIRECTORIES = \ + $(OBJDIR) $(GENSPRITES) $(GENBACKGROUNDS) $(DSTDIR) + +$(foreach i, $(PREPARE_DIRECTORIES), $(eval $(call ensure-directory,$i))) + +# Clean up generated directories. +clean: + rm -rfv $(PREPARE_DIRECTORIES) +# Declare these targets as "not actually files". +.PHONY: clean all + diff --git a/galactic-armada/libs/input.asm b/galactic-armada/libs/input.asm index 453c9e3f..afe1a030 100644 --- a/galactic-armada/libs/input.asm +++ b/galactic-armada/libs/input.asm @@ -1,50 +1,50 @@ -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html -; In their words (paraphrased): reading player input for gameboy is NOT a trivial task -; So it's best to use some tested code -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -include "src/main/includes/hardware.inc" - - SECTION "Input", ROM0 - -Input:: - - ; Save th previous state - ld a, [wCurKeys] - ld [wLastKeys], a - - ; Poll half the controller - ld a, P1F_GET_BTN - call .onenibble - ld b, a ; B7-4 = 1; B3-0 = unpressed buttons - - ; Poll the other half - ld a, P1F_GET_DPAD - call .onenibble - swap a ; A3-0 = unpressed directions; A7-4 = 1 - xor a, b ; A = pressed buttons + directions - ld b, a ; B = pressed buttons + directions - - ; And release the controller - ld a, P1F_GET_NONE - ldh [rP1], a - - ; Combine with previous wCurKeys to make wNewKeys - ld a, [wCurKeys] - xor a, b ; A = keys that changed state - and a, b ; A = keys that changed to pressed - ld [wNewKeys], a - ld a, b - ld [wCurKeys], a - ret - -.onenibble - ldh [rP1], a ; switch the key matrix - call .knownret ; burn 10 cycles calling a known ret - ldh a, [rP1] ; ignore value while waiting for the key matrix to settle - ldh a, [rP1] - ldh a, [rP1] ; this read counts - or a, $F0 ; A7-4 = 1; A3-0 = unpressed keys -.knownret +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html +; In their words (paraphrased): reading player input for gameboy is NOT a trivial task +; So it's best to use some tested code +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +include "src/main/includes/hardware.inc" + + SECTION "Input", ROM0 + +Input:: + + ; Save th previous state + ld a, [wCurKeys] + ld [wLastKeys], a + + ; Poll half the controller + ld a, P1F_GET_BTN + call .onenibble + ld b, a ; B7-4 = 1; B3-0 = unpressed buttons + + ; Poll the other half + ld a, P1F_GET_DPAD + call .onenibble + swap a ; A3-0 = unpressed directions; A7-4 = 1 + xor a, b ; A = pressed buttons + directions + ld b, a ; B = pressed buttons + directions + + ; And release the controller + ld a, P1F_GET_NONE + ldh [rP1], a + + ; Combine with previous wCurKeys to make wNewKeys + ld a, [wCurKeys] + xor a, b ; A = keys that changed state + and a, b ; A = keys that changed to pressed + ld [wNewKeys], a + ld a, b + ld [wCurKeys], a + ret + +.onenibble + ldh [rP1], a ; switch the key matrix + call .knownret ; burn 10 cycles calling a known ret + ldh a, [rP1] ; ignore value while waiting for the key matrix to settle + ldh a, [rP1] + ldh a, [rP1] ; this read counts + or a, $F0 ; A7-4 = 1; A3-0 = unpressed keys +.knownret ret \ No newline at end of file diff --git a/galactic-armada/libs/sporbs_lib.asm b/galactic-armada/libs/sporbs_lib.asm index a36cebd3..2d462546 100644 --- a/galactic-armada/libs/sporbs_lib.asm +++ b/galactic-armada/libs/sporbs_lib.asm @@ -1,224 +1,224 @@ -; Sprite Objects Library - by Eievui -; -; This is a small, lightweight library meant to facilitate the rendering of -; sprite objects, including Shadow OAM and OAM DMA, single-entry "simple" sprite -; objects, and Q12.4 fixed-point position metasprite rendering. -; -; The library is only 127 bytes of ROM0, 160 bytes of WRAM0 for Shadow OAM, and a -; single HRAM byte for tracking the current position in OAM. -; -; The library is relatively simple to use, with 4 steps to rendering: -; 1. Call InitSprObjLib during initilizations - This copies the OAMDMA function to -; HRAM. -; 2. Call ResetShadowOAM at the beginning of each frame - This hides all sprites -; and resets hOAMIndex, allowing you to render a new frame of sprites. -; 3. Call rendering functions - Push simple sprites or metasprites to Shadow OAM. -; 4. Wait for VBlank and call hOAMDMA - Copies wShadowOAM to the Game Boy's OAM in -; just 160 M-cycles. Make sure to pass HIGH(wShadowOAM) in the a register. -; -; Copyright 2021, Eievui -; -; This software is provided 'as-is', without any express or implied -; warranty. In no event will the authors be held liable for any damages -; arising from the use of this software. -; -; Permission is granted to anyone to use this software for any purpose, -; including commercial applications, and to alter it and redistribute it -; freely, subject to the following restrictions: -; -; 1. The origin of this software must not be misrepresented; you must not -; claim that you wrote the original software. If you use this software -; in a product, an acknowledgment in the product documentation would be -; appreciated but is not required. -; 2. Altered source versions must be plainly marked as such, and must not be -; misrepresented as being the original software. -; 3. This notice may not be removed or altered from any source distribution. -; - -INCLUDE "src/main/includes/hardware.inc" - -SECTION "OAM DMA Code", ROM0 -OAMDMACode:: -LOAD "OAM DMA", HRAM -; Begin an OAM DMA, waiting 160 cycles for the DMA to finish. -; This quickly copies Shadow OAM to the Game Boy's OAM, allowing the PPU to draw -; the objects. hOAMDMA should be called once per frame near the end of your -; VBlank interrupt. While an OAM DMA is running no sprites objects can be drawn -; by the PPU, which makes it preferrable to run within the VBlank interrupt, but -; it can be run at any point if more than 40 sprite objects are needed. -; @param a: High byte of active Shadow OAM. Shadow OAM must be aligned to start -; at the beginning of a page (low byte == $00). -hOAMDMA:: - ldh [rDMA], a - ld a, 40 -.wait - dec a - jr nz, .wait - ret -ENDL -OAMDMACodeEnd:: - -SECTION "Initialize Sprite Object Library", ROM0 - -; A wrapper or the InitSprObjLib code -; from: https://github.com/eievui5/gb-sprobj-lib -; The library is relatively simple to get set up. First, put the following in your initialization code: -; Initilize Sprite Object Library. -InitSprObjLibWrapper:: - - call InitSprObjLib - ; Reset hardware OAM - xor a, a - ld b, 160 - ld hl, _OAMRAM - -.resetOAM - ld [hli], a - dec b - jr nz, .resetOAM - - ret - -; Initializes the sprite object library, copying things such as the hOAMDMA -; function and reseting hOAMIndex -; @clobbers: a, bc, hl -InitSprObjLib:: - ; Copy OAM DMA. - ld b, OAMDMACodeEnd - OAMDMACode - ld c, LOW(hOAMDMA) - ld hl, OAMDMACode -.memcpy - ld a, [hli] - ldh [c], a - inc c - dec b - jr nz, .memcpy - xor a, a - ldh [hOAMIndex], a ; hOAMIndex must be reset before running ResetShadowOAM. - ret - -SECTION "Reset Shadow OAM", ROM0 -; Reset the Y positions of every sprite object that was used in the last frame, -; effectily hiding them, and reset hOAMIndex. Run this function each frame -; before rendering sprite objects. -; @clobbers: a, c, hl -ResetShadowOAM:: - xor a, a ; clear carry - ldh a, [hOAMIndex] - rra - rra ; a / 4 - and a, a - jr z, .skip - ld c, a - ld hl, wShadowOAM - xor a, a -.clearOAM - ld [hli], a - inc l - inc l - inc l - dec c - jr nz, .clearOAM - ldh [hOAMIndex], a -.skip - ret - -SECTION "Render Simple Sprite", ROM0 -; Render a single object, or sprite, to OAM. -; @param b: Y position -; @param c: X position -; @param d: Tile ID -; @param e: Tile Attribute -; @clobbers: hl -RenderSimpleSprite:: - ld h, HIGH(wShadowOAM) - ldh a, [hOAMIndex] - ld l, a - ld a, b - add a, 16 - ld [hli], a - ld a, c - add a, 8 - ld [hli], a - ld a, d - ld [hli], a - ld a, e - ld [hli], a - ld a, l - ldh [hOAMIndex], a - ret -;ANCHOR: render-metasprites - -SECTION "Render Metasprite", ROM0 -; Render a metasprite to OAM. -; @param bc: Q12.4 fixed-point Y position. -; @param de: Q12.4 fixed-point X position. -; @param hl: Pointer to current metasprite. -RenderMetasprite:: - ; Adjust Y and store in b. - ld a, c - rrc b - rra - rrc b - rra - rrc b - rra - rrc b - rra - ld b, a - ; Adjust X and store in c. - ld a, e - rrc d - rra - rrc d - rra - rrc d - rra - rrc d - rra - ld c, a - ; Load Shadow OAM pointer. - ld d, HIGH(wShadowOAM) - ldh a, [hOAMIndex] - ld e, a - ; Now: - ; bc - Y, X - ; de - Shadow OAM - ; hl - Metasprite - ; Time to render! -.loop - ; Load Y. - ld a, [hli] - add a, b - ld [de], a - inc e - ; Load X. - ld a, [hli] - add a, c - ld [de], a - inc e - ; Load Tile. - ld a, [hli] - ld [de], a - inc e - ; Load Attribute. - ld a, [hli] - ld [de], a - inc e - ; Check for null end byte. - ld a, [hl] - cp a, 128 - jr nz, .loop - ld a, e - ldh [hOAMIndex], a - ret - -;ANCHOR_END: render-metasprites -SECTION "Shadow OAM", WRAM0, ALIGN[8] -wShadowOAM:: - ds 160 - -SECTION "Shadow OAM Index", HRAM -; The current low byte of shadow OAM. -hOAMIndex:: +; Sprite Objects Library - by Eievui +; +; This is a small, lightweight library meant to facilitate the rendering of +; sprite objects, including Shadow OAM and OAM DMA, single-entry "simple" sprite +; objects, and Q12.4 fixed-point position metasprite rendering. +; +; The library is only 127 bytes of ROM0, 160 bytes of WRAM0 for Shadow OAM, and a +; single HRAM byte for tracking the current position in OAM. +; +; The library is relatively simple to use, with 4 steps to rendering: +; 1. Call InitSprObjLib during initilizations - This copies the OAMDMA function to +; HRAM. +; 2. Call ResetShadowOAM at the beginning of each frame - This hides all sprites +; and resets hOAMIndex, allowing you to render a new frame of sprites. +; 3. Call rendering functions - Push simple sprites or metasprites to Shadow OAM. +; 4. Wait for VBlank and call hOAMDMA - Copies wShadowOAM to the Game Boy's OAM in +; just 160 M-cycles. Make sure to pass HIGH(wShadowOAM) in the a register. +; +; Copyright 2021, Eievui +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; + +INCLUDE "src/main/includes/hardware.inc" + +SECTION "OAM DMA Code", ROM0 +OAMDMACode:: +LOAD "OAM DMA", HRAM +; Begin an OAM DMA, waiting 160 cycles for the DMA to finish. +; This quickly copies Shadow OAM to the Game Boy's OAM, allowing the PPU to draw +; the objects. hOAMDMA should be called once per frame near the end of your +; VBlank interrupt. While an OAM DMA is running no sprites objects can be drawn +; by the PPU, which makes it preferrable to run within the VBlank interrupt, but +; it can be run at any point if more than 40 sprite objects are needed. +; @param a: High byte of active Shadow OAM. Shadow OAM must be aligned to start +; at the beginning of a page (low byte == $00). +hOAMDMA:: + ldh [rDMA], a + ld a, 40 +.wait + dec a + jr nz, .wait + ret +ENDL +OAMDMACodeEnd:: + +SECTION "Initialize Sprite Object Library", ROM0 + +; A wrapper or the InitSprObjLib code +; from: https://github.com/eievui5/gb-sprobj-lib +; The library is relatively simple to get set up. First, put the following in your initialization code: +; Initilize Sprite Object Library. +InitSprObjLibWrapper:: + + call InitSprObjLib + ; Reset hardware OAM + xor a, a + ld b, 160 + ld hl, _OAMRAM + +.resetOAM + ld [hli], a + dec b + jr nz, .resetOAM + + ret + +; Initializes the sprite object library, copying things such as the hOAMDMA +; function and reseting hOAMIndex +; @clobbers: a, bc, hl +InitSprObjLib:: + ; Copy OAM DMA. + ld b, OAMDMACodeEnd - OAMDMACode + ld c, LOW(hOAMDMA) + ld hl, OAMDMACode +.memcpy + ld a, [hli] + ldh [c], a + inc c + dec b + jr nz, .memcpy + xor a, a + ldh [hOAMIndex], a ; hOAMIndex must be reset before running ResetShadowOAM. + ret + +SECTION "Reset Shadow OAM", ROM0 +; Reset the Y positions of every sprite object that was used in the last frame, +; effectily hiding them, and reset hOAMIndex. Run this function each frame +; before rendering sprite objects. +; @clobbers: a, c, hl +ResetShadowOAM:: + xor a, a ; clear carry + ldh a, [hOAMIndex] + rra + rra ; a / 4 + and a, a + jr z, .skip + ld c, a + ld hl, wShadowOAM + xor a, a +.clearOAM + ld [hli], a + inc l + inc l + inc l + dec c + jr nz, .clearOAM + ldh [hOAMIndex], a +.skip + ret + +SECTION "Render Simple Sprite", ROM0 +; Render a single object, or sprite, to OAM. +; @param b: Y position +; @param c: X position +; @param d: Tile ID +; @param e: Tile Attribute +; @clobbers: hl +RenderSimpleSprite:: + ld h, HIGH(wShadowOAM) + ldh a, [hOAMIndex] + ld l, a + ld a, b + add a, 16 + ld [hli], a + ld a, c + add a, 8 + ld [hli], a + ld a, d + ld [hli], a + ld a, e + ld [hli], a + ld a, l + ldh [hOAMIndex], a + ret +;ANCHOR: render-metasprites + +SECTION "Render Metasprite", ROM0 +; Render a metasprite to OAM. +; @param bc: Q12.4 fixed-point Y position. +; @param de: Q12.4 fixed-point X position. +; @param hl: Pointer to current metasprite. +RenderMetasprite:: + ; Adjust Y and store in b. + ld a, c + rrc b + rra + rrc b + rra + rrc b + rra + rrc b + rra + ld b, a + ; Adjust X and store in c. + ld a, e + rrc d + rra + rrc d + rra + rrc d + rra + rrc d + rra + ld c, a + ; Load Shadow OAM pointer. + ld d, HIGH(wShadowOAM) + ldh a, [hOAMIndex] + ld e, a + ; Now: + ; bc - Y, X + ; de - Shadow OAM + ; hl - Metasprite + ; Time to render! +.loop + ; Load Y. + ld a, [hli] + add a, b + ld [de], a + inc e + ; Load X. + ld a, [hli] + add a, c + ld [de], a + inc e + ; Load Tile. + ld a, [hli] + ld [de], a + inc e + ; Load Attribute. + ld a, [hli] + ld [de], a + inc e + ; Check for null end byte. + ld a, [hl] + cp a, 128 + jr nz, .loop + ld a, e + ldh [hOAMIndex], a + ret + +;ANCHOR_END: render-metasprites +SECTION "Shadow OAM", WRAM0, ALIGN[8] +wShadowOAM:: + ds 160 + +SECTION "Shadow OAM Index", HRAM +; The current low byte of shadow OAM. +hOAMIndex:: db \ No newline at end of file diff --git a/galactic-armada/src/main/GalacticArmada.asm b/galactic-armada/src/main/GalacticArmada.asm index 2abc94ea..17ea96d5 100644 --- a/galactic-armada/src/main/GalacticArmada.asm +++ b/galactic-armada/src/main/GalacticArmada.asm @@ -1,72 +1,72 @@ -; ANCHOR: entry-point -INCLUDE "src/main/includes/hardware.inc" -SECTION "GameVariables", WRAM0 - -; ANCHOR: joypad-input-variables -wCurKeys:: db -wNewKeys:: db -wLastKeys:: db -; ANCHOR_END: joypad-input-variables - -SECTION "Header", ROM0[$100] - - jp EntryPoint - - ds $150 - @, 0 ; Make room for the header - -EntryPoint: -; ANCHOR_END: entry-point - - ; Wait for the vertical blank phase before initiating the library - call WaitForVBlankStart - - ; Turn the LCD off - ld a, 0 - ld [rLCDC], a - - ; from: https://github.com/eievui5/gb-sprobj-lib - ; The library is relatively simple to get set up. First, put the following in your initialization code: - ; Initilize Sprite Object Library. - call InitSprObjLibWrapper - - ; Turn the LCD on - ld a, LCDCF_ON | LCDCF_BGON|LCDCF_OBJON | LCDCF_OBJ16 | LCDCF_WINON | LCDCF_WIN9C00 - ld [rLCDC], a - - ; During the first (blank) frame, initialize display registers - ld a, %11100100 - ld [rBGP], a - ld a, %11100100 - ld [rOBP0], a - - call InitializeGameStateManagment - -; ANCHOR_END: entry-point-end - -; ANCHOR: update-galactic-armada -GalacticArmadaGameLoop: - - ; This is in input.asm - ; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html - ; In their words (paraphrased): reading player input for gameboy is NOT a trivial task - ; So it's best to use some tested code - call Input - - ; from: https://github.com/eievui5/gb-sprobj-lib - ; hen put a call to ResetShadowOAM at the beginning of your main loop. - call ResetShadowOAM - -; ANCHOR: update-game-state-management - call InitiateNewCurrentGameState - call UpdateCurrentGameState -; ANCHOR_END: update-game-state-management - - call WaitForVBlankStart - - ; from: https://github.com/eievui5/gb-sprobj-lib - ; Finally, run the following code during VBlank: - ld a, HIGH(wShadowOAM) - call hOAMDMA - - jp GalacticArmadaGameLoop -; ANCHOR_END: update-galactic-armada +; ANCHOR: entry-point +INCLUDE "src/main/includes/hardware.inc" +SECTION "GameVariables", WRAM0 + +; ANCHOR: joypad-input-variables +wCurKeys:: db +wNewKeys:: db +wLastKeys:: db +; ANCHOR_END: joypad-input-variables + +SECTION "Header", ROM0[$100] + + jp EntryPoint + + ds $150 - @, 0 ; Make room for the header + +EntryPoint: +; ANCHOR_END: entry-point + + ; Wait for the vertical blank phase before initiating the library + call WaitForVBlankStart + + ; Turn the LCD off + ld a, 0 + ld [rLCDC], a + + ; from: https://github.com/eievui5/gb-sprobj-lib + ; The library is relatively simple to get set up. First, put the following in your initialization code: + ; Initilize Sprite Object Library. + call InitSprObjLibWrapper + + ; Turn the LCD on + ld a, LCDCF_ON | LCDCF_BGON|LCDCF_OBJON | LCDCF_OBJ16 | LCDCF_WINON | LCDCF_WIN9C00 + ld [rLCDC], a + + ; During the first (blank) frame, initialize display registers + ld a, %11100100 + ld [rBGP], a + ld a, %11100100 + ld [rOBP0], a + + call InitializeGameStateManagment + +; ANCHOR_END: entry-point-end + +; ANCHOR: update-galactic-armada +GalacticArmadaGameLoop: + + ; This is in input.asm + ; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html + ; In their words (paraphrased): reading player input for gameboy is NOT a trivial task + ; So it's best to use some tested code + call Input + + ; from: https://github.com/eievui5/gb-sprobj-lib + ; hen put a call to ResetShadowOAM at the beginning of your main loop. + call ResetShadowOAM + +; ANCHOR: update-game-state-management + call InitiateNewCurrentGameState + call UpdateCurrentGameState +; ANCHOR_END: update-game-state-management + + call WaitForVBlankStart + + ; from: https://github.com/eievui5/gb-sprobj-lib + ; Finally, run the following code during VBlank: + ld a, HIGH(wShadowOAM) + call hOAMDMA + + jp GalacticArmadaGameLoop +; ANCHOR_END: update-galactic-armada diff --git a/galactic-armada/src/main/assets/backgrounds.asm b/galactic-armada/src/main/assets/backgrounds.asm index abd19ed6..c4199399 100644 --- a/galactic-armada/src/main/assets/backgrounds.asm +++ b/galactic-armada/src/main/assets/backgrounds.asm @@ -1,69 +1,69 @@ - -; ANCHOR: draw-title-screen -SECTION "BackgroundsAssets", ROM0 - -titleScreenTileData: INCBIN "src/generated/backgrounds/title-screen.2bpp" -titleScreenTileDataEnd: - -titleScreenTileMap: INCBIN "src/generated/backgrounds/title-screen.tilemap" -titleScreenTileMapEnd: - -DrawTitleScreen:: - - ; Copy the tile data - ld de, titleScreenTileData ; de contains the address where data will be copied from; - ld hl, $9340 ; hl contains the address where data will be copied to; - ld bc, titleScreenTileDataEnd - titleScreenTileData ; bc contains how many bytes we have to copy. - call CopyDEintoMemoryAtHL; - - ; Copy the tilemap - ld de, titleScreenTileMap - ld hl, $9800 - ld bc, titleScreenTileMapEnd - titleScreenTileMap - - ; Our text font has 52 tiles, and comes before the title screen tiles in VRAM - ; So each value in our tilemap must be offset by 52 - call CopyDEintoMemoryAtHL_With52Offset - - ret -; ANCHOR_END: draw-title-screen - - -textFontTileData: INCBIN "src/generated/backgrounds/text-font.2bpp" -textFontTileDataEnd: - -LoadTextFontIntoVRAM:: - ; Copy the tile data - ld de, textFontTileData ; de contains the address where data will be copied from; - ld hl, $9000 ; hl contains the address where data will be copied to; - ld bc, textFontTileDataEnd - textFontTileData ; bc contains how many bytes we have to copy. - call CopyDEintoMemoryAtHL - ret - - - -starFieldMap: INCBIN "src/generated/backgrounds/star-field.tilemap" -starFieldMapEnd: - -starFieldTileData: INCBIN "src/generated/backgrounds/star-field.2bpp" -starFieldTileDataEnd: - - -DrawStarFieldBackground:: - - ; Copy the tile data - ld de, starFieldTileData ; de contains the address where data will be copied from; - ld hl, $9340 ; hl contains the address where data will be copied to; - ld bc, starFieldTileDataEnd - starFieldTileData ; bc contains how many bytes we have to copy. - call CopyDEintoMemoryAtHL - - ; Copy the tilemap - ld de, starFieldMap - ld hl, $9800 - ld bc, starFieldMapEnd - starFieldMap - - ; Our text font has 52 tiles, and comes before the title screen tiles in VRAM - ; So each value in our tilemap must be offset by 52 - call CopyDEintoMemoryAtHL_With52Offset - + +; ANCHOR: draw-title-screen +SECTION "BackgroundsAssets", ROM0 + +titleScreenTileData: INCBIN "src/generated/backgrounds/title-screen.2bpp" +titleScreenTileDataEnd: + +titleScreenTileMap: INCBIN "src/generated/backgrounds/title-screen.tilemap" +titleScreenTileMapEnd: + +DrawTitleScreen:: + + ; Copy the tile data + ld de, titleScreenTileData ; de contains the address where data will be copied from; + ld hl, $9340 ; hl contains the address where data will be copied to; + ld bc, titleScreenTileDataEnd - titleScreenTileData ; bc contains how many bytes we have to copy. + call CopyDEintoMemoryAtHL; + + ; Copy the tilemap + ld de, titleScreenTileMap + ld hl, $9800 + ld bc, titleScreenTileMapEnd - titleScreenTileMap + + ; Our text font has 52 tiles, and comes before the title screen tiles in VRAM + ; So each value in our tilemap must be offset by 52 + call CopyDEintoMemoryAtHL_With52Offset + + ret +; ANCHOR_END: draw-title-screen + + +textFontTileData: INCBIN "src/generated/backgrounds/text-font.2bpp" +textFontTileDataEnd: + +LoadTextFontIntoVRAM:: + ; Copy the tile data + ld de, textFontTileData ; de contains the address where data will be copied from; + ld hl, $9000 ; hl contains the address where data will be copied to; + ld bc, textFontTileDataEnd - textFontTileData ; bc contains how many bytes we have to copy. + call CopyDEintoMemoryAtHL + ret + + + +starFieldMap: INCBIN "src/generated/backgrounds/star-field.tilemap" +starFieldMapEnd: + +starFieldTileData: INCBIN "src/generated/backgrounds/star-field.2bpp" +starFieldTileDataEnd: + + +DrawStarFieldBackground:: + + ; Copy the tile data + ld de, starFieldTileData ; de contains the address where data will be copied from; + ld hl, $9340 ; hl contains the address where data will be copied to; + ld bc, starFieldTileDataEnd - starFieldTileData ; bc contains how many bytes we have to copy. + call CopyDEintoMemoryAtHL + + ; Copy the tilemap + ld de, starFieldMap + ld hl, $9800 + ld bc, starFieldMapEnd - starFieldMap + + ; Our text font has 52 tiles, and comes before the title screen tiles in VRAM + ; So each value in our tilemap must be offset by 52 + call CopyDEintoMemoryAtHL_With52Offset + ret \ No newline at end of file diff --git a/galactic-armada/src/main/assets/metasprites.asm b/galactic-armada/src/main/assets/metasprites.asm index 5b87cd04..275c8a03 100644 --- a/galactic-armada/src/main/assets/metasprites.asm +++ b/galactic-armada/src/main/assets/metasprites.asm @@ -1,19 +1,19 @@ - -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" - -SECTION "MetaspritesAssets", ROM0 - -playerTestMetaSprite:: - .metasprite1 db 0,0,0,0 - .metasprite2 db 0,8,2,0 - .metaspriteEnd db 128 - -bulletMetasprite:: - .metasprite1 db 0,0,8,0 - .metaspriteEnd db 128 - -enemyShipMetasprite:: - .metasprite1 db 0,0,4,0 - .metasprite2 db 0,8,6,0 + +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" + +SECTION "MetaspritesAssets", ROM0 + +playerTestMetaSprite:: + .metasprite1 db 0,0,0,0 + .metasprite2 db 0,8,2,0 + .metaspriteEnd db 128 + +bulletMetasprite:: + .metasprite1 db 0,0,8,0 + .metaspriteEnd db 128 + +enemyShipMetasprite:: + .metasprite1 db 0,0,4,0 + .metasprite2 db 0,8,6,0 .metaspriteEnd db 128 \ No newline at end of file diff --git a/galactic-armada/src/main/assets/sprites.asm b/galactic-armada/src/main/assets/sprites.asm index ba7583db..b624e45e 100644 --- a/galactic-armada/src/main/assets/sprites.asm +++ b/galactic-armada/src/main/assets/sprites.asm @@ -1,46 +1,46 @@ -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" - -SECTION "SpritesAssets", ROM0 - -playerShipTileData: INCBIN "src/generated/sprites/player-ship.2bpp" -playerShipTileDataEnd: - -CopyPlayerTileDataIntoVRAM:: - - ; Copy the player's tile data into VRAM - ld de, playerShipTileData - ld hl, PLAYER_TILES_START - ld bc, playerShipTileDataEnd - playerShipTileData - call CopyDEintoMemoryAtHL - - ret; - - -bulletTileData:: INCBIN "src/generated/sprites/bullet.2bpp" -bulletTileDataEnd:: - - -CopyBulletTileDataIntoVRAM:: - - - ; Copy the bullet tile data intto vram - ld de, bulletTileData - ld hl, BULLET_TILES_START - ld bc, bulletTileDataEnd - bulletTileData - call CopyDEintoMemoryAtHL - ret - -enemyShipTileData:: INCBIN "src/generated/sprites/enemy-ship.2bpp" -enemyShipTileDataEnd:: - - -CopyEnemyTileDataIntoVRAM:: - - - ld de, enemyShipTileData - ld hl, ENEMY_TILES_START - ld bc, enemyShipTileDataEnd - enemyShipTileData - call CopyDEintoMemoryAtHL - +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" + +SECTION "SpritesAssets", ROM0 + +playerShipTileData: INCBIN "src/generated/sprites/player-ship.2bpp" +playerShipTileDataEnd: + +CopyPlayerTileDataIntoVRAM:: + + ; Copy the player's tile data into VRAM + ld de, playerShipTileData + ld hl, PLAYER_TILES_START + ld bc, playerShipTileDataEnd - playerShipTileData + call CopyDEintoMemoryAtHL + + ret; + + +bulletTileData:: INCBIN "src/generated/sprites/bullet.2bpp" +bulletTileDataEnd:: + + +CopyBulletTileDataIntoVRAM:: + + + ; Copy the bullet tile data intto vram + ld de, bulletTileData + ld hl, BULLET_TILES_START + ld bc, bulletTileDataEnd - bulletTileData + call CopyDEintoMemoryAtHL + ret + +enemyShipTileData:: INCBIN "src/generated/sprites/enemy-ship.2bpp" +enemyShipTileDataEnd:: + + +CopyEnemyTileDataIntoVRAM:: + + + ld de, enemyShipTileData + ld hl, ENEMY_TILES_START + ld bc, enemyShipTileDataEnd - enemyShipTileData + call CopyDEintoMemoryAtHL + ret \ No newline at end of file diff --git a/galactic-armada/src/main/game-state-management.asm b/galactic-armada/src/main/game-state-management.asm index 87a05fad..0a9a3e45 100644 --- a/galactic-armada/src/main/game-state-management.asm +++ b/galactic-armada/src/main/game-state-management.asm @@ -1,89 +1,89 @@ - -; ANCHOR: game-state-variables -INCLUDE "src/main/includes/hardware.inc" - -SECTION "GameStateManagementVariables", WRAM0 - -wCurrentGameState_Update:: dw -wNextGameState_Initiate:: dw -wNextGameState_Update:: dw -; ANCHOR_END: game-state-variables - -SECTION "GameStateManagement", ROM0 - -; ANCHOR: initialize-default-game-state-function -InitializeGameStateManagment:: - - ; ANCHOR: initialize-game-state-variables - ; Default our game state variables - ld a, 0 - ld [wCurrentGameState_Update+0], a - ld [wCurrentGameState_Update+1], a - ld [wNextGameState_Initiate+0], a - ld [wNextGameState_Initiate+1], a - ld [wNextGameState_Update+0], a - ld [wNextGameState_Update+1], a - ; ANCHOR_END: initialize-game-state-variables - - call InitTitleScreenState - - ld hl, UpdateTitleScreenState - ld a, l - ld [wCurrentGameState_Update+0], a - ld a, h - ld [wCurrentGameState_Update+1], a - - ret -; ANCHOR_END: initialize-default-game-state-function - - -; ANCHOR: update-current-game-state-function -UpdateCurrentGameState:: - - ; Get the address of the current game state - ld a, [wCurrentGameState_Update+0] - ld l, a - ld a, [wCurrentGameState_Update+1] - or a, l - - ; Stop if we have a 0 value - ret z - - ; call the function in HL - ld a, [wCurrentGameState_Update+1] - ld h, a - call callHL - - ret -; ANCHOR_END: update-current-game-state-function - -; ANCHOR: initiate-new-game-state-function -InitiateNewCurrentGameState:: - - ; If this is 0, we are not changing game states - ld a, [wNextGameState_Initiate+0] - ld l, a - ld a, [wNextGameState_Initiate+1] - or a, l - ret z - - ld a, [wNextGameState_Initiate+1] - ld h, a - call callHL - - ld a, [wNextGameState_Update+0] - ld [wCurrentGameState_Update+0], a - ld a, [wNextGameState_Update+1] - ld [wCurrentGameState_Update+1], a - - ; Reset these to zero - ld a, 0 - ld [wNextGameState_Initiate+0],a - ld [wNextGameState_Initiate+1], a - ld [wNextGameState_Update+0], a - ld [wNextGameState_Update+1], a - - - ret - + +; ANCHOR: game-state-variables +INCLUDE "src/main/includes/hardware.inc" + +SECTION "GameStateManagementVariables", WRAM0 + +wCurrentGameState_Update:: dw +wNextGameState_Initiate:: dw +wNextGameState_Update:: dw +; ANCHOR_END: game-state-variables + +SECTION "GameStateManagement", ROM0 + +; ANCHOR: initialize-default-game-state-function +InitializeGameStateManagment:: + + ; ANCHOR: initialize-game-state-variables + ; Default our game state variables + ld a, 0 + ld [wCurrentGameState_Update+0], a + ld [wCurrentGameState_Update+1], a + ld [wNextGameState_Initiate+0], a + ld [wNextGameState_Initiate+1], a + ld [wNextGameState_Update+0], a + ld [wNextGameState_Update+1], a + ; ANCHOR_END: initialize-game-state-variables + + call InitTitleScreenState + + ld hl, UpdateTitleScreenState + ld a, l + ld [wCurrentGameState_Update+0], a + ld a, h + ld [wCurrentGameState_Update+1], a + + ret +; ANCHOR_END: initialize-default-game-state-function + + +; ANCHOR: update-current-game-state-function +UpdateCurrentGameState:: + + ; Get the address of the current game state + ld a, [wCurrentGameState_Update+0] + ld l, a + ld a, [wCurrentGameState_Update+1] + or a, l + + ; Stop if we have a 0 value + ret z + + ; call the function in HL + ld a, [wCurrentGameState_Update+1] + ld h, a + call callHL + + ret +; ANCHOR_END: update-current-game-state-function + +; ANCHOR: initiate-new-game-state-function +InitiateNewCurrentGameState:: + + ; If this is 0, we are not changing game states + ld a, [wNextGameState_Initiate+0] + ld l, a + ld a, [wNextGameState_Initiate+1] + or a, l + ret z + + ld a, [wNextGameState_Initiate+1] + ld h, a + call callHL + + ld a, [wNextGameState_Update+0] + ld [wCurrentGameState_Update+0], a + ld a, [wNextGameState_Update+1] + ld [wCurrentGameState_Update+1], a + + ; Reset these to zero + ld a, 0 + ld [wNextGameState_Initiate+0],a + ld [wNextGameState_Initiate+1], a + ld [wNextGameState_Update+0], a + ld [wNextGameState_Update+1], a + + + ret + ; ANCHOR_END: initiate-new-game-state-function \ No newline at end of file diff --git a/galactic-armada/src/main/includes/character-mapping.inc b/galactic-armada/src/main/includes/character-mapping.inc index c10589d7..2719dd40 100644 --- a/galactic-armada/src/main/includes/character-mapping.inc +++ b/galactic-armada/src/main/includes/character-mapping.inc @@ -1,30 +1,30 @@ -; The character map for the text-font -CHARMAP " ", 0 -CHARMAP ".", 24 -CHARMAP "-", 25 -CHARMAP "a", 26 -CHARMAP "b", 27 -CHARMAP "c", 28 -CHARMAP "d", 29 -CHARMAP "e", 30 -CHARMAP "f", 31 -CHARMAP "g", 32 -CHARMAP "h", 33 -CHARMAP "i", 34 -CHARMAP "j", 35 -CHARMAP "k", 36 -CHARMAP "l", 37 -CHARMAP "m", 38 -CHARMAP "n", 39 -CHARMAP "o", 40 -CHARMAP "p", 41 -CHARMAP "q", 42 -CHARMAP "r", 43 -CHARMAP "s", 44 -CHARMAP "t", 45 -CHARMAP "u", 46 -CHARMAP "v", 47 -CHARMAP "w", 48 -CHARMAP "x", 49 -CHARMAP "y", 50 +; The character map for the text-font +CHARMAP " ", 0 +CHARMAP ".", 24 +CHARMAP "-", 25 +CHARMAP "a", 26 +CHARMAP "b", 27 +CHARMAP "c", 28 +CHARMAP "d", 29 +CHARMAP "e", 30 +CHARMAP "f", 31 +CHARMAP "g", 32 +CHARMAP "h", 33 +CHARMAP "i", 34 +CHARMAP "j", 35 +CHARMAP "k", 36 +CHARMAP "l", 37 +CHARMAP "m", 38 +CHARMAP "n", 39 +CHARMAP "o", 40 +CHARMAP "p", 41 +CHARMAP "q", 42 +CHARMAP "r", 43 +CHARMAP "s", 44 +CHARMAP "t", 45 +CHARMAP "u", 46 +CHARMAP "v", 47 +CHARMAP "w", 48 +CHARMAP "x", 49 +CHARMAP "y", 50 CHARMAP "z", 51 \ No newline at end of file diff --git a/galactic-armada/src/main/includes/constants.inc b/galactic-armada/src/main/includes/constants.inc index 3891fc23..fe2c40d5 100644 --- a/galactic-armada/src/main/includes/constants.inc +++ b/galactic-armada/src/main/includes/constants.inc @@ -1,46 +1,46 @@ -include "src/main/includes/hardware.inc" - -DEF MAX_ENEMY_COUNT EQU 10 -DEF MAX_BULLET_COUNT EQU 5 -DEF MAX_OBJECT_COUNT EQU 16 - -DEF ENEMY_SPAWN_DELAY_MAX EQU 70 - -;ANCHOR: object-bytes - -; from https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#EXPRESSIONS -; The RS group of commands is a handy way of defining structure offsets: -RSRESET -DEF object_activeByte RB 1 -DEF object_yLowByte RB 1 -DEF object_yHighByte RB 1 -DEF object_xLowByte RB 1 -DEF object_xHighByte RB 1 -DEF object_metaspriteLowByte RB 1 -DEF object_metaspriteHighByte RB 1 -DEF object_healthByte RB 1 -DEF object_updateLowByte RB 1 -DEF object_updateHighByte RB 1 -DEF object_damageByte RB 1 -DEF PER_OBJECT_BYTES_COUNT RB 0 -;ANCHOR_END: object-bytes - -RSRESET -DEF PLAYER_START RB PER_OBJECT_BYTES_COUNT -DEF ENEMIES_START RB MAX_ENEMY_COUNT*PER_OBJECT_BYTES_COUNT -DEF BULLETS_START RB MAX_OBJECT_COUNT*PER_OBJECT_BYTES_COUNT - -; ANCHOR: sprite-vram-constants -RSRESET -DEF spriteTilesStart RB _VRAM -DEF PLAYER_TILES_START RB 4*16 -DEF ENEMY_TILES_START RB 4*16 -DEF BULLET_TILES_START RB 0 -; ANCHOR_END: sprite-vram-constants - - -DEF ENEMY_MOVE_SPEED EQU 11 -DEF BULLET_MOVE_SPEED EQU 20 - -DEF PLAYER_MOVE_SPEED EQU 15 -DEF PADDLE_Y_POSITION EQU 136 +include "src/main/includes/hardware.inc" + +DEF MAX_ENEMY_COUNT EQU 10 +DEF MAX_BULLET_COUNT EQU 5 +DEF MAX_OBJECT_COUNT EQU 16 + +DEF ENEMY_SPAWN_DELAY_MAX EQU 70 + +;ANCHOR: object-bytes + +; from https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#EXPRESSIONS +; The RS group of commands is a handy way of defining structure offsets: +RSRESET +DEF object_activeByte RB 1 +DEF object_yLowByte RB 1 +DEF object_yHighByte RB 1 +DEF object_xLowByte RB 1 +DEF object_xHighByte RB 1 +DEF object_metaspriteLowByte RB 1 +DEF object_metaspriteHighByte RB 1 +DEF object_healthByte RB 1 +DEF object_updateLowByte RB 1 +DEF object_updateHighByte RB 1 +DEF object_damageByte RB 1 +DEF PER_OBJECT_BYTES_COUNT RB 0 +;ANCHOR_END: object-bytes + +RSRESET +DEF PLAYER_START RB PER_OBJECT_BYTES_COUNT +DEF ENEMIES_START RB MAX_ENEMY_COUNT*PER_OBJECT_BYTES_COUNT +DEF BULLETS_START RB MAX_OBJECT_COUNT*PER_OBJECT_BYTES_COUNT + +; ANCHOR: sprite-vram-constants +RSRESET +DEF spriteTilesStart RB _VRAM +DEF PLAYER_TILES_START RB 4*16 +DEF ENEMY_TILES_START RB 4*16 +DEF BULLET_TILES_START RB 0 +; ANCHOR_END: sprite-vram-constants + + +DEF ENEMY_MOVE_SPEED EQU 11 +DEF BULLET_MOVE_SPEED EQU 20 + +DEF PLAYER_MOVE_SPEED EQU 15 +DEF PADDLE_Y_POSITION EQU 136 diff --git a/galactic-armada/src/main/includes/hardware.inc b/galactic-armada/src/main/includes/hardware.inc index 281c7724..ee6304c0 100644 --- a/galactic-armada/src/main/includes/hardware.inc +++ b/galactic-armada/src/main/includes/hardware.inc @@ -1,922 +1,922 @@ -;* -;* Gameboy Hardware definitions -;* -;* Based on Jones' hardware.inc -;* And based on Carsten Sorensen's ideas. -;* -;* Rev 1.1 - 15-Jul-97 : Added define check -;* Rev 1.2 - 18-Jul-97 : Added revision check macro -;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05 -;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes -;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines -;* : and Nintendo Logo -;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC -;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1 -;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC -;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format -;* Rev 2.0 - : Added GBC registers -;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines -;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates -;* Rev 2.3 - : Fixed incorrect _HRAM equate -;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND) -;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND) -;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND) -;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm) -;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (Álvaro Cuesta) -;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants -;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object) -;* Rev 4.0 - 03-May-21 : Updated to use RGBDS 0.5.0 syntax, changed IEF_LCDC to IEF_STAT (Eievui) - -IF __RGBDS_MAJOR__ == 0 && __RGBDS_MINOR__ < 5 - FAIL "This version of 'hardware.inc' requires RGBDS version 0.5.0 or later." -ENDC - -; If all of these are already defined, don't do it again. - - IF !DEF(HARDWARE_INC) -DEF HARDWARE_INC EQU 1 - -MACRO rev_Check_hardware_inc -;NOTE: REVISION NUMBER CHANGES MUST BE ADDED -;TO SECOND PARAMETER IN FOLLOWING LINE. - IF \1 > 4.0 ;PUT REVISION NUMBER HERE - WARN "Version \1 or later of 'hardware.inc' is required." - ENDC -ENDM - -DEF _VRAM EQU $8000 ; $8000->$9FFF -DEF _VRAM8000 EQU _VRAM -DEF _VRAM8800 EQU _VRAM+$800 -DEF _VRAM9000 EQU _VRAM+$1000 -DEF _SCRN0 EQU $9800 ; $9800->$9BFF -DEF _SCRN1 EQU $9C00 ; $9C00->$9FFF -DEF _SRAM EQU $A000 ; $A000->$BFFF -DEF _RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF -DEF _RAMBANK EQU $D000 ; $D000->$DFFF -DEF _OAMRAM EQU $FE00 ; $FE00->$FE9F -DEF _IO EQU $FF00 ; $FF00->$FF7F,$FFFF -DEF _AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F -DEF _HRAM EQU $FF80 ; $FF80->$FFFE - -; *** MBC5 Equates *** - -DEF rRAMG EQU $0000 ; $0000->$1fff -DEF rROMB0 EQU $2000 ; $2000->$2fff -DEF rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present. -DEF rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present) - - -;*************************************************************************** -;* -;* Custom registers -;* -;*************************************************************************** - -; -- -; -- P1 ($FF00) -; -- Register for reading joy pad info. (R/W) -; -- -DEF rP1 EQU $FF00 - -DEF P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons -DEF P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad -DEF P1F_3 EQU %00001000 ; P13 in port -DEF P1F_2 EQU %00000100 ; P12 in port -DEF P1F_1 EQU %00000010 ; P11 in port -DEF P1F_0 EQU %00000001 ; P10 in port - -DEF P1F_GET_DPAD EQU P1F_5 -DEF P1F_GET_BTN EQU P1F_4 -DEF P1F_GET_NONE EQU P1F_4 | P1F_5 - - -; -- -; -- SB ($FF01) -; -- Serial Transfer Data (R/W) -; -- -DEF rSB EQU $FF01 - - -; -- -; -- SC ($FF02) -; -- Serial I/O Control (R/W) -; -- -DEF rSC EQU $FF02 - - -; -- -; -- DIV ($FF04) -; -- Divider register (R/W) -; -- -DEF rDIV EQU $FF04 - - -; -- -; -- TIMA ($FF05) -; -- Timer counter (R/W) -; -- -DEF rTIMA EQU $FF05 - - -; -- -; -- TMA ($FF06) -; -- Timer modulo (R/W) -; -- -DEF rTMA EQU $FF06 - - -; -- -; -- TAC ($FF07) -; -- Timer control (R/W) -; -- -DEF rTAC EQU $FF07 - -DEF TACF_START EQU %00000100 -DEF TACF_STOP EQU %00000000 -DEF TACF_4KHZ EQU %00000000 -DEF TACF_16KHZ EQU %00000011 -DEF TACF_65KHZ EQU %00000010 -DEF TACF_262KHZ EQU %00000001 - - -; -- -; -- IF ($FF0F) -; -- Interrupt Flag (R/W) -; -- -DEF rIF EQU $FF0F - - -; -- -; -- AUD1SWEEP/NR10 ($FF10) -; -- Sweep register (R/W) -; -- -; -- Bit 6-4 - Sweep Time -; -- Bit 3 - Sweep Increase/Decrease -; -- 0: Addition (frequency increases???) -; -- 1: Subtraction (frequency increases???) -; -- Bit 2-0 - Number of sweep shift (# 0-7) -; -- Sweep Time: (n*7.8ms) -; -- -DEF rNR10 EQU $FF10 -DEF rAUD1SWEEP EQU rNR10 - -DEF AUD1SWEEP_UP EQU %00000000 -DEF AUD1SWEEP_DOWN EQU %00001000 - - -; -- -; -- AUD1LEN/NR11 ($FF11) -; -- Sound length/Wave pattern duty (R/W) -; -- -; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%) -; -- Bit 5-0 - Sound length data (# 0-63) -; -- -DEF rNR11 EQU $FF11 -DEF rAUD1LEN EQU rNR11 - - -; -- -; -- AUD1ENV/NR12 ($FF12) -; -- Envelope (R/W) -; -- -; -- Bit 7-4 - Initial value of envelope -; -- Bit 3 - Envelope UP/DOWN -; -- 0: Decrease -; -- 1: Range of increase -; -- Bit 2-0 - Number of envelope sweep (# 0-7) -; -- -DEF rNR12 EQU $FF12 -DEF rAUD1ENV EQU rNR12 - - -; -- -; -- AUD1LOW/NR13 ($FF13) -; -- Frequency low byte (W) -; -- -DEF rNR13 EQU $FF13 -DEF rAUD1LOW EQU rNR13 - - -; -- -; -- AUD1HIGH/NR14 ($FF14) -; -- Frequency high byte (W) -; -- -; -- Bit 7 - Initial (when set, sound restarts) -; -- Bit 6 - Counter/consecutive selection -; -- Bit 2-0 - Frequency's higher 3 bits -; -- -DEF rNR14 EQU $FF14 -DEF rAUD1HIGH EQU rNR14 - - -; -- -; -- AUD2LEN/NR21 ($FF16) -; -- Sound Length; Wave Pattern Duty (R/W) -; -- -; -- see AUD1LEN for info -; -- -DEF rNR21 EQU $FF16 -DEF rAUD2LEN EQU rNR21 - - -; -- -; -- AUD2ENV/NR22 ($FF17) -; -- Envelope (R/W) -; -- -; -- see AUD1ENV for info -; -- -DEF rNR22 EQU $FF17 -DEF rAUD2ENV EQU rNR22 - - -; -- -; -- AUD2LOW/NR23 ($FF18) -; -- Frequency low byte (W) -; -- -DEF rNR23 EQU $FF18 -DEF rAUD2LOW EQU rNR23 - - -; -- -; -- AUD2HIGH/NR24 ($FF19) -; -- Frequency high byte (W) -; -- -; -- see AUD1HIGH for info -; -- -DEF rNR24 EQU $FF19 -DEF rAUD2HIGH EQU rNR24 - - -; -- -; -- AUD3ENA/NR30 ($FF1A) -; -- Sound on/off (R/W) -; -- -; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF) -; -- -DEF rNR30 EQU $FF1A -DEF rAUD3ENA EQU rNR30 - - -; -- -; -- AUD3LEN/NR31 ($FF1B) -; -- Sound length (R/W) -; -- -; -- Bit 7-0 - Sound length -; -- -DEF rNR31 EQU $FF1B -DEF rAUD3LEN EQU rNR31 - - -; -- -; -- AUD3LEVEL/NR32 ($FF1C) -; -- Select output level -; -- -; -- Bit 6-5 - Select output level -; -- 00: 0/1 (mute) -; -- 01: 1/1 -; -- 10: 1/2 -; -- 11: 1/4 -; -- -DEF rNR32 EQU $FF1C -DEF rAUD3LEVEL EQU rNR32 - - -; -- -; -- AUD3LOW/NR33 ($FF1D) -; -- Frequency low byte (W) -; -- -; -- see AUD1LOW for info -; -- -DEF rNR33 EQU $FF1D -DEF rAUD3LOW EQU rNR33 - - -; -- -; -- AUD3HIGH/NR34 ($FF1E) -; -- Frequency high byte (W) -; -- -; -- see AUD1HIGH for info -; -- -DEF rNR34 EQU $FF1E -DEF rAUD3HIGH EQU rNR34 - - -; -- -; -- AUD4LEN/NR41 ($FF20) -; -- Sound length (R/W) -; -- -; -- Bit 5-0 - Sound length data (# 0-63) -; -- -DEF rNR41 EQU $FF20 -DEF rAUD4LEN EQU rNR41 - - -; -- -; -- AUD4ENV/NR42 ($FF21) -; -- Envelope (R/W) -; -- -; -- see AUD1ENV for info -; -- -DEF rNR42 EQU $FF21 -DEF rAUD4ENV EQU rNR42 - - -; -- -; -- AUD4POLY/NR43 ($FF22) -; -- Polynomial counter (R/W) -; -- -; -- Bit 7-4 - Selection of the shift clock frequency of the (scf) -; -- polynomial counter (0000-1101) -; -- freq=drf*1/2^scf (not sure) -; -- Bit 3 - Selection of the polynomial counter's step -; -- 0: 15 steps -; -- 1: 7 steps -; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf) -; -- 000: f/4 001: f/8 010: f/16 011: f/24 -; -- 100: f/32 101: f/40 110: f/48 111: f/56 (f=4.194304 Mhz) -; -- -DEF rNR43 EQU $FF22 -DEF rAUD4POLY EQU rNR43 - - -; -- -; -- AUD4GO/NR44 ($FF23) -; -- -; -- Bit 7 - Inital -; -- Bit 6 - Counter/consecutive selection -; -- -DEF rNR44 EQU $FF23 -DEF rAUD4GO EQU rNR44 - - -; -- -; -- AUDVOL/NR50 ($FF24) -; -- Channel control / ON-OFF / Volume (R/W) -; -- -; -- Bit 7 - Vin->SO2 ON/OFF (Vin??) -; -- Bit 6-4 - SO2 output level (volume) (# 0-7) -; -- Bit 3 - Vin->SO1 ON/OFF (Vin??) -; -- Bit 2-0 - SO1 output level (volume) (# 0-7) -; -- -DEF rNR50 EQU $FF24 -DEF rAUDVOL EQU rNR50 - -DEF AUDVOL_VIN_LEFT EQU %10000000 ; SO2 -DEF AUDVOL_VIN_RIGHT EQU %00001000 ; SO1 - - -; -- -; -- AUDTERM/NR51 ($FF25) -; -- Selection of Sound output terminal (R/W) -; -- -; -- Bit 7 - Output sound 4 to SO2 terminal -; -- Bit 6 - Output sound 3 to SO2 terminal -; -- Bit 5 - Output sound 2 to SO2 terminal -; -- Bit 4 - Output sound 1 to SO2 terminal -; -- Bit 3 - Output sound 4 to SO1 terminal -; -- Bit 2 - Output sound 3 to SO1 terminal -; -- Bit 1 - Output sound 2 to SO1 terminal -; -- Bit 0 - Output sound 0 to SO1 terminal -; -- -DEF rNR51 EQU $FF25 -DEF rAUDTERM EQU rNR51 - -; SO2 -DEF AUDTERM_4_LEFT EQU %10000000 -DEF AUDTERM_3_LEFT EQU %01000000 -DEF AUDTERM_2_LEFT EQU %00100000 -DEF AUDTERM_1_LEFT EQU %00010000 -; SO1 -DEF AUDTERM_4_RIGHT EQU %00001000 -DEF AUDTERM_3_RIGHT EQU %00000100 -DEF AUDTERM_2_RIGHT EQU %00000010 -DEF AUDTERM_1_RIGHT EQU %00000001 - - -; -- -; -- AUDENA/NR52 ($FF26) -; -- Sound on/off (R/W) -; -- -; -- Bit 7 - All sound on/off (sets all audio regs to 0!) -; -- Bit 3 - Sound 4 ON flag (read only) -; -- Bit 2 - Sound 3 ON flag (read only) -; -- Bit 1 - Sound 2 ON flag (read only) -; -- Bit 0 - Sound 1 ON flag (read only) -; -- -DEF rNR52 EQU $FF26 -DEF rAUDENA EQU rNR52 - -DEF AUDENA_ON EQU %10000000 -DEF AUDENA_OFF EQU %00000000 ; sets all audio regs to 0! - - -; -- -; -- LCDC ($FF40) -; -- LCD Control (R/W) -; -- -DEF rLCDC EQU $FF40 - -DEF LCDCF_OFF EQU %00000000 ; LCD Control Operation -DEF LCDCF_ON EQU %10000000 ; LCD Control Operation -DEF LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select -DEF LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select -DEF LCDCF_WINOFF EQU %00000000 ; Window Display -DEF LCDCF_WINON EQU %00100000 ; Window Display -DEF LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select -DEF LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select -DEF LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select -DEF LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select -DEF LCDCF_OBJ8 EQU %00000000 ; OBJ Construction -DEF LCDCF_OBJ16 EQU %00000100 ; OBJ Construction -DEF LCDCF_OBJOFF EQU %00000000 ; OBJ Display -DEF LCDCF_OBJON EQU %00000010 ; OBJ Display -DEF LCDCF_BGOFF EQU %00000000 ; BG Display -DEF LCDCF_BGON EQU %00000001 ; BG Display -; "Window Character Data Select" follows BG - - -; -- -; -- STAT ($FF41) -; -- LCDC Status (R/W) -; -- -DEF rSTAT EQU $FF41 - -DEF STATF_LYC EQU %01000000 ; LYC=LY Coincidence (Selectable) -DEF STATF_MODE10 EQU %00100000 ; Mode 10 -DEF STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank) -DEF STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank) -DEF STATF_LYCF EQU %00000100 ; Coincidence Flag -DEF STATF_HBL EQU %00000000 ; H-Blank -DEF STATF_VBL EQU %00000001 ; V-Blank -DEF STATF_OAM EQU %00000010 ; OAM-RAM is used by system -DEF STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system -DEF STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe - - -; -- -; -- SCY ($FF42) -; -- Scroll Y (R/W) -; -- -DEF rSCY EQU $FF42 - - -; -- -; -- SCX ($FF43) -; -- Scroll X (R/W) -; -- -DEF rSCX EQU $FF43 - - -; -- -; -- LY ($FF44) -; -- LCDC Y-Coordinate (R) -; -- -; -- Values range from 0->153. 144->153 is the VBlank period. -; -- -DEF rLY EQU $FF44 - - -; -- -; -- LYC ($FF45) -; -- LY Compare (R/W) -; -- -; -- When LY==LYC, STATF_LYCF will be set in STAT -; -- -DEF rLYC EQU $FF45 - - -; -- -; -- DMA ($FF46) -; -- DMA Transfer and Start Address (W) -; -- -DEF rDMA EQU $FF46 - - -; -- -; -- BGP ($FF47) -; -- BG Palette Data (W) -; -- -; -- Bit 7-6 - Intensity for %11 -; -- Bit 5-4 - Intensity for %10 -; -- Bit 3-2 - Intensity for %01 -; -- Bit 1-0 - Intensity for %00 -; -- -DEF rBGP EQU $FF47 - - -; -- -; -- OBP0 ($FF48) -; -- Object Palette 0 Data (W) -; -- -; -- See BGP for info -; -- -DEF rOBP0 EQU $FF48 - - -; -- -; -- OBP1 ($FF49) -; -- Object Palette 1 Data (W) -; -- -; -- See BGP for info -; -- -DEF rOBP1 EQU $FF49 - - -; -- -; -- WY ($FF4A) -; -- Window Y Position (R/W) -; -- -; -- 0 <= WY <= 143 -; -- When WY = 0, the window is displayed from the top edge of the LCD screen. -; -- -DEF rWY EQU $FF4A - - -; -- -; -- WX ($FF4B) -; -- Window X Position (R/W) -; -- -; -- 7 <= WX <= 166 -; -- When WX = 7, the window is displayed from the left edge of the LCD screen. -; -- Values of 0-6 and 166 are unreliable due to hardware bugs. -; -- -DEF rWX EQU $FF4B - - -; -- -; -- SPEED ($FF4D) -; -- Select CPU Speed (R/W) -; -- -DEF rKEY1 EQU $FF4D -DEF rSPD EQU rKEY1 - -DEF KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R) -DEF KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W) - - -; -- -; -- VBK ($FF4F) -; -- Select Video RAM Bank (R/W) -; -- -; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1) -; -- -DEF rVBK EQU $FF4F - - -; -- -; -- HDMA1 ($FF51) -; -- High byte for Horizontal Blanking/General Purpose DMA source address (W) -; -- CGB Mode Only -; -- -DEF rHDMA1 EQU $FF51 - - -; -- -; -- HDMA2 ($FF52) -; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W) -; -- CGB Mode Only -; -- -DEF rHDMA2 EQU $FF52 - - -; -- -; -- HDMA3 ($FF53) -; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W) -; -- CGB Mode Only -; -- -DEF rHDMA3 EQU $FF53 - - -; -- -; -- HDMA4 ($FF54) -; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W) -; -- CGB Mode Only -; -- -DEF rHDMA4 EQU $FF54 - - -; -- -; -- HDMA5 ($FF55) -; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W) -; -- CGB Mode Only -; -- -DEF rHDMA5 EQU $FF55 - -DEF HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W) -DEF HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W) - -; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete -DEF HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R) - - -; -- -; -- RP ($FF56) -; -- Infrared Communications Port (R/W) -; -- CGB Mode Only -; -- -DEF rRP EQU $FF56 - -DEF RPF_ENREAD EQU %11000000 -DEF RPF_DATAIN EQU %00000010 ; 0=Receiving IR Signal, 1=Normal -DEF RPF_WRITE_HI EQU %00000001 -DEF RPF_WRITE_LO EQU %00000000 - - -; -- -; -- BCPS ($FF68) -; -- Background Color Palette Specification (R/W) -; -- -DEF rBCPS EQU $FF68 - -DEF BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) - - -; -- -; -- BCPD ($FF69) -; -- Background Color Palette Data (R/W) -; -- -DEF rBCPD EQU $FF69 - - -; -- -; -- OCPS ($FF6A) -; -- Object Color Palette Specification (R/W) -; -- -DEF rOCPS EQU $FF6A - -DEF OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) - - -; -- -; -- OCPD ($FF6B) -; -- Object Color Palette Data (R/W) -; -- -DEF rOCPD EQU $FF6B - - -; -- -; -- SMBK/SVBK ($FF70) -; -- Select Main RAM Bank (R/W) -; -- -; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7) -; -- -DEF rSVBK EQU $FF70 -DEF rSMBK EQU rSVBK - - -; -- -; -- PCM12 ($FF76) -; -- Sound channel 1&2 PCM amplitude (R) -; -- -; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude -; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude -; -- -DEF rPCM12 EQU $FF76 - - -; -- -; -- PCM34 ($FF77) -; -- Sound channel 3&4 PCM amplitude (R) -; -- -; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude -; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude -; -- -DEF rPCM34 EQU $FF77 - - -; -- -; -- IE ($FFFF) -; -- Interrupt Enable (R/W) -; -- -DEF rIE EQU $FFFF - -DEF IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13 -DEF IEF_SERIAL EQU %00001000 ; Serial I/O transfer end -DEF IEF_TIMER EQU %00000100 ; Timer Overflow -DEF IEF_STAT EQU %00000010 ; STAT -DEF IEF_VBLANK EQU %00000001 ; V-Blank - - -;*************************************************************************** -;* -;* Flags common to multiple sound channels -;* -;*************************************************************************** - -; -- -; -- Square wave duty cycle -; -- -; -- Can be used with AUD1LEN and AUD2LEN -; -- See AUD1LEN for more info -; -- -DEF AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5% -DEF AUDLEN_DUTY_25 EQU %01000000 ; 25% -DEF AUDLEN_DUTY_50 EQU %10000000 ; 50% -DEF AUDLEN_DUTY_75 EQU %11000000 ; 75% - - -; -- -; -- Audio envelope flags -; -- -; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV -; -- See AUD1ENV for more info -; -- -DEF AUDENV_UP EQU %00001000 -DEF AUDENV_DOWN EQU %00000000 - - -; -- -; -- Audio trigger flags -; -- -; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH -; -- See AUD1HIGH for more info -; -- - -DEF AUDHIGH_RESTART EQU %10000000 -DEF AUDHIGH_LENGTH_ON EQU %01000000 -DEF AUDHIGH_LENGTH_OFF EQU %00000000 - - -;*************************************************************************** -;* -;* CPU values on bootup (a=type, b=qualifier) -;* -;*************************************************************************** - -DEF BOOTUP_A_DMG EQU $01 ; Dot Matrix Game -DEF BOOTUP_A_CGB EQU $11 ; Color GameBoy -DEF BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy) - -; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or -; other system running in GBC mode -DEF BOOTUP_B_CGB EQU %00000000 -DEF BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP - - -;*************************************************************************** -;* -;* Cart related -;* -;*************************************************************************** - -; $0143 Color GameBoy compatibility code -DEF CART_COMPATIBLE_DMG EQU $00 -DEF CART_COMPATIBLE_DMG_GBC EQU $80 -DEF CART_COMPATIBLE_GBC EQU $C0 - -; $0146 GameBoy/Super GameBoy indicator -DEF CART_INDICATOR_GB EQU $00 -DEF CART_INDICATOR_SGB EQU $03 - -; $0147 Cartridge type -DEF CART_ROM EQU $00 -DEF CART_ROM_MBC1 EQU $01 -DEF CART_ROM_MBC1_RAM EQU $02 -DEF CART_ROM_MBC1_RAM_BAT EQU $03 -DEF CART_ROM_MBC2 EQU $05 -DEF CART_ROM_MBC2_BAT EQU $06 -DEF CART_ROM_RAM EQU $08 -DEF CART_ROM_RAM_BAT EQU $09 -DEF CART_ROM_MMM01 EQU $0B -DEF CART_ROM_MMM01_RAM EQU $0C -DEF CART_ROM_MMM01_RAM_BAT EQU $0D -DEF CART_ROM_MBC3_BAT_RTC EQU $0F -DEF CART_ROM_MBC3_RAM_BAT_RTC EQU $10 -DEF CART_ROM_MBC3 EQU $11 -DEF CART_ROM_MBC3_RAM EQU $12 -DEF CART_ROM_MBC3_RAM_BAT EQU $13 -DEF CART_ROM_MBC5 EQU $19 -DEF CART_ROM_MBC5_BAT EQU $1A -DEF CART_ROM_MBC5_RAM_BAT EQU $1B -DEF CART_ROM_MBC5_RUMBLE EQU $1C -DEF CART_ROM_MBC5_RAM_RUMBLE EQU $1D -DEF CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E -DEF CART_ROM_MBC7_RAM_BAT_GYRO EQU $22 -DEF CART_ROM_POCKET_CAMERA EQU $FC -DEF CART_ROM_BANDAI_TAMA5 EQU $FD -DEF CART_ROM_HUDSON_HUC3 EQU $FE -DEF CART_ROM_HUDSON_HUC1 EQU $FF - -; $0148 ROM size -; these are kilobytes -DEF CART_ROM_32KB EQU $00 ; 2 banks -DEF CART_ROM_64KB EQU $01 ; 4 banks -DEF CART_ROM_128KB EQU $02 ; 8 banks -DEF CART_ROM_256KB EQU $03 ; 16 banks -DEF CART_ROM_512KB EQU $04 ; 32 banks -DEF CART_ROM_1024KB EQU $05 ; 64 banks -DEF CART_ROM_2048KB EQU $06 ; 128 banks -DEF CART_ROM_4096KB EQU $07 ; 256 banks -DEF CART_ROM_8192KB EQU $08 ; 512 banks -DEF CART_ROM_1152KB EQU $52 ; 72 banks -DEF CART_ROM_1280KB EQU $53 ; 80 banks -DEF CART_ROM_1536KB EQU $54 ; 96 banks - -; $0149 SRAM size -; these are kilobytes -DEF CART_SRAM_NONE EQU 0 -DEF CART_SRAM_2KB EQU 1 ; 1 incomplete bank -DEF CART_SRAM_8KB EQU 2 ; 1 bank -DEF CART_SRAM_32KB EQU 3 ; 4 banks -DEF CART_SRAM_128KB EQU 4 ; 16 banks - -DEF CART_SRAM_ENABLE EQU $0A -DEF CART_SRAM_DISABLE EQU $00 - -; $014A Destination code -DEF CART_DEST_JAPANESE EQU $00 -DEF CART_DEST_NON_JAPANESE EQU $01 - - -;*************************************************************************** -;* -;* Keypad related -;* -;*************************************************************************** - -DEF PADF_DOWN EQU $80 -DEF PADF_UP EQU $40 -DEF PADF_LEFT EQU $20 -DEF PADF_RIGHT EQU $10 -DEF PADF_START EQU $08 -DEF PADF_SELECT EQU $04 -DEF PADF_B EQU $02 -DEF PADF_A EQU $01 - -DEF PADB_DOWN EQU $7 -DEF PADB_UP EQU $6 -DEF PADB_LEFT EQU $5 -DEF PADB_RIGHT EQU $4 -DEF PADB_START EQU $3 -DEF PADB_SELECT EQU $2 -DEF PADB_B EQU $1 -DEF PADB_A EQU $0 - - -;*************************************************************************** -;* -;* Screen related -;* -;*************************************************************************** - -DEF SCRN_X EQU 160 ; Width of screen in pixels -DEF SCRN_Y EQU 144 ; Height of screen in pixels -DEF SCRN_X_B EQU 20 ; Width of screen in bytes -DEF SCRN_Y_B EQU 18 ; Height of screen in bytes - -DEF SCRN_VX EQU 256 ; Virtual width of screen in pixels -DEF SCRN_VY EQU 256 ; Virtual height of screen in pixels -DEF SCRN_VX_B EQU 32 ; Virtual width of screen in bytes -DEF SCRN_VY_B EQU 32 ; Virtual height of screen in bytes - - -;*************************************************************************** -;* -;* OAM related -;* -;*************************************************************************** - -; OAM attributes -; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS) -RSRESET -DEF OAMA_Y RB 1 ; y pos -DEF OAMA_X RB 1 ; x pos -DEF OAMA_TILEID RB 1 ; tile id -DEF OAMA_FLAGS RB 1 ; flags (see below) -DEF sizeof_OAM_ATTRS RB 0 - -DEF OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM - -; flags -DEF OAMF_PRI EQU %10000000 ; Priority -DEF OAMF_YFLIP EQU %01000000 ; Y flip -DEF OAMF_XFLIP EQU %00100000 ; X flip -DEF OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG) -DEF OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG) -DEF OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC) -DEF OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC) - -DEF OAMF_PALMASK EQU %00000111 ; Palette (GBC) - -DEF OAMB_PRI EQU 7 ; Priority -DEF OAMB_YFLIP EQU 6 ; Y flip -DEF OAMB_XFLIP EQU 5 ; X flip -DEF OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG) -DEF OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC) - - -;* -;* Nintendo scrolling logo -;* (Code won't work on a real GameBoy) -;* (if next lines are altered.) -MACRO NINTENDO_LOGO - DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D - DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 - DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E -ENDM - -; Deprecated constants. Please avoid using. - -DEF IEF_LCDC EQU %00000010 ; LCDC (see STAT) - +;* +;* Gameboy Hardware definitions +;* +;* Based on Jones' hardware.inc +;* And based on Carsten Sorensen's ideas. +;* +;* Rev 1.1 - 15-Jul-97 : Added define check +;* Rev 1.2 - 18-Jul-97 : Added revision check macro +;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05 +;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes +;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines +;* : and Nintendo Logo +;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC +;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1 +;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC +;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format +;* Rev 2.0 - : Added GBC registers +;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines +;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates +;* Rev 2.3 - : Fixed incorrect _HRAM equate +;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND) +;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND) +;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND) +;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm) +;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (Álvaro Cuesta) +;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants +;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object) +;* Rev 4.0 - 03-May-21 : Updated to use RGBDS 0.5.0 syntax, changed IEF_LCDC to IEF_STAT (Eievui) + +IF __RGBDS_MAJOR__ == 0 && __RGBDS_MINOR__ < 5 + FAIL "This version of 'hardware.inc' requires RGBDS version 0.5.0 or later." +ENDC + +; If all of these are already defined, don't do it again. + + IF !DEF(HARDWARE_INC) +DEF HARDWARE_INC EQU 1 + +MACRO rev_Check_hardware_inc +;NOTE: REVISION NUMBER CHANGES MUST BE ADDED +;TO SECOND PARAMETER IN FOLLOWING LINE. + IF \1 > 4.0 ;PUT REVISION NUMBER HERE + WARN "Version \1 or later of 'hardware.inc' is required." + ENDC +ENDM + +DEF _VRAM EQU $8000 ; $8000->$9FFF +DEF _VRAM8000 EQU _VRAM +DEF _VRAM8800 EQU _VRAM+$800 +DEF _VRAM9000 EQU _VRAM+$1000 +DEF _SCRN0 EQU $9800 ; $9800->$9BFF +DEF _SCRN1 EQU $9C00 ; $9C00->$9FFF +DEF _SRAM EQU $A000 ; $A000->$BFFF +DEF _RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF +DEF _RAMBANK EQU $D000 ; $D000->$DFFF +DEF _OAMRAM EQU $FE00 ; $FE00->$FE9F +DEF _IO EQU $FF00 ; $FF00->$FF7F,$FFFF +DEF _AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F +DEF _HRAM EQU $FF80 ; $FF80->$FFFE + +; *** MBC5 Equates *** + +DEF rRAMG EQU $0000 ; $0000->$1fff +DEF rROMB0 EQU $2000 ; $2000->$2fff +DEF rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present. +DEF rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present) + + +;*************************************************************************** +;* +;* Custom registers +;* +;*************************************************************************** + +; -- +; -- P1 ($FF00) +; -- Register for reading joy pad info. (R/W) +; -- +DEF rP1 EQU $FF00 + +DEF P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons +DEF P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad +DEF P1F_3 EQU %00001000 ; P13 in port +DEF P1F_2 EQU %00000100 ; P12 in port +DEF P1F_1 EQU %00000010 ; P11 in port +DEF P1F_0 EQU %00000001 ; P10 in port + +DEF P1F_GET_DPAD EQU P1F_5 +DEF P1F_GET_BTN EQU P1F_4 +DEF P1F_GET_NONE EQU P1F_4 | P1F_5 + + +; -- +; -- SB ($FF01) +; -- Serial Transfer Data (R/W) +; -- +DEF rSB EQU $FF01 + + +; -- +; -- SC ($FF02) +; -- Serial I/O Control (R/W) +; -- +DEF rSC EQU $FF02 + + +; -- +; -- DIV ($FF04) +; -- Divider register (R/W) +; -- +DEF rDIV EQU $FF04 + + +; -- +; -- TIMA ($FF05) +; -- Timer counter (R/W) +; -- +DEF rTIMA EQU $FF05 + + +; -- +; -- TMA ($FF06) +; -- Timer modulo (R/W) +; -- +DEF rTMA EQU $FF06 + + +; -- +; -- TAC ($FF07) +; -- Timer control (R/W) +; -- +DEF rTAC EQU $FF07 + +DEF TACF_START EQU %00000100 +DEF TACF_STOP EQU %00000000 +DEF TACF_4KHZ EQU %00000000 +DEF TACF_16KHZ EQU %00000011 +DEF TACF_65KHZ EQU %00000010 +DEF TACF_262KHZ EQU %00000001 + + +; -- +; -- IF ($FF0F) +; -- Interrupt Flag (R/W) +; -- +DEF rIF EQU $FF0F + + +; -- +; -- AUD1SWEEP/NR10 ($FF10) +; -- Sweep register (R/W) +; -- +; -- Bit 6-4 - Sweep Time +; -- Bit 3 - Sweep Increase/Decrease +; -- 0: Addition (frequency increases???) +; -- 1: Subtraction (frequency increases???) +; -- Bit 2-0 - Number of sweep shift (# 0-7) +; -- Sweep Time: (n*7.8ms) +; -- +DEF rNR10 EQU $FF10 +DEF rAUD1SWEEP EQU rNR10 + +DEF AUD1SWEEP_UP EQU %00000000 +DEF AUD1SWEEP_DOWN EQU %00001000 + + +; -- +; -- AUD1LEN/NR11 ($FF11) +; -- Sound length/Wave pattern duty (R/W) +; -- +; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%) +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +DEF rNR11 EQU $FF11 +DEF rAUD1LEN EQU rNR11 + + +; -- +; -- AUD1ENV/NR12 ($FF12) +; -- Envelope (R/W) +; -- +; -- Bit 7-4 - Initial value of envelope +; -- Bit 3 - Envelope UP/DOWN +; -- 0: Decrease +; -- 1: Range of increase +; -- Bit 2-0 - Number of envelope sweep (# 0-7) +; -- +DEF rNR12 EQU $FF12 +DEF rAUD1ENV EQU rNR12 + + +; -- +; -- AUD1LOW/NR13 ($FF13) +; -- Frequency low byte (W) +; -- +DEF rNR13 EQU $FF13 +DEF rAUD1LOW EQU rNR13 + + +; -- +; -- AUD1HIGH/NR14 ($FF14) +; -- Frequency high byte (W) +; -- +; -- Bit 7 - Initial (when set, sound restarts) +; -- Bit 6 - Counter/consecutive selection +; -- Bit 2-0 - Frequency's higher 3 bits +; -- +DEF rNR14 EQU $FF14 +DEF rAUD1HIGH EQU rNR14 + + +; -- +; -- AUD2LEN/NR21 ($FF16) +; -- Sound Length; Wave Pattern Duty (R/W) +; -- +; -- see AUD1LEN for info +; -- +DEF rNR21 EQU $FF16 +DEF rAUD2LEN EQU rNR21 + + +; -- +; -- AUD2ENV/NR22 ($FF17) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +DEF rNR22 EQU $FF17 +DEF rAUD2ENV EQU rNR22 + + +; -- +; -- AUD2LOW/NR23 ($FF18) +; -- Frequency low byte (W) +; -- +DEF rNR23 EQU $FF18 +DEF rAUD2LOW EQU rNR23 + + +; -- +; -- AUD2HIGH/NR24 ($FF19) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +DEF rNR24 EQU $FF19 +DEF rAUD2HIGH EQU rNR24 + + +; -- +; -- AUD3ENA/NR30 ($FF1A) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF) +; -- +DEF rNR30 EQU $FF1A +DEF rAUD3ENA EQU rNR30 + + +; -- +; -- AUD3LEN/NR31 ($FF1B) +; -- Sound length (R/W) +; -- +; -- Bit 7-0 - Sound length +; -- +DEF rNR31 EQU $FF1B +DEF rAUD3LEN EQU rNR31 + + +; -- +; -- AUD3LEVEL/NR32 ($FF1C) +; -- Select output level +; -- +; -- Bit 6-5 - Select output level +; -- 00: 0/1 (mute) +; -- 01: 1/1 +; -- 10: 1/2 +; -- 11: 1/4 +; -- +DEF rNR32 EQU $FF1C +DEF rAUD3LEVEL EQU rNR32 + + +; -- +; -- AUD3LOW/NR33 ($FF1D) +; -- Frequency low byte (W) +; -- +; -- see AUD1LOW for info +; -- +DEF rNR33 EQU $FF1D +DEF rAUD3LOW EQU rNR33 + + +; -- +; -- AUD3HIGH/NR34 ($FF1E) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +DEF rNR34 EQU $FF1E +DEF rAUD3HIGH EQU rNR34 + + +; -- +; -- AUD4LEN/NR41 ($FF20) +; -- Sound length (R/W) +; -- +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +DEF rNR41 EQU $FF20 +DEF rAUD4LEN EQU rNR41 + + +; -- +; -- AUD4ENV/NR42 ($FF21) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +DEF rNR42 EQU $FF21 +DEF rAUD4ENV EQU rNR42 + + +; -- +; -- AUD4POLY/NR43 ($FF22) +; -- Polynomial counter (R/W) +; -- +; -- Bit 7-4 - Selection of the shift clock frequency of the (scf) +; -- polynomial counter (0000-1101) +; -- freq=drf*1/2^scf (not sure) +; -- Bit 3 - Selection of the polynomial counter's step +; -- 0: 15 steps +; -- 1: 7 steps +; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf) +; -- 000: f/4 001: f/8 010: f/16 011: f/24 +; -- 100: f/32 101: f/40 110: f/48 111: f/56 (f=4.194304 Mhz) +; -- +DEF rNR43 EQU $FF22 +DEF rAUD4POLY EQU rNR43 + + +; -- +; -- AUD4GO/NR44 ($FF23) +; -- +; -- Bit 7 - Inital +; -- Bit 6 - Counter/consecutive selection +; -- +DEF rNR44 EQU $FF23 +DEF rAUD4GO EQU rNR44 + + +; -- +; -- AUDVOL/NR50 ($FF24) +; -- Channel control / ON-OFF / Volume (R/W) +; -- +; -- Bit 7 - Vin->SO2 ON/OFF (Vin??) +; -- Bit 6-4 - SO2 output level (volume) (# 0-7) +; -- Bit 3 - Vin->SO1 ON/OFF (Vin??) +; -- Bit 2-0 - SO1 output level (volume) (# 0-7) +; -- +DEF rNR50 EQU $FF24 +DEF rAUDVOL EQU rNR50 + +DEF AUDVOL_VIN_LEFT EQU %10000000 ; SO2 +DEF AUDVOL_VIN_RIGHT EQU %00001000 ; SO1 + + +; -- +; -- AUDTERM/NR51 ($FF25) +; -- Selection of Sound output terminal (R/W) +; -- +; -- Bit 7 - Output sound 4 to SO2 terminal +; -- Bit 6 - Output sound 3 to SO2 terminal +; -- Bit 5 - Output sound 2 to SO2 terminal +; -- Bit 4 - Output sound 1 to SO2 terminal +; -- Bit 3 - Output sound 4 to SO1 terminal +; -- Bit 2 - Output sound 3 to SO1 terminal +; -- Bit 1 - Output sound 2 to SO1 terminal +; -- Bit 0 - Output sound 0 to SO1 terminal +; -- +DEF rNR51 EQU $FF25 +DEF rAUDTERM EQU rNR51 + +; SO2 +DEF AUDTERM_4_LEFT EQU %10000000 +DEF AUDTERM_3_LEFT EQU %01000000 +DEF AUDTERM_2_LEFT EQU %00100000 +DEF AUDTERM_1_LEFT EQU %00010000 +; SO1 +DEF AUDTERM_4_RIGHT EQU %00001000 +DEF AUDTERM_3_RIGHT EQU %00000100 +DEF AUDTERM_2_RIGHT EQU %00000010 +DEF AUDTERM_1_RIGHT EQU %00000001 + + +; -- +; -- AUDENA/NR52 ($FF26) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - All sound on/off (sets all audio regs to 0!) +; -- Bit 3 - Sound 4 ON flag (read only) +; -- Bit 2 - Sound 3 ON flag (read only) +; -- Bit 1 - Sound 2 ON flag (read only) +; -- Bit 0 - Sound 1 ON flag (read only) +; -- +DEF rNR52 EQU $FF26 +DEF rAUDENA EQU rNR52 + +DEF AUDENA_ON EQU %10000000 +DEF AUDENA_OFF EQU %00000000 ; sets all audio regs to 0! + + +; -- +; -- LCDC ($FF40) +; -- LCD Control (R/W) +; -- +DEF rLCDC EQU $FF40 + +DEF LCDCF_OFF EQU %00000000 ; LCD Control Operation +DEF LCDCF_ON EQU %10000000 ; LCD Control Operation +DEF LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select +DEF LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select +DEF LCDCF_WINOFF EQU %00000000 ; Window Display +DEF LCDCF_WINON EQU %00100000 ; Window Display +DEF LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select +DEF LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select +DEF LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select +DEF LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select +DEF LCDCF_OBJ8 EQU %00000000 ; OBJ Construction +DEF LCDCF_OBJ16 EQU %00000100 ; OBJ Construction +DEF LCDCF_OBJOFF EQU %00000000 ; OBJ Display +DEF LCDCF_OBJON EQU %00000010 ; OBJ Display +DEF LCDCF_BGOFF EQU %00000000 ; BG Display +DEF LCDCF_BGON EQU %00000001 ; BG Display +; "Window Character Data Select" follows BG + + +; -- +; -- STAT ($FF41) +; -- LCDC Status (R/W) +; -- +DEF rSTAT EQU $FF41 + +DEF STATF_LYC EQU %01000000 ; LYC=LY Coincidence (Selectable) +DEF STATF_MODE10 EQU %00100000 ; Mode 10 +DEF STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank) +DEF STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank) +DEF STATF_LYCF EQU %00000100 ; Coincidence Flag +DEF STATF_HBL EQU %00000000 ; H-Blank +DEF STATF_VBL EQU %00000001 ; V-Blank +DEF STATF_OAM EQU %00000010 ; OAM-RAM is used by system +DEF STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system +DEF STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe + + +; -- +; -- SCY ($FF42) +; -- Scroll Y (R/W) +; -- +DEF rSCY EQU $FF42 + + +; -- +; -- SCX ($FF43) +; -- Scroll X (R/W) +; -- +DEF rSCX EQU $FF43 + + +; -- +; -- LY ($FF44) +; -- LCDC Y-Coordinate (R) +; -- +; -- Values range from 0->153. 144->153 is the VBlank period. +; -- +DEF rLY EQU $FF44 + + +; -- +; -- LYC ($FF45) +; -- LY Compare (R/W) +; -- +; -- When LY==LYC, STATF_LYCF will be set in STAT +; -- +DEF rLYC EQU $FF45 + + +; -- +; -- DMA ($FF46) +; -- DMA Transfer and Start Address (W) +; -- +DEF rDMA EQU $FF46 + + +; -- +; -- BGP ($FF47) +; -- BG Palette Data (W) +; -- +; -- Bit 7-6 - Intensity for %11 +; -- Bit 5-4 - Intensity for %10 +; -- Bit 3-2 - Intensity for %01 +; -- Bit 1-0 - Intensity for %00 +; -- +DEF rBGP EQU $FF47 + + +; -- +; -- OBP0 ($FF48) +; -- Object Palette 0 Data (W) +; -- +; -- See BGP for info +; -- +DEF rOBP0 EQU $FF48 + + +; -- +; -- OBP1 ($FF49) +; -- Object Palette 1 Data (W) +; -- +; -- See BGP for info +; -- +DEF rOBP1 EQU $FF49 + + +; -- +; -- WY ($FF4A) +; -- Window Y Position (R/W) +; -- +; -- 0 <= WY <= 143 +; -- When WY = 0, the window is displayed from the top edge of the LCD screen. +; -- +DEF rWY EQU $FF4A + + +; -- +; -- WX ($FF4B) +; -- Window X Position (R/W) +; -- +; -- 7 <= WX <= 166 +; -- When WX = 7, the window is displayed from the left edge of the LCD screen. +; -- Values of 0-6 and 166 are unreliable due to hardware bugs. +; -- +DEF rWX EQU $FF4B + + +; -- +; -- SPEED ($FF4D) +; -- Select CPU Speed (R/W) +; -- +DEF rKEY1 EQU $FF4D +DEF rSPD EQU rKEY1 + +DEF KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R) +DEF KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W) + + +; -- +; -- VBK ($FF4F) +; -- Select Video RAM Bank (R/W) +; -- +; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1) +; -- +DEF rVBK EQU $FF4F + + +; -- +; -- HDMA1 ($FF51) +; -- High byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +DEF rHDMA1 EQU $FF51 + + +; -- +; -- HDMA2 ($FF52) +; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +DEF rHDMA2 EQU $FF52 + + +; -- +; -- HDMA3 ($FF53) +; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +DEF rHDMA3 EQU $FF53 + + +; -- +; -- HDMA4 ($FF54) +; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +DEF rHDMA4 EQU $FF54 + + +; -- +; -- HDMA5 ($FF55) +; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W) +; -- CGB Mode Only +; -- +DEF rHDMA5 EQU $FF55 + +DEF HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W) +DEF HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W) + +; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete +DEF HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R) + + +; -- +; -- RP ($FF56) +; -- Infrared Communications Port (R/W) +; -- CGB Mode Only +; -- +DEF rRP EQU $FF56 + +DEF RPF_ENREAD EQU %11000000 +DEF RPF_DATAIN EQU %00000010 ; 0=Receiving IR Signal, 1=Normal +DEF RPF_WRITE_HI EQU %00000001 +DEF RPF_WRITE_LO EQU %00000000 + + +; -- +; -- BCPS ($FF68) +; -- Background Color Palette Specification (R/W) +; -- +DEF rBCPS EQU $FF68 + +DEF BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + +; -- +; -- BCPD ($FF69) +; -- Background Color Palette Data (R/W) +; -- +DEF rBCPD EQU $FF69 + + +; -- +; -- OCPS ($FF6A) +; -- Object Color Palette Specification (R/W) +; -- +DEF rOCPS EQU $FF6A + +DEF OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + +; -- +; -- OCPD ($FF6B) +; -- Object Color Palette Data (R/W) +; -- +DEF rOCPD EQU $FF6B + + +; -- +; -- SMBK/SVBK ($FF70) +; -- Select Main RAM Bank (R/W) +; -- +; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7) +; -- +DEF rSVBK EQU $FF70 +DEF rSMBK EQU rSVBK + + +; -- +; -- PCM12 ($FF76) +; -- Sound channel 1&2 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude +; -- +DEF rPCM12 EQU $FF76 + + +; -- +; -- PCM34 ($FF77) +; -- Sound channel 3&4 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude +; -- +DEF rPCM34 EQU $FF77 + + +; -- +; -- IE ($FFFF) +; -- Interrupt Enable (R/W) +; -- +DEF rIE EQU $FFFF + +DEF IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13 +DEF IEF_SERIAL EQU %00001000 ; Serial I/O transfer end +DEF IEF_TIMER EQU %00000100 ; Timer Overflow +DEF IEF_STAT EQU %00000010 ; STAT +DEF IEF_VBLANK EQU %00000001 ; V-Blank + + +;*************************************************************************** +;* +;* Flags common to multiple sound channels +;* +;*************************************************************************** + +; -- +; -- Square wave duty cycle +; -- +; -- Can be used with AUD1LEN and AUD2LEN +; -- See AUD1LEN for more info +; -- +DEF AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5% +DEF AUDLEN_DUTY_25 EQU %01000000 ; 25% +DEF AUDLEN_DUTY_50 EQU %10000000 ; 50% +DEF AUDLEN_DUTY_75 EQU %11000000 ; 75% + + +; -- +; -- Audio envelope flags +; -- +; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV +; -- See AUD1ENV for more info +; -- +DEF AUDENV_UP EQU %00001000 +DEF AUDENV_DOWN EQU %00000000 + + +; -- +; -- Audio trigger flags +; -- +; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH +; -- See AUD1HIGH for more info +; -- + +DEF AUDHIGH_RESTART EQU %10000000 +DEF AUDHIGH_LENGTH_ON EQU %01000000 +DEF AUDHIGH_LENGTH_OFF EQU %00000000 + + +;*************************************************************************** +;* +;* CPU values on bootup (a=type, b=qualifier) +;* +;*************************************************************************** + +DEF BOOTUP_A_DMG EQU $01 ; Dot Matrix Game +DEF BOOTUP_A_CGB EQU $11 ; Color GameBoy +DEF BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy) + +; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or +; other system running in GBC mode +DEF BOOTUP_B_CGB EQU %00000000 +DEF BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP + + +;*************************************************************************** +;* +;* Cart related +;* +;*************************************************************************** + +; $0143 Color GameBoy compatibility code +DEF CART_COMPATIBLE_DMG EQU $00 +DEF CART_COMPATIBLE_DMG_GBC EQU $80 +DEF CART_COMPATIBLE_GBC EQU $C0 + +; $0146 GameBoy/Super GameBoy indicator +DEF CART_INDICATOR_GB EQU $00 +DEF CART_INDICATOR_SGB EQU $03 + +; $0147 Cartridge type +DEF CART_ROM EQU $00 +DEF CART_ROM_MBC1 EQU $01 +DEF CART_ROM_MBC1_RAM EQU $02 +DEF CART_ROM_MBC1_RAM_BAT EQU $03 +DEF CART_ROM_MBC2 EQU $05 +DEF CART_ROM_MBC2_BAT EQU $06 +DEF CART_ROM_RAM EQU $08 +DEF CART_ROM_RAM_BAT EQU $09 +DEF CART_ROM_MMM01 EQU $0B +DEF CART_ROM_MMM01_RAM EQU $0C +DEF CART_ROM_MMM01_RAM_BAT EQU $0D +DEF CART_ROM_MBC3_BAT_RTC EQU $0F +DEF CART_ROM_MBC3_RAM_BAT_RTC EQU $10 +DEF CART_ROM_MBC3 EQU $11 +DEF CART_ROM_MBC3_RAM EQU $12 +DEF CART_ROM_MBC3_RAM_BAT EQU $13 +DEF CART_ROM_MBC5 EQU $19 +DEF CART_ROM_MBC5_BAT EQU $1A +DEF CART_ROM_MBC5_RAM_BAT EQU $1B +DEF CART_ROM_MBC5_RUMBLE EQU $1C +DEF CART_ROM_MBC5_RAM_RUMBLE EQU $1D +DEF CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E +DEF CART_ROM_MBC7_RAM_BAT_GYRO EQU $22 +DEF CART_ROM_POCKET_CAMERA EQU $FC +DEF CART_ROM_BANDAI_TAMA5 EQU $FD +DEF CART_ROM_HUDSON_HUC3 EQU $FE +DEF CART_ROM_HUDSON_HUC1 EQU $FF + +; $0148 ROM size +; these are kilobytes +DEF CART_ROM_32KB EQU $00 ; 2 banks +DEF CART_ROM_64KB EQU $01 ; 4 banks +DEF CART_ROM_128KB EQU $02 ; 8 banks +DEF CART_ROM_256KB EQU $03 ; 16 banks +DEF CART_ROM_512KB EQU $04 ; 32 banks +DEF CART_ROM_1024KB EQU $05 ; 64 banks +DEF CART_ROM_2048KB EQU $06 ; 128 banks +DEF CART_ROM_4096KB EQU $07 ; 256 banks +DEF CART_ROM_8192KB EQU $08 ; 512 banks +DEF CART_ROM_1152KB EQU $52 ; 72 banks +DEF CART_ROM_1280KB EQU $53 ; 80 banks +DEF CART_ROM_1536KB EQU $54 ; 96 banks + +; $0149 SRAM size +; these are kilobytes +DEF CART_SRAM_NONE EQU 0 +DEF CART_SRAM_2KB EQU 1 ; 1 incomplete bank +DEF CART_SRAM_8KB EQU 2 ; 1 bank +DEF CART_SRAM_32KB EQU 3 ; 4 banks +DEF CART_SRAM_128KB EQU 4 ; 16 banks + +DEF CART_SRAM_ENABLE EQU $0A +DEF CART_SRAM_DISABLE EQU $00 + +; $014A Destination code +DEF CART_DEST_JAPANESE EQU $00 +DEF CART_DEST_NON_JAPANESE EQU $01 + + +;*************************************************************************** +;* +;* Keypad related +;* +;*************************************************************************** + +DEF PADF_DOWN EQU $80 +DEF PADF_UP EQU $40 +DEF PADF_LEFT EQU $20 +DEF PADF_RIGHT EQU $10 +DEF PADF_START EQU $08 +DEF PADF_SELECT EQU $04 +DEF PADF_B EQU $02 +DEF PADF_A EQU $01 + +DEF PADB_DOWN EQU $7 +DEF PADB_UP EQU $6 +DEF PADB_LEFT EQU $5 +DEF PADB_RIGHT EQU $4 +DEF PADB_START EQU $3 +DEF PADB_SELECT EQU $2 +DEF PADB_B EQU $1 +DEF PADB_A EQU $0 + + +;*************************************************************************** +;* +;* Screen related +;* +;*************************************************************************** + +DEF SCRN_X EQU 160 ; Width of screen in pixels +DEF SCRN_Y EQU 144 ; Height of screen in pixels +DEF SCRN_X_B EQU 20 ; Width of screen in bytes +DEF SCRN_Y_B EQU 18 ; Height of screen in bytes + +DEF SCRN_VX EQU 256 ; Virtual width of screen in pixels +DEF SCRN_VY EQU 256 ; Virtual height of screen in pixels +DEF SCRN_VX_B EQU 32 ; Virtual width of screen in bytes +DEF SCRN_VY_B EQU 32 ; Virtual height of screen in bytes + + +;*************************************************************************** +;* +;* OAM related +;* +;*************************************************************************** + +; OAM attributes +; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS) +RSRESET +DEF OAMA_Y RB 1 ; y pos +DEF OAMA_X RB 1 ; x pos +DEF OAMA_TILEID RB 1 ; tile id +DEF OAMA_FLAGS RB 1 ; flags (see below) +DEF sizeof_OAM_ATTRS RB 0 + +DEF OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM + +; flags +DEF OAMF_PRI EQU %10000000 ; Priority +DEF OAMF_YFLIP EQU %01000000 ; Y flip +DEF OAMF_XFLIP EQU %00100000 ; X flip +DEF OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG) +DEF OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG) +DEF OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC) +DEF OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC) + +DEF OAMF_PALMASK EQU %00000111 ; Palette (GBC) + +DEF OAMB_PRI EQU 7 ; Priority +DEF OAMB_YFLIP EQU 6 ; Y flip +DEF OAMB_XFLIP EQU 5 ; X flip +DEF OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG) +DEF OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC) + + +;* +;* Nintendo scrolling logo +;* (Code won't work on a real GameBoy) +;* (if next lines are altered.) +MACRO NINTENDO_LOGO + DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D + DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 + DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E +ENDM + +; Deprecated constants. Please avoid using. + +DEF IEF_LCDC EQU %00000010 ; LCDC (see STAT) + ENDC ;HARDWARE_INC \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/gameplay-background.asm b/galactic-armada/src/main/states/gameplay/gameplay-background.asm index a8d98e4a..4cb32fd6 100644 --- a/galactic-armada/src/main/states/gameplay/gameplay-background.asm +++ b/galactic-armada/src/main/states/gameplay/gameplay-background.asm @@ -1,57 +1,57 @@ -; ANCHOR: gameplay-background-initialize -INCLUDE "src/main/includes/hardware.inc" -INCLUDE "src/main/includes/character-mapping.inc" - -SECTION "BackgroundVariables", WRAM0 - -mBackgroundScroll:: dw - -SECTION "GameplayBackgroundSection", ROM0 - - -InitializeBackground:: - - call DrawStarFieldBackground - - ld a, 0 - ld [mBackgroundScroll+0],a - ld a, 0 - ld [mBackgroundScroll+1],a - - ret -; ANCHOR_END: gameplay-background-initialize - -; ANCHOR: gameplay-background-update-start -; This is called during gameplay state on every frame -UpdateBackground:: - - ; Increase our scaled integer by 5 - ; Get our true (non-scaled) value, and save it for later usage in bc - ld a , [mBackgroundScroll+0] - add a , 5 - ld b,a - ld [mBackgroundScroll+0], a - ld a , [mBackgroundScroll+1] - adc a , 0 - ld c,a - ld [mBackgroundScroll+1], a -; ANCHOR_END: gameplay-background-update-start - -; ANCHOR: gameplay-background-update-end - ; Descale our scaled integer - ; shift bits to the right 4 spaces - srl c - rr b - srl c - rr b - srl c - rr b - srl c - rr b - - ; Use the de-scaled low byte as the backgrounds position - ld a,b - ld [rSCY], a - - ret +; ANCHOR: gameplay-background-initialize +INCLUDE "src/main/includes/hardware.inc" +INCLUDE "src/main/includes/character-mapping.inc" + +SECTION "BackgroundVariables", WRAM0 + +mBackgroundScroll:: dw + +SECTION "GameplayBackgroundSection", ROM0 + + +InitializeBackground:: + + call DrawStarFieldBackground + + ld a, 0 + ld [mBackgroundScroll+0],a + ld a, 0 + ld [mBackgroundScroll+1],a + + ret +; ANCHOR_END: gameplay-background-initialize + +; ANCHOR: gameplay-background-update-start +; This is called during gameplay state on every frame +UpdateBackground:: + + ; Increase our scaled integer by 5 + ; Get our true (non-scaled) value, and save it for later usage in bc + ld a , [mBackgroundScroll+0] + add a , 5 + ld b,a + ld [mBackgroundScroll+0], a + ld a , [mBackgroundScroll+1] + adc a , 0 + ld c,a + ld [mBackgroundScroll+1], a +; ANCHOR_END: gameplay-background-update-start + +; ANCHOR: gameplay-background-update-end + ; Descale our scaled integer + ; shift bits to the right 4 spaces + srl c + rr b + srl c + rr b + srl c + rr b + srl c + rr b + + ; Use the de-scaled low byte as the backgrounds position + ld a,b + ld [rSCY], a + + ret ; ANCHOR_END: gameplay-background-update-end \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/gameplay-state.asm b/galactic-armada/src/main/states/gameplay/gameplay-state.asm index 9efeda90..9435c149 100644 --- a/galactic-armada/src/main/states/gameplay/gameplay-state.asm +++ b/galactic-armada/src/main/states/gameplay/gameplay-state.asm @@ -1,122 +1,122 @@ -; ANCHOR: gameplay-data-variables -INCLUDE "src/main/includes/hardware.inc" -INCLUDE "src/main/includes/constants.inc" -INCLUDE "src/main/includes/character-mapping.inc" - -SECTION "GameplayVariables", WRAM0 - -wScore:: ds 6 -wLives:: db - -SECTION "GameplayState", ROM0 - -wScoreText:: db "score", 255 -wLivesText:: db "lives", 255 -; ANCHOR_END: gameplay-data-variables - -; ANCHOR: init-gameplay-state -InitGameplayState:: - - ld a, 3 - ld [wLives+0], a - - ld a, 0 - ld [wScore+0], a - ld [wScore+1], a - ld [wScore+2], a - ld [wScore+3], a - ld [wScore+4], a - ld [wScore+5], a - - call InitializeObjectPool - call InitializePlayer - - call WaitForVBlankStart - - ; Turn the LCD off - ld a, 0 - ld [rLCDC], a - - call ClearBackground - call ResetShadowOAM - call hOAMDMA - - call CopyPlayerTileDataIntoVRAM - call CopyEnemyTileDataIntoVRAM - call CopyBulletTileDataIntoVRAM - - ; Initiate STAT interrupts - call InitStatInterrupts - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ; Call Our function that draws text onto background/window tiles - ld de, $9c00 - ld hl, wScoreText - call DrawTextInHL_AtDE - - ; Call Our function that draws text onto background/window tiles - ld de, $9c0D - ld hl, wLivesText - call DrawTextInHL_AtDE - -; ANCHOR: draw-score - ld hl, wScore - ld de, $9C06 ; The window tilemap starts at $9C00 - ld b, 6 - call DrawBDigitsHL_OnDE -; ANCHOR_END: draw-score - - ld hl, wLives - ld de, $9C13 ; The window tilemap starts at $9C00 - ld b, 1 - call DrawBDigitsHL_OnDE - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ld a, 0 - ld [rWY], a - - ld a, 7 - ld [rWX], a - - ; Turn the LCD on - ld a, LCDCF_ON | LCDCF_BGON|LCDCF_OBJON | LCDCF_OBJ16 | LCDCF_WINON | LCDCF_WIN9C00|LCDCF_BG9800 - ld [rLCDC], a - - ret; -; ANCHOR_END: init-gameplay-state - -; ANCHOR: update-gameplay-state -UpdateGameplayState:: - - call TryToSpawnEnemies - call UpdateObjectPool - call UpdateBackground - - ld a, [wObjects+object_healthByte] - cp a, 250 - jp z, EndGameplay - - ret -; ANCHOR_END: update-gameplay-state - -; ANCHOR: end-gameplay-state -EndGameplay: - - ld hl, InitTitleScreenState - ld a, l - ld [wNextGameState_Initiate+0], a - ld a, h - ld [wNextGameState_Initiate+1], a - - ld hl, UpdateTitleScreenState - ld a, l - ld [wNextGameState_Update+0], a - ld a, h - ld [wNextGameState_Update+1], a - - ret +; ANCHOR: gameplay-data-variables +INCLUDE "src/main/includes/hardware.inc" +INCLUDE "src/main/includes/constants.inc" +INCLUDE "src/main/includes/character-mapping.inc" + +SECTION "GameplayVariables", WRAM0 + +wScore:: ds 6 +wLives:: db + +SECTION "GameplayState", ROM0 + +wScoreText:: db "score", 255 +wLivesText:: db "lives", 255 +; ANCHOR_END: gameplay-data-variables + +; ANCHOR: init-gameplay-state +InitGameplayState:: + + ld a, 3 + ld [wLives+0], a + + ld a, 0 + ld [wScore+0], a + ld [wScore+1], a + ld [wScore+2], a + ld [wScore+3], a + ld [wScore+4], a + ld [wScore+5], a + + call InitializeObjectPool + call InitializePlayer + + call WaitForVBlankStart + + ; Turn the LCD off + ld a, 0 + ld [rLCDC], a + + call ClearBackground + call ResetShadowOAM + call hOAMDMA + + call CopyPlayerTileDataIntoVRAM + call CopyEnemyTileDataIntoVRAM + call CopyBulletTileDataIntoVRAM + + ; Initiate STAT interrupts + call InitStatInterrupts + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ; Call Our function that draws text onto background/window tiles + ld de, $9c00 + ld hl, wScoreText + call DrawTextInHL_AtDE + + ; Call Our function that draws text onto background/window tiles + ld de, $9c0D + ld hl, wLivesText + call DrawTextInHL_AtDE + +; ANCHOR: draw-score + ld hl, wScore + ld de, $9C06 ; The window tilemap starts at $9C00 + ld b, 6 + call DrawBDigitsHL_OnDE +; ANCHOR_END: draw-score + + ld hl, wLives + ld de, $9C13 ; The window tilemap starts at $9C00 + ld b, 1 + call DrawBDigitsHL_OnDE + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ld a, 0 + ld [rWY], a + + ld a, 7 + ld [rWX], a + + ; Turn the LCD on + ld a, LCDCF_ON | LCDCF_BGON|LCDCF_OBJON | LCDCF_OBJ16 | LCDCF_WINON | LCDCF_WIN9C00|LCDCF_BG9800 + ld [rLCDC], a + + ret; +; ANCHOR_END: init-gameplay-state + +; ANCHOR: update-gameplay-state +UpdateGameplayState:: + + call TryToSpawnEnemies + call UpdateObjectPool + call UpdateBackground + + ld a, [wObjects+object_healthByte] + cp a, 250 + jp z, EndGameplay + + ret +; ANCHOR_END: update-gameplay-state + +; ANCHOR: end-gameplay-state +EndGameplay: + + ld hl, InitTitleScreenState + ld a, l + ld [wNextGameState_Initiate+0], a + ld a, h + ld [wNextGameState_Initiate+1], a + + ld hl, UpdateTitleScreenState + ld a, l + ld [wNextGameState_Update+0], a + ld a, h + ld [wNextGameState_Update+1], a + + ret ; ANCHOR_END: end-gameplay-state \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/hud.asm b/galactic-armada/src/main/states/gameplay/hud.asm index 196b64aa..771fd46b 100644 --- a/galactic-armada/src/main/states/gameplay/hud.asm +++ b/galactic-armada/src/main/states/gameplay/hud.asm @@ -1,70 +1,70 @@ -INCLUDE "src/main/includes/hardware.inc" - - -SECTION "GameplayHUD", ROM0 - -; ANCHOR: hud-increase-score -IncreaseScore:: - - ; We have 6 digits, start with the right-most digit (the last byte) - ld c, 0 - ld hl, wScore+5 - -IncreaseScore_Loop: - - ; Increase the digit - ld a, [hl] - inc a - ld [hl], a - - ; Stop if it hasn't gone past 0 - cp a, 9 - ret c - -; If it HAS gone past 9 -IncreaseScore_Next: - - ; Increase a counter so we can not go out of our scores bounds - ld a, c - inc a - ld c, a - - ; Check if we've gone our o our scores bounds - cp a, 6 - ret z - - ; Reset the current digit to zero - ; Then go to the previous byte (visually: to the left) - ld a, 0 - ld [hl], a - ld [hld], a - - jp IncreaseScore_Loop -; ANCHOR_END: hud-increase-score - - -; ANCHOR: hud-draw-lives -DrawBDigitsHL_OnDE:: - - ; How many digits remain in b - ld a, b - and a - ret z - - ; Decrease b by one - dec a - ld b,a - - ld a, [hl] - add a, 10 ; our numeric tiles start at tile 10, so add to 10 to each bytes value - ld [de], a - - ; Increase which tile we are drawing to - inc de - - ; Increase the tile we are drawing - inc hl - - jp DrawBDigitsHL_OnDE -; ANCHOR_END: hud-draw-lives +INCLUDE "src/main/includes/hardware.inc" + + +SECTION "GameplayHUD", ROM0 + +; ANCHOR: hud-increase-score +IncreaseScore:: + + ; We have 6 digits, start with the right-most digit (the last byte) + ld c, 0 + ld hl, wScore+5 + +IncreaseScore_Loop: + + ; Increase the digit + ld a, [hl] + inc a + ld [hl], a + + ; Stop if it hasn't gone past 0 + cp a, 9 + ret c + +; If it HAS gone past 9 +IncreaseScore_Next: + + ; Increase a counter so we can not go out of our scores bounds + ld a, c + inc a + ld c, a + + ; Check if we've gone our o our scores bounds + cp a, 6 + ret z + + ; Reset the current digit to zero + ; Then go to the previous byte (visually: to the left) + ld a, 0 + ld [hl], a + ld [hld], a + + jp IncreaseScore_Loop +; ANCHOR_END: hud-increase-score + + +; ANCHOR: hud-draw-lives +DrawBDigitsHL_OnDE:: + + ; How many digits remain in b + ld a, b + and a + ret z + + ; Decrease b by one + dec a + ld b,a + + ld a, [hl] + add a, 10 ; our numeric tiles start at tile 10, so add to 10 to each bytes value + ld [de], a + + ; Increase which tile we are drawing to + inc de + + ; Increase the tile we are drawing + inc hl + + jp DrawBDigitsHL_OnDE +; ANCHOR_END: hud-draw-lives \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/interrupts.asm b/galactic-armada/src/main/states/gameplay/interrupts.asm index d2c02b9f..51c428e2 100644 --- a/galactic-armada/src/main/states/gameplay/interrupts.asm +++ b/galactic-armada/src/main/states/gameplay/interrupts.asm @@ -1,73 +1,73 @@ - -; ANCHOR: interrupts-start -INCLUDE "src/main/includes/hardware.inc" - - SECTION "Interrupts", ROM0 - - DisableInterrupts:: - ld a, 0 - ldh [rSTAT], a - di - ret - -InitStatInterrupts:: - - ld a, IEF_STAT - ldh [rIE], a - xor a, a ; This is equivalent to `ld a, 0`! - ldh [rIF], a - ei - - ; This makes our stat interrupts occur when the current scanline is equal to the rLYC register - ld a, STATF_LYC - ldh [rSTAT], a - - ; We'll start with the first scanline - ; The first stat interrupt will call the next time rLY = 0 - ld a, 0 - ldh [rLYC], a - - ret -; ANCHOR_END: interrupts-start - -; ANCHOR: interrupts-section -; Define a new section and hard-code it to be at $0048. -SECTION "Stat Interrupt", ROM0[$0048] -StatInterrupt: - - push af - - ; Check if we are on the first scanline - ldh a, [rLYC] - cp 0 - jp z, LYCEqualsZero - -LYCEquals8: - - ; Don't call the next stat interrupt until scanline 8 - ld a, 0 - ldh [rLYC], a - - ; Turn the LCD on including sprites. But no window - ld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJON | LCDCF_OBJ16 | LCDCF_WINOFF | LCDCF_WIN9C00 - ldh [rLCDC], a - - jp EndStatInterrupts - -LYCEqualsZero: - - ; Don't call the next stat interrupt until scanline 8 - ld a, 8 - ldh [rLYC], a - - ; Turn the LCD on including the window. But no sprites - ld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJOFF | LCDCF_OBJ16| LCDCF_WINON | LCDCF_WIN9C00 - ldh [rLCDC], a - - -EndStatInterrupts: - - pop af - - reti; + +; ANCHOR: interrupts-start +INCLUDE "src/main/includes/hardware.inc" + + SECTION "Interrupts", ROM0 + + DisableInterrupts:: + ld a, 0 + ldh [rSTAT], a + di + ret + +InitStatInterrupts:: + + ld a, IEF_STAT + ldh [rIE], a + xor a, a ; This is equivalent to `ld a, 0`! + ldh [rIF], a + ei + + ; This makes our stat interrupts occur when the current scanline is equal to the rLYC register + ld a, STATF_LYC + ldh [rSTAT], a + + ; We'll start with the first scanline + ; The first stat interrupt will call the next time rLY = 0 + ld a, 0 + ldh [rLYC], a + + ret +; ANCHOR_END: interrupts-start + +; ANCHOR: interrupts-section +; Define a new section and hard-code it to be at $0048. +SECTION "Stat Interrupt", ROM0[$0048] +StatInterrupt: + + push af + + ; Check if we are on the first scanline + ldh a, [rLYC] + cp 0 + jp z, LYCEqualsZero + +LYCEquals8: + + ; Don't call the next stat interrupt until scanline 8 + ld a, 0 + ldh [rLYC], a + + ; Turn the LCD on including sprites. But no window + ld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJON | LCDCF_OBJ16 | LCDCF_WINOFF | LCDCF_WIN9C00 + ldh [rLCDC], a + + jp EndStatInterrupts + +LYCEqualsZero: + + ; Don't call the next stat interrupt until scanline 8 + ld a, 8 + ldh [rLYC], a + + ; Turn the LCD on including the window. But no sprites + ld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJOFF | LCDCF_OBJ16| LCDCF_WINON | LCDCF_WIN9C00 + ldh [rLCDC], a + + +EndStatInterrupts: + + pop af + + reti; ; ANCHOR_END: interrupts-section \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/objects/bullets.asm b/galactic-armada/src/main/states/gameplay/objects/bullets.asm index fb8a9315..b5a7c670 100644 --- a/galactic-armada/src/main/states/gameplay/objects/bullets.asm +++ b/galactic-armada/src/main/states/gameplay/objects/bullets.asm @@ -1,89 +1,89 @@ - -; ANCHOR: bullets-top -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" - -SECTION "Bullets", ROM0 - - -; ANCHOR_END: bullets-top - -UpdateBullet:: - - ; The start of our object will be in bc - ; Copy that to hl so we can check/adjust some bytes - ld h,b - ld l, c - - ; Get to our y position - ld de, object_yLowByte - add hl, de - - ; subtract our speed from our y position - ld a, [hl] - sub a, BULLET_MOVE_SPEED - ld [hli], a - ld a, [hl] - sbc a, 0 - ld [hl], a - - ; If our high byte is below 10, we're not offscreen - ld a, [hl] - cp a, 10 - ret c - -UpdateBullet_OutOfScreen: - - ; get the start of our object back in hl - ld h,b - ld l, c - - ; Set the first (active) byte as 0 (inactive) - ld a, 0 - ld [hl], a - - ret -; ANCHOR_END: draw-bullets - -; ANCHOR: fire-bullets -FireNextBullet:: - - ld hl, wObjects+BULLETS_START - ld b, MAX_BULLET_COUNT - - ; Get the next available bullet, and put it's address in hl - ; if the zero flag is set, stop early - call GetNextAvailableObject_InHL - ret z -; ANCHOR_END: fire-bullets - -; ANCHOR: fire-bullets2 - ld a, 1 - ld [hli], a - - ld a, [wObjects+object_yLowByte] - ld [hli], a - ld a, [wObjects+object_yHighByte] - ld [hli], a - ld a, [wObjects+object_xLowByte] - ld [hli], a - ld a, [wObjects+object_xHighByte] - ld [hli], a - - ld a, LOW(bulletMetasprite) - ld [hli], a - - ld a, HIGH(bulletMetasprite) - ld [hli], a - - ld a, 1 - ld [hli], a - - ld a, LOW(UpdateBullet) - ld [hli], a - ld a, HIGH(UpdateBullet) - ld [hli], a - - - ret + +; ANCHOR: bullets-top +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" + +SECTION "Bullets", ROM0 + + +; ANCHOR_END: bullets-top + +UpdateBullet:: + + ; The start of our object will be in bc + ; Copy that to hl so we can check/adjust some bytes + ld h,b + ld l, c + + ; Get to our y position + ld de, object_yLowByte + add hl, de + + ; subtract our speed from our y position + ld a, [hl] + sub a, BULLET_MOVE_SPEED + ld [hli], a + ld a, [hl] + sbc a, 0 + ld [hl], a + + ; If our high byte is below 10, we're not offscreen + ld a, [hl] + cp a, 10 + ret c + +UpdateBullet_OutOfScreen: + + ; get the start of our object back in hl + ld h,b + ld l, c + + ; Set the first (active) byte as 0 (inactive) + ld a, 0 + ld [hl], a + + ret +; ANCHOR_END: draw-bullets + +; ANCHOR: fire-bullets +FireNextBullet:: + + ld hl, wObjects+BULLETS_START + ld b, MAX_BULLET_COUNT + + ; Get the next available bullet, and put it's address in hl + ; if the zero flag is set, stop early + call GetNextAvailableObject_InHL + ret z +; ANCHOR_END: fire-bullets + +; ANCHOR: fire-bullets2 + ld a, 1 + ld [hli], a + + ld a, [wObjects+object_yLowByte] + ld [hli], a + ld a, [wObjects+object_yHighByte] + ld [hli], a + ld a, [wObjects+object_xLowByte] + ld [hli], a + ld a, [wObjects+object_xHighByte] + ld [hli], a + + ld a, LOW(bulletMetasprite) + ld [hli], a + + ld a, HIGH(bulletMetasprite) + ld [hli], a + + ld a, 1 + ld [hli], a + + ld a, LOW(UpdateBullet) + ld [hli], a + ld a, HIGH(UpdateBullet) + ld [hli], a + + + ret ; ANCHOR_END: fire-bullets2 \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/objects/collision/object-object-collision.asm b/galactic-armada/src/main/states/gameplay/objects/collision/object-object-collision.asm index c8425516..df35a657 100644 --- a/galactic-armada/src/main/states/gameplay/objects/collision/object-object-collision.asm +++ b/galactic-armada/src/main/states/gameplay/objects/collision/object-object-collision.asm @@ -1,115 +1,115 @@ - -; ANCHOR: enemy-bullet-collision-start -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" -include "src/main/includes/hardware.inc" - - -SECTION "ObjectObjectCollisionVariables", WRAM0 -wSizeY:: db -wSizeX:: db -wCheckByte: db - -SECTION "ObjectObjectCollision", ROM0 - -CheckCollisionWithObjectsInHL_andDE:: - - ; SAve original values for y axis - push de - push hl - -XAxis: - - ; Save which byte we are checking - ld a, object_xLowByte - ld [wCheckByte], a - - ; Save if the minimum distance - ld a, [wSizeX] - ld [wSize], a - - call CheckObjectBytesOfObjects_InDE_AndHL - - ; Restore original vaues just in case - pop hl - pop de - - jp nz, YAxis - - ld a,0 - and a - ret - -YAxis: - - ; Save which byte we are checking - ld a, object_yLowByte - ld [wCheckByte], a - - ; Save if the minimum distance - ld a, [wSizeY] - ld [wSize], a - - call CheckObjectBytesOfObjects_InDE_AndHL - - ; Normal return with the z/c flags as-is - ret - - -CheckObjectBytesOfObjects_InDE_AndHL:: - - ; put de in hl so we can get the x bytes (for the de object) in bc and descale just to c - - push hl - - ; Offset de by the check byte - ld a, [wCheckByte] - add a,e - ld e,a - - ; copy the low byte to c - ld a, [de] - ld c, a - - ; move to the high byte - inc de - - ; copy the high byte to b - ld a, [de] - ld b, a - - ; Descale - REPT 4 - srl b - rr c - ENDR - - ld a, c - ld [wObject1Value], a - - pop hl - - ; get the bytes (for the hl object) in bc and descale just to c - ld a, [wCheckByte] - add a, l - ld l, a - - ; move to the high byte - ld a, [hli] - ld c, a - - ; copy the high byte to b - ld a, [hl] - ld b, a - - ; Descale - REPT 4 - srl b - rr c - ENDR - - ld a, c - ld [wObject2Value], a - - call CheckObjectPositionDifference + +; ANCHOR: enemy-bullet-collision-start +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" +include "src/main/includes/hardware.inc" + + +SECTION "ObjectObjectCollisionVariables", WRAM0 +wSizeY:: db +wSizeX:: db +wCheckByte: db + +SECTION "ObjectObjectCollision", ROM0 + +CheckCollisionWithObjectsInHL_andDE:: + + ; SAve original values for y axis + push de + push hl + +XAxis: + + ; Save which byte we are checking + ld a, object_xLowByte + ld [wCheckByte], a + + ; Save if the minimum distance + ld a, [wSizeX] + ld [wSize], a + + call CheckObjectBytesOfObjects_InDE_AndHL + + ; Restore original vaues just in case + pop hl + pop de + + jp nz, YAxis + + ld a,0 + and a + ret + +YAxis: + + ; Save which byte we are checking + ld a, object_yLowByte + ld [wCheckByte], a + + ; Save if the minimum distance + ld a, [wSizeY] + ld [wSize], a + + call CheckObjectBytesOfObjects_InDE_AndHL + + ; Normal return with the z/c flags as-is + ret + + +CheckObjectBytesOfObjects_InDE_AndHL:: + + ; put de in hl so we can get the x bytes (for the de object) in bc and descale just to c + + push hl + + ; Offset de by the check byte + ld a, [wCheckByte] + add a,e + ld e,a + + ; copy the low byte to c + ld a, [de] + ld c, a + + ; move to the high byte + inc de + + ; copy the high byte to b + ld a, [de] + ld b, a + + ; Descale + REPT 4 + srl b + rr c + ENDR + + ld a, c + ld [wObject1Value], a + + pop hl + + ; get the bytes (for the hl object) in bc and descale just to c + ld a, [wCheckByte] + add a, l + ld l, a + + ; move to the high byte + ld a, [hli] + ld c, a + + ; copy the high byte to b + ld a, [hl] + ld b, a + + ; Descale + REPT 4 + srl b + rr c + ENDR + + ld a, c + ld [wObject2Value], a + + call CheckObjectPositionDifference ret \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/objects/enemies-spawning.asm b/galactic-armada/src/main/states/gameplay/objects/enemies-spawning.asm index c10319f0..f187bd74 100644 --- a/galactic-armada/src/main/states/gameplay/objects/enemies-spawning.asm +++ b/galactic-armada/src/main/states/gameplay/objects/enemies-spawning.asm @@ -1,112 +1,112 @@ -; ANCHOR: enemies-start -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" - -; ANCHOR_END: enemies-start -SECTION "EnemySpawningVariables", WRAM0 - -wSpawnCounter: db - -SECTION "EnemySpawning", ROM0 - - -; ANCHOR: enemies-spawn -TryToSpawnEnemies:: - - ; Increase our spwncounter - ld a, [wSpawnCounter] - inc a - ld [wSpawnCounter], a - - ; Check our spawn acounter - ; Stop if it's below a given value - ld a, [wSpawnCounter] - cp a, ENEMY_SPAWN_DELAY_MAX - ret c - - - ld hl, wObjects+ENEMIES_START - ld b, MAX_ENEMY_COUNT - - ; Get the next available enemy, and put it's address in hl - ; if the zero flag is set, stop early - call GetNextAvailableObject_InHL - ret z - -.GetSpawnPosition - - push hl - - ; Generate a semi random value - call rand - - pop hl - - ; make sure it's not above 150 - ld a,b - cp a, 150 - ret nc - - ; make sure it's not below 24 - ld a, b - cp a, 24 - ret c - -.SpawnEnemy - - ; reset our spawn counter - ld a, 0 - ld [wSpawnCounter], a - - ; Set as active - ld a, 1 - ld [hli], a - - ; y position = 0 - ld a, 0 - ld [hli], a - ld [hli], a - - ; x high byte = 0 - ; b will become our x low byte (originally set from 'rand') - ld a, 0 - - REPT 4 - sla b - rl a - - ENDR - ; got to the high byte - inc hl - - ; set our high byte from a - ; go back to the low byte - ld [hld], a - - ; set the low byte from b - ld a, b - ld [hli], a - - inc hl - - ; set our metasprite - ld a, LOW(enemyShipMetasprite) - ld [hli], a - ld a, HIGH(enemyShipMetasprite) - ld [hli], a - - ; set our health - ld a, 1 - ld [hli], a - - ld a, LOW(UpdateEnemy) - ld [hli], a - ld a, HIGH(UpdateEnemy) - ld [hli], a - - ; set our damage - ld a, 0 - ld [hli], a - - ret +; ANCHOR: enemies-start +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" + +; ANCHOR_END: enemies-start +SECTION "EnemySpawningVariables", WRAM0 + +wSpawnCounter: db + +SECTION "EnemySpawning", ROM0 + + +; ANCHOR: enemies-spawn +TryToSpawnEnemies:: + + ; Increase our spwncounter + ld a, [wSpawnCounter] + inc a + ld [wSpawnCounter], a + + ; Check our spawn acounter + ; Stop if it's below a given value + ld a, [wSpawnCounter] + cp a, ENEMY_SPAWN_DELAY_MAX + ret c + + + ld hl, wObjects+ENEMIES_START + ld b, MAX_ENEMY_COUNT + + ; Get the next available enemy, and put it's address in hl + ; if the zero flag is set, stop early + call GetNextAvailableObject_InHL + ret z + +.GetSpawnPosition + + push hl + + ; Generate a semi random value + call rand + + pop hl + + ; make sure it's not above 150 + ld a,b + cp a, 150 + ret nc + + ; make sure it's not below 24 + ld a, b + cp a, 24 + ret c + +.SpawnEnemy + + ; reset our spawn counter + ld a, 0 + ld [wSpawnCounter], a + + ; Set as active + ld a, 1 + ld [hli], a + + ; y position = 0 + ld a, 0 + ld [hli], a + ld [hli], a + + ; x high byte = 0 + ; b will become our x low byte (originally set from 'rand') + ld a, 0 + + REPT 4 + sla b + rl a + + ENDR + ; got to the high byte + inc hl + + ; set our high byte from a + ; go back to the low byte + ld [hld], a + + ; set the low byte from b + ld a, b + ld [hli], a + + inc hl + + ; set our metasprite + ld a, LOW(enemyShipMetasprite) + ld [hli], a + ld a, HIGH(enemyShipMetasprite) + ld [hli], a + + ; set our health + ld a, 1 + ld [hli], a + + ld a, LOW(UpdateEnemy) + ld [hli], a + ld a, HIGH(UpdateEnemy) + ld [hli], a + + ; set our damage + ld a, 0 + ld [hli], a + + ret ; ANCHOR_END: enemies-spawn \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/objects/enemies.asm b/galactic-armada/src/main/states/gameplay/objects/enemies.asm index f2077821..826bfa1a 100644 --- a/galactic-armada/src/main/states/gameplay/objects/enemies.asm +++ b/galactic-armada/src/main/states/gameplay/objects/enemies.asm @@ -1,179 +1,179 @@ -; ANCHOR: enemies-start -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" - -SECTION "EnemyVariables", WRAM0 - -wCurrentEnemyX:: db -wCurrentEnemyY:: db - -wSpawnCounter: db -wNextEnemyXPosition: db -wActiveEnemyCounter::db -wUpdateEnemiesCounter:db -wUpdateEnemiesCurrentEnemyAddress::dw - -; ANCHOR_END: enemies-start -SECTION "Enemies", ROM0 - -; ANCHOR: enemies-update-per-enemy2 -UpdateEnemy:: - - ; get the start of our object back in hl - ld h,b - ld l, c - - ; Save our first bytye - push hl - - ; Get our y position - ld bc, object_yLowByte - add hl, bc - - ; add 10 to our y position - ld a, [hl] - add a, 10 - ld [hli], a - ld a, [hl] - adc a, 0 - ld [hl], a - - - ; If our high byte is below 10, we're not offscreen - ld a, [hl] - pop hl - - cp a, 10 - jp nc, DeactivateEnemy - - -.UpdateEnemy_CheckPlayerCollision - - push hl - - - ld a, 16 - ld [wSizeX], a - ld [wSizeY], a - ld de, wObjects - call CheckCollisionWithObjectsInHL_andDE - - pop hl - jp nz, EnemyPlayerCollision - -.UpdateEnemy_CheckAllBulletCollision - - ld b,MAX_BULLET_COUNT - ld de, wObjects+BULLETS_START - -UpdateEnemy_CheckBulletCollision: - - ; Save the start of our enemy's bytes - ; Save the current bullet counter - ; Save which bullet we are looking at - push hl - push bc - push de - - ld a, 16 - ld [wSizeX], a - ld [wSizeY], a - call CheckCollisionWithObjectsInHL_andDE - - ; Retrieve the curernt bullet counter - ; Return hl to the start of our enemies bytes - ; Retrieve which object we were looking at - pop de - pop bc - pop hl - - push hl - - jp nz, DamageEnemy - - pop hl - -MoveToNextEnemy: - - ; Decrease b - ; return if it reaches zero - ld a, b - dec a - ld b, a - and a - ret z - - ; Move to the next object - ld a, e - add a, PER_OBJECT_BYTES_COUNT - ld e, a - - jp UpdateEnemy_CheckBulletCollision - -EnemyPlayerCollision:: - - push hl - push bc - - call DamagePlayer - - ld hl, wLives - ld de, $9C13 ; The window tilemap starts at $9C00 - ld b, 1 - call DrawBDigitsHL_OnDE - - pop bc - pop hl - - jp DeactivateEnemy - -KillEnemy:: - - ; Deactivate our bullet in de - ld a,0 - ld [de], a - - push hl - push bc - - call IncreaseScore; - - ld hl, wScore - ld de, $9C06 ; The window tilemap starts at $9C00 - ld b, 6 - call DrawBDigitsHL_OnDE - - pop bc - pop hl - -DeactivateEnemy:: - - ld a,0 - ld [hl], a - - ret - -DamageEnemy: - - push de - ld de, object_healthByte - add hl, de - ld a, [hl] - dec a - - pop hl - pop de - cp a, 255 - jp z, KillEnemy - - ld [hl], a - - push de - ld de, object_damageByte-object_healthByte - add hl, de - pop de - - ld a, 128 - ld [hl], a - - jp MoveToNextEnemy +; ANCHOR: enemies-start +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" + +SECTION "EnemyVariables", WRAM0 + +wCurrentEnemyX:: db +wCurrentEnemyY:: db + +wSpawnCounter: db +wNextEnemyXPosition: db +wActiveEnemyCounter::db +wUpdateEnemiesCounter:db +wUpdateEnemiesCurrentEnemyAddress::dw + +; ANCHOR_END: enemies-start +SECTION "Enemies", ROM0 + +; ANCHOR: enemies-update-per-enemy2 +UpdateEnemy:: + + ; get the start of our object back in hl + ld h,b + ld l, c + + ; Save our first bytye + push hl + + ; Get our y position + ld bc, object_yLowByte + add hl, bc + + ; add 10 to our y position + ld a, [hl] + add a, 10 + ld [hli], a + ld a, [hl] + adc a, 0 + ld [hl], a + + + ; If our high byte is below 10, we're not offscreen + ld a, [hl] + pop hl + + cp a, 10 + jp nc, DeactivateEnemy + + +.UpdateEnemy_CheckPlayerCollision + + push hl + + + ld a, 16 + ld [wSizeX], a + ld [wSizeY], a + ld de, wObjects + call CheckCollisionWithObjectsInHL_andDE + + pop hl + jp nz, EnemyPlayerCollision + +.UpdateEnemy_CheckAllBulletCollision + + ld b,MAX_BULLET_COUNT + ld de, wObjects+BULLETS_START + +UpdateEnemy_CheckBulletCollision: + + ; Save the start of our enemy's bytes + ; Save the current bullet counter + ; Save which bullet we are looking at + push hl + push bc + push de + + ld a, 16 + ld [wSizeX], a + ld [wSizeY], a + call CheckCollisionWithObjectsInHL_andDE + + ; Retrieve the curernt bullet counter + ; Return hl to the start of our enemies bytes + ; Retrieve which object we were looking at + pop de + pop bc + pop hl + + push hl + + jp nz, DamageEnemy + + pop hl + +MoveToNextEnemy: + + ; Decrease b + ; return if it reaches zero + ld a, b + dec a + ld b, a + and a + ret z + + ; Move to the next object + ld a, e + add a, PER_OBJECT_BYTES_COUNT + ld e, a + + jp UpdateEnemy_CheckBulletCollision + +EnemyPlayerCollision:: + + push hl + push bc + + call DamagePlayer + + ld hl, wLives + ld de, $9C13 ; The window tilemap starts at $9C00 + ld b, 1 + call DrawBDigitsHL_OnDE + + pop bc + pop hl + + jp DeactivateEnemy + +KillEnemy:: + + ; Deactivate our bullet in de + ld a,0 + ld [de], a + + push hl + push bc + + call IncreaseScore; + + ld hl, wScore + ld de, $9C06 ; The window tilemap starts at $9C00 + ld b, 6 + call DrawBDigitsHL_OnDE + + pop bc + pop hl + +DeactivateEnemy:: + + ld a,0 + ld [hl], a + + ret + +DamageEnemy: + + push de + ld de, object_healthByte + add hl, de + ld a, [hl] + dec a + + pop hl + pop de + cp a, 255 + jp z, KillEnemy + + ld [hl], a + + push de + ld de, object_damageByte-object_healthByte + add hl, de + pop de + + ld a, 128 + ld [hl], a + + jp MoveToNextEnemy diff --git a/galactic-armada/src/main/states/gameplay/objects/object-pool.asm b/galactic-armada/src/main/states/gameplay/objects/object-pool.asm index 6a0f9303..9d0fc80e 100644 --- a/galactic-armada/src/main/states/gameplay/objects/object-pool.asm +++ b/galactic-armada/src/main/states/gameplay/objects/object-pool.asm @@ -1,216 +1,216 @@ -; ANCHOR: objects-pool-top -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" - -SECTION "ObjectVariables", WRAM0 - -wObjects:: ds MAX_OBJECT_COUNT*PER_OBJECT_BYTES_COUNT -wObjectsEnd:: db -wObjectsFlash:: db - -SECTION "Objects", ROM0 - -; ANCHOR_END: objects-pool-top - -; ANCHOR: initialize-objects -InitializeObjectPool:: - - ; The active byte will awlays be 0 or 1 - ; When looping through the wObjects, if we read 255 we've reached wObjectsEnd - ld a, 255 - ld [wObjectsEnd], a - - ld hl, wObjects - ld b, MAX_OBJECT_COUNT - -InitializeObjectPool_Loop: - - ld a, 0 - - ; Default each byte as 0 - ; Using REPT incase the object size changes - REPT PER_OBJECT_BYTES_COUNT - ld [hli], a - ENDR - - ; Decrease how many we have to initialize - ; Stop this loop when b reaches zero - ld a, b - dec a - and a - ret z - - ld b, a - jp InitializeObjectPool_Loop -; ANCHOR_END: initialize-objects - -; ANCHOR: update-objects-1 -UpdateObjectPool:: - - ; Increase our flash - ld a, [wObjectsFlash] - add a,25 - ld [wObjectsFlash], a -; ANCHOR_END: update-objects-1 - -; ANCHOR: update-objects-2 - - ld hl, wObjects - -UpdateObjectPool_Loop: - - ; The active byte should be 0 or 1 - ; When we reach a 255, we've reached the wObjectsEnd - ld a, [hl] - cp a, 255 - ret z - - ; Check if the object is active - and a - jp z, UpdateObjectPool_GoToNextObject - -; ANCHOR_END: update-objects-2 - -; ANCHOR: update-objects-3 -.UpdateObject - - push hl - - ld b, h - ld c, l - - ; Move to the update - ld de, object_updateLowByte - add hl, de - - ; hl points to the low byte for the address of the update function - ; copy that address INTO hl - ld a, [hli] - ld h, [hl] - ld l, a - - call callHL - - pop hl - - ; Check if we're inactive after updating - ld a, [hl] - and a - jp z , UpdateObjectPool_GoToNextObject -; ANCHOR_END: update-objects-3 - -; ANCHOR: update-objects-4 -.CheckIsDamaged - - push hl - - ; Move to the y low byte - ld de, object_damageByte - add hl, de - - ; Check this object is damaged - ld a, [hl] - and a - jp z, NotDamaged - jp Damaged - -Damaged: - - ; decrease our damage byte - dec a - ld [hl], a - - pop hl - - ; if our objects timer is greater than 0 we'll not draw - ld a, [wObjectsFlash] - cp a, 128 - - jp c, UpdateObjectPool_GoToNextObject - -NotDamaged: - - pop hl - jp z, GetXAndY -; ANCHOR_END: update-objects-4 - -; ANCHOR: update-objects-5 -GetXAndY: - - push hl - - ; Move to the y low byte - ld de, object_yLowByte - add hl, de - - ; Copy our y position to bc - ld a, [hli] - ld c, a - ld a, [hli] - ld b, a - - ; Copy our x position to de - ld a, [hli] - ld e, a - ld a, [hli] - ld d, a - -.RenderObjectMetasprite - ld a, [hli] - ld h, [hl] - ld l, a - - call RenderMetasprite - - pop hl - - jp UpdateObjectPool_GoToNextObject -; ANCHOR_END: update-objects-5 -; ANCHOR: update-objects-6 -UpdateObjectPool_GoToNextObject: - - ld de, PER_OBJECT_BYTES_COUNT - add hl, de - - jp UpdateObjectPool_Loop -; ANCHOR_END: update-objects-6 - -; ANCHOR: get-next-available-object -; parameters -; hl = start of array bytes -; b = number of objects to check -; example: -; ld hl, wObjects+BULLETS_START -; ld b, MAX_BULLET_COUNT -GetNextAvailableObject_InHL:: - -GetNextAvailableObject_Loop: - - ld a, [hl] - and a - jp nz, GetNextAvailableObject_Next - - - ld a, 1 - and a - ret - -GetNextAvailableObject_Next: - - ld a, b - dec a - ld b, a - - jp z, GetNextAvailableObject_End - - ; move to the next object - ld de, PER_OBJECT_BYTES_COUNT - add hl, de - - jp GetNextAvailableObject_Loop -GetNextAvailableObject_End: - - ld a, 0 - and a - ret; +; ANCHOR: objects-pool-top +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" + +SECTION "ObjectVariables", WRAM0 + +wObjects:: ds MAX_OBJECT_COUNT*PER_OBJECT_BYTES_COUNT +wObjectsEnd:: db +wObjectsFlash:: db + +SECTION "Objects", ROM0 + +; ANCHOR_END: objects-pool-top + +; ANCHOR: initialize-objects +InitializeObjectPool:: + + ; The active byte will awlays be 0 or 1 + ; When looping through the wObjects, if we read 255 we've reached wObjectsEnd + ld a, 255 + ld [wObjectsEnd], a + + ld hl, wObjects + ld b, MAX_OBJECT_COUNT + +InitializeObjectPool_Loop: + + ld a, 0 + + ; Default each byte as 0 + ; Using REPT incase the object size changes + REPT PER_OBJECT_BYTES_COUNT + ld [hli], a + ENDR + + ; Decrease how many we have to initialize + ; Stop this loop when b reaches zero + ld a, b + dec a + and a + ret z + + ld b, a + jp InitializeObjectPool_Loop +; ANCHOR_END: initialize-objects + +; ANCHOR: update-objects-1 +UpdateObjectPool:: + + ; Increase our flash + ld a, [wObjectsFlash] + add a,25 + ld [wObjectsFlash], a +; ANCHOR_END: update-objects-1 + +; ANCHOR: update-objects-2 + + ld hl, wObjects + +UpdateObjectPool_Loop: + + ; The active byte should be 0 or 1 + ; When we reach a 255, we've reached the wObjectsEnd + ld a, [hl] + cp a, 255 + ret z + + ; Check if the object is active + and a + jp z, UpdateObjectPool_GoToNextObject + +; ANCHOR_END: update-objects-2 + +; ANCHOR: update-objects-3 +.UpdateObject + + push hl + + ld b, h + ld c, l + + ; Move to the update + ld de, object_updateLowByte + add hl, de + + ; hl points to the low byte for the address of the update function + ; copy that address INTO hl + ld a, [hli] + ld h, [hl] + ld l, a + + call callHL + + pop hl + + ; Check if we're inactive after updating + ld a, [hl] + and a + jp z , UpdateObjectPool_GoToNextObject +; ANCHOR_END: update-objects-3 + +; ANCHOR: update-objects-4 +.CheckIsDamaged + + push hl + + ; Move to the y low byte + ld de, object_damageByte + add hl, de + + ; Check this object is damaged + ld a, [hl] + and a + jp z, NotDamaged + jp Damaged + +Damaged: + + ; decrease our damage byte + dec a + ld [hl], a + + pop hl + + ; if our objects timer is greater than 0 we'll not draw + ld a, [wObjectsFlash] + cp a, 128 + + jp c, UpdateObjectPool_GoToNextObject + +NotDamaged: + + pop hl + jp z, GetXAndY +; ANCHOR_END: update-objects-4 + +; ANCHOR: update-objects-5 +GetXAndY: + + push hl + + ; Move to the y low byte + ld de, object_yLowByte + add hl, de + + ; Copy our y position to bc + ld a, [hli] + ld c, a + ld a, [hli] + ld b, a + + ; Copy our x position to de + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + +.RenderObjectMetasprite + ld a, [hli] + ld h, [hl] + ld l, a + + call RenderMetasprite + + pop hl + + jp UpdateObjectPool_GoToNextObject +; ANCHOR_END: update-objects-5 +; ANCHOR: update-objects-6 +UpdateObjectPool_GoToNextObject: + + ld de, PER_OBJECT_BYTES_COUNT + add hl, de + + jp UpdateObjectPool_Loop +; ANCHOR_END: update-objects-6 + +; ANCHOR: get-next-available-object +; parameters +; hl = start of array bytes +; b = number of objects to check +; example: +; ld hl, wObjects+BULLETS_START +; ld b, MAX_BULLET_COUNT +GetNextAvailableObject_InHL:: + +GetNextAvailableObject_Loop: + + ld a, [hl] + and a + jp nz, GetNextAvailableObject_Next + + + ld a, 1 + and a + ret + +GetNextAvailableObject_Next: + + ld a, b + dec a + ld b, a + + jp z, GetNextAvailableObject_End + + ; move to the next object + ld de, PER_OBJECT_BYTES_COUNT + add hl, de + + jp GetNextAvailableObject_Loop +GetNextAvailableObject_End: + + ld a, 0 + and a + ret; ; ANCHOR_END: get-next-available-object \ No newline at end of file diff --git a/galactic-armada/src/main/states/gameplay/objects/player.asm b/galactic-armada/src/main/states/gameplay/objects/player.asm index c68ecb96..00eb8120 100644 --- a/galactic-armada/src/main/states/gameplay/objects/player.asm +++ b/galactic-armada/src/main/states/gameplay/objects/player.asm @@ -1,137 +1,137 @@ -; ANCHOR: player-start -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" - -SECTION "Player", ROM0 - -; ANCHOR_END: player-start -; ANCHOR: player-initialize -InitializePlayer:: - - ld hl, wObjects - - ; Set the active byte - ld a,1 - ld [hli], a - - ; Set the y position - ld a,0 - ld [hli], a - ld a, 5 - ld [hli], a - - ; Set the x position - ld a, 0 - ld [hli], a - ld a, 5 - ld [hli], a - - ; Set the metasprite - ld a, LOW(playerTestMetaSprite) - ld [hli], a - ld a, HIGH(playerTestMetaSprite) - ld [hli], a - - ; Set the health - ld a, 3 - ld [hli], a - - ; Set the update - ld a, LOW(UpdatePlayer) - ld [hli], a - ld a, HIGH(UpdatePlayer) - ld [hli], a - ret - -; ANCHOR_END: player-initialize - -; ANCHOR: player-update-start -UpdatePlayer:: - - ld a, [wCurKeys] - and a, PADF_UP - call nz, MoveUp - - ld a, [wCurKeys] - and a, PADF_DOWN - call nz, MoveDown - - ld a, [wCurKeys] - and a, PADF_LEFT - call nz, MoveLeft - - ld a, [wCurKeys] - and a, PADF_RIGHT - call nz, MoveRight - - ld a, [wNewKeys] - and a, PADF_A - call nz, FireNextBullet - - ret -; ANCHOR_END: player-update-start - -; ANCHOR: player-damage -DamagePlayer:: - - ld a, [wObjects+object_healthByte] - dec a - ld [wObjects+object_healthByte], a - - ld a, 128 - ld [wObjects+object_damageByte], a - - ret -; ANCHOR_END: player-damage - -; ANCHOR: player-movement -MoveUp: - - ld hl, wObjects+object_yLowByte - ld a, [hl] - sub a, PLAYER_MOVE_SPEED - ld [hli], a - ld a, [hl] - sbc a, 0 - ld [hl], a - - ret - -MoveDown: - - ld hl, wObjects+object_yLowByte - ld a, [hl] - add a, PLAYER_MOVE_SPEED - ld [hli], a - ld a, [hl] - adc a, 0 - ld [hl], a - - ret - -MoveLeft: - - ld hl, wObjects+object_xLowByte - ld a, [hl] - sub a, PLAYER_MOVE_SPEED - ld [hli], a - ld a, [hl] - sbc a, 0 - ld [hl], a - - ret - -MoveRight: - - ld hl, wObjects+object_xLowByte - ld a, [hl] - add a, PLAYER_MOVE_SPEED - ld [hli], a - ld a, [hl] - adc a, 0 - ld [hl], a - - ret -; ANCHOR_END: player-movement - - +; ANCHOR: player-start +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" + +SECTION "Player", ROM0 + +; ANCHOR_END: player-start +; ANCHOR: player-initialize +InitializePlayer:: + + ld hl, wObjects + + ; Set the active byte + ld a,1 + ld [hli], a + + ; Set the y position + ld a,0 + ld [hli], a + ld a, 5 + ld [hli], a + + ; Set the x position + ld a, 0 + ld [hli], a + ld a, 5 + ld [hli], a + + ; Set the metasprite + ld a, LOW(playerTestMetaSprite) + ld [hli], a + ld a, HIGH(playerTestMetaSprite) + ld [hli], a + + ; Set the health + ld a, 3 + ld [hli], a + + ; Set the update + ld a, LOW(UpdatePlayer) + ld [hli], a + ld a, HIGH(UpdatePlayer) + ld [hli], a + ret + +; ANCHOR_END: player-initialize + +; ANCHOR: player-update-start +UpdatePlayer:: + + ld a, [wCurKeys] + and a, PADF_UP + call nz, MoveUp + + ld a, [wCurKeys] + and a, PADF_DOWN + call nz, MoveDown + + ld a, [wCurKeys] + and a, PADF_LEFT + call nz, MoveLeft + + ld a, [wCurKeys] + and a, PADF_RIGHT + call nz, MoveRight + + ld a, [wNewKeys] + and a, PADF_A + call nz, FireNextBullet + + ret +; ANCHOR_END: player-update-start + +; ANCHOR: player-damage +DamagePlayer:: + + ld a, [wObjects+object_healthByte] + dec a + ld [wObjects+object_healthByte], a + + ld a, 128 + ld [wObjects+object_damageByte], a + + ret +; ANCHOR_END: player-damage + +; ANCHOR: player-movement +MoveUp: + + ld hl, wObjects+object_yLowByte + ld a, [hl] + sub a, PLAYER_MOVE_SPEED + ld [hli], a + ld a, [hl] + sbc a, 0 + ld [hl], a + + ret + +MoveDown: + + ld hl, wObjects+object_yLowByte + ld a, [hl] + add a, PLAYER_MOVE_SPEED + ld [hli], a + ld a, [hl] + adc a, 0 + ld [hl], a + + ret + +MoveLeft: + + ld hl, wObjects+object_xLowByte + ld a, [hl] + sub a, PLAYER_MOVE_SPEED + ld [hli], a + ld a, [hl] + sbc a, 0 + ld [hl], a + + ret + +MoveRight: + + ld hl, wObjects+object_xLowByte + ld a, [hl] + add a, PLAYER_MOVE_SPEED + ld [hli], a + ld a, [hl] + adc a, 0 + ld [hl], a + + ret +; ANCHOR_END: player-movement + + diff --git a/galactic-armada/src/main/states/story/story-state.asm b/galactic-armada/src/main/states/story/story-state.asm index e57bfad3..dffbfdea 100644 --- a/galactic-armada/src/main/states/story/story-state.asm +++ b/galactic-armada/src/main/states/story/story-state.asm @@ -1,89 +1,89 @@ -; ANCHOR: header -INCLUDE "src/main/includes/hardware.inc" -INCLUDE "src/main/includes/character-mapping.inc" - -SECTION "StoryStateASM", ROM0 - -; ANCHOR_END: header -; ANCHOR: init-story-state -InitStoryState:: - - call WaitForVBlankStart - - ; Turn the LCD off - ld a, 0 - ld [rLCDC], a - - call ClearBackground - call ResetShadowOAM - call hOAMDMA - - ; Turn the LCD on - ld a, LCDCF_ON | LCDCF_BGON - ld [rLCDC], a - - ret; -; ANCHOR_END: init-story-state - -; ANCHOR: story-screen-data -Story: - .Line1 db "the galatic empire", 255 - .Line2 db "rules the galaxy", 255 - .Line3 db "with an iron", 255 - .Line4 db "fist.", 255, 255 -Story2: - .Line1 db "the rebel force", 255 - .Line2 db "remain hopeful of", 255 - .Line3 db "freedoms light", 255, 255 - -; ANCHOR_END: story-screen-data -; ANCHOR: story-screen-page1 -UpdateStoryState:: - - ; Call Our function that typewrites text onto background/window tiles - ld de, $9821 - ld hl, Story.Line1 - call MultilineTypewriteTextInHL_AtDE -; ANCHOR_END: story-screen-page1 -; ANCHOR: between-pages - - call WaitForAToBePressed - call WaitForVBlankStart - - ; Turn the LCD off - ld a, 0 - ld [rLCDC], a - - call ClearBackground - - ; Turn the LCD on - ld a, LCDCF_ON | LCDCF_BGON - ld [rLCDC], a -; ANCHOR_END: between-pages - -; ANCHOR: story-screen-page2 - ; Call Our function that typewrites text onto background/window tiles - ld de, $9821 - ld hl, Story2.Line1 - call MultilineTypewriteTextInHL_AtDE - -; ANCHOR_END: story-screen-page2 - - -; ANCHOR: story-screen-end - call WaitForAToBePressed - - ld hl, InitGameplayState - ld a, l - ld [wNextGameState_Initiate+0], a - ld a, h - ld [wNextGameState_Initiate+1], a - - ld hl, UpdateGameplayState - ld a, l - ld [wNextGameState_Update+0], a - ld a, h - ld [wNextGameState_Update+1], a - - ret +; ANCHOR: header +INCLUDE "src/main/includes/hardware.inc" +INCLUDE "src/main/includes/character-mapping.inc" + +SECTION "StoryStateASM", ROM0 + +; ANCHOR_END: header +; ANCHOR: init-story-state +InitStoryState:: + + call WaitForVBlankStart + + ; Turn the LCD off + ld a, 0 + ld [rLCDC], a + + call ClearBackground + call ResetShadowOAM + call hOAMDMA + + ; Turn the LCD on + ld a, LCDCF_ON | LCDCF_BGON + ld [rLCDC], a + + ret; +; ANCHOR_END: init-story-state + +; ANCHOR: story-screen-data +Story: + .Line1 db "the galatic empire", 255 + .Line2 db "rules the galaxy", 255 + .Line3 db "with an iron", 255 + .Line4 db "fist.", 255, 255 +Story2: + .Line1 db "the rebel force", 255 + .Line2 db "remain hopeful of", 255 + .Line3 db "freedoms light", 255, 255 + +; ANCHOR_END: story-screen-data +; ANCHOR: story-screen-page1 +UpdateStoryState:: + + ; Call Our function that typewrites text onto background/window tiles + ld de, $9821 + ld hl, Story.Line1 + call MultilineTypewriteTextInHL_AtDE +; ANCHOR_END: story-screen-page1 +; ANCHOR: between-pages + + call WaitForAToBePressed + call WaitForVBlankStart + + ; Turn the LCD off + ld a, 0 + ld [rLCDC], a + + call ClearBackground + + ; Turn the LCD on + ld a, LCDCF_ON | LCDCF_BGON + ld [rLCDC], a +; ANCHOR_END: between-pages + +; ANCHOR: story-screen-page2 + ; Call Our function that typewrites text onto background/window tiles + ld de, $9821 + ld hl, Story2.Line1 + call MultilineTypewriteTextInHL_AtDE + +; ANCHOR_END: story-screen-page2 + + +; ANCHOR: story-screen-end + call WaitForAToBePressed + + ld hl, InitGameplayState + ld a, l + ld [wNextGameState_Initiate+0], a + ld a, h + ld [wNextGameState_Initiate+1], a + + ld hl, UpdateGameplayState + ld a, l + ld [wNextGameState_Update+0], a + ld a, h + ld [wNextGameState_Update+1], a + + ret ; ANCHOR_END: story-screen-end \ No newline at end of file diff --git a/galactic-armada/src/main/states/title-screen/title-screen-state.asm b/galactic-armada/src/main/states/title-screen/title-screen-state.asm index 8ea73224..fb61e15f 100644 --- a/galactic-armada/src/main/states/title-screen/title-screen-state.asm +++ b/galactic-armada/src/main/states/title-screen/title-screen-state.asm @@ -1,64 +1,64 @@ -; ANCHOR: title-screen-start -INCLUDE "src/main/includes/hardware.inc" -INCLUDE "src/main/includes/character-mapping.inc" - -SECTION "TitleScreenState", ROM0 - -; ANCHOR_END: title-screen-start - -wPressPlayText:: db "press a to play", 255 - -; ANCHOR: title-screen-init -InitTitleScreenState:: - - call WaitForVBlankStart - - ; Turn the LCD off - ld a, 0 - ldh [rLCDC], a - - ; reset the position of the background - ld a, 0 - ld [rSCX], a - ld [rSCY], a - - ; Clear the background and all sprites - call ClearBackground - call ResetShadowOAM - call hOAMDMA - call DisableInterrupts - - call LoadTextFontIntoVRAM - call DrawTitleScreen - - ; Call Our function that draws text onto background/window tiles - ld de, $99C3 - ld hl, wPressPlayText - call DrawTextInHL_AtDE - - ; Turn the LCD on - ld a, LCDCF_ON | LCDCF_BGON| LCDCF_WIN9C00|LCDCF_BG9800 - ldh [rLCDC], a - - ret; -; ANCHOR_END: title-screen-init -; ANCHOR: update-title-screen -UpdateTitleScreenState:: - - - call WaitForAToBePressed - - ld hl, InitStoryState - ld a, l - ld [wNextGameState_Initiate+0], a - ld a, h - ld [wNextGameState_Initiate+1], a - - ld hl, UpdateStoryState - ld a, l - ld [wNextGameState_Update+0], a - ld a, h - ld [wNextGameState_Update+1], a - - ret -; ANCHOR_END: update-title-screen +; ANCHOR: title-screen-start +INCLUDE "src/main/includes/hardware.inc" +INCLUDE "src/main/includes/character-mapping.inc" + +SECTION "TitleScreenState", ROM0 + +; ANCHOR_END: title-screen-start + +wPressPlayText:: db "press a to play", 255 + +; ANCHOR: title-screen-init +InitTitleScreenState:: + + call WaitForVBlankStart + + ; Turn the LCD off + ld a, 0 + ldh [rLCDC], a + + ; reset the position of the background + ld a, 0 + ld [rSCX], a + ld [rSCY], a + + ; Clear the background and all sprites + call ClearBackground + call ResetShadowOAM + call hOAMDMA + call DisableInterrupts + + call LoadTextFontIntoVRAM + call DrawTitleScreen + + ; Call Our function that draws text onto background/window tiles + ld de, $99C3 + ld hl, wPressPlayText + call DrawTextInHL_AtDE + + ; Turn the LCD on + ld a, LCDCF_ON | LCDCF_BGON| LCDCF_WIN9C00|LCDCF_BG9800 + ldh [rLCDC], a + + ret; +; ANCHOR_END: title-screen-init +; ANCHOR: update-title-screen +UpdateTitleScreenState:: + + + call WaitForAToBePressed + + ld hl, InitStoryState + ld a, l + ld [wNextGameState_Initiate+0], a + ld a, h + ld [wNextGameState_Initiate+1], a + + ld hl, UpdateStoryState + ld a, l + ld [wNextGameState_Update+0], a + ld a, h + ld [wNextGameState_Update+1], a + + ret +; ANCHOR_END: update-title-screen diff --git a/galactic-armada/src/main/utils/background-utils.asm b/galactic-armada/src/main/utils/background-utils.asm index 98e0ffc4..cc2c1a2c 100644 --- a/galactic-armada/src/main/utils/background-utils.asm +++ b/galactic-armada/src/main/utils/background-utils.asm @@ -1,25 +1,25 @@ -; ANCHOR: background-utils -include "src/main/includes/hardware.inc" - -SECTION "Background", ROM0 - -ClearBackground:: - - ld bc,1024 - ld hl, $9800 - -ClearBackgroundLoop: - - ld a,0 - ld [hli], a - - - dec bc - ld a, b - or a, c - - jp nz, ClearBackgroundLoop - - - ret +; ANCHOR: background-utils +include "src/main/includes/hardware.inc" + +SECTION "Background", ROM0 + +ClearBackground:: + + ld bc,1024 + ld hl, $9800 + +ClearBackgroundLoop: + + ld a,0 + ld [hli], a + + + dec bc + ld a, b + or a, c + + jp nz, ClearBackgroundLoop + + + ret ; ANCHOR_END: background-utils \ No newline at end of file diff --git a/galactic-armada/src/main/utils/collision-utils.asm b/galactic-armada/src/main/utils/collision-utils.asm index 16223d33..c92fe212 100644 --- a/galactic-armada/src/main/utils/collision-utils.asm +++ b/galactic-armada/src/main/utils/collision-utils.asm @@ -1,56 +1,56 @@ -; ANCHOR: collision-utils -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" -include "src/main/includes/hardware.inc" - -SECTION "CollisionUtilsVariables", WRAM0 - -wSize::db; -wObject1Value:: db -wObject2Value:: db - -SECTION "CollisionUtils", ROM0 - -CheckObjectPositionDifference:: - - ; at this point in time; e = enemy.y, b =bullet.y - - ld a, [wObject1Value] - ld e, a - ld a, [wObject2Value] - ld b, a - - ld a, [wSize] - ld d, a - - ; subtract bullet.y, (aka b) - (enemy.y+8, aka e) - ; carry means eb, means enemy.top is visually below bullet.y (no collision) - ld a, e - sub a, d - cp a, b - - ; no carry means no collision - jp nc, CheckObjectPositionDifference_Failure - - ld a,1 - and a - ret; - - -CheckObjectPositionDifference_Failure: - - ld a,0 - and a - ret; - +; ANCHOR: collision-utils +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" +include "src/main/includes/hardware.inc" + +SECTION "CollisionUtilsVariables", WRAM0 + +wSize::db; +wObject1Value:: db +wObject2Value:: db + +SECTION "CollisionUtils", ROM0 + +CheckObjectPositionDifference:: + + ; at this point in time; e = enemy.y, b =bullet.y + + ld a, [wObject1Value] + ld e, a + ld a, [wObject2Value] + ld b, a + + ld a, [wSize] + ld d, a + + ; subtract bullet.y, (aka b) - (enemy.y+8, aka e) + ; carry means eb, means enemy.top is visually below bullet.y (no collision) + ld a, e + sub a, d + cp a, b + + ; no carry means no collision + jp nc, CheckObjectPositionDifference_Failure + + ld a,1 + and a + ret; + + +CheckObjectPositionDifference_Failure: + + ld a,0 + and a + ret; + ; ANCHOR_END: collision-utils \ No newline at end of file diff --git a/galactic-armada/src/main/utils/input-utils.asm b/galactic-armada/src/main/utils/input-utils.asm index 15c84ff7..5e0b69bb 100644 --- a/galactic-armada/src/main/utils/input-utils.asm +++ b/galactic-armada/src/main/utils/input-utils.asm @@ -1,64 +1,64 @@ -include "src/main/includes/hardware.inc" -; ANCHOR: input-utils -SECTION "InputUtilsVariables", WRAM0 - -mWaitKey:: db - -SECTION "InputUtils", ROM0 - -WaitForAToBePressed:: - - ; Save the passed value into the variable: mWaitKey - ; The WaitForKeyFunction always checks against this vriable - ld a,PADF_A - ld [mWaitKey], a - -WaitForKeyFunction:: - - ; Save our original value - push bc - - -WaitForKeyFunction_Loop: - - ; save the keys last frame - ld a, [wCurKeys] - ld [wLastKeys], a - - ; This is in input.asm - ; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html - ; In their words (paraphrased): reading player input for gameboy is NOT a trivial task - ; So it's best to use some tested code - call Input - - - ld a, [mWaitKey] - ld b,a - ld a, [wCurKeys] - and a, b - jp z,WaitForKeyFunction_NotPressed - - ld a, [wLastKeys] - and a, b - jp nz,WaitForKeyFunction_NotPressed - - ; restore our original value - pop bc - - ret - - -WaitForKeyFunction_NotPressed: - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ; Wait a small amount of time - ; Save our count in this variable - ld a, 1 - ld [wVBlankCount], a - - ; Call our function that performs the code - call WaitForVBlankFunction - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - jp WaitForKeyFunction_Loop +include "src/main/includes/hardware.inc" +; ANCHOR: input-utils +SECTION "InputUtilsVariables", WRAM0 + +mWaitKey:: db + +SECTION "InputUtils", ROM0 + +WaitForAToBePressed:: + + ; Save the passed value into the variable: mWaitKey + ; The WaitForKeyFunction always checks against this vriable + ld a,PADF_A + ld [mWaitKey], a + +WaitForKeyFunction:: + + ; Save our original value + push bc + + +WaitForKeyFunction_Loop: + + ; save the keys last frame + ld a, [wCurKeys] + ld [wLastKeys], a + + ; This is in input.asm + ; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html + ; In their words (paraphrased): reading player input for gameboy is NOT a trivial task + ; So it's best to use some tested code + call Input + + + ld a, [mWaitKey] + ld b,a + ld a, [wCurKeys] + and a, b + jp z,WaitForKeyFunction_NotPressed + + ld a, [wLastKeys] + and a, b + jp nz,WaitForKeyFunction_NotPressed + + ; restore our original value + pop bc + + ret + + +WaitForKeyFunction_NotPressed: + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; Wait a small amount of time + ; Save our count in this variable + ld a, 1 + ld [wVBlankCount], a + + ; Call our function that performs the code + call WaitForVBlankFunction + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + jp WaitForKeyFunction_Loop ; ANCHOR_END: input-utils \ No newline at end of file diff --git a/galactic-armada/src/main/utils/math-utils.asm b/galactic-armada/src/main/utils/math-utils.asm index 69d434f8..5b41a181 100644 --- a/galactic-armada/src/main/utils/math-utils.asm +++ b/galactic-armada/src/main/utils/math-utils.asm @@ -1,28 +1,28 @@ - -SECTION "MathVariables", WRAM0 -randstate:: ds 4 - -SECTION "Math", ROM0 - - -;; From: https://github.com/pinobatch/libbet/blob/master/src/rand.z80#L34-L54 -; Generates a pseudorandom 16-bit integer in BC -; using the LCG formula from cc65 rand(): -; x[i + 1] = x[i] * 0x01010101 + 0xB3B3B3B3 -; @return A=B=state bits 31-24 (which have the best entropy), -; C=state bits 23-16, HL trashed -rand:: - ; Add 0xB3 then multiply by 0x01010101 - ld hl, randstate+0 - ld a, [hl] - add a, $B3 - ld [hl+], a - adc a, [hl] - ld [hl+], a - adc a, [hl] - ld [hl+], a - ld c, a - adc a, [hl] - ld [hl], a - ld b, a + +SECTION "MathVariables", WRAM0 +randstate:: ds 4 + +SECTION "Math", ROM0 + + +;; From: https://github.com/pinobatch/libbet/blob/master/src/rand.z80#L34-L54 +; Generates a pseudorandom 16-bit integer in BC +; using the LCG formula from cc65 rand(): +; x[i + 1] = x[i] * 0x01010101 + 0xB3B3B3B3 +; @return A=B=state bits 31-24 (which have the best entropy), +; C=state bits 23-16, HL trashed +rand:: + ; Add 0xB3 then multiply by 0x01010101 + ld hl, randstate+0 + ld a, [hl] + add a, $B3 + ld [hl+], a + adc a, [hl] + ld [hl+], a + adc a, [hl] + ld [hl+], a + ld c, a + adc a, [hl] + ld [hl], a + ld b, a ret \ No newline at end of file diff --git a/galactic-armada/src/main/utils/memory-utils.asm b/galactic-armada/src/main/utils/memory-utils.asm index bc440d2b..92880cf2 100644 --- a/galactic-armada/src/main/utils/memory-utils.asm +++ b/galactic-armada/src/main/utils/memory-utils.asm @@ -1,27 +1,27 @@ -; ANCHOR: memory-utils -SECTION "MemoryUtilsSection", ROM0 - -CopyDEintoMemoryAtHL:: - ld a, [de] - ld [hli], a - inc de - dec bc - ld a, b - or a, c - jp nz, CopyDEintoMemoryAtHL ; Jump to COpyTiles, if the z flag is not set. (the last operation had a non zero result) - ret; - -CopyDEintoMemoryAtHL_With52Offset:: - ld a, [de] - add a, 52 - ld [hli], a - inc de - dec bc - ld a, b - or a, c - jp nz, CopyDEintoMemoryAtHL_With52Offset ; Jump to COpyTiles, if the z flag is not set. (the last operation had a non zero result) - ret; -; ANCHOR_END: memory-utils - -callHL:: +; ANCHOR: memory-utils +SECTION "MemoryUtilsSection", ROM0 + +CopyDEintoMemoryAtHL:: + ld a, [de] + ld [hli], a + inc de + dec bc + ld a, b + or a, c + jp nz, CopyDEintoMemoryAtHL ; Jump to COpyTiles, if the z flag is not set. (the last operation had a non zero result) + ret; + +CopyDEintoMemoryAtHL_With52Offset:: + ld a, [de] + add a, 52 + ld [hli], a + inc de + dec bc + ld a, b + or a, c + jp nz, CopyDEintoMemoryAtHL_With52Offset ; Jump to COpyTiles, if the z flag is not set. (the last operation had a non zero result) + ret; +; ANCHOR_END: memory-utils + +callHL:: jp hl \ No newline at end of file diff --git a/galactic-armada/src/main/utils/text-utils.asm b/galactic-armada/src/main/utils/text-utils.asm index cac2abf3..efad87b7 100644 --- a/galactic-armada/src/main/utils/text-utils.asm +++ b/galactic-armada/src/main/utils/text-utils.asm @@ -1,90 +1,90 @@ - -SECTION "Text", ROM0 - - -; ANCHOR: draw-text-tiles -DrawTextInHL_AtDE:: - - ; Check for the end of string character 255 - ld a, [hl] - cp 255 - ret z - - ; Write the current character (in hl) to the address - ; on the tilemap (in de) - ld a, [hl] - ld [de], a - - inc hl - inc de - - ; move to the next character and next background tile - jp DrawTextInHL_AtDE -; ANCHOR_END: draw-text-tiles - - -; ANCHOR: typewriter-effect -TypewriteTextInHL_AtDE:: - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ; Wait a small amount of time - ; Save our count in this variable - ld a, 3 - ld [wVBlankCount], a - - ; Call our function that performs the code - call WaitForVBlankFunction - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ; Check for the end of string character 255 - ld a, [hl] - cp 255 - ret z - - ; Write the current character (in hl) to the address - ; on the tilemap (in de) - ld a, [hl] - ld [de], a - - ; move to the next character and next background tile - inc hl - inc de - - jp TypewriteTextInHL_AtDE -; ANCHOR_END: typewriter-effect - - -; ANCHOR: multiline-typewriter-effect -MultilineTypewriteTextInHL_AtDE:: - - ; Save where we are writing to, the "current line" - push de - -MultilineTypewriteTextInHL_AtDE_NewLine: - - call TypewriteTextInHL_AtDE - - ; hl should point to a 255 after `TypewriteTextInHL_AtDE` - ; move past that 255 - inc hl - - ; Restore the "current line" - pop de - - ; Check for the end of string character 255 - ; consecutive 255's mean were all done - ld a, [hl] - cp 255 - ret z - - ; Skip a line - ld a, 64 - add a, e - ld e, a - - ; Save where we are writing to, the "current line" - push de - - ; continue until we read those consecutive 255's - jp MultilineTypewriteTextInHL_AtDE_NewLine + +SECTION "Text", ROM0 + + +; ANCHOR: draw-text-tiles +DrawTextInHL_AtDE:: + + ; Check for the end of string character 255 + ld a, [hl] + cp 255 + ret z + + ; Write the current character (in hl) to the address + ; on the tilemap (in de) + ld a, [hl] + ld [de], a + + inc hl + inc de + + ; move to the next character and next background tile + jp DrawTextInHL_AtDE +; ANCHOR_END: draw-text-tiles + + +; ANCHOR: typewriter-effect +TypewriteTextInHL_AtDE:: + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; Wait a small amount of time + ; Save our count in this variable + ld a, 3 + ld [wVBlankCount], a + + ; Call our function that performs the code + call WaitForVBlankFunction + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ; Check for the end of string character 255 + ld a, [hl] + cp 255 + ret z + + ; Write the current character (in hl) to the address + ; on the tilemap (in de) + ld a, [hl] + ld [de], a + + ; move to the next character and next background tile + inc hl + inc de + + jp TypewriteTextInHL_AtDE +; ANCHOR_END: typewriter-effect + + +; ANCHOR: multiline-typewriter-effect +MultilineTypewriteTextInHL_AtDE:: + + ; Save where we are writing to, the "current line" + push de + +MultilineTypewriteTextInHL_AtDE_NewLine: + + call TypewriteTextInHL_AtDE + + ; hl should point to a 255 after `TypewriteTextInHL_AtDE` + ; move past that 255 + inc hl + + ; Restore the "current line" + pop de + + ; Check for the end of string character 255 + ; consecutive 255's mean were all done + ld a, [hl] + cp 255 + ret z + + ; Skip a line + ld a, 64 + add a, e + ld e, a + + ; Save where we are writing to, the "current line" + push de + + ; continue until we read those consecutive 255's + jp MultilineTypewriteTextInHL_AtDE_NewLine ; ANCHOR_END: multiline-typewriter-effect \ No newline at end of file diff --git a/galactic-armada/src/main/utils/vblank-utils.asm b/galactic-armada/src/main/utils/vblank-utils.asm index 6a958511..a3e1422d 100644 --- a/galactic-armada/src/main/utils/vblank-utils.asm +++ b/galactic-armada/src/main/utils/vblank-utils.asm @@ -1,46 +1,46 @@ -; ANCHOR: vblank-utils -INCLUDE "src/main/includes/hardware.inc" - -SECTION "VBlankVariables", WRAM0 - -wVBlankCount:: db - -SECTION "VBlankFunctions", ROM0 - -WaitForVBlankStart:: - - ld a, [rLY] ; Copy the vertical line to a - cp 144 ; Check if the vertical line (in a) is 0 - jp c, WaitForVBlankStart ; A conditional jump. The condition is that 'c' is set, the last operation overflowed - - ret - -WaitForOneVBlank:: - - ; Wait a small amount of time - ; Save our count in this variable - ld a, 1 - ld [wVBlankCount], a - -WaitForVBlankFunction:: - -WaitForVBlankFunction_Loop:: - - ld a, [rLY] ; Copy the vertical line to a - cp 144 ; Check if the vertical line (in a) is 0 - jp c, WaitForVBlankFunction_Loop ; A conditional jump. The condition is that 'c' is set, the last operation overflowed - - ld a, [wVBlankCount] - sub a, 1 - ld [wVBlankCount], a - ret z - -WaitForVBlankFunction_Loop2:: - - ld a, [rLY] ; Copy the vertical line to a - cp 144 ; Check if the vertical line (in a) is 0 - jp nc, WaitForVBlankFunction_Loop2 ; A conditional jump. The condition is that 'c' is set, the last operation overflowed - - jp WaitForVBlankFunction_Loop - +; ANCHOR: vblank-utils +INCLUDE "src/main/includes/hardware.inc" + +SECTION "VBlankVariables", WRAM0 + +wVBlankCount:: db + +SECTION "VBlankFunctions", ROM0 + +WaitForVBlankStart:: + + ld a, [rLY] ; Copy the vertical line to a + cp 144 ; Check if the vertical line (in a) is 0 + jp c, WaitForVBlankStart ; A conditional jump. The condition is that 'c' is set, the last operation overflowed + + ret + +WaitForOneVBlank:: + + ; Wait a small amount of time + ; Save our count in this variable + ld a, 1 + ld [wVBlankCount], a + +WaitForVBlankFunction:: + +WaitForVBlankFunction_Loop:: + + ld a, [rLY] ; Copy the vertical line to a + cp 144 ; Check if the vertical line (in a) is 0 + jp c, WaitForVBlankFunction_Loop ; A conditional jump. The condition is that 'c' is set, the last operation overflowed + + ld a, [wVBlankCount] + sub a, 1 + ld [wVBlankCount], a + ret z + +WaitForVBlankFunction_Loop2:: + + ld a, [rLY] ; Copy the vertical line to a + cp 144 ; Check if the vertical line (in a) is 0 + jp nc, WaitForVBlankFunction_Loop2 ; A conditional jump. The condition is that 'c' is set, the last operation overflowed + + jp WaitForVBlankFunction_Loop + ; ANCHOR_END: vblank-utils \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 72860a48..f846c5e5 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,63 +1,63 @@ -[Home](index.md) -[Roadmap](roadmap.md) -[Help](help-feedback.md) - -# Part Ⅰ — Hello World! - -- [Setup](part1/setup.md) -- [Hello World!](part1/hello_world.md) -- [The toolchain](part1/toolchain.md) -- [Binary and hexadecimal](part1/bin_and_hex.md) -- [Registers](part1/registers.md) -- [Assembly basics](part1/assembly.md) -- [Memory](part1/memory.md) -- [The header](part1/header.md) -- [Operations & flags](part1/operations.md) -- [Jumps](part1/jumps.md) -- [Tracing](part1/tracing.md) -- [Graphics]() - - [Tiles](part1/tiles.md) - - [Palettes](part1/palettes.md) - - [Tilemap](part1/tilemap.md) -- [Wrapping up](part1/wrapup.md) - -# Part Ⅱ — Our first game - -- [Getting started](part2/getting-started.md) -- [Objects](part2/objects.md) -- [Functions](part2/functions.md) -- [Input](part2/input.md) -- [Collision](part2/collision.md) -- [Bricks](part2/bricks.md) -- [Work in progress](part2/wip.md) - -# Part III — Our second game - -- [Getting Started](part3/getting-started.md) -- [Download the Starter](part3/the-starter.md) - - [Project Structure](part3/project-structure.md) - - [Dependent Libraries](part3/dependent-libraries.md) - - [Sprites & Backgrounds](part3/sprites-backgrounds.md) - - [Metasprites](part3/sporbs-metasprites.md) - - [Drawing Text](part3/drawing-text.md) - - [Included Utilities](part3/utilities.md) - - [Compilation](part3/compilation.md) -- [Game State Management](part3/game-state-management.md) -- [Title Screen](part3/title-screen.md) -- [Story Screen](part3/story-screen.md) -- [Gameplay](part3/gameplay.md) - - [Object Pools](part3/object-pools.md) - - [Scrolling Background](part3/scrolling-background.md) - - [Heads-Up Interface](part3/heads-up-interface.md) - - [The Player](part3/the-player.md) - - [Bullets](part3/bullets.md) - - [Enemies](part3/enemies.md) - - [Collision Detection](part3/collision.md) - - [Enemy Collisions](part3/collision.md) -- [Conclusion](part3/conclusion.md) - - - -[Where to go next](next.md) -[Resources](resources.md) -[Thanks](thanks.md) +[Home](index.md) +[Roadmap](roadmap.md) +[Help](help-feedback.md) + +# Part Ⅰ — Hello World! + +- [Setup](part1/setup.md) +- [Hello World!](part1/hello_world.md) +- [The toolchain](part1/toolchain.md) +- [Binary and hexadecimal](part1/bin_and_hex.md) +- [Registers](part1/registers.md) +- [Assembly basics](part1/assembly.md) +- [Memory](part1/memory.md) +- [The header](part1/header.md) +- [Operations & flags](part1/operations.md) +- [Jumps](part1/jumps.md) +- [Tracing](part1/tracing.md) +- [Graphics]() + - [Tiles](part1/tiles.md) + - [Palettes](part1/palettes.md) + - [Tilemap](part1/tilemap.md) +- [Wrapping up](part1/wrapup.md) + +# Part Ⅱ — Our first game + +- [Getting started](part2/getting-started.md) +- [Objects](part2/objects.md) +- [Functions](part2/functions.md) +- [Input](part2/input.md) +- [Collision](part2/collision.md) +- [Bricks](part2/bricks.md) +- [Work in progress](part2/wip.md) + +# Part III — Our second game + +- [Getting Started](part3/getting-started.md) +- [Download the Starter](part3/the-starter.md) + - [Project Structure](part3/project-structure.md) + - [Dependent Libraries](part3/dependent-libraries.md) + - [Sprites & Backgrounds](part3/sprites-backgrounds.md) + - [Metasprites](part3/sporbs-metasprites.md) + - [Drawing Text](part3/drawing-text.md) + - [Included Utilities](part3/utilities.md) + - [Compilation](part3/compilation.md) +- [Game State Management](part3/game-state-management.md) +- [Title Screen](part3/title-screen.md) +- [Story Screen](part3/story-screen.md) +- [Gameplay](part3/gameplay.md) + - [Object Pools](part3/object-pools.md) + - [Scrolling Background](part3/scrolling-background.md) + - [Heads-Up Interface](part3/heads-up-interface.md) + - [The Player](part3/the-player.md) + - [Bullets](part3/bullets.md) + - [Enemies](part3/enemies.md) + - [Collision Detection](part3/collision.md) + - [Enemy Collisions](part3/collision.md) +- [Conclusion](part3/conclusion.md) + + + +[Where to go next](next.md) +[Resources](resources.md) +[Thanks](thanks.md) diff --git a/src/part3/bullets.md b/src/part3/bullets.md index 90693a0b..83bc9476 100644 --- a/src/part3/bullets.md +++ b/src/part3/bullets.md @@ -1,71 +1,71 @@ -# Bullets - -Bullets are relatively simple, logic-wise. They all travel straight-forward, and de-activate themselves when they leave the screen. - -At the top of our "src/main/states/gameplay/objects/bullets.asm" file we'll setup some variables for bullets and include our tile data. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:bullets-top}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:bullets-top}} -``` - -We'll need to loop through the bullet object pool in the following sections. - -## Initiating Bullets - -In our "InitializeBullets" function, we'll copy the tile data for the bullet sprites into VRAM, and set every bullet as inactive. Each bullet is 4 bytes, the first byte signaling if the bullet is active or not. - -![BulletBytesVisualized.png](../assets/part3/img/BulletBytesVisualized.png) - -We'll iterate through bullet object pool, named "wBullets", and activate the first of the the four bytes. Then skipping the next 3 bytes, to go onto the next bullet. We'll do this until we've looped for each bullet in our pool. - - -## Updating Bullets - -When we want to update each of bullets, first we should check if any bullets are active. If no bullets are active we can stop early. - -If we have active bullets, we'll reset how many bullets we've checked and set our "hl" registers to point to the first bullets address. - -When were updating each bullet, we'll check each byte, changing hl (the byte we want to read) as we go. At the start, "hl" should point to the first byte. "hl" should point to the first byte at the end too: - -> HL should point to the first byte at the end so we can easily do one of two things: -> * deactivate the bullet -> * jump to the next bullet (by simply adding 4 to hl) - -For we each bullet, we'll do the following: - -* Check if active -* Get our x position, save into b -* Get our y scaled positon, save into c (low byte), and d (high byte) -* Decrease our y position to move the bullet upwards -* Reset HL to the first byte of our bullet -* Descale the y position we have in c & d, and jump to our deactivation code if c (the low byte) is high enough -* Draw our bullet metasprit, if it wasn't previously deactivated - -### Drawing the Bullets - -We'll draw our bullet metasprite like we drew the player, using our "DrawMetasprites" function. This function may alter the 'h' or 'l' registers, so we'll push the hl register onto the stack before hand. After drawing, we'll pop the hl register off of the stack to restore it's value. - - -### Deactivating the Bullets - -If a bullet needs to be deactivated, we simply set it's first byte to 0. At this point in time, the "hl" registers should point at our bullets first byte. This makes deactivation a really simple task. In addition to changing the first byte, we'll decrease how many bullets we have that are active. - - -### Updating the next bullet - -After we've updated a single bullet, we'll increase how many bullet's we've updated. If we've updated all the bullets, we can stop our "UpdateBullets" function. Otherwise, we'll add 4 bytes to the addressed stored in "hl", and update the next bullet. - - -## Firing New Bullets - -During the "UpdatePlayer" function previously, when use pressed A we called the "FireNextBullet" function. - -This function will loop through each bullet in the bullet object pool. When it finds an inactive bullet, it will activate it and set it's position equal to the players. - -> Our bullets only use one 8-bit integer for their x position, so need to de-scale the player's 16-bit scaled x position - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} -``` - +# Bullets + +Bullets are relatively simple, logic-wise. They all travel straight-forward, and de-activate themselves when they leave the screen. + +At the top of our "src/main/states/gameplay/objects/bullets.asm" file we'll setup some variables for bullets and include our tile data. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:bullets-top}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:bullets-top}} +``` + +We'll need to loop through the bullet object pool in the following sections. + +## Initiating Bullets + +In our "InitializeBullets" function, we'll copy the tile data for the bullet sprites into VRAM, and set every bullet as inactive. Each bullet is 4 bytes, the first byte signaling if the bullet is active or not. + +![BulletBytesVisualized.png](../assets/part3/img/BulletBytesVisualized.png) + +We'll iterate through bullet object pool, named "wBullets", and activate the first of the the four bytes. Then skipping the next 3 bytes, to go onto the next bullet. We'll do this until we've looped for each bullet in our pool. + + +## Updating Bullets + +When we want to update each of bullets, first we should check if any bullets are active. If no bullets are active we can stop early. + +If we have active bullets, we'll reset how many bullets we've checked and set our "hl" registers to point to the first bullets address. + +When were updating each bullet, we'll check each byte, changing hl (the byte we want to read) as we go. At the start, "hl" should point to the first byte. "hl" should point to the first byte at the end too: + +> HL should point to the first byte at the end so we can easily do one of two things: +> * deactivate the bullet +> * jump to the next bullet (by simply adding 4 to hl) + +For we each bullet, we'll do the following: + +* Check if active +* Get our x position, save into b +* Get our y scaled positon, save into c (low byte), and d (high byte) +* Decrease our y position to move the bullet upwards +* Reset HL to the first byte of our bullet +* Descale the y position we have in c & d, and jump to our deactivation code if c (the low byte) is high enough +* Draw our bullet metasprit, if it wasn't previously deactivated + +### Drawing the Bullets + +We'll draw our bullet metasprite like we drew the player, using our "DrawMetasprites" function. This function may alter the 'h' or 'l' registers, so we'll push the hl register onto the stack before hand. After drawing, we'll pop the hl register off of the stack to restore it's value. + + +### Deactivating the Bullets + +If a bullet needs to be deactivated, we simply set it's first byte to 0. At this point in time, the "hl" registers should point at our bullets first byte. This makes deactivation a really simple task. In addition to changing the first byte, we'll decrease how many bullets we have that are active. + + +### Updating the next bullet + +After we've updated a single bullet, we'll increase how many bullet's we've updated. If we've updated all the bullets, we can stop our "UpdateBullets" function. Otherwise, we'll add 4 bytes to the addressed stored in "hl", and update the next bullet. + + +## Firing New Bullets + +During the "UpdatePlayer" function previously, when use pressed A we called the "FireNextBullet" function. + +This function will loop through each bullet in the bullet object pool. When it finds an inactive bullet, it will activate it and set it's position equal to the players. + +> Our bullets only use one 8-bit integer for their x position, so need to de-scale the player's 16-bit scaled x position + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} +``` + That's it for bullets logic. Next we'll cover enemies, and after that we'll step back into the world of bullets with "Bullet vs Enemy" Collision. \ No newline at end of file diff --git a/src/part3/changing-game-states.md b/src/part3/changing-game-states.md index 8fcb6d1f..e74098e6 100644 --- a/src/part3/changing-game-states.md +++ b/src/part3/changing-game-states.md @@ -1 +1 @@ -# Game State Management +# Game State Management diff --git a/src/part3/collision.md b/src/part3/collision.md index 82db7c72..6513340e 100644 --- a/src/part3/collision.md +++ b/src/part3/collision.md @@ -1,29 +1,29 @@ -# Collision Detection - -Collision Detection is cruical to games. It can be a very complicated topic. In Galactic Armada, things will be kept super simple. We're going to perform a basic implementation of "Axis-Aligned Bounding Box Collision Detection": - -> One of the simpler forms of collision detection is between two rectangles that are axis aligned — meaning no rotation. The algorithm works by ensuring there is no gap between any of the 4 sides of the rectangles. Any gap means a collision does not exist.[^mdn_source] - -The easiest way to check for overlap, is to check the difference bewteen their centers. If the absolute value of their x & y differences (I'll refer to as "the absolute difference") are BOTH smaller than the sum of their half widths, we have a collision. This collision detection is run for bullets against enemies, and enemies against the player. Here's a visualization with bullets and enemies. - -![CollisionDetectionVisualized.png](../assets/part3/img/CollisionDetectionVisualized.png) - -For this, we've created a basic function called "CheckObjectPositionDifference". This function will help us check for overlap on the x or y axis. When the (absolute) difference between the first two values passed is greater than the third value passed, it jump's to the label passed in the fourth parameter. - -Here's an example of how to call this function: - -> We have the player's x & y position in registers d & e respectively. We have the enemy's x & y position in registers b & c respectively. If there is no overlap on the x or y axis, the program jumps to the "NoCollisionWithPlayer" label. - - -When checking for collision, we'll use that function twice. Once for the x-axis, and again for the y-axis. - -> NOTE: We don't need to test the y-axis if the x-axis fails. - -The source code for that function looks like this: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/collision-utils.asm:collision-utils}} -{{#include ../../galactic-armada/src/main/utils/collision-utils.asm:collision-utils}} -``` - -[^mdn_source]: +# Collision Detection + +Collision Detection is cruical to games. It can be a very complicated topic. In Galactic Armada, things will be kept super simple. We're going to perform a basic implementation of "Axis-Aligned Bounding Box Collision Detection": + +> One of the simpler forms of collision detection is between two rectangles that are axis aligned — meaning no rotation. The algorithm works by ensuring there is no gap between any of the 4 sides of the rectangles. Any gap means a collision does not exist.[^mdn_source] + +The easiest way to check for overlap, is to check the difference bewteen their centers. If the absolute value of their x & y differences (I'll refer to as "the absolute difference") are BOTH smaller than the sum of their half widths, we have a collision. This collision detection is run for bullets against enemies, and enemies against the player. Here's a visualization with bullets and enemies. + +![CollisionDetectionVisualized.png](../assets/part3/img/CollisionDetectionVisualized.png) + +For this, we've created a basic function called "CheckObjectPositionDifference". This function will help us check for overlap on the x or y axis. When the (absolute) difference between the first two values passed is greater than the third value passed, it jump's to the label passed in the fourth parameter. + +Here's an example of how to call this function: + +> We have the player's x & y position in registers d & e respectively. We have the enemy's x & y position in registers b & c respectively. If there is no overlap on the x or y axis, the program jumps to the "NoCollisionWithPlayer" label. + + +When checking for collision, we'll use that function twice. Once for the x-axis, and again for the y-axis. + +> NOTE: We don't need to test the y-axis if the x-axis fails. + +The source code for that function looks like this: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/collision-utils.asm:collision-utils}} +{{#include ../../galactic-armada/src/main/utils/collision-utils.asm:collision-utils}} +``` + +[^mdn_source]: From [mdn web docs - 2D collision detection](https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection) \ No newline at end of file diff --git a/src/part3/compilation.md b/src/part3/compilation.md index 0f533f9c..ce6414cd 100644 --- a/src/part3/compilation.md +++ b/src/part3/compilation.md @@ -1,45 +1,45 @@ -# Compilation - -Compilation is done via a Makefile. This Makefile can be run using the `make` command. Make should be preinstalled on Linux and Mac systems. For Windows users, check out [cygwin](https://www.cygwin.com/). - -Without going over everything in detail, here’s what the Makefile does: - -- Clean generated folders -- Recreate generated folders -- Convert PNGs in src/resources to `.2bpp`, and `.tilemap` formats -- Convert `.asm` files to `.o` -- Use the `.o` files to build the ROM file -- Apply the RGBDS “fix” utility. - -> **Note:** The base template already does all of this. Additionally, it will automatically pick up any new .asm files you create. - -## Converting our graphics to binary files -As previosly explained, all of our graphics were originally created in Aseprite. They were exported as a PNG **with a specific color palette**. - -Ater being exported as a PNG, when you run `make`, they are converted into `.2bpp` and `.tilemap` files via the RGBDS tool: RGBGFX. - -> The **`rgbgfx`** program converts PNG images into data suitable for display on the Game Boy and Game Boy Color, or vice-versa. -> -> The main function of **`rgbgfx`** is to divide the input PNG into 8×8 pixel *[squares](https://rgbds.gbdev.io/docs/v0.6.1/rgbgfx.1#squares)*, convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file. It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below. - -RGBGFX can be found here: [https://rgbds.gbdev.io/docs/v0.6.1/rgbgfx.1](https://rgbds.gbdev.io/docs/v0.6.1/rgbgfx.1) - -We'll use it to convert all of our graphics to .2bpp, and .tilemap formats (binary files) - -```bash,linenos,start={{#line_no_of "" ../../galactic-armada/Makefile:generate-graphics}} -{{#include ../../galactic-armada/Makefile:generate-graphics}} -``` -> **Note:** You can see the full makefile [here](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/Makefile) - -From there, INCBIN commands are used to store reference the binary tile data. - -```rgbasm,linenos -playerShipTileData: INCBIN "src/generated/sprites/player-ship.2bpp" -playerShipTileDataEnd: - -enemyShipTileData:: INCBIN "src/generated/sprites/enemy-ship.2bpp" -enemyShipTileDataEnd:: - -bulletTileData:: INCBIN "src/generated/sprites/bullet.2bpp" -bulletTileDataEnd:: +# Compilation + +Compilation is done via a Makefile. This Makefile can be run using the `make` command. Make should be preinstalled on Linux and Mac systems. For Windows users, check out [cygwin](https://www.cygwin.com/). + +Without going over everything in detail, here’s what the Makefile does: + +- Clean generated folders +- Recreate generated folders +- Convert PNGs in src/resources to `.2bpp`, and `.tilemap` formats +- Convert `.asm` files to `.o` +- Use the `.o` files to build the ROM file +- Apply the RGBDS “fix” utility. + +> **Note:** The base template already does all of this. Additionally, it will automatically pick up any new .asm files you create. + +## Converting our graphics to binary files +As previosly explained, all of our graphics were originally created in Aseprite. They were exported as a PNG **with a specific color palette**. + +Ater being exported as a PNG, when you run `make`, they are converted into `.2bpp` and `.tilemap` files via the RGBDS tool: RGBGFX. + +> The **`rgbgfx`** program converts PNG images into data suitable for display on the Game Boy and Game Boy Color, or vice-versa. +> +> The main function of **`rgbgfx`** is to divide the input PNG into 8×8 pixel *[squares](https://rgbds.gbdev.io/docs/v0.6.1/rgbgfx.1#squares)*, convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file. It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below. + +RGBGFX can be found here: [https://rgbds.gbdev.io/docs/v0.6.1/rgbgfx.1](https://rgbds.gbdev.io/docs/v0.6.1/rgbgfx.1) + +We'll use it to convert all of our graphics to .2bpp, and .tilemap formats (binary files) + +```bash,linenos,start={{#line_no_of "" ../../galactic-armada/Makefile:generate-graphics}} +{{#include ../../galactic-armada/Makefile:generate-graphics}} +``` +> **Note:** You can see the full makefile [here](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/Makefile) + +From there, INCBIN commands are used to store reference the binary tile data. + +```rgbasm,linenos +playerShipTileData: INCBIN "src/generated/sprites/player-ship.2bpp" +playerShipTileDataEnd: + +enemyShipTileData:: INCBIN "src/generated/sprites/enemy-ship.2bpp" +enemyShipTileDataEnd:: + +bulletTileData:: INCBIN "src/generated/sprites/bullet.2bpp" +bulletTileDataEnd:: ``` \ No newline at end of file diff --git a/src/part3/dependent-libraries.md b/src/part3/dependent-libraries.md index 3affbb2d..fa0aa237 100644 --- a/src/part3/dependent-libraries.md +++ b/src/part3/dependent-libraries.md @@ -1,61 +1,61 @@ -# Dependent Libraries - -This project uses 2 additional libraries. -- [Eievui's Sprite Object Library](https://github.com/eievui5/gb-sprobj-lib) -- The joypad input handler from [the previous tutorial](https://gbdev.io/gb-asm-tutorial/part2/input.html) -## Eievui's sprite object library - -For Eievui's sprite object library, we have already initialized it at the start of the game: - -*Inside the 'EntryPoint' function in "GalacticArmada.asm"* -```rgbasm, linenos -; from: https://github.com/eievui5/gb-sprobj-lib -; The library is relatively simple to get set up. First, put the following in your initialization code: -; Initilize Sprite Object Library. -call InitSprObjLibWrapper -``` - -Once Initialized, we must reset it at the start of your game loop. This is done using the `ResetShadowOAM` function. Later, we must call it's `hOAMDMA` function at the end of the game loop (during the vertical blank phase). - -*Inside the 'GalacticArmadaGameLoop' function in "GalacticArmada.asm"* - -```rgbasm, linenos -; then put a call to ResetShadowOAM at the beginning of your main loop. -call ResetShadowOAM - -; Our core game loop will go here - -call WaitForVBlankStart - -; from: https://github.com/eievui5/gb-sprobj-lib -; Finally, run the following code during VBlank: -ld a, HIGH(wShadowOAM) -call hOAMDMA -``` -## Joypad Input - -For joypad input, we've already setup 2 variables in working ram: `wCurKeys` and `wNewKeys`. - -*At the top of our "GalacticArmada.asm" file* - -```rgbasm,linenos -SECTION "GameVariables", WRAM0 - -{{#include ../../galactic-armada/src/main/GalacticArmada.asm:joypad-input-variables}} -``` - -Besides that, the final touch is calling the `Input` function at the start of the game loop: -```rgbasm, linenos -GalacticArmadaGameLoop: - - ; This is in input.asm - ; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html - ; In their words (paraphrased): reading player input for gameboy is NOT a trivial task - ; So it's best to use some tested code - call Input - - ; ... the rest of the game loop - -``` -That covers everything about our library implementations. Next we'll explain the folder structure, graphical assets, and compilation process. - +# Dependent Libraries + +This project uses 2 additional libraries. +- [Eievui's Sprite Object Library](https://github.com/eievui5/gb-sprobj-lib) +- The joypad input handler from [the previous tutorial](https://gbdev.io/gb-asm-tutorial/part2/input.html) +## Eievui's sprite object library + +For Eievui's sprite object library, we have already initialized it at the start of the game: + +*Inside the 'EntryPoint' function in "GalacticArmada.asm"* +```rgbasm, linenos +; from: https://github.com/eievui5/gb-sprobj-lib +; The library is relatively simple to get set up. First, put the following in your initialization code: +; Initilize Sprite Object Library. +call InitSprObjLibWrapper +``` + +Once Initialized, we must reset it at the start of your game loop. This is done using the `ResetShadowOAM` function. Later, we must call it's `hOAMDMA` function at the end of the game loop (during the vertical blank phase). + +*Inside the 'GalacticArmadaGameLoop' function in "GalacticArmada.asm"* + +```rgbasm, linenos +; then put a call to ResetShadowOAM at the beginning of your main loop. +call ResetShadowOAM + +; Our core game loop will go here + +call WaitForVBlankStart + +; from: https://github.com/eievui5/gb-sprobj-lib +; Finally, run the following code during VBlank: +ld a, HIGH(wShadowOAM) +call hOAMDMA +``` +## Joypad Input + +For joypad input, we've already setup 2 variables in working ram: `wCurKeys` and `wNewKeys`. + +*At the top of our "GalacticArmada.asm" file* + +```rgbasm,linenos +SECTION "GameVariables", WRAM0 + +{{#include ../../galactic-armada/src/main/GalacticArmada.asm:joypad-input-variables}} +``` + +Besides that, the final touch is calling the `Input` function at the start of the game loop: +```rgbasm, linenos +GalacticArmadaGameLoop: + + ; This is in input.asm + ; It's straight from: https://gbdev.io/gb-asm-tutorial/part2/input.html + ; In their words (paraphrased): reading player input for gameboy is NOT a trivial task + ; So it's best to use some tested code + call Input + + ; ... the rest of the game loop + +``` +That covers everything about our library implementations. Next we'll explain the folder structure, graphical assets, and compilation process. + diff --git a/src/part3/drawing-text.md b/src/part3/drawing-text.md index d42e8d9c..be27048d 100644 --- a/src/part3/drawing-text.md +++ b/src/part3/drawing-text.md @@ -1,95 +1,95 @@ -# Drawing Text - -On each game state in Galactic Armada, you'll see dynamically drawn text. The Game Boy doesn't support "fonts", in the traditional sense. To draw text, you first populate VRAM with tiles that have letters/numbers/puncation on them. Secondly, you render those tiles in a sequence on the window or background tilemap. - -You can see those text tiles in the text font asset included in the starter: - -![Text Font.png](../assets/part3/img/text-font.png) - ->**Note:** A function is included with the starter called `LoadTextFontIntoVRAM`. This function loads the tiles for the text font into VRAM. -## Mapping Characters to bytes - -Everything with Game Boy game development uses bytes. There's no concept of "characters", "letters", or "strings". RGBDS allows you to use string when defining data. - -```rgbasm -wScoreText:: db "score", 255 -``` - -The compiler will convert these strings to their byte equivalents. To do this, we need a [character mapping](https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#Character_maps). The starter comes with a basic character mapping: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/includes/character-mapping.inc}} -{{#include ../../galactic-armada/src/main/includes/character-mapping.inc}} -``` - -The above character mapping will convert (during the compile process) the previous `wScoreText` data to: "44, 28 ,40 ,43 , 30, 255". As per the character mapping: -- The `s` converts to 44 -- The `c` converts to 28 -- The `o` converts to 40 -- The `r` converts to 43 -- The `e` converts to 30 - ->**Note:** These values come from the text font. 's' is the 44th tile, 'c' is the 28th tile, and so on... - -The final 255 byte will be used by our text drawing function: `DrawTextInHL_AtDE`. It will let that function know we've reached the end. -## Drawing Basic Text - -Our `DrawTextInHL_AtDE` function from the starter will write to the address defined in "de" the value in "hl". Then increasing both address, and looping again. This is done until we reach the "end-of-string" byte (255). You can find this function in the ["src/main/utils/text-utils.asm"](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/src/main/utils/text-utils.asm) file: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/text-utils.asm:draw-text-tiles}} -{{#include ../../galactic-armada/src/main/utils/text-utils.asm:draw-text-tiles}} -``` - -## Animating Text with a Typewriter effect - -To achieve a typewriter effect, we just need to wait between drawing each letters. It's would be identical to `DrawTextInHL_AtDE`, in terms of concepts. The difference would be that this function would wait for 3 vblank phases to pass, before drawing the next letter. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/text-utils.asm:typewriter-effect}} -{{#include ../../galactic-armada/src/main/utils/text-utils.asm:typewriter-effect}} -``` - -The starer takes this to the next level by adding a function for writing multiline text. This is used during the story game state. - -![Story Game State.png](../assets/part3/img/rgbds-story-state.gif) - -## Animating Multiline Text with a typewriter effect - -The starter extends on the previous function to define `MultilineTypewriteTextInHL_AtDE`. This function simply uses `TypewriteTextInHL_AtDE`, adding 64 bytes to "de" (Where the text is drawn), until - -When the `TypewriteTextInHL_AtDE` function reaches the end of string character, a 255 byte; it will - - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/text-utils.asm:multiline-typewriter-effect}} -{{#include ../../galactic-armada/src/main/utils/text-utils.asm:multiline-typewriter-effect}} -``` - -In a later part of this tutorial, we will use that function with this data: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} -``` - -Calling that function like so: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} -``` -## Drawing Numbers - -For drawing numbers, we've created a function called `DrawBDigitsHL_OnDE`. To call this function, we need to specifiy: -- how many digits we want to draw in the `b` register -- a pointer to the digits in `hl` -- the address on the window/background where we want to draw them in `de` - ->**Note:** The numbers in our text font start at tile 10. So, for each number read, we'll add 10 to it. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-draw-lives}} -{{#include ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-draw-lives}} -``` - -We will later call that function like so: - ->**Note:** In this example, our `wScore` variable has 6 bytes. Each byte represents one digit. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:draw-score}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:draw-score}} +# Drawing Text + +On each game state in Galactic Armada, you'll see dynamically drawn text. The Game Boy doesn't support "fonts", in the traditional sense. To draw text, you first populate VRAM with tiles that have letters/numbers/puncation on them. Secondly, you render those tiles in a sequence on the window or background tilemap. + +You can see those text tiles in the text font asset included in the starter: + +![Text Font.png](../assets/part3/img/text-font.png) + +>**Note:** A function is included with the starter called `LoadTextFontIntoVRAM`. This function loads the tiles for the text font into VRAM. +## Mapping Characters to bytes + +Everything with Game Boy game development uses bytes. There's no concept of "characters", "letters", or "strings". RGBDS allows you to use string when defining data. + +```rgbasm +wScoreText:: db "score", 255 +``` + +The compiler will convert these strings to their byte equivalents. To do this, we need a [character mapping](https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#Character_maps). The starter comes with a basic character mapping: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/includes/character-mapping.inc}} +{{#include ../../galactic-armada/src/main/includes/character-mapping.inc}} +``` + +The above character mapping will convert (during the compile process) the previous `wScoreText` data to: "44, 28 ,40 ,43 , 30, 255". As per the character mapping: +- The `s` converts to 44 +- The `c` converts to 28 +- The `o` converts to 40 +- The `r` converts to 43 +- The `e` converts to 30 + +>**Note:** These values come from the text font. 's' is the 44th tile, 'c' is the 28th tile, and so on... + +The final 255 byte will be used by our text drawing function: `DrawTextInHL_AtDE`. It will let that function know we've reached the end. +## Drawing Basic Text + +Our `DrawTextInHL_AtDE` function from the starter will write to the address defined in "de" the value in "hl". Then increasing both address, and looping again. This is done until we reach the "end-of-string" byte (255). You can find this function in the ["src/main/utils/text-utils.asm"](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/src/main/utils/text-utils.asm) file: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/text-utils.asm:draw-text-tiles}} +{{#include ../../galactic-armada/src/main/utils/text-utils.asm:draw-text-tiles}} +``` + +## Animating Text with a Typewriter effect + +To achieve a typewriter effect, we just need to wait between drawing each letters. It's would be identical to `DrawTextInHL_AtDE`, in terms of concepts. The difference would be that this function would wait for 3 vblank phases to pass, before drawing the next letter. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/text-utils.asm:typewriter-effect}} +{{#include ../../galactic-armada/src/main/utils/text-utils.asm:typewriter-effect}} +``` + +The starer takes this to the next level by adding a function for writing multiline text. This is used during the story game state. + +![Story Game State.png](../assets/part3/img/rgbds-story-state.gif) + +## Animating Multiline Text with a typewriter effect + +The starter extends on the previous function to define `MultilineTypewriteTextInHL_AtDE`. This function simply uses `TypewriteTextInHL_AtDE`, adding 64 bytes to "de" (Where the text is drawn), until + +When the `TypewriteTextInHL_AtDE` function reaches the end of string character, a 255 byte; it will + + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/text-utils.asm:multiline-typewriter-effect}} +{{#include ../../galactic-armada/src/main/utils/text-utils.asm:multiline-typewriter-effect}} +``` + +In a later part of this tutorial, we will use that function with this data: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} +``` + +Calling that function like so: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} +``` +## Drawing Numbers + +For drawing numbers, we've created a function called `DrawBDigitsHL_OnDE`. To call this function, we need to specifiy: +- how many digits we want to draw in the `b` register +- a pointer to the digits in `hl` +- the address on the window/background where we want to draw them in `de` + +>**Note:** The numbers in our text font start at tile 10. So, for each number read, we'll add 10 to it. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-draw-lives}} +{{#include ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-draw-lives}} +``` + +We will later call that function like so: + +>**Note:** In this example, our `wScore` variable has 6 bytes. Each byte represents one digit. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:draw-score}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:draw-score}} ``` \ No newline at end of file diff --git a/src/part3/enemies.md b/src/part3/enemies.md index 77688269..c9a89a4f 100644 --- a/src/part3/enemies.md +++ b/src/part3/enemies.md @@ -1,75 +1,75 @@ -# Enemies - -Enemies in SHMUPS often come in a variety of types, and travel also in a variety of patterns. To keep things simple for this tutorial, we'll have one enemy that flys straight downward. Because of this decision, the logic for enemies is going to be similar to bullets in a way. They both travel vertically and disappear when off screeen. Some differences to point out are: - -- Enemies are not spawned by the player, so we need logic that spawns them at random times and locations. -- Enemies must check for collision against the player -- We'll check for collision against bullets in the enemy update function. - -Here are the RAM variables we'll use for our enemies: - -- wCurrentEnemyX & wCurrentEnemyY - When we check for collisions, we'll save the current enemy's position in these two variables. -- wNextEnemyXPosition - When this variable has a non-zero value, we'll spawn a new enemy at that position -- wSpawnCounter - We'll decrease this, when it reaches zero we'll spawn a new enemy (by setting 'wNextEnemyXPosition' to a non-zero value). -- wActiveEnemyCounter - This tracks how many enemies we have on screen -- wUpdateEnemiesCounter - This is used when updating enemies so we know how many we have updated. -- wUpdateEnemiesCurrentEnemyAddress - When we check for enemy v. bullet collision, we'll save the address of our current enemy here. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-start}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-start}} -``` - -Just like with bullets, we'll setup ROM data for our enemies tile data and metasprites. - -## Initializing Enemies - -When initializing the enemies (at the start of gameplay), we'll copy the enemy tile data into VRAM. Also, like with bullets, we'll loop through and make sure each enemy is set to inactive. - -## Updating Enemies - -When "UpdateEnemies" is called from gameplay, the first thing we try to do is spawn new enemies. After that, if we have no active enemies (and are not trying to spawn a new enemy), we stop the "UpdateEnemies" function. From here, like with bullets, we'll save the address of our first enemy in hl and start looping through. - -When we are looping through our enemy object pool, let's check if the current enemy is active. If it's active, we'll update it like normal. If it isn't active, the game checks if we want to spawn a new enemy. We specify we want to spawn a new enemy by setting 'wNextEnemyXPosition' to a non-zero value. If we don't want to spawn a new enemy, we'll move on to the next enemy. - -If we want to spawn a new enemy, we'll set the current inactive enemy to active. Afterwards, we'll set it's y position to zero, and it's x position to whatever was in the 'wNextEnemyXPosition' variable. After that, we'll increase our active enemy counter, and go on to update the enemy like normal. - - -When We are done updating a single enemy, we'll jump to the "UpdateEnemies_Loop" label. Here we'll increase how many enemies we've updated, and end if we've done them all. If we still have more enemies left, we'll increase the address stored in hl by 6 and update the next enemy. - -> The "hl" registers should always point to the current enemies first byte when this label is reached. - - -For updating enemies, we'll first get the enemies speed. Afterwards we'll increase the enemies 16-bit y position. Once we've done that, we'll descale the y position so we can check for collisions and draw the ennemy. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update-per-enemy2}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update-per-enemy2}} -``` - -## Player & Bullet Collision - -One of the differences between enemies and bullets is that enemies must check for collision against the player and also against bullets. For both of these cases, we'll use a simple Axis-Aligned Bounding Box test. We'll cover the specific logic in a later section. - -If we have a collison against the player we need to damage the player, and redraw how many lives they have. In addition, it's optional, but we'll deactivate the enemy too when they collide with the player. - -> Our "hl" registers should point to the active byte of the current enemy. We push and pop our "hl" registers to make sure we get back to that same address for later logic. - - -If there is no collision with the player, we'll draw the enemies. This is done just as we did the player and bullets, with the "DrawMetasprites" function. - - -## Deactivating Enemies - -Deactivating an enemy is just like with bullets. We'll set it's first byte to 0, and decrease our counter variable. - -> Here, we can just use the current address in HL. This is the second reason we wanted to keep the address of our first byte on the stack. - - -## Spawning Enemies - -Randomly, we want to spawn enemies. We'll increase a counter called "wEnemyCounter". When it reaches a preset maximum value, we'll **maybe** try to spawn a new enemy. - -Firstly, We need to make sure we aren't at maximum enemy capacity, if so, we will not spawn enemy more enemies. If we are not at maximum capacity, we'll try to get a x position to spawn the enemy at. If our x position is below 24 or above 150, we'll also NOT spawn a new enemy. - -> All enemies are spawned with y position of 0, so we only need to get the x position. - -If we have a valid x position, we'll reset our spawn counter, and save that x position in the "wNextEnemyXPosition" variable. With this variable set, We'll later activate and update a enemy that we find in the inactive state. +# Enemies + +Enemies in SHMUPS often come in a variety of types, and travel also in a variety of patterns. To keep things simple for this tutorial, we'll have one enemy that flys straight downward. Because of this decision, the logic for enemies is going to be similar to bullets in a way. They both travel vertically and disappear when off screeen. Some differences to point out are: + +- Enemies are not spawned by the player, so we need logic that spawns them at random times and locations. +- Enemies must check for collision against the player +- We'll check for collision against bullets in the enemy update function. + +Here are the RAM variables we'll use for our enemies: + +- wCurrentEnemyX & wCurrentEnemyY - When we check for collisions, we'll save the current enemy's position in these two variables. +- wNextEnemyXPosition - When this variable has a non-zero value, we'll spawn a new enemy at that position +- wSpawnCounter - We'll decrease this, when it reaches zero we'll spawn a new enemy (by setting 'wNextEnemyXPosition' to a non-zero value). +- wActiveEnemyCounter - This tracks how many enemies we have on screen +- wUpdateEnemiesCounter - This is used when updating enemies so we know how many we have updated. +- wUpdateEnemiesCurrentEnemyAddress - When we check for enemy v. bullet collision, we'll save the address of our current enemy here. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-start}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-start}} +``` + +Just like with bullets, we'll setup ROM data for our enemies tile data and metasprites. + +## Initializing Enemies + +When initializing the enemies (at the start of gameplay), we'll copy the enemy tile data into VRAM. Also, like with bullets, we'll loop through and make sure each enemy is set to inactive. + +## Updating Enemies + +When "UpdateEnemies" is called from gameplay, the first thing we try to do is spawn new enemies. After that, if we have no active enemies (and are not trying to spawn a new enemy), we stop the "UpdateEnemies" function. From here, like with bullets, we'll save the address of our first enemy in hl and start looping through. + +When we are looping through our enemy object pool, let's check if the current enemy is active. If it's active, we'll update it like normal. If it isn't active, the game checks if we want to spawn a new enemy. We specify we want to spawn a new enemy by setting 'wNextEnemyXPosition' to a non-zero value. If we don't want to spawn a new enemy, we'll move on to the next enemy. + +If we want to spawn a new enemy, we'll set the current inactive enemy to active. Afterwards, we'll set it's y position to zero, and it's x position to whatever was in the 'wNextEnemyXPosition' variable. After that, we'll increase our active enemy counter, and go on to update the enemy like normal. + + +When We are done updating a single enemy, we'll jump to the "UpdateEnemies_Loop" label. Here we'll increase how many enemies we've updated, and end if we've done them all. If we still have more enemies left, we'll increase the address stored in hl by 6 and update the next enemy. + +> The "hl" registers should always point to the current enemies first byte when this label is reached. + + +For updating enemies, we'll first get the enemies speed. Afterwards we'll increase the enemies 16-bit y position. Once we've done that, we'll descale the y position so we can check for collisions and draw the ennemy. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update-per-enemy2}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update-per-enemy2}} +``` + +## Player & Bullet Collision + +One of the differences between enemies and bullets is that enemies must check for collision against the player and also against bullets. For both of these cases, we'll use a simple Axis-Aligned Bounding Box test. We'll cover the specific logic in a later section. + +If we have a collison against the player we need to damage the player, and redraw how many lives they have. In addition, it's optional, but we'll deactivate the enemy too when they collide with the player. + +> Our "hl" registers should point to the active byte of the current enemy. We push and pop our "hl" registers to make sure we get back to that same address for later logic. + + +If there is no collision with the player, we'll draw the enemies. This is done just as we did the player and bullets, with the "DrawMetasprites" function. + + +## Deactivating Enemies + +Deactivating an enemy is just like with bullets. We'll set it's first byte to 0, and decrease our counter variable. + +> Here, we can just use the current address in HL. This is the second reason we wanted to keep the address of our first byte on the stack. + + +## Spawning Enemies + +Randomly, we want to spawn enemies. We'll increase a counter called "wEnemyCounter". When it reaches a preset maximum value, we'll **maybe** try to spawn a new enemy. + +Firstly, We need to make sure we aren't at maximum enemy capacity, if so, we will not spawn enemy more enemies. If we are not at maximum capacity, we'll try to get a x position to spawn the enemy at. If our x position is below 24 or above 150, we'll also NOT spawn a new enemy. + +> All enemies are spawned with y position of 0, so we only need to get the x position. + +If we have a valid x position, we'll reset our spawn counter, and save that x position in the "wNextEnemyXPosition" variable. With this variable set, We'll later activate and update a enemy that we find in the inactive state. diff --git a/src/part3/enemy-bullet-collision.md b/src/part3/enemy-bullet-collision.md index 700fb74a..77196d13 100644 --- a/src/part3/enemy-bullet-collision.md +++ b/src/part3/enemy-bullet-collision.md @@ -1 +1 @@ -# Enemy-Bullet Collision +# Enemy-Bullet Collision diff --git a/src/part3/enemy-player-collision.md b/src/part3/enemy-player-collision.md index 6b77ac54..99606872 100644 --- a/src/part3/enemy-player-collision.md +++ b/src/part3/enemy-player-collision.md @@ -1 +1 @@ -# Enemy-Player Collision +# Enemy-Player Collision diff --git a/src/part3/entry-point.md b/src/part3/entry-point.md index 57a72847..b58ad554 100644 --- a/src/part3/entry-point.md +++ b/src/part3/entry-point.md @@ -1 +1 @@ -# Entry Point +# Entry Point diff --git a/src/part3/game-state-management.md b/src/part3/game-state-management.md index 7613df87..b195dee6 100644 --- a/src/part3/game-state-management.md +++ b/src/part3/game-state-management.md @@ -1,109 +1,109 @@ -# Changing Game States - -In our [GalacticArmada.asm](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/src/main/GalacticArmada.asm) file, we'll define label called "NextGameState". Our game will have 3 game states: - -- Title Screen -- Story Screen -- Gameplay - -Here is how they will flow: - -![Game States Visualized.png](../assets/part3/img/Game_States_Visualized.png) - -This page will show you how to setup basic game state management. For organization, we'll put our game state management code inside of a new file. - -**Create "game-state-management.asm" right next to the entrypoint "GalacticArmada.asm"** -## Setting up Game State Management - -First thing we'll do in our new "game-state-management.asm" file is setup 3 variables in working ram. -- **wCurrentGameState_Update** - the address of the current game state's update function -- **wNextGameState_Initiate** - If we are changing game states, this will be non-zero. In that case, it will be the address of the "initiate" function for that game state. -- **wNextGameState_Update** - If we are changing game states, this will be non-zero. In that case, it will be the address of the "update" function for that game state. It will overwrite the `wCurrentGameState_Update` variable after the above `wNextGameState_Initiate` is called. - -**Create those 3 variables as "words" at the top of our game-state-management.asm file, in the working ram section titled "GameStateManagementVariables":** - -> **Note:** See the RGBDS page on ["Defining Data"](https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#DEFINING_DATA) for more information about variable types. - -```rgbasm, linenos,start={{#line_no_of "" ../../galactic-armada/src/main/game-state-management.asm:game-state-variables}} -{{#include ../../galactic-armada/src/main/game-state-management.asm:game-state-variables}} -``` - -**Next, create a function called `InitializeGameStateManagement`.** This function should go inside of a section called "GameStateManagement", and be exported. See [here](https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#Labels) for more information about exporting. - -In this function we'll default all of our game state variables to 0. - -```rgbasm, linenos - -SECTION "GameStateManagement", ROM0 - -InitializeGameStateManagment:: - - ; Default our game state variables - ld a, 0 - ld [wCurrentGameState_Update+0], a - ld [wCurrentGameState_Update+1], a - ld [wNextGameState_Initiate+0], a - ld [wNextGameState_Initiate+1], a - ld [wNextGameState_Update+0], a - ld [wNextGameState_Update+1], a - - ret -``` - -If we return back to our GalacticArmada.asm file, we'll put in a call to our new `InitializeGameStateManagement` function. This function call will go right before our game loop: -```rgbasm, linenos - ; Inside of GalacticArmada.asm - ; ... Previous "EntryPoint" logic - - call InitializeGameStateManagment - -GalacticArmadaGameLoop: -``` - -Now we have to setup and implement those variables. To do that, we'll create the following functions: -- **InitiateNewGameStates** - This will initialize the new game state, if we are changing game states. -- **UpdateCurrentGameState** - This will update the current game state, if it exists. -### Initiate New Game States - -We previously created a `wNextGameState_Initiate` variable. This variable will be used to hold an address. That address will point to the initiation logic for the next game state. If this variable is 0, then the game is NOT changing game states. If the variable is NOT 0, then we'll call the function it specifies. - -After we've called that initiate function, we'll update our `wCurrentGameState_Update` variable. We'll override it's current value, with the value specified in our other variable: `wNextGameState_Update`. This will tell the game to start calling the new game state's update logic instead of our curernt/old one. - -With those changes done, we'll reset our `wNextGameState_Initiate` and `wNextGameState_Update` variables back to 0. This will prevent the initiation logic from executing again until we change the game state. - -**Create this function at the bottom of the game-state-management.asm file:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/game-state-management.asm:initiate-new-game-state-function}} -{{#include ../../galactic-armada/src/main/game-state-management.asm:initiate-new-game-state-function}} -``` - -> **Note:** The `callHL` function will already be included in the starter. It simply provides an easy way to jump to dynamic addresses and return afterwards. -### Updating the current Game State - -For updating the current game state, we'll get the address in our `wCurrentGameState_Update` variable. If it's 0, we'll return early. Otherwise, we'll call the function located at that address and return when the function is done. - -**Create this function at the bottom of the game-state-management.asm file:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/game-state-management.asm:update-current-game-state-function}} -{{#include ../../galactic-armada/src/main/game-state-management.asm:update-current-game-state-function}} -``` -## Adding Game State Management to our Game Loop - -Now that we have created our `InitiateNewCurrentGameState` and `UpdateCurrentGameState` functions, we can implement them - -**Go back to our "GalacticArmada.asm" file. In the `GalacticArmadaGameLoop` function, (after we call `ResetShadowOAM`) add calls to those 2 functions** - -```rgbasm, linenos -; Inside of GalacticArmada.asm -GalacticArmadaGameLoop: - - ; ... existing logic calling 'Input' and `ResetShadowOAM` - -{{#include ../../galactic-armada/src/main/GalacticArmada.asm:update-game-state-management}} - - ; ... existing logic waiting for VBlank start, before calling `hOAMDMA` and looping. -``` - -That wraps up game state management for now. We've got one more thing to do, setup a default game state. That task won't be done yet. - +# Changing Game States + +In our [GalacticArmada.asm](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/src/main/GalacticArmada.asm) file, we'll define label called "NextGameState". Our game will have 3 game states: + +- Title Screen +- Story Screen +- Gameplay + +Here is how they will flow: + +![Game States Visualized.png](../assets/part3/img/Game_States_Visualized.png) + +This page will show you how to setup basic game state management. For organization, we'll put our game state management code inside of a new file. + +**Create "game-state-management.asm" right next to the entrypoint "GalacticArmada.asm"** +## Setting up Game State Management + +First thing we'll do in our new "game-state-management.asm" file is setup 3 variables in working ram. +- **wCurrentGameState_Update** - the address of the current game state's update function +- **wNextGameState_Initiate** - If we are changing game states, this will be non-zero. In that case, it will be the address of the "initiate" function for that game state. +- **wNextGameState_Update** - If we are changing game states, this will be non-zero. In that case, it will be the address of the "update" function for that game state. It will overwrite the `wCurrentGameState_Update` variable after the above `wNextGameState_Initiate` is called. + +**Create those 3 variables as "words" at the top of our game-state-management.asm file, in the working ram section titled "GameStateManagementVariables":** + +> **Note:** See the RGBDS page on ["Defining Data"](https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#DEFINING_DATA) for more information about variable types. + +```rgbasm, linenos,start={{#line_no_of "" ../../galactic-armada/src/main/game-state-management.asm:game-state-variables}} +{{#include ../../galactic-armada/src/main/game-state-management.asm:game-state-variables}} +``` + +**Next, create a function called `InitializeGameStateManagement`.** This function should go inside of a section called "GameStateManagement", and be exported. See [here](https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#Labels) for more information about exporting. + +In this function we'll default all of our game state variables to 0. + +```rgbasm, linenos + +SECTION "GameStateManagement", ROM0 + +InitializeGameStateManagment:: + + ; Default our game state variables + ld a, 0 + ld [wCurrentGameState_Update+0], a + ld [wCurrentGameState_Update+1], a + ld [wNextGameState_Initiate+0], a + ld [wNextGameState_Initiate+1], a + ld [wNextGameState_Update+0], a + ld [wNextGameState_Update+1], a + + ret +``` + +If we return back to our GalacticArmada.asm file, we'll put in a call to our new `InitializeGameStateManagement` function. This function call will go right before our game loop: +```rgbasm, linenos + ; Inside of GalacticArmada.asm + ; ... Previous "EntryPoint" logic + + call InitializeGameStateManagment + +GalacticArmadaGameLoop: +``` + +Now we have to setup and implement those variables. To do that, we'll create the following functions: +- **InitiateNewGameStates** - This will initialize the new game state, if we are changing game states. +- **UpdateCurrentGameState** - This will update the current game state, if it exists. +### Initiate New Game States + +We previously created a `wNextGameState_Initiate` variable. This variable will be used to hold an address. That address will point to the initiation logic for the next game state. If this variable is 0, then the game is NOT changing game states. If the variable is NOT 0, then we'll call the function it specifies. + +After we've called that initiate function, we'll update our `wCurrentGameState_Update` variable. We'll override it's current value, with the value specified in our other variable: `wNextGameState_Update`. This will tell the game to start calling the new game state's update logic instead of our curernt/old one. + +With those changes done, we'll reset our `wNextGameState_Initiate` and `wNextGameState_Update` variables back to 0. This will prevent the initiation logic from executing again until we change the game state. + +**Create this function at the bottom of the game-state-management.asm file:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/game-state-management.asm:initiate-new-game-state-function}} +{{#include ../../galactic-armada/src/main/game-state-management.asm:initiate-new-game-state-function}} +``` + +> **Note:** The `callHL` function will already be included in the starter. It simply provides an easy way to jump to dynamic addresses and return afterwards. +### Updating the current Game State + +For updating the current game state, we'll get the address in our `wCurrentGameState_Update` variable. If it's 0, we'll return early. Otherwise, we'll call the function located at that address and return when the function is done. + +**Create this function at the bottom of the game-state-management.asm file:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/game-state-management.asm:update-current-game-state-function}} +{{#include ../../galactic-armada/src/main/game-state-management.asm:update-current-game-state-function}} +``` +## Adding Game State Management to our Game Loop + +Now that we have created our `InitiateNewCurrentGameState` and `UpdateCurrentGameState` functions, we can implement them + +**Go back to our "GalacticArmada.asm" file. In the `GalacticArmadaGameLoop` function, (after we call `ResetShadowOAM`) add calls to those 2 functions** + +```rgbasm, linenos +; Inside of GalacticArmada.asm +GalacticArmadaGameLoop: + + ; ... existing logic calling 'Input' and `ResetShadowOAM` + +{{#include ../../galactic-armada/src/main/GalacticArmada.asm:update-game-state-management}} + + ; ... existing logic waiting for VBlank start, before calling `hOAMDMA` and looping. +``` + +That wraps up game state management for now. We've got one more thing to do, setup a default game state. That task won't be done yet. + In the next page, you'll create the title screen. Once we've fully setup that game state, we'll come back to the GalacticArmada.asm file and specify it as our default game state. \ No newline at end of file diff --git a/src/part3/game-states.md b/src/part3/game-states.md index 0d28b393..9275a9ab 100644 --- a/src/part3/game-states.md +++ b/src/part3/game-states.md @@ -1,29 +1,29 @@ -# Game States - -Galactic armada comes with 3 basic game states. A title screen, a story screen, and gameplay. Each game state has different logic and are separated into their own folders. Each state has the responsibility to - -- Initiate itself -- Turn the LCD screen back on after initiating (turned off before initiating each game state) -- Poll for input -- Wait or VBlank phases -- Provide most of it’s own logic. - -The default game state is the title screen. - -![Game States Visualized.png](../assets/part3/img/Game_States_Visualized.png) - -## Common Tile Data - -All the game states utilize the tiles from the “text-font.png” image. This is a basic alphanumeric set of characters. - -![Untitled](../assets/part3/img/text-font-large.png) - - This character-set is called “Area51”. It, and more 8x8 pixel fonts can ne found here: [https://damieng.com/typography/zx-origins/](https://damieng.com/typography/zx-origins/) . These 52 tiles will be placed at the beginning of our background/window VRAM region. - -![TextFontDiagram.png](../assets/part3/img/TextFontDiagram.png) - -Because they are shared, we’ll put them in VRAM at the start and not touch them. - - -Because of this, our background tilemaps will be need to use a offset of 52 tiles. Each tile is 16 bytes, so tile data also needs to start at $9440. Here’s an example for the title screen. - +# Game States + +Galactic armada comes with 3 basic game states. A title screen, a story screen, and gameplay. Each game state has different logic and are separated into their own folders. Each state has the responsibility to + +- Initiate itself +- Turn the LCD screen back on after initiating (turned off before initiating each game state) +- Poll for input +- Wait or VBlank phases +- Provide most of it’s own logic. + +The default game state is the title screen. + +![Game States Visualized.png](../assets/part3/img/Game_States_Visualized.png) + +## Common Tile Data + +All the game states utilize the tiles from the “text-font.png” image. This is a basic alphanumeric set of characters. + +![Untitled](../assets/part3/img/text-font-large.png) + + This character-set is called “Area51”. It, and more 8x8 pixel fonts can ne found here: [https://damieng.com/typography/zx-origins/](https://damieng.com/typography/zx-origins/) . These 52 tiles will be placed at the beginning of our background/window VRAM region. + +![TextFontDiagram.png](../assets/part3/img/TextFontDiagram.png) + +Because they are shared, we’ll put them in VRAM at the start and not touch them. + + +Because of this, our background tilemaps will be need to use a offset of 52 tiles. Each tile is 16 bytes, so tile data also needs to start at $9440. Here’s an example for the title screen. + diff --git a/src/part3/gameplay.md b/src/part3/gameplay.md index 70e1a1c2..a74bc18b 100644 --- a/src/part3/gameplay.md +++ b/src/part3/gameplay.md @@ -1,63 +1,63 @@ -# Gameplay State - -In this game state, the player will control a spaceship. Flying over a vertically scrolling space background. They’ll be able to freely move in 4 directions , and shoot oncoming alien ships. As alien ships are destroyed by bullets, the player’s score will increase. - -![rgbds-shmup-gameplay.gif](../assets/part3/img/rgbds-shmup-gameplay.gif) - -Gameplay is the core chunk of the source code. It also took the most time to create. Because of such, this game state has to be split into multiple sub-pages. Each page will explain a different gameplay concept. - -**Create `gameplay-state.asm`, and add the following data and variables:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:gameplay-data-variables}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:gameplay-data-variables}} -``` - -For simplicity reasons, our score uses 6 bytes. Each byte repesents one digit in the score. - -## Initiating the Gameplay Game State: - -When gameplay starts we want to do all of the following: -- reset the player's score to 0 -- reset the player's lives to 3. -- Initialize all of our object pool -- Clear the background and any existing sprites -- Setup VRAM with the neccessary tile data -- Enable STAT interrupts for the HUD -- Draw our "score" & "lives" on the HUD. -- Reset the window's position back to 7,0 -- Enable the window using the tilemap at $9C00 - ->**Note:** Object pools will be covered in the next page. - -**Copy the following code to the bottom of your `gameplay-state.asm` file** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:init-gameplay-state}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:init-gameplay-state}} -``` - -The initialization logic for our the background, the player, the enemies, the bullets will be explained in later pages. Every game state is responsible for turning the LCD back on. The gameplay game state needs to use the window layer, so we'll make sure that's enabled before we return. - -## Updating the Gameplay Game State - -Our "UpdateGameplayState" function doesn't have very complicated logic. Most of the logic has been split into separate files for the background, player, enemies, and bullets. - -During gameplay, we do all of the following: -* Try to spawn enemies -* Update our object pool -* Update our Background -* Check our player's health, if it's gone below zero we'll end gameplay - -**Copy the following code to the bottom of your `gameplay-state.asm` file** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:update-gameplay-state}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:update-gameplay-state}} -``` - -Ending gameplay is very simple, we'll do the same thing we did to transition TO gameplay (from the story screen). We'll simply put the address of the title screen's init & update functions inside of our `wNextGameState_Initiate` and `wNextGameState_Update` variables. - -**Copy the following code to the bottom of your `gameplay-state.asm` file** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:end-gameplay-state}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:end-gameplay-state}} -``` +# Gameplay State + +In this game state, the player will control a spaceship. Flying over a vertically scrolling space background. They’ll be able to freely move in 4 directions , and shoot oncoming alien ships. As alien ships are destroyed by bullets, the player’s score will increase. + +![rgbds-shmup-gameplay.gif](../assets/part3/img/rgbds-shmup-gameplay.gif) + +Gameplay is the core chunk of the source code. It also took the most time to create. Because of such, this game state has to be split into multiple sub-pages. Each page will explain a different gameplay concept. + +**Create `gameplay-state.asm`, and add the following data and variables:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:gameplay-data-variables}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:gameplay-data-variables}} +``` + +For simplicity reasons, our score uses 6 bytes. Each byte repesents one digit in the score. + +## Initiating the Gameplay Game State: + +When gameplay starts we want to do all of the following: +- reset the player's score to 0 +- reset the player's lives to 3. +- Initialize all of our object pool +- Clear the background and any existing sprites +- Setup VRAM with the neccessary tile data +- Enable STAT interrupts for the HUD +- Draw our "score" & "lives" on the HUD. +- Reset the window's position back to 7,0 +- Enable the window using the tilemap at $9C00 + +>**Note:** Object pools will be covered in the next page. + +**Copy the following code to the bottom of your `gameplay-state.asm` file** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:init-gameplay-state}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:init-gameplay-state}} +``` + +The initialization logic for our the background, the player, the enemies, the bullets will be explained in later pages. Every game state is responsible for turning the LCD back on. The gameplay game state needs to use the window layer, so we'll make sure that's enabled before we return. + +## Updating the Gameplay Game State + +Our "UpdateGameplayState" function doesn't have very complicated logic. Most of the logic has been split into separate files for the background, player, enemies, and bullets. + +During gameplay, we do all of the following: +* Try to spawn enemies +* Update our object pool +* Update our Background +* Check our player's health, if it's gone below zero we'll end gameplay + +**Copy the following code to the bottom of your `gameplay-state.asm` file** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:update-gameplay-state}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:update-gameplay-state}} +``` + +Ending gameplay is very simple, we'll do the same thing we did to transition TO gameplay (from the story screen). We'll simply put the address of the title screen's init & update functions inside of our `wNextGameState_Initiate` and `wNextGameState_Update` variables. + +**Copy the following code to the bottom of your `gameplay-state.asm` file** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:end-gameplay-state}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-state.asm:end-gameplay-state}} +``` That's it for gameplay, next we'll go over object pools. \ No newline at end of file diff --git a/src/part3/getting-started.md b/src/part3/getting-started.md index 4e17210c..3473592a 100644 --- a/src/part3/getting-started.md +++ b/src/part3/getting-started.md @@ -1,25 +1,25 @@ - - -# Introducing Galactic Armada - - - -This guide will help you create a classic shoot-em-up in RGBDS. This guide builds on knowledge from the previous tutorials, so some basic (or previously explained) concepts will not be explained. - -You can find the full source code for this tutorial here. -## Feature set - -Here's a list of features that will be included in the final product. - -- Vertical Scrolling Background -- Basic HUD (via Window) & Score -- 4-Directional Player Movement -- Enemies -- Bullets -- Enemy/Bullet Collision -- Enemy/Player Collision -- Smooth Movement via Scaled Integers - Instead of using counters, smoother motion can be achieved using 16-bit (scaled) integers. -- Multiple Game States: Title Screen, Gameplay, Story State -- STAT Interrupts - used to properly draw the HUD at the top of gameplay. -- RGBGFX & INCBIN -- Writing Text + + +# Introducing Galactic Armada + + + +This guide will help you create a classic shoot-em-up in RGBDS. This guide builds on knowledge from the previous tutorials, so some basic (or previously explained) concepts will not be explained. + +You can find the full source code for this tutorial here. +## Feature set + +Here's a list of features that will be included in the final product. + +- Vertical Scrolling Background +- Basic HUD (via Window) & Score +- 4-Directional Player Movement +- Enemies +- Bullets +- Enemy/Bullet Collision +- Enemy/Player Collision +- Smooth Movement via Scaled Integers - Instead of using counters, smoother motion can be achieved using 16-bit (scaled) integers. +- Multiple Game States: Title Screen, Gameplay, Story State +- STAT Interrupts - used to properly draw the HUD at the top of gameplay. +- RGBGFX & INCBIN +- Writing Text diff --git a/src/part3/heads-up-interface.md b/src/part3/heads-up-interface.md index 779a6314..3a7c6019 100644 --- a/src/part3/heads-up-interface.md +++ b/src/part3/heads-up-interface.md @@ -1,62 +1,62 @@ -# Heads Up Interface - -The gameboy normally draws sprites over both the window and background, and the window over the background. In Galactic Armada, The background is vertically scrolling. This means the HUD (the score text and number) needs to be draw on the window, which is separate from the background. - -On our HUD, we'll draw both our score and our lives. We'll also use STAT interrupts to make sure nothing covers the HUD. - -## STAT Interrupts & the window - - -The window is not enabled by default. We can enable the window using the `LCDC` register. RGBDS comes with constants that will help us. - -> ⚠️ NOTE: The window can essentially be a copy of the background. The `LCDCF_WIN9C00|LCDCF_BG9800` portion makes the background and window use different tilemaps when drawn. -There’s only one problem. Since the window is drawn between sprites and the background. Without any extra effort, our scrolling background tilemap will be covered by our window. In addition, our sprites will be drawn over our hud. For this, we’ll need STAT interrupts. Fore more information on STAT interrupts, check out the pandocs: [https://gbdev.io/pandocs/Interrupt_Sources.html](https://gbdev.io/pandocs/Interrupt_Sources.html) - - -![InterruptsDiagram.png](../assets/part3/img/StatInterruptsVisualized.png) - -> ### **[Using the STAT interrupt](https://gbdev.io/pandocs/Interrupt_Sources.html#using-the-stat-interrupt)** -> -> One very popular use is to indicate to the user when the video hardware is about to redraw a given LCD line. This can be useful for dynamically controlling the SCX/SCY registers ($FF43/$FF42) to [perform special video effects](https://github.com/gb-archive/DeadCScroll). -> -> Example application: set LYC to WY, enable LY=LYC interrupt, and have the handler disable sprites. This can be used if you use the window for a text box (at the bottom of the screen), and you want sprites to be hidden by the text box. - - -With STAT interrupts, we can implement raster effects. in our case, we’ll enable the window and stop drawing sprites on the first 8 scanlines. Afterwards, we’ll show sprites and disable the window layer for the remaining scanlines. This makes sure nothing overlaps our HUD, and that our background is fully shown also. - -### Initiating & Disabling STAT interrupts - -In our gameplay game state, at different points in time, we initialized and disabled interrupts. Here's the logic for those functions in our "src/main/states/gameplay/hud.asm" file: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-start}} -{{#include ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-start}} -``` - -### Defining STAT interrupts - -Our actual STAT interrupts must be located at $0048. We'll define different paths depending on what our LYC variable's value is when executed. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-section}} -{{#include ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-section}} -``` - -That should be all it takes to get a properly drawn HUD. For more details, check out the code in the repo or [ask questions](https://gbdev.io/gb-asm-tutorial/help-feedback.html) on the gbdev discord server. - -## Keeping Score and Drawing Score on the HUD - -To keep things simple, back in our gameplay game state, we used 6 different bytes to hold our score.Each byte will hold a value between 0 and 9, and represents a specific digit in the score. So it’s easy to loop through and edit the score number on the HUD: The First byte represents the left-most digit, and the last byte represents the right-most digit. - -![DrawingScoreVisualized.png](../assets/part3/img/DrawingScoreVisualized.png) - -When the score increases, we’ll increase digits on the right. As they go higher than 9, we’ll reset back to 0 and increase the previous byte . - - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-increase-score}} -{{#include ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-increase-score}} -``` - - -We can call that score whenever a bullet hits an enemy. This function however does not draw our score on the background. We do that the same way we drew text previously: - - +# Heads Up Interface + +The gameboy normally draws sprites over both the window and background, and the window over the background. In Galactic Armada, The background is vertically scrolling. This means the HUD (the score text and number) needs to be draw on the window, which is separate from the background. + +On our HUD, we'll draw both our score and our lives. We'll also use STAT interrupts to make sure nothing covers the HUD. + +## STAT Interrupts & the window + + +The window is not enabled by default. We can enable the window using the `LCDC` register. RGBDS comes with constants that will help us. + +> ⚠️ NOTE: The window can essentially be a copy of the background. The `LCDCF_WIN9C00|LCDCF_BG9800` portion makes the background and window use different tilemaps when drawn. +There’s only one problem. Since the window is drawn between sprites and the background. Without any extra effort, our scrolling background tilemap will be covered by our window. In addition, our sprites will be drawn over our hud. For this, we’ll need STAT interrupts. Fore more information on STAT interrupts, check out the pandocs: [https://gbdev.io/pandocs/Interrupt_Sources.html](https://gbdev.io/pandocs/Interrupt_Sources.html) + + +![InterruptsDiagram.png](../assets/part3/img/StatInterruptsVisualized.png) + +> ### **[Using the STAT interrupt](https://gbdev.io/pandocs/Interrupt_Sources.html#using-the-stat-interrupt)** +> +> One very popular use is to indicate to the user when the video hardware is about to redraw a given LCD line. This can be useful for dynamically controlling the SCX/SCY registers ($FF43/$FF42) to [perform special video effects](https://github.com/gb-archive/DeadCScroll). +> +> Example application: set LYC to WY, enable LY=LYC interrupt, and have the handler disable sprites. This can be used if you use the window for a text box (at the bottom of the screen), and you want sprites to be hidden by the text box. + + +With STAT interrupts, we can implement raster effects. in our case, we’ll enable the window and stop drawing sprites on the first 8 scanlines. Afterwards, we’ll show sprites and disable the window layer for the remaining scanlines. This makes sure nothing overlaps our HUD, and that our background is fully shown also. + +### Initiating & Disabling STAT interrupts + +In our gameplay game state, at different points in time, we initialized and disabled interrupts. Here's the logic for those functions in our "src/main/states/gameplay/hud.asm" file: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-start}} +{{#include ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-start}} +``` + +### Defining STAT interrupts + +Our actual STAT interrupts must be located at $0048. We'll define different paths depending on what our LYC variable's value is when executed. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-section}} +{{#include ../../galactic-armada/src/main/states/gameplay/interrupts.asm:interrupts-section}} +``` + +That should be all it takes to get a properly drawn HUD. For more details, check out the code in the repo or [ask questions](https://gbdev.io/gb-asm-tutorial/help-feedback.html) on the gbdev discord server. + +## Keeping Score and Drawing Score on the HUD + +To keep things simple, back in our gameplay game state, we used 6 different bytes to hold our score.Each byte will hold a value between 0 and 9, and represents a specific digit in the score. So it’s easy to loop through and edit the score number on the HUD: The First byte represents the left-most digit, and the last byte represents the right-most digit. + +![DrawingScoreVisualized.png](../assets/part3/img/DrawingScoreVisualized.png) + +When the score increases, we’ll increase digits on the right. As they go higher than 9, we’ll reset back to 0 and increase the previous byte . + + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-increase-score}} +{{#include ../../galactic-armada/src/main/states/gameplay/hud.asm:hud-increase-score}} +``` + + +We can call that score whenever a bullet hits an enemy. This function however does not draw our score on the background. We do that the same way we drew text previously: + + Because we'll only ever have 3 lives, drawing our lives is much easier. The numeric characters in our text font start at 10, so we just need to put on the window, our lives plus 10. \ No newline at end of file diff --git a/src/part3/object-pools.md b/src/part3/object-pools.md index 212a6670..7f10560b 100644 --- a/src/part3/object-pools.md +++ b/src/part3/object-pools.md @@ -1,128 +1,128 @@ -# Object Pools - -Galactic Armada will use a single "object pool" for all obejcts (the player, enemies, and bullets). This pool repsents an array of objects, but is realy just a collection of bytes. Each object has the same number of bytes allocated for it. - -- Active (1 byte) -- Y Position (2 bytes) -- X Position (2 bytes) -- Metasprite address (2 bytes) -- Health (1 byte) -- Update function address (2 bytes) -- Damage Timer (1 byte) - -We've pre-defined that in the starter. - -*inside of our `constants.inc` include file:* - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/includes/constants.inc:object-bytes}} -{{#include ../../galactic-armada/src/main/includes/constants.inc:object-bytes}} -``` - -We need to next setup and implement variables that use that structure. - -**Create a file called `object-pool.asm`, add the following code to it:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:objects-pool-top}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:objects-pool-top}} -``` - -We'll explain each variable soon, but notice how we allocated space in WRAM for `wObjects`. Rather than using a literal number for how many objects our game can handle, we use the constant `MAX_OBJECT_COUNT`. This constant is declared in `constants.inc`, and prevents any sort of inconsistincies if we change our minds. - -## Initializing the object pool - -When we initialize the object pool, we need to do 2 primary things: -- Set all bytes in the pool to 0 -- Set our `wObjectsEnd` variable to 255 - -Our `wObjectsEnd` variable is used to simplify looping through all objects. More on that later. - -**Add the following code to the bottom of your `object-pool.asm` file:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:initialize-objects}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:initialize-objects}} -``` - -The above code is just going to loop through each object, and set all of it's bytes to 0. - -## Updating objects in our object pool - -We've created a variable called `wObjectsFlash`. This will be used as a counter. We'll increase it each frame. Because it's a a unsigned 8-bit integer, it's values will be between 0 and 255. Later, When it's value is larger than 128, any object that is damaged will not be shown. This overall creates a "blinking" damaged effect. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-1}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-1}} -``` - -We're going to loop through each object in our object pool. Our `wObjectsEnd` variable is used to simplify looping through all objects. When iterating through our `wObjects`, the first byte for an object is the active byte (aka `object_activeByte` in constants.inc). The valid values of this byte are 0 and 1. If the code reads a 255 (from `wObjectsEnd`), then we know we've reached the end of the bytes associated with our object pool. - -If we haven't read 255 yet, then we need to check if the current object is active. We can use `and a` (where the value in the 'a' register comes from the previous 'ld' instruction). If the zero flag is set, then that object is inactive and we'll jump to the next object. - -The Code will proceed on, if the object is active. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-2}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-2}} -``` - -The first thing we'll do for an active object is call it's update function. We'll copy the address of that function into it 'hl' and call it. Before such, we need to push hl onto the stack. When we're done calling our object's update function, we'll pop it off the stack. - -> **Note:** Before we change 'hl', we'll copy it's value into 'bc'. For each object's update function, 'bc' will have the address of that object's first byte. - -After updating, we want to draw the object. Before so, we need to check if the object is inactive. If so, we'll avoid drawing and jump to the next object. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-3}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-3}} -``` - -AFter updating, if our object is still active, we'll conditionally draw the object. Now we're going to put into use the previously mentioned `wObjectsFlash` variable. - -Each object has a damage byte (aka `object_damageByte` in constants.inc). If this byte is non-zero, the associated object has been damaged and we want it to blink. We'll skip drawing the object if the damage byte is non-zero and the `wObjectsFlash` variable is greater than 128. - - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-4}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-4}} -``` - -For drawing our object, we'll use the `RenderMetasprite` function from Evieue's Sprite Object library. This function requires the following parameters: -- the Q12.4 Fixed-point y position in bc -- The Q12.4 fixed-point X position in de -- The Pointer to current metasprite in hl - -To prepare for that function, we'll copy bytes from our object to the proper registers. - -> **Note:** After copying our x position to de, our 'hl' registers are not exactly what we need for `RenderMetasprite`. At that point in time, 'hl' doesn't contain the address of our metasprite. It contains a pointer to that address. - -After rendering our metasprite, we'll pop the start of our metasprite off the stack. This makes going to the next object simple. With 'hl' pointing to our object's first byte, we simply need to increment 'hl' - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-5}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-5}} -``` - -When 'hl' points to the first byte of an object ,we can easily move on to the next object. This is done by adding to it: the dynamic constant `PER_OBJECT_BYTES_COUNT` (from constants.inc). From there, we'll go back to our `UpdateObjectPool_Loop` label and repeat until we read 255. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-6}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-6}} -``` - -## Getting an inactive object - -When firing bullets and/or when spawning enemies, we'll need to find an object in our pool that is inactive. For this, we'll create a function called `GetNextAvailableObject_InHL` - -This function takes two parameters -- the starting byte in hl -- how many objects to check in b - -When this function is done, if the zero flag is not set: an inactive object has been found. At that point in time, 'hl' will point to the first byte of that object. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:get-next-available-object}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:get-next-available-object}} -``` - -Later, when spawning bullets, we'll call that function like so: - -*This code will be covered later* - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} - - ; ... More FireNextBullet logic -``` - +# Object Pools + +Galactic Armada will use a single "object pool" for all obejcts (the player, enemies, and bullets). This pool repsents an array of objects, but is realy just a collection of bytes. Each object has the same number of bytes allocated for it. + +- Active (1 byte) +- Y Position (2 bytes) +- X Position (2 bytes) +- Metasprite address (2 bytes) +- Health (1 byte) +- Update function address (2 bytes) +- Damage Timer (1 byte) + +We've pre-defined that in the starter. + +*inside of our `constants.inc` include file:* + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/includes/constants.inc:object-bytes}} +{{#include ../../galactic-armada/src/main/includes/constants.inc:object-bytes}} +``` + +We need to next setup and implement variables that use that structure. + +**Create a file called `object-pool.asm`, add the following code to it:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:objects-pool-top}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:objects-pool-top}} +``` + +We'll explain each variable soon, but notice how we allocated space in WRAM for `wObjects`. Rather than using a literal number for how many objects our game can handle, we use the constant `MAX_OBJECT_COUNT`. This constant is declared in `constants.inc`, and prevents any sort of inconsistincies if we change our minds. + +## Initializing the object pool + +When we initialize the object pool, we need to do 2 primary things: +- Set all bytes in the pool to 0 +- Set our `wObjectsEnd` variable to 255 + +Our `wObjectsEnd` variable is used to simplify looping through all objects. More on that later. + +**Add the following code to the bottom of your `object-pool.asm` file:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:initialize-objects}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:initialize-objects}} +``` + +The above code is just going to loop through each object, and set all of it's bytes to 0. + +## Updating objects in our object pool + +We've created a variable called `wObjectsFlash`. This will be used as a counter. We'll increase it each frame. Because it's a a unsigned 8-bit integer, it's values will be between 0 and 255. Later, When it's value is larger than 128, any object that is damaged will not be shown. This overall creates a "blinking" damaged effect. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-1}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-1}} +``` + +We're going to loop through each object in our object pool. Our `wObjectsEnd` variable is used to simplify looping through all objects. When iterating through our `wObjects`, the first byte for an object is the active byte (aka `object_activeByte` in constants.inc). The valid values of this byte are 0 and 1. If the code reads a 255 (from `wObjectsEnd`), then we know we've reached the end of the bytes associated with our object pool. + +If we haven't read 255 yet, then we need to check if the current object is active. We can use `and a` (where the value in the 'a' register comes from the previous 'ld' instruction). If the zero flag is set, then that object is inactive and we'll jump to the next object. + +The Code will proceed on, if the object is active. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-2}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-2}} +``` + +The first thing we'll do for an active object is call it's update function. We'll copy the address of that function into it 'hl' and call it. Before such, we need to push hl onto the stack. When we're done calling our object's update function, we'll pop it off the stack. + +> **Note:** Before we change 'hl', we'll copy it's value into 'bc'. For each object's update function, 'bc' will have the address of that object's first byte. + +After updating, we want to draw the object. Before so, we need to check if the object is inactive. If so, we'll avoid drawing and jump to the next object. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-3}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-3}} +``` + +AFter updating, if our object is still active, we'll conditionally draw the object. Now we're going to put into use the previously mentioned `wObjectsFlash` variable. + +Each object has a damage byte (aka `object_damageByte` in constants.inc). If this byte is non-zero, the associated object has been damaged and we want it to blink. We'll skip drawing the object if the damage byte is non-zero and the `wObjectsFlash` variable is greater than 128. + + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-4}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-4}} +``` + +For drawing our object, we'll use the `RenderMetasprite` function from Evieue's Sprite Object library. This function requires the following parameters: +- the Q12.4 Fixed-point y position in bc +- The Q12.4 fixed-point X position in de +- The Pointer to current metasprite in hl + +To prepare for that function, we'll copy bytes from our object to the proper registers. + +> **Note:** After copying our x position to de, our 'hl' registers are not exactly what we need for `RenderMetasprite`. At that point in time, 'hl' doesn't contain the address of our metasprite. It contains a pointer to that address. + +After rendering our metasprite, we'll pop the start of our metasprite off the stack. This makes going to the next object simple. With 'hl' pointing to our object's first byte, we simply need to increment 'hl' + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-5}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-5}} +``` + +When 'hl' points to the first byte of an object ,we can easily move on to the next object. This is done by adding to it: the dynamic constant `PER_OBJECT_BYTES_COUNT` (from constants.inc). From there, we'll go back to our `UpdateObjectPool_Loop` label and repeat until we read 255. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-6}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:update-objects-6}} +``` + +## Getting an inactive object + +When firing bullets and/or when spawning enemies, we'll need to find an object in our pool that is inactive. For this, we'll create a function called `GetNextAvailableObject_InHL` + +This function takes two parameters +- the starting byte in hl +- how many objects to check in b + +When this function is done, if the zero flag is not set: an inactive object has been found. At that point in time, 'hl' will point to the first byte of that object. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:get-next-available-object}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-pool.asm:get-next-available-object}} +``` + +Later, when spawning bullets, we'll call that function like so: + +*This code will be covered later* + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/bullets.asm:fire-bullets}} + + ; ... More FireNextBullet logic +``` + diff --git a/src/part3/project-structure.md b/src/part3/project-structure.md index dcd60e23..570468ef 100644 --- a/src/part3/project-structure.md +++ b/src/part3/project-structure.md @@ -4,7 +4,7 @@ For organizational purposes, many parts of the logic are separated into reusable Here’s a basic look at how the project is structured: -:::tip +::: tip Generated files should never be included in VCS repositories. It unneccessarily bloats the repo. The folders below marked with \* contains assets generated from running the Makefile and are not included in the repository. diff --git a/src/part3/scrolling-background.md b/src/part3/scrolling-background.md index b8eaad18..f0c63837 100644 --- a/src/part3/scrolling-background.md +++ b/src/part3/scrolling-background.md @@ -1,34 +1,34 @@ - -# Scrolling Background - -Scrolling the background is an easy task. However, for a SMOOTH slow scrolling background: scaled integers[^1] will be used. - ->⚠️ Scaled Integers[^1] are a way to provide smooth “sub-pixel” movement. They are slightly more difficult to understand & implement than implementing a counter, but they provide smoother motion. - -## Initializing the Background - -At the start of the gameplay game state we called the initialize background function. This function shows the star field background, and resets our background scroll variables: - -**Create a file called `backgrounds.asm` and add the following code:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-initialize}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-initialize}} -``` - -To scroll the background in a gameboy game, we simply need to gradually change the `SCX` or `SCX` registers. Our code is a tiny bit more complicated because of scaled integer usage. Our background's scroll position is stored in a 16-bit integer called `mBackgroundScroll`. We'l increase that 16-bit integer by a set amount. - -**Copy the `UpdateBackground` code below into your backgrounds.asm** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-start}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-start}} -``` - -We won't directly draw the background using this value. De-scaling a scaled integer simulates having a (more precise and useful for smooth movement) floating-point number. The value we draw our background at will be the de-scaled version of that 16-bit integer. To get that non-scaled version, we'll simply shift all of it's bit rightward 4 places. The final result will saved for when we update our background's y position. - -**Copy the code below into your backgrounds.asm** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-end}} -{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-end}} -``` - + +# Scrolling Background + +Scrolling the background is an easy task. However, for a SMOOTH slow scrolling background: scaled integers[^1] will be used. + +>⚠️ Scaled Integers[^1] are a way to provide smooth “sub-pixel” movement. They are slightly more difficult to understand & implement than implementing a counter, but they provide smoother motion. + +## Initializing the Background + +At the start of the gameplay game state we called the initialize background function. This function shows the star field background, and resets our background scroll variables: + +**Create a file called `backgrounds.asm` and add the following code:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-initialize}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-initialize}} +``` + +To scroll the background in a gameboy game, we simply need to gradually change the `SCX` or `SCX` registers. Our code is a tiny bit more complicated because of scaled integer usage. Our background's scroll position is stored in a 16-bit integer called `mBackgroundScroll`. We'l increase that 16-bit integer by a set amount. + +**Copy the `UpdateBackground` code below into your backgrounds.asm** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-start}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-start}} +``` + +We won't directly draw the background using this value. De-scaling a scaled integer simulates having a (more precise and useful for smooth movement) floating-point number. The value we draw our background at will be the de-scaled version of that 16-bit integer. To get that non-scaled version, we'll simply shift all of it's bit rightward 4 places. The final result will saved for when we update our background's y position. + +**Copy the code below into your backgrounds.asm** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-end}} +{{#include ../../galactic-armada/src/main/states/gameplay/gameplay-background.asm:gameplay-background-update-end}} +``` + [^1]: [Scaled Factor on Wikipedia](https://en.wikipedia.org/wiki/Scale_factor_(computer_science)) \ No newline at end of file diff --git a/src/part3/sporbs-metasprites.md b/src/part3/sporbs-metasprites.md index b45187dc..f7150b8e 100644 --- a/src/part3/sporbs-metasprites.md +++ b/src/part3/sporbs-metasprites.md @@ -1,32 +1,32 @@ -# Metasprites - -We'll use the metasprite implementation that comes with Eievui's Sprite Object Library. For this we've pre-defined metasprites that we'll use for the bullets, enemies, and player. A single metasprite instructs how/where to draw multiple OAM sprites. - -A single OAM sprite has 4 bytes: -- Y Position (relative to previous metasprite) -- X Position (relative to previous metasprite) -- Which tile in VRAM it will use -- Any additional OAM attributes (priority, flipping, palette, etc..) - -After the final OAM sprite, the sprite object library will know it's done when it reads a 128 byte. - -![MetaspriteDIagram.png](../assets/part3/img/MetaspriteDIagram.png) - -*Inside of "src/main/assets/metasprites.asm" - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/assets/metasprites.asm}} -{{#include ../../galactic-armada/src/main/assets/metasprites.asm}} -``` - -## Drawing Metasprites - -Eievui's Sprite Object Library defines a "RenderMetasprite" function we'll use later. This function takes 3 parameters: -- A pointer to the metasprite data, in HL -- The metasprite's y position, in BC -- The metasprite's x position, in DE - -*Inside of "libs/sporbs_lib.asm" - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/libs/sporbs_lib.asm:render-metasprites}} -{{#include ../../galactic-armada/libs/sporbs_lib.asm:render-metasprites}} +# Metasprites + +We'll use the metasprite implementation that comes with Eievui's Sprite Object Library. For this we've pre-defined metasprites that we'll use for the bullets, enemies, and player. A single metasprite instructs how/where to draw multiple OAM sprites. + +A single OAM sprite has 4 bytes: +- Y Position (relative to previous metasprite) +- X Position (relative to previous metasprite) +- Which tile in VRAM it will use +- Any additional OAM attributes (priority, flipping, palette, etc..) + +After the final OAM sprite, the sprite object library will know it's done when it reads a 128 byte. + +![MetaspriteDIagram.png](../assets/part3/img/MetaspriteDIagram.png) + +*Inside of "src/main/assets/metasprites.asm" + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/assets/metasprites.asm}} +{{#include ../../galactic-armada/src/main/assets/metasprites.asm}} +``` + +## Drawing Metasprites + +Eievui's Sprite Object Library defines a "RenderMetasprite" function we'll use later. This function takes 3 parameters: +- A pointer to the metasprite data, in HL +- The metasprite's y position, in BC +- The metasprite's x position, in DE + +*Inside of "libs/sporbs_lib.asm" + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/libs/sporbs_lib.asm:render-metasprites}} +{{#include ../../galactic-armada/libs/sporbs_lib.asm:render-metasprites}} ``` \ No newline at end of file diff --git a/src/part3/sprites-backgrounds.md b/src/part3/sprites-backgrounds.md index cb205ef0..52ef452a 100644 --- a/src/part3/sprites-backgrounds.md +++ b/src/part3/sprites-backgrounds.md @@ -1,58 +1,58 @@ -# Sprites & Backgrounds - -Drawing backgrounds and populating VRAM with tile data has already been covered in the previous tutorials. To make the code more organized and readable, the code for sprites and backgrounds will already be completed in the starter. - -The following backgrounds and sprites are used in Galactic Armada: - -- Backgrounds - [Github Link](https://github.com/gbdev/gb-asm-tutorial/tree/master/galactic-armada/src/resources/backgrounds) - - Star Field - - Title Screen - - Text Font (Tiles only) -- Sprites - [Github Link](https://github.com/gbdev/gb-asm-tutorial/tree/master/galactic-armada/src/resources/sprites) - - Enemy Ship - - Player Ship - - Bullet - -These images were originally created in Aseprite. The original templates are also included in the repository. - -## The Backgrounds - -We have 2 **full** backgrounds. Our Title Screen, and the Star Field for gameplay. - -
- - - - - -
- -For these 2 backgrounds, we need to use some VRAM space for our text font. This text font will be at the beginning and occupy 52 tiles. For this reason, tilemaps we use later need their values to be offset by 52. That's why you'll see `CopyDEintoMemoryAtHL_With52Offset` used instead of just `CopyDEintoMemoryAtHL`. - -> **Note:** We'll cover the text-font.png in the [Drawing Text](#drawing-text) section. - -*Inside of "src/main/assets/backgrounds.asm" - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/assets/backgrounds.asm}} -{{#include ../../galactic-armada/src/main/assets/backgrounds.asm}} -``` -## The Sprites - -Our sprites will later use metasprites, so there we only need to populate VRAM with their tile data. - -
- - - - - - - -
- - -*Inside of "src/main/assets/sprites.asm" - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/assets/sprites.asm}} -{{#include ../../galactic-armada/src/main/assets/sprites.asm}} -``` +# Sprites & Backgrounds + +Drawing backgrounds and populating VRAM with tile data has already been covered in the previous tutorials. To make the code more organized and readable, the code for sprites and backgrounds will already be completed in the starter. + +The following backgrounds and sprites are used in Galactic Armada: + +- Backgrounds - [Github Link](https://github.com/gbdev/gb-asm-tutorial/tree/master/galactic-armada/src/resources/backgrounds) + - Star Field + - Title Screen + - Text Font (Tiles only) +- Sprites - [Github Link](https://github.com/gbdev/gb-asm-tutorial/tree/master/galactic-armada/src/resources/sprites) + - Enemy Ship + - Player Ship + - Bullet + +These images were originally created in Aseprite. The original templates are also included in the repository. + +## The Backgrounds + +We have 2 **full** backgrounds. Our Title Screen, and the Star Field for gameplay. + +
+ + + + + +
+ +For these 2 backgrounds, we need to use some VRAM space for our text font. This text font will be at the beginning and occupy 52 tiles. For this reason, tilemaps we use later need their values to be offset by 52. That's why you'll see `CopyDEintoMemoryAtHL_With52Offset` used instead of just `CopyDEintoMemoryAtHL`. + +> **Note:** We'll cover the text-font.png in the [Drawing Text](#drawing-text) section. + +*Inside of "src/main/assets/backgrounds.asm" + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/assets/backgrounds.asm}} +{{#include ../../galactic-armada/src/main/assets/backgrounds.asm}} +``` +## The Sprites + +Our sprites will later use metasprites, so there we only need to populate VRAM with their tile data. + +
+ + + + + + + +
+ + +*Inside of "src/main/assets/sprites.asm" + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/assets/sprites.asm}} +{{#include ../../galactic-armada/src/main/assets/sprites.asm}} +``` diff --git a/src/part3/sprites-metasprites.md b/src/part3/sprites-metasprites.md index 111a8945..920d1f22 100644 --- a/src/part3/sprites-metasprites.md +++ b/src/part3/sprites-metasprites.md @@ -1,33 +1,33 @@ -# Sprites & Metasprites - -Before we dive into the player, bullets, and enemies; how they are drawn using metasprites should be explained. - -For sprites, the following library is used: https://github.com/eievui5/gb-sprobj-lib - -> This is a small, lightweight library meant to facilitate the rendering of sprite objects, including Shadow OAM and OAM DMA, single-entry "simple" sprite objects, and Q12.4 fixed-point position metasprite rendering. - -All objects are drawn using "metasprites", or groups of sprites that define one single object. A custom “metasprite” implementation is used in addition. Metasprite definitions should a multiple of 4 plus one additional byte for the end. - -- Relative Y offset ( relative to the previous sprite, or the actual metasprite’s draw position) -- Relative X offset ( relative to the previous sprite, or the actual metasprite’s draw position) -- Tile to draw -- Tile Props (not used in this project) - -The logic stops drawing when it reads 128. - -An example of metasprite is the enemy ship: - - - -The Previous snippet draws two sprites. One that the object’s actual position, which uses tile 4 and 5. The second sprite is 8 pixels to the right, and uses tile 6 and 7 - ->⚠️ **NOTE**: Sprites are in 8x16 mode for this project. - -I can later draw such metasprite by calling the "DrawMetasprite" function that - - -We previously mentioned a variable called "wLastOAMAddress". The "DrawMetasprites" function can be found in the "src/main/utils/metasprites.asm" file: - - -When we call the "DrawMetasprites" function, the "wLastOAMAddress" variable will be advanced to point at the next available shadow OAM sprite. This is done using the "NextOAMSprite" function in "src/main/utils/sprites-utils.asm" - +# Sprites & Metasprites + +Before we dive into the player, bullets, and enemies; how they are drawn using metasprites should be explained. + +For sprites, the following library is used: https://github.com/eievui5/gb-sprobj-lib + +> This is a small, lightweight library meant to facilitate the rendering of sprite objects, including Shadow OAM and OAM DMA, single-entry "simple" sprite objects, and Q12.4 fixed-point position metasprite rendering. + +All objects are drawn using "metasprites", or groups of sprites that define one single object. A custom “metasprite” implementation is used in addition. Metasprite definitions should a multiple of 4 plus one additional byte for the end. + +- Relative Y offset ( relative to the previous sprite, or the actual metasprite’s draw position) +- Relative X offset ( relative to the previous sprite, or the actual metasprite’s draw position) +- Tile to draw +- Tile Props (not used in this project) + +The logic stops drawing when it reads 128. + +An example of metasprite is the enemy ship: + + + +The Previous snippet draws two sprites. One that the object’s actual position, which uses tile 4 and 5. The second sprite is 8 pixels to the right, and uses tile 6 and 7 + +>⚠️ **NOTE**: Sprites are in 8x16 mode for this project. + +I can later draw such metasprite by calling the "DrawMetasprite" function that + + +We previously mentioned a variable called "wLastOAMAddress". The "DrawMetasprites" function can be found in the "src/main/utils/metasprites.asm" file: + + +When we call the "DrawMetasprites" function, the "wLastOAMAddress" variable will be advanced to point at the next available shadow OAM sprite. This is done using the "NextOAMSprite" function in "src/main/utils/sprites-utils.asm" + diff --git a/src/part3/story-screen.md b/src/part3/story-screen.md index 1a867e97..4a55ea2b 100644 --- a/src/part3/story-screen.md +++ b/src/part3/story-screen.md @@ -1,71 +1,71 @@ -# Story Screen - -The story screen shows a basic story on 2 pages. Afterwards, it sends the player to the gameplay game state. - - - - - -## Initiating up the Story Screen - -In the `InitStoryState` we'll just going to turn on the LCD. Most of the game state's logic will occur in its update function. - -**Create a file named `story-screen.asm`. In that file add includes to `hardware.inc` and `character-mapping.inc`, and create a section in ROM0.** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:header}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:header}} -``` - -Like we did with the title screen, we'll need to setup a function for the Story State's initation logic. This function, called `InitStoryState` will be very similar to that of the title screen. The major difference is that nothing will be drawn in the `InitStoryState` function. - -**Add the following to your new `story-screen.asm` file.** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:init-story-state}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:init-story-state}} -``` - -## Updating the Story Screen - -Here's the data for our story screen. We have this defined just above our `UpdateStoryState` function. - -**Copy this data into your `story-screen.asm` file.** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} -``` - -The story text is shown using a typewriter effect. This effect is done similarly to the “press a to play” text that was done before, but here we wait for 3 vertical blank phases between writing each letter, giving some additional delay. - -> **Note: The `WaitForAToBePressed` is a utility function that comes with the starter. You can find more info on it in the [utilties page](utilities.md). ** - -We'll call the `MultilineTypewriteTextInHL_AtDE` function exactly how we called the `DrawTextTilesLoop` function. - -**Create a function called `UpdateStoryState` in `story-state.asm`. Export this function and tell it to call the `MultilineTypewriteTextInHL_AtDE` function. Pass `$9821` t DE as the location to start writing/drawing. Pass `Story.Line1` to HL as the text draw.** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} -``` - -Our basic story has 2 pages. After the first page has drawn, we'll wait until the A button is pressed. After such, we'll start drawing the second page. In-between pages we need to clear the background, so no extra text tiles linger. - -**Add the following code immediately after your previous call to `MultilineTypewriteTextInHL_AtDE` with `Story.Line1`** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:between-pages}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:between-pages}} -``` - -After we've shown the first page and cleared the background, we'll do the same thing for page 2: - -**Add this second implementation of the `MultilineTypewriteTextInHL_AtDE` function to draw the second page of our story:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page2}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page2}} -``` - -With our story full shown, once the player presses the A button, we're ready to move onto the next game state: Gameplay. We'll end our `UpdateStoryState` function by updating our game state variable and jump back to the `NextGameState` label like previously discussed. - -**Complete the story state and our `UpdateStoryState` function using the code below:** - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-end}} -{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-end}} -``` +# Story Screen + +The story screen shows a basic story on 2 pages. Afterwards, it sends the player to the gameplay game state. + + + + + +## Initiating up the Story Screen + +In the `InitStoryState` we'll just going to turn on the LCD. Most of the game state's logic will occur in its update function. + +**Create a file named `story-screen.asm`. In that file add includes to `hardware.inc` and `character-mapping.inc`, and create a section in ROM0.** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:header}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:header}} +``` + +Like we did with the title screen, we'll need to setup a function for the Story State's initation logic. This function, called `InitStoryState` will be very similar to that of the title screen. The major difference is that nothing will be drawn in the `InitStoryState` function. + +**Add the following to your new `story-screen.asm` file.** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:init-story-state}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:init-story-state}} +``` + +## Updating the Story Screen + +Here's the data for our story screen. We have this defined just above our `UpdateStoryState` function. + +**Copy this data into your `story-screen.asm` file.** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-data}} +``` + +The story text is shown using a typewriter effect. This effect is done similarly to the “press a to play” text that was done before, but here we wait for 3 vertical blank phases between writing each letter, giving some additional delay. + +> **Note: The `WaitForAToBePressed` is a utility function that comes with the starter. You can find more info on it in the [utilties page](utilities.md). ** + +We'll call the `MultilineTypewriteTextInHL_AtDE` function exactly how we called the `DrawTextTilesLoop` function. + +**Create a function called `UpdateStoryState` in `story-state.asm`. Export this function and tell it to call the `MultilineTypewriteTextInHL_AtDE` function. Pass `$9821` t DE as the location to start writing/drawing. Pass `Story.Line1` to HL as the text draw.** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page1}} +``` + +Our basic story has 2 pages. After the first page has drawn, we'll wait until the A button is pressed. After such, we'll start drawing the second page. In-between pages we need to clear the background, so no extra text tiles linger. + +**Add the following code immediately after your previous call to `MultilineTypewriteTextInHL_AtDE` with `Story.Line1`** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:between-pages}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:between-pages}} +``` + +After we've shown the first page and cleared the background, we'll do the same thing for page 2: + +**Add this second implementation of the `MultilineTypewriteTextInHL_AtDE` function to draw the second page of our story:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page2}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-page2}} +``` + +With our story full shown, once the player presses the A button, we're ready to move onto the next game state: Gameplay. We'll end our `UpdateStoryState` function by updating our game state variable and jump back to the `NextGameState` label like previously discussed. + +**Complete the story state and our `UpdateStoryState` function using the code below:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-end}} +{{#include ../../galactic-armada/src/main/states/story/story-state.asm:story-screen-end}} +``` diff --git a/src/part3/the-player.md b/src/part3/the-player.md index 80bede84..db3a802b 100644 --- a/src/part3/the-player.md +++ b/src/part3/the-player.md @@ -1,82 +1,82 @@ -# The Player - -The player’s logic is pretty simple. The player can move in 4 directions and fire bullets. We update the player by checking our input directions and the A button. We’ll move in the proper direction if its associated d-pad button is pressed. If the A button is pressed, we’ll spawn a new bullet at the player’s position. - -Our player will have 3 variables: -- wePlayerPositionX - a 16-bit scaled integer -- wePlayerPositionY - a 16-bit scaled integer -- wPlayerFlash - a 16-bit integer used when the player gets damaged - -> ⚠️ **NOTE**: The player can move vertically AND horizontally. So, unlike bullets and enemies, it’s x position is a 16-bit scaled integer. - -These are declared at the top of the "src/main/states/gameplay/objects/player.asm" file - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-start}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-start}} -``` - -Well draw our player, a simple ship, using the previously discussed metasprites implementation. Here is what we have for the players metasprites and tile data: -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-data}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-data}} -``` - -## Initializing the Player - -Initializing the player is pretty simple. Here's a list of things we need to do: -* Reset oir wPlayerFlash variable -* Reset our wPlayerPositionX variable -* Reset our wPlayerPositionU variable -* Copy the player's ship into VRAM - -We'll use a constant we declared in "src/main/utils/constants.inc" to copy the player ship's tile data into VRAM. Our enemy ship and player ship both have 4 tiles (16 bytes for each tile). In the snippet below, we can define where we'll place the tile data in VRAM relative to the _VRAM constant: - - -Here's what our "InitializePlayer" function looks like. Recall, this was called when initiating the gameplay game state: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-initialize}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-initialize}} -``` - -## Updating the Player - -We can break our player's update logic into 2 parts: -* Check for joypad input, move with the d-pad, shoot with A -* Depending on our "wPlayerFlash" variable: Draw our metasprites at our location - -Checking the joypad is done like the previous tutorials, we'll perform bitwise "and" operations with constants for each d-pad direction. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-update-start}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-update-start}} -``` - -For player movement, our X & Y are 16-bit integers. These both require two bytes. There is a little endian ordering, the first byte will be the low byte. The second byte will be the high byte. To increase/decrease these values, we add/subtract our change amount to/from the low byte. Then afterwards, we add/subtract the remainder of that operation to/from the high byte. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-movement}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-movement}} -``` - -When the player wants to shoot, we first check if the A button previously was down. If it was, we won't shoot a new bullet. This avoids bullet spamming a little. For spawning bullets, we have a function called "FireNextBullet". This function will need the new bullet's 8-bit X coordinate and 16-bit Y coordinate, both set in a variable it uses called "wNextBullet" - - -After we've potentially moved the player and/or shot a new bullet. We need to draw our player. However, to create the "flashing" effect when damaged, we'll conditionally NOT draw our player sprite. We do this based on the "wPlayerFlash" variable. - -- If the "wPlayerFlash" variable is 0, the player is not damaged, we'll skip to drawing our player sprite. -- Otherwise, decrease the "wPlayerFlash" variable by 5. - - We'll shift all the bits of the "wPlayerFlash" variable to the right 4 times - - If the result is less than 5, we'll stop flashing and draw our player metasprite. - - Otherwise, if the first bit of the decscaled "wPlayerFLash" variable is 1, we'll skip drawing the player. - -> ***NOTE:** The following resumes from where the "UpdatePlayer_HandleInput" label ended above. - - - -If we get past all of the "wPlayerFlash" logic, we'll draw our player using the "DrawMetasprite" function we previously discussed. - - -That's the end our our "UpdatePlayer" function. The final bit of code for our player handles when they are damaged. When an enemy damages the player, we want to decrease our lives by one. We'll also start flashing by giving our 'mPlayerFlash' variable a non-zero value. In the gameplay game state, if we've lost all lives, gameplay will end. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-damage}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-damage}} -``` - +# The Player + +The player’s logic is pretty simple. The player can move in 4 directions and fire bullets. We update the player by checking our input directions and the A button. We’ll move in the proper direction if its associated d-pad button is pressed. If the A button is pressed, we’ll spawn a new bullet at the player’s position. + +Our player will have 3 variables: +- wePlayerPositionX - a 16-bit scaled integer +- wePlayerPositionY - a 16-bit scaled integer +- wPlayerFlash - a 16-bit integer used when the player gets damaged + +> ⚠️ **NOTE**: The player can move vertically AND horizontally. So, unlike bullets and enemies, it’s x position is a 16-bit scaled integer. + +These are declared at the top of the "src/main/states/gameplay/objects/player.asm" file + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-start}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-start}} +``` + +Well draw our player, a simple ship, using the previously discussed metasprites implementation. Here is what we have for the players metasprites and tile data: +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-data}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-data}} +``` + +## Initializing the Player + +Initializing the player is pretty simple. Here's a list of things we need to do: +* Reset oir wPlayerFlash variable +* Reset our wPlayerPositionX variable +* Reset our wPlayerPositionU variable +* Copy the player's ship into VRAM + +We'll use a constant we declared in "src/main/utils/constants.inc" to copy the player ship's tile data into VRAM. Our enemy ship and player ship both have 4 tiles (16 bytes for each tile). In the snippet below, we can define where we'll place the tile data in VRAM relative to the _VRAM constant: + + +Here's what our "InitializePlayer" function looks like. Recall, this was called when initiating the gameplay game state: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-initialize}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-initialize}} +``` + +## Updating the Player + +We can break our player's update logic into 2 parts: +* Check for joypad input, move with the d-pad, shoot with A +* Depending on our "wPlayerFlash" variable: Draw our metasprites at our location + +Checking the joypad is done like the previous tutorials, we'll perform bitwise "and" operations with constants for each d-pad direction. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-update-start}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-update-start}} +``` + +For player movement, our X & Y are 16-bit integers. These both require two bytes. There is a little endian ordering, the first byte will be the low byte. The second byte will be the high byte. To increase/decrease these values, we add/subtract our change amount to/from the low byte. Then afterwards, we add/subtract the remainder of that operation to/from the high byte. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-movement}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-movement}} +``` + +When the player wants to shoot, we first check if the A button previously was down. If it was, we won't shoot a new bullet. This avoids bullet spamming a little. For spawning bullets, we have a function called "FireNextBullet". This function will need the new bullet's 8-bit X coordinate and 16-bit Y coordinate, both set in a variable it uses called "wNextBullet" + + +After we've potentially moved the player and/or shot a new bullet. We need to draw our player. However, to create the "flashing" effect when damaged, we'll conditionally NOT draw our player sprite. We do this based on the "wPlayerFlash" variable. + +- If the "wPlayerFlash" variable is 0, the player is not damaged, we'll skip to drawing our player sprite. +- Otherwise, decrease the "wPlayerFlash" variable by 5. + - We'll shift all the bits of the "wPlayerFlash" variable to the right 4 times + - If the result is less than 5, we'll stop flashing and draw our player metasprite. + - Otherwise, if the first bit of the decscaled "wPlayerFLash" variable is 1, we'll skip drawing the player. + +> ***NOTE:** The following resumes from where the "UpdatePlayer_HandleInput" label ended above. + + + +If we get past all of the "wPlayerFlash" logic, we'll draw our player using the "DrawMetasprite" function we previously discussed. + + +That's the end our our "UpdatePlayer" function. The final bit of code for our player handles when they are damaged. When an enemy damages the player, we want to decrease our lives by one. We'll also start flashing by giving our 'mPlayerFlash' variable a non-zero value. In the gameplay game state, if we've lost all lives, gameplay will end. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-damage}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/player.asm:player-damage}} +``` + That's everything for our player. Next, we'll go over bullets and then onto the enemies. \ No newline at end of file diff --git a/src/part3/the-starter.md b/src/part3/the-starter.md index bcc8b108..d002c146 100644 --- a/src/part3/the-starter.md +++ b/src/part3/the-starter.md @@ -1,16 +1,16 @@ -# Download the Starter -This tutorial builds upon a “starter”. You can find that starter here. - -To get started, download the zip file for this tutorial. You can find it on Github [here](#). This file contains everything you need to get started. -- Dependent Libraries are included -- Graphics assets are present and organized -- The makefile is set to compile all changes -- A basic entry point & Game Loop has been setup for you. -- Some helpful utilities have already been included. - -> **Note:** Utilties that are more complicated will be explained on the utilities page. - -This section is going to explain how the Galactic Armada project is structured. This includes the folders, resources, tools, entry point, and compilation process. - -> **Note:** All of this has been done and is a part of the template you can find here. These explanations are for understanding purposes, you don't need to do anything yet. - +# Download the Starter +This tutorial builds upon a “starter”. You can find that starter here. + +To get started, download the zip file for this tutorial. You can find it on Github [here](#). This file contains everything you need to get started. +- Dependent Libraries are included +- Graphics assets are present and organized +- The makefile is set to compile all changes +- A basic entry point & Game Loop has been setup for you. +- Some helpful utilities have already been included. + +> **Note:** Utilties that are more complicated will be explained on the utilities page. + +This section is going to explain how the Galactic Armada project is structured. This includes the folders, resources, tools, entry point, and compilation process. + +> **Note:** All of this has been done and is a part of the template you can find here. These explanations are for understanding purposes, you don't need to do anything yet. + diff --git a/src/part3/title-screen.md b/src/part3/title-screen.md index 07c902af..f4b3775b 100644 --- a/src/part3/title-screen.md +++ b/src/part3/title-screen.md @@ -1,66 +1,66 @@ -# Title Screen - -The title screen shows a basic title image using the background and draws text asking the player to press A. Once the user presses A, it will go to the story screen. - - - -Our title screen has 3 pieces of data: - -* The "Press A to play" text -* The title screen tile data -* The title screen tilemap - -**Create a new assembly file called "title-screen-state.asm". You can put it anywhere, but we've organized ours in the "src/main/states/title-screen" folder.** - -In that file, create ROM0 section, and add includes for "hardware.inc", and "character-mapping.inc" - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-start}} -{{#include ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-start}} -``` - -Like with pretty much every other file, we'll need the hardware.inc because of all it's useful helper constants. The character-mapping.inc comes with the starter. It's needed so RGBDS knows how to map our text to bytes. - -Next, We're going to add 2 more functions to this file: -- InitTitleScreenState -- UpdateTitleScreenState - -## Initiating the Title Screen - -In our title screen's `InitTitleScreen` function, we'll do the following: -* Clear the background and any sprites (because other game states may change/use them) -* Reset the position of the background (because gameplay later will move it) -* draw the title screen graphic -* draw our "Press A to play" - -However, like in the [second tutorial](https://gbdev.io/gb-asm-tutorial/part2/getting-started.html), before we cance change our background we need to turn off the LCD. - -> "[We] wait until “VBlank”, which is the only time you can safely turn off the screen (doing so at the wrong time could damage a real Game Boy, so this is very crucial). We’ll explain what VBlank is and talk about it more later in the tutorial. -> -> Turning off the screen is important because loading new tiles while the screen is on is tricky" -> *From Tutorial 2 - Regarding setting tile data and the LCD* - -For drawing our title screen, we'll use the `LoadTextFontIntoVRAM` and `DrawTitleScreen` functions that came with the starter. (Explained in the Previous section) - -With those 2 functions done, Here is what our "InitTitleScreenState" function looks like - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-init}} -{{#include ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-init}} -``` - -> **Note:** We clear our background and reset our shadow OAM to avoid any lingering sprites/tiles when the game transitions from gameplay to title screen. - -In order to draw text in our game, we've created a function called `DrawTextInHL_AtDE`. We'll pass this function which tile to start on in `de`, and the address of our text in `hl`. - -Next, we need to update our logic for our title screen. - -## Updating the Title Screen - -The title screen's update logic is the simplest of the 3. All we are going to do is wait until the A button is pressed. Afterwards, we'll go to the story screen game state. - -We tell our game state management code the next game state to go to by passing that game state's initate function and update function into the `wNextGameState_Initiate` and `wNextGameState_Update` variables repsectively. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:update-title-screen}} -{{#include ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:update-title-screen}} -``` - -That's it for our title screen. Next up is our story screen. +# Title Screen + +The title screen shows a basic title image using the background and draws text asking the player to press A. Once the user presses A, it will go to the story screen. + + + +Our title screen has 3 pieces of data: + +* The "Press A to play" text +* The title screen tile data +* The title screen tilemap + +**Create a new assembly file called "title-screen-state.asm". You can put it anywhere, but we've organized ours in the "src/main/states/title-screen" folder.** + +In that file, create ROM0 section, and add includes for "hardware.inc", and "character-mapping.inc" + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-start}} +{{#include ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-start}} +``` + +Like with pretty much every other file, we'll need the hardware.inc because of all it's useful helper constants. The character-mapping.inc comes with the starter. It's needed so RGBDS knows how to map our text to bytes. + +Next, We're going to add 2 more functions to this file: +- InitTitleScreenState +- UpdateTitleScreenState + +## Initiating the Title Screen + +In our title screen's `InitTitleScreen` function, we'll do the following: +* Clear the background and any sprites (because other game states may change/use them) +* Reset the position of the background (because gameplay later will move it) +* draw the title screen graphic +* draw our "Press A to play" + +However, like in the [second tutorial](https://gbdev.io/gb-asm-tutorial/part2/getting-started.html), before we cance change our background we need to turn off the LCD. + +> "[We] wait until “VBlank”, which is the only time you can safely turn off the screen (doing so at the wrong time could damage a real Game Boy, so this is very crucial). We’ll explain what VBlank is and talk about it more later in the tutorial. +> +> Turning off the screen is important because loading new tiles while the screen is on is tricky" +> *From Tutorial 2 - Regarding setting tile data and the LCD* + +For drawing our title screen, we'll use the `LoadTextFontIntoVRAM` and `DrawTitleScreen` functions that came with the starter. (Explained in the Previous section) + +With those 2 functions done, Here is what our "InitTitleScreenState" function looks like + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-init}} +{{#include ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:title-screen-init}} +``` + +> **Note:** We clear our background and reset our shadow OAM to avoid any lingering sprites/tiles when the game transitions from gameplay to title screen. + +In order to draw text in our game, we've created a function called `DrawTextInHL_AtDE`. We'll pass this function which tile to start on in `de`, and the address of our text in `hl`. + +Next, we need to update our logic for our title screen. + +## Updating the Title Screen + +The title screen's update logic is the simplest of the 3. All we are going to do is wait until the A button is pressed. Afterwards, we'll go to the story screen game state. + +We tell our game state management code the next game state to go to by passing that game state's initate function and update function into the `wNextGameState_Initiate` and `wNextGameState_Update` variables repsectively. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:update-title-screen}} +{{#include ../../galactic-armada/src/main/states/title-screen/title-screen-state.asm:update-title-screen}} +``` + +That's it for our title screen. Next up is our story screen. diff --git a/src/part3/utilities.md b/src/part3/utilities.md index 4e5ecae8..737bcb97 100644 --- a/src/part3/utilities.md +++ b/src/part3/utilities.md @@ -1,18 +1,18 @@ -# Utilties - - -## Waiting for Buttons to be pressed - -Our "WaitForKeyFunction" is defined in [ "src/main/utils/input-utils.asm"](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/src/main/utils/input-utils.asm). We'll poll for input and infinitely loop until the specified button is pressed down. - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/input-utils.asm:input-utils}} -{{#include ../../galactic-armada/src/main/utils/input-utils.asm:input-utils}} -``` - -## Clearing the background - -Once the user presses the A button, we want to show the second page. To avoid any lingering "leftover" letters, we'll clear the background. All this function does is turn off the LCD, fill our background tilemap with the first tile, then turn back on the lcd. We've defined this function in the "src/main/utils/background.utils.asm" file: - -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/background-utils.asm:background-utils}} -{{#include ../../galactic-armada/src/main/utils/background-utils.asm:background-utils}} +# Utilties + + +## Waiting for Buttons to be pressed + +Our "WaitForKeyFunction" is defined in [ "src/main/utils/input-utils.asm"](https://github.com/gbdev/gb-asm-tutorial/blob/master/galactic-armada/src/main/utils/input-utils.asm). We'll poll for input and infinitely loop until the specified button is pressed down. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/input-utils.asm:input-utils}} +{{#include ../../galactic-armada/src/main/utils/input-utils.asm:input-utils}} +``` + +## Clearing the background + +Once the user presses the A button, we want to show the second page. To avoid any lingering "leftover" letters, we'll clear the background. All this function does is turn off the LCD, fill our background tilemap with the first tile, then turn back on the lcd. We've defined this function in the "src/main/utils/background.utils.asm" file: + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/utils/background-utils.asm:background-utils}} +{{#include ../../galactic-armada/src/main/utils/background-utils.asm:background-utils}} ``` \ No newline at end of file