Skip to content

Commit

Permalink
Add Part 3: coding a complete shoot-em-up game (#57)
Browse files Browse the repository at this point in the history
Co-authored-by: Antonio Vivace <avivace4@gmail.com>
Co-authored-by: Zumi <13794376+ZoomTen@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 21, 2023
1 parent c085011 commit 7c400f9
Show file tree
Hide file tree
Showing 88 changed files with 5,659 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
/target/
.DS_Store
/po/messages.pot
/galactic-armada/src/generated
/galactic-armada/dist/
/galactic-armada/obj/
120 changes: 120 additions & 0 deletions galactic-armada/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +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) -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

45 changes: 45 additions & 0 deletions galactic-armada/libs/input.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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/utils/hardware.inc"

SECTION "Input", ROM0

Input::
; 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
222 changes: 222 additions & 0 deletions galactic-armada/libs/sporbs_lib.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
; 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/utils/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

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

SECTION "Shadow OAM", WRAM0, ALIGN[8]
wShadowOAM::
ds 160

SECTION "Shadow OAM Index", HRAM
; The current low byte of shadow OAM.
hOAMIndex::
db
Loading

0 comments on commit 7c400f9

Please sign in to comment.