diff --git a/it/.nojekyll b/it/.nojekyll deleted file mode 100644 index f173110..0000000 --- a/it/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/it/part1/setup.html b/it/part1/setup.html index 8ef5773..60b4f88 100644 --- a/it/part1/setup.html +++ b/it/part1/setup.html @@ -236,9 +236,9 @@
Una volta installato RGBDS, aprite il terminale ed eseguite il comando make --version
per controllare la vostra versione di Make (che probabilmente è GNU Make).
Se make
non è presente, potrebbe essere necessario installare le build-essentials
della propria distribuzione.
La triste verità è che Windows è un pessimo sistema operativo per noi sviluppatori; tuttavia, è possibile installare degli strumenti che risolvono la maggior parte dei suoi problemi.
-Su Windows 10, la scelta migliore è WSL, che permette di eseguire una distribuzione Linux all’interno di Windows. -Installate WSL 1 o WSL 2, poi una distribuzione a vostra scelta e quindi seguite nuovamente questi passaggi per la distribuzione Linux appena installata.
+The modern tools we’ll be using for Game Boy development have been designed for a Unix environment, so setup on Windows is not fully straightfoward. However, it’s possible to install an environment that will provide everything we need.
+On Windows 10 and Windows 11, your best bet is WSL, which is a method for running a Linux distribution within Windows. +Install WSL, then a distribution of your choice (pick Ubuntu if unsure), and then follow these steps again, but for the Linux distribution you installed.
In alternativa a WSL, si può usare MSYS2 o Cygwin; per poi consultare le istruzioni per l’installazione di Windows di [RGBDS] (https://rgbds.gbdev.io/install). Per quanto ne so, entrambi dovrebbero fornire una versione sufficientemente aggiornata di GNU Make.
Quindi: Codice sorgente → rgbasm
→ File oggetto → rgblink
→ ROM, giusto?
Beh, non esattamente.
RGBLINK produce sì una ROM, ma se la provassimo su un GameBoy non funzionerebbe. -Nelle ROM deve sempre essere presente qualcosa chiamato header: -questa sezione contiene informazioni sulla ROM, come il nome del gioco, il nome dell’autore, se sia compatibile con il GameBoy Color ed altro. -Per il momento abbiamo impostato tutti i valori a zero nel programma per semplicità, ma ne riparleremo nella seconda parte del tutorial.
+RGBLINK does produce a ROM, but it’s not quite usable yet. +See, actual ROMs have what’s called a header. +It’s a special area of the ROM that contains metadata about the ROM; for example, the game’s name, Game Boy Color compatibility, and more. +For simplicity, we defaulted a lot of these values to 0 for the time being; we’ll come back to them in Part Ⅱ.
However, the header contains three crucial fields:
Una volta installato RGBDS, aprite il terminale ed eseguite il comando make --version
per controllare la vostra versione di Make (che probabilmente è GNU Make).
Se make
non è presente, potrebbe essere necessario installare le build-essentials
della propria distribuzione.
La triste verità è che Windows è un pessimo sistema operativo per noi sviluppatori; tuttavia, è possibile installare degli strumenti che risolvono la maggior parte dei suoi problemi.
-Su Windows 10, la scelta migliore è WSL, che permette di eseguire una distribuzione Linux all’interno di Windows. -Installate WSL 1 o WSL 2, poi una distribuzione a vostra scelta e quindi seguite nuovamente questi passaggi per la distribuzione Linux appena installata.
+The modern tools we’ll be using for Game Boy development have been designed for a Unix environment, so setup on Windows is not fully straightfoward. However, it’s possible to install an environment that will provide everything we need.
+On Windows 10 and Windows 11, your best bet is WSL, which is a method for running a Linux distribution within Windows. +Install WSL, then a distribution of your choice (pick Ubuntu if unsure), and then follow these steps again, but for the Linux distribution you installed.
In alternativa a WSL, si può usare MSYS2 o Cygwin; per poi consultare le istruzioni per l’installazione di Windows di [RGBDS] (https://rgbds.gbdev.io/install). Per quanto ne so, entrambi dovrebbero fornire una versione sufficientemente aggiornata di GNU Make.
Quindi: Codice sorgente → rgbasm
→ File oggetto → rgblink
→ ROM, giusto?
Beh, non esattamente.
RGBLINK produce sì una ROM, ma se la provassimo su un GameBoy non funzionerebbe. -Nelle ROM deve sempre essere presente qualcosa chiamato header: -questa sezione contiene informazioni sulla ROM, come il nome del gioco, il nome dell’autore, se sia compatibile con il GameBoy Color ed altro. -Per il momento abbiamo impostato tutti i valori a zero nel programma per semplicità, ma ne riparleremo nella seconda parte del tutorial.
+RGBLINK does produce a ROM, but it’s not quite usable yet. +See, actual ROMs have what’s called a header. +It’s a special area of the ROM that contains metadata about the ROM; for example, the game’s name, Game Boy Color compatibility, and more. +For simplicity, we defaulted a lot of these values to 0 for the time being; we’ll come back to them in Part Ⅱ.
However, the header contains three crucial fields:
Once RGBDS is installed, open a terminal and run make --version
to check your Make version (which is likely GNU Make).
If make
cannot be found, you may need to install your distribution’s build-essentials
.
The sad truth is that Windows is a terrible OS for development; however, you can install environments that solve most issues.
-On Windows 10, your best bet is WSL, which sort of allows running a Linux distribution within Windows. -Install WSL 1 or WSL 2, then a distribution of your choice, and then follow these steps again, but for the Linux distribution you installed.
+The modern tools we’ll be using for Game Boy development have been designed for a Unix environment, so setup on Windows is not fully straightfoward. However, it’s possible to install an environment that will provide everything we need.
+On Windows 10 and Windows 11, your best bet is WSL, which is a method for running a Linux distribution within Windows. +Install WSL, then a distribution of your choice (pick Ubuntu if unsure), and then follow these steps again, but for the Linux distribution you installed.
If WSL is not an option, you can use MSYS2 or Cygwin instead; then check out RGBDS’ Windows install instructions. As far as I’m aware, both of these provide a sufficiently up-to-date version of GNU Make.
So: Source code → rgbasm
→ Object files → rgblink
→ ROM, right?
Well, not exactly.
RGBLINK does produces a ROM, but it’s not quite usable yet. +
RGBLINK does produce a ROM, but it’s not quite usable yet. See, actual ROMs have what’s called a header. It’s a special area of the ROM that contains metadata about the ROM; for example, the game’s name, Game Boy Color compatibility, and more. For simplicity, we defaulted a lot of these values to 0 for the time being; we’ll come back to them in Part Ⅱ.
diff --git a/print.html b/print.html index 27b673e..da8497c 100644 --- a/print.html +++ b/print.html @@ -293,9 +293,9 @@Once RGBDS is installed, open a terminal and run make --version
to check your Make version (which is likely GNU Make).
If make
cannot be found, you may need to install your distribution’s build-essentials
.
The sad truth is that Windows is a terrible OS for development; however, you can install environments that solve most issues.
-On Windows 10, your best bet is WSL, which sort of allows running a Linux distribution within Windows. -Install WSL 1 or WSL 2, then a distribution of your choice, and then follow these steps again, but for the Linux distribution you installed.
+The modern tools we’ll be using for Game Boy development have been designed for a Unix environment, so setup on Windows is not fully straightfoward. However, it’s possible to install an environment that will provide everything we need.
+On Windows 10 and Windows 11, your best bet is WSL, which is a method for running a Linux distribution within Windows. +Install WSL, then a distribution of your choice (pick Ubuntu if unsure), and then follow these steps again, but for the Linux distribution you installed.
If WSL is not an option, you can use MSYS2 or Cygwin instead; then check out RGBDS’ Windows install instructions. As far as I’m aware, both of these provide a sufficiently up-to-date version of GNU Make.
So: Source code → rgbasm
→ Object files → rgblink
→ ROM, right?
Well, not exactly.
RGBLINK does produces a ROM, but it’s not quite usable yet. +
RGBLINK does produce a ROM, but it’s not quite usable yet. See, actual ROMs have what’s called a header. It’s a special area of the ROM that contains metadata about the ROM; for example, the game’s name, Game Boy Color compatibility, and more. For simplicity, we defaulted a lot of these values to 0 for the time being; we’ll come back to them in Part Ⅱ.
diff --git a/searchindex.js b/searchindex.js index 0e14f63..f04fd79 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Object.assign(window.search, {"doc_urls":["index.html#home","index.html#controls","index.html#authors","index.html#contributing","index.html#licensing","roadmap.html#roadmap","help-feedback.html#help","part1/setup.html#setup","part1/setup.html#tools","part1/setup.html#linux--macos","part1/setup.html#windows","part1/setup.html#code-editor","part1/setup.html#emulator","part1/hello_world.html#hello-world","part1/toolchain.html#the-toolchain","part1/toolchain.html#rgbasm-and-rgblink","part1/toolchain.html#rgbfix","part1/toolchain.html#file-names","part1/bin_and_hex.html#binary-and-hexadecimal","part1/bin_and_hex.html#hexadecimal","part1/bin_and_hex.html#summary","part1/registers.html#registers","part1/registers.html#general-purpose-registers","part1/assembly.html#assembly-basics","part1/assembly.html#comments","part1/assembly.html#instructions","part1/assembly.html#directives","part1/assembly.html#including-other-files","part1/assembly.html#sections","part1/assembly.html#reserving-space","part1/memory.html#memory","part1/memory.html#whats-a-memory","part1/memory.html#the-many-types-of-memory","part1/memory.html#game-boy-memory-map","part1/memory.html#labels","part1/memory.html#whats-with-the-brackets","part1/memory.html#hli","part1/memory.html#an-example","part1/header.html#header","part1/header.html#what-is-the-header","part1/header.html#boot-rom","part1/header.html#rgbfix","part1/header.html#so-whats-the-deal-with-that-line","part1/header.html#bonus-the-infinite-loop","part1/operations.html#operations--flags","part1/operations.html#arithmetic","part1/operations.html#flags","part1/operations.html#comparison","part1/operations.html#instruction-summary","part1/jumps.html#jumps","part1/jumps.html#conditional-jumps","part1/tracing.html#tracing","part1/tracing.html#setup","part1/tracing.html#stepping","part1/tracing.html#what-next","part1/tiles.html#tiles","part1/tiles.html#helpful-hand","part1/tiles.html#short-primer","part1/tiles.html#bpp","part1/tiles.html#encoding","part1/palettes.html#palettes","part1/palettes.html#getting-our-hands-dirty","part1/palettes.html#encoding","part1/palettes.html#lights-out","part1/tilemap.html#tilemap","part1/wrapup.html#wrapping-up","part2/getting-started.html#getting-started","part2/objects.html#objects","part2/objects.html#movement","part2/functions.html#functions","part2/input.html#input","part2/collision.html#collision","part2/collision.html#graphics","part2/collision.html#prep-work","part2/collision.html#putting-it-together","part2/collision.html#paddle-bounce","part2/collision.html#bonus-tweaking-the-bounce-height","part2/bricks.html#bricks","part2/bricks.html#destroying-bricks","part2/wip.html#work-in-progress","part3/getting-started.html#introducing-galactic-armada","part3/getting-started.html#feature-set","part3/project-structure.html#project-structure","part3/project-structure.html#folder-layout","part3/project-structure.html#background--sprite-resources","part3/project-structure.html#compilation","part3/entry-point.html#entry-point","part3/changing-game-states.html#changing-game-states","part3/title-screen.html#title-screen","part3/title-screen.html#initiating-the-title-screen","part3/title-screen.html#updating-the-title-screen","part3/story-screen.html#story-screen","part3/story-screen.html#initiating-up-the-story-screen","part3/story-screen.html#updating-the-story-screen","part3/gameplay.html#gameplay-state","part3/gameplay.html#initiating-the-gameplay-game-state","part3/gameplay.html#updating-the-gameplay-game-state","part3/scrolling-background.html#scrolling-background","part3/scrolling-background.html#initializing-the-background","part3/heads-up-interface.html#heads-up-interface","part3/heads-up-interface.html#stat-interrupts--the-window","part3/heads-up-interface.html#using-the-stat-interrupt","part3/heads-up-interface.html#initiating--disabling-stat-interrupts","part3/heads-up-interface.html#defining-stat-interrupts","part3/heads-up-interface.html#keeping-score-and-drawing-score-on-the-hud","part3/sprites-metasprites.html#sprites--metasprites","part3/object-pools.html#object-pools","part3/object-pools.html#activating-a-pooled-object","part3/the-player.html#the-player","part3/the-player.html#initializing-the-player","part3/the-player.html#updating-the-player","part3/bullets.html#bullets","part3/bullets.html#initiating-bullets","part3/bullets.html#updating-bullets","part3/bullets.html#drawing-the-bullets","part3/bullets.html#deactivating-the-bullets","part3/bullets.html#updating-the-next-bullet","part3/bullets.html#firing-new-bullets","part3/enemies.html#enemies","part3/enemies.html#initializing-enemies","part3/enemies.html#updating-enemies","part3/enemies.html#player--bullet-collision","part3/enemies.html#deactivating-enemies","part3/enemies.html#spawning-enemies","part3/collision.html#collision-detection","part3/enemy-player-collision.html#enemy-player-collision","part3/enemy-bullet-collision.html#enemy-bullet-collision","part3/conclusion.html#conclusion","next.html#where-to-go-next","cheatsheet.html#rgbds-cheatsheet","cheatsheet.html#table-of-contents","cheatsheet.html#display","cheatsheet.html#wait-for-the-vertical-blank-phase","cheatsheet.html#turn-onoff-the-lcd","cheatsheet.html#turn-onoff-the-background","cheatsheet.html#turn-onoff-the-window","cheatsheet.html#switch-which-tilemaps-are-used-by-the-window-andor-background","cheatsheet.html#turn-onoff-sprites","cheatsheet.html#turn-onoff-tall-8x16-sprites","cheatsheet.html#backgrounds","cheatsheet.html#put-backgroundwindow-tile-data-into-vram","cheatsheet.html#draw-on-the-backgroundwindow","cheatsheet.html#move-the-background","cheatsheet.html#move-the-window","cheatsheet.html#joypad-input","cheatsheet.html#check-if-a-button-is-down","cheatsheet.html#check-if-a-button-was-just-pressed","cheatsheet.html#wait-for-a-button-press","cheatsheet.html#hud","cheatsheet.html#draw-text","cheatsheet.html#draw-a-bottom-hud","cheatsheet.html#sprites","cheatsheet.html#put-sprite-tile-data-in-vram","cheatsheet.html#manipulate-hardware-oam-sprites","cheatsheet.html#implement-a-shadow-oam-using-eievui5s-sprite-object-library","cheatsheet.html#manipulate-shadow-oam-oam-sprites","cheatsheet.html#miscellaneous","cheatsheet.html#save-data","cheatsheet.html#generate-random-numbers","resources.html#resources","resources.html#help-channels","resources.html#other-tutorials","resources.html#complements","thanks.html#special-thanks"],"index":{"documentStore":{"docInfo":{"0":{"body":50,"breadcrumbs":2,"title":1},"1":{"body":70,"breadcrumbs":2,"title":1},"10":{"body":75,"breadcrumbs":2,"title":1},"100":{"body":61,"breadcrumbs":7,"title":3},"101":{"body":77,"breadcrumbs":7,"title":3},"102":{"body":64,"breadcrumbs":8,"title":4},"103":{"body":109,"breadcrumbs":7,"title":3},"104":{"body":253,"breadcrumbs":9,"title":5},"105":{"body":347,"breadcrumbs":5,"title":2},"106":{"body":235,"breadcrumbs":5,"title":2},"107":{"body":32,"breadcrumbs":6,"title":3},"108":{"body":128,"breadcrumbs":3,"title":1},"109":{"body":121,"breadcrumbs":4,"title":2},"11":{"body":29,"breadcrumbs":3,"title":2},"110":{"body":539,"breadcrumbs":4,"title":2},"111":{"body":77,"breadcrumbs":3,"title":1},"112":{"body":116,"breadcrumbs":4,"title":2},"113":{"body":355,"breadcrumbs":4,"title":2},"114":{"body":76,"breadcrumbs":4,"title":2},"115":{"body":51,"breadcrumbs":4,"title":2},"116":{"body":65,"breadcrumbs":5,"title":3},"117":{"body":82,"breadcrumbs":5,"title":3},"118":{"body":177,"breadcrumbs":3,"title":1},"119":{"body":75,"breadcrumbs":4,"title":2},"12":{"body":127,"breadcrumbs":2,"title":1},"120":{"body":417,"breadcrumbs":4,"title":2},"121":{"body":161,"breadcrumbs":5,"title":3},"122":{"body":40,"breadcrumbs":4,"title":2},"123":{"body":169,"breadcrumbs":4,"title":2},"124":{"body":300,"breadcrumbs":5,"title":2},"125":{"body":234,"breadcrumbs":7,"title":3},"126":{"body":464,"breadcrumbs":7,"title":3},"127":{"body":41,"breadcrumbs":2,"title":1},"128":{"body":58,"breadcrumbs":4,"title":2},"129":{"body":57,"breadcrumbs":3,"title":2},"13":{"body":199,"breadcrumbs":4,"title":2},"130":{"body":90,"breadcrumbs":3,"title":2},"131":{"body":16,"breadcrumbs":2,"title":1},"132":{"body":25,"breadcrumbs":5,"title":4},"133":{"body":35,"breadcrumbs":4,"title":3},"134":{"body":29,"breadcrumbs":4,"title":3},"135":{"body":30,"breadcrumbs":4,"title":3},"136":{"body":56,"breadcrumbs":7,"title":6},"137":{"body":36,"breadcrumbs":4,"title":3},"138":{"body":27,"breadcrumbs":6,"title":5},"139":{"body":0,"breadcrumbs":2,"title":1},"14":{"body":13,"breadcrumbs":2,"title":1},"140":{"body":46,"breadcrumbs":6,"title":5},"141":{"body":93,"breadcrumbs":3,"title":2},"142":{"body":43,"breadcrumbs":3,"title":2},"143":{"body":71,"breadcrumbs":3,"title":2},"144":{"body":163,"breadcrumbs":3,"title":2},"145":{"body":28,"breadcrumbs":4,"title":3},"146":{"body":12,"breadcrumbs":4,"title":3},"147":{"body":58,"breadcrumbs":4,"title":3},"148":{"body":32,"breadcrumbs":2,"title":1},"149":{"body":244,"breadcrumbs":3,"title":2},"15":{"body":71,"breadcrumbs":3,"title":2},"150":{"body":36,"breadcrumbs":4,"title":3},"151":{"body":0,"breadcrumbs":2,"title":1},"152":{"body":53,"breadcrumbs":6,"title":5},"153":{"body":117,"breadcrumbs":5,"title":4},"154":{"body":80,"breadcrumbs":9,"title":8},"155":{"body":49,"breadcrumbs":6,"title":5},"156":{"body":0,"breadcrumbs":2,"title":1},"157":{"body":220,"breadcrumbs":3,"title":2},"158":{"body":91,"breadcrumbs":4,"title":3},"159":{"body":0,"breadcrumbs":2,"title":1},"16":{"body":221,"breadcrumbs":2,"title":1},"160":{"body":6,"breadcrumbs":3,"title":2},"161":{"body":52,"breadcrumbs":2,"title":1},"162":{"body":54,"breadcrumbs":2,"title":1},"163":{"body":68,"breadcrumbs":3,"title":2},"17":{"body":23,"breadcrumbs":3,"title":2},"18":{"body":298,"breadcrumbs":4,"title":2},"19":{"body":209,"breadcrumbs":3,"title":1},"2":{"body":10,"breadcrumbs":2,"title":1},"20":{"body":34,"breadcrumbs":3,"title":1},"21":{"body":172,"breadcrumbs":2,"title":1},"22":{"body":133,"breadcrumbs":4,"title":3},"23":{"body":64,"breadcrumbs":4,"title":2},"24":{"body":67,"breadcrumbs":3,"title":1},"25":{"body":184,"breadcrumbs":3,"title":1},"26":{"body":24,"breadcrumbs":3,"title":1},"27":{"body":86,"breadcrumbs":4,"title":2},"28":{"body":221,"breadcrumbs":3,"title":1},"29":{"body":226,"breadcrumbs":4,"title":2},"3":{"body":30,"breadcrumbs":2,"title":1},"30":{"body":30,"breadcrumbs":2,"title":1},"31":{"body":81,"breadcrumbs":3,"title":2},"32":{"body":174,"breadcrumbs":4,"title":3},"33":{"body":158,"breadcrumbs":5,"title":4},"34":{"body":114,"breadcrumbs":2,"title":1},"35":{"body":57,"breadcrumbs":3,"title":2},"36":{"body":40,"breadcrumbs":2,"title":1},"37":{"body":134,"breadcrumbs":2,"title":1},"38":{"body":23,"breadcrumbs":2,"title":1},"39":{"body":115,"breadcrumbs":2,"title":1},"4":{"body":94,"breadcrumbs":2,"title":1},"40":{"body":153,"breadcrumbs":3,"title":2},"41":{"body":76,"breadcrumbs":2,"title":1},"42":{"body":279,"breadcrumbs":4,"title":3},"43":{"body":61,"breadcrumbs":4,"title":3},"44":{"body":30,"breadcrumbs":4,"title":2},"45":{"body":76,"breadcrumbs":3,"title":1},"46":{"body":233,"breadcrumbs":3,"title":1},"47":{"body":95,"breadcrumbs":3,"title":1},"48":{"body":17,"breadcrumbs":4,"title":2},"49":{"body":163,"breadcrumbs":2,"title":1},"5":{"body":91,"breadcrumbs":2,"title":1},"50":{"body":353,"breadcrumbs":3,"title":2},"51":{"body":50,"breadcrumbs":2,"title":1},"52":{"body":74,"breadcrumbs":2,"title":1},"53":{"body":216,"breadcrumbs":2,"title":1},"54":{"body":39,"breadcrumbs":2,"title":1},"55":{"body":83,"breadcrumbs":3,"title":1},"56":{"body":90,"breadcrumbs":4,"title":2},"57":{"body":92,"breadcrumbs":4,"title":2},"58":{"body":78,"breadcrumbs":3,"title":1},"59":{"body":148,"breadcrumbs":3,"title":1},"6":{"body":40,"breadcrumbs":2,"title":1},"60":{"body":198,"breadcrumbs":3,"title":1},"61":{"body":52,"breadcrumbs":5,"title":3},"62":{"body":65,"breadcrumbs":3,"title":1},"63":{"body":148,"breadcrumbs":4,"title":2},"64":{"body":308,"breadcrumbs":3,"title":1},"65":{"body":148,"breadcrumbs":4,"title":2},"66":{"body":701,"breadcrumbs":4,"title":2},"67":{"body":505,"breadcrumbs":2,"title":1},"68":{"body":250,"breadcrumbs":2,"title":1},"69":{"body":298,"breadcrumbs":2,"title":1},"7":{"body":43,"breadcrumbs":2,"title":1},"70":{"body":409,"breadcrumbs":2,"title":1},"71":{"body":19,"breadcrumbs":2,"title":1},"72":{"body":204,"breadcrumbs":2,"title":1},"73":{"body":347,"breadcrumbs":3,"title":2},"74":{"body":220,"breadcrumbs":3,"title":2},"75":{"body":253,"breadcrumbs":3,"title":2},"76":{"body":61,"breadcrumbs":5,"title":4},"77":{"body":86,"breadcrumbs":2,"title":1},"78":{"body":243,"breadcrumbs":3,"title":2},"79":{"body":36,"breadcrumbs":4,"title":2},"8":{"body":0,"breadcrumbs":2,"title":1},"80":{"body":18,"breadcrumbs":5,"title":3},"81":{"body":60,"breadcrumbs":4,"title":2},"82":{"body":22,"breadcrumbs":4,"title":2},"83":{"body":118,"breadcrumbs":4,"title":2},"84":{"body":267,"breadcrumbs":5,"title":3},"85":{"body":52,"breadcrumbs":3,"title":1},"86":{"body":482,"breadcrumbs":4,"title":2},"87":{"body":171,"breadcrumbs":6,"title":3},"88":{"body":57,"breadcrumbs":4,"title":2},"89":{"body":238,"breadcrumbs":5,"title":3},"9":{"body":86,"breadcrumbs":3,"title":2},"90":{"body":149,"breadcrumbs":5,"title":3},"91":{"body":13,"breadcrumbs":4,"title":2},"92":{"body":38,"breadcrumbs":6,"title":4},"93":{"body":415,"breadcrumbs":5,"title":3},"94":{"body":93,"breadcrumbs":3,"title":2},"95":{"body":150,"breadcrumbs":5,"title":4},"96":{"body":387,"breadcrumbs":5,"title":4},"97":{"body":31,"breadcrumbs":5,"title":2},"98":{"body":258,"breadcrumbs":5,"title":2},"99":{"body":41,"breadcrumbs":7,"title":3}},"docs":{"0":{"body":"👋 Welcome to gb-asm-tutorial! This tutorial will teach you how to make games for the Game Boy and Game Boy Color. ⚠️ While the Game Boy and Game Boy Color are almost the same console, the Game Boy Advance is entirely different . However, the GBA is able to run GB and GBC games! If you are looking to program GBC games and run them on a GBA, you're at the right place; however, if you want to make games specifically for the GBA, please check out the Tonc tutorial instead.","breadcrumbs":"Home » Home","id":"0","title":"Home"},"1":{"body":"There are some handy icons near the top of your screen! The \"burger\" toggles the navigation side panel; The brush allows selecting a different color theme; The magnifying glass pops up a search bar; The world icon lets you change the language of the tutorial; The printer gives a single-page version of the entire tutorial, which you can print if you want; The GitHub icon links to the tutorial's source repository; The edit button allows you to suggest changes to the tutorial, provided that you have a GitHub account. Additionally, there are arrows to the left and to the right of the page (they are at the bottom instead on mobile) to more easily navigate to the next page. With that said, you can get started by simply navigating to the following page :)","breadcrumbs":"Home » Controls","id":"1","title":"Controls"},"10":{"body":"The sad truth is that Windows is a terrible OS for development; however, you can install environments that solve most issues. On Windows 10, your best bet is WSL , which sort of allows running a Linux distribution within Windows. Install WSL 1 or WSL 2, then a distribution of your choice, and then follow these steps again, but for the Linux distribution you installed. If WSL is not an option, you can use MSYS2 or Cygwin instead; then check out RGBDS' Windows install instructions . As far as I'm aware, both of these provide a sufficiently up-to-date version of GNU Make. If you have programmed for other consoles, such as the GBA, check if MSYS2 isn't already installed on your machine. This is because devkitPro, a popular homebrew development bundle, includes MSYS2.","breadcrumbs":"Setup » Windows","id":"10","title":"Windows"},"100":{"body":"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 InterruptsDiagram.png","breadcrumbs":"Gameplay » Heads-Up Interface » STAT Interrupts & the window","id":"100","title":"STAT Interrupts & the window"},"101":{"body":"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 . 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.","breadcrumbs":"Gameplay » Heads-Up Interface » Using the STAT interrupt","id":"101","title":"Using the STAT interrupt"},"102":{"body":"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: INCLUDE \"src/main/utils/hardware.inc\" SECTION \"Interrupts\", ROM0 DisableInterrupts:: xor a ldh [rSTAT], a di ret InitStatInterrupts:: ld a, IEF_STAT ldh [rIE], a xor a 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 xor a ldh [rLYC], a ret","breadcrumbs":"Gameplay » Heads-Up Interface » Initiating & Disabling STAT interrupts","id":"102","title":"Initiating & Disabling STAT interrupts"},"103":{"body":"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. ; Define a new section and hard-code it to be at $0048.\nSECTION \"Stat Interrupt\", ROM0[$0048]\nStatInterrupt: push af ; Check if we are on the first scanline ldh a, [rLYC] and a jp z, LYCEqualsZero LYCEquals8: ; Don't call the next stat interrupt until scanline 8 xor a 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; 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 on the gbdev discord server.","breadcrumbs":"Gameplay » Heads-Up Interface » Defining STAT interrupts","id":"103","title":"Defining STAT interrupts"},"104":{"body":"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 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 . 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 9 ret c ; If it HAS gone past 9\nIncreaseScore_Next: ; Increase a counter so we can not go out of our scores bounds inc c ld a, c ; Check if we've gone over our scores bounds cp 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 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: DrawScore:: ; Our score has max 6 digits ; We'll start with the left-most digit (visually) which is also the first byte ld c, 6 ld hl, wScore ld de, $9C06 ; The window tilemap starts at $9C00 DrawScore_Loop: ld a, [hli] add 10 ; our numeric tiles start at tile 10, so add to 10 to each bytes value ld [de], a ; Decrease how many numbers we have drawn dec c ; Stop when we've drawn all the numbers ret z ; Increase which tile we are drawing to inc de jp DrawScore_Loop 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. DrawLives:: ld hl, wLives ld de, $9C13 ; The window tilemap starts at $9C00 ld a, [hl] add 10 ; our numeric tiles start at tile 10, so add 10 to each bytes value ld [de], a ret","breadcrumbs":"Gameplay » Heads-Up Interface » Keeping Score and Drawing Score on the HUD","id":"104","title":"Keeping Score and Drawing Score on the HUD"},"105":{"body":"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: enemyShipMetasprite:: .metasprite1 db 0,0,4,0 .metasprite2 db 0,8,6,0 .metaspriteEnd db 128 MetaspriteDIagram.png 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; call the 'DrawMetasprites function. setup variables and call ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Save the address of the metasprite into the 'wMetaspriteAddress' variable ; Our DrawMetasprites functoin uses that variable ld a, LOW(enemyShipMetasprite) ld [wMetaspriteAddress+0], a ld a, HIGH(enemyShipMetasprite) ld [wMetaspriteAddress+1], a ; Save the x position ld a, [wCurrentEnemyX] ld [wMetaspriteX], a ; Save the y position ld a, [wCurrentEnemyY] ld [wMetaspriteY], a ; Actually call the 'DrawMetasprites function call DrawMetasprites We previously mentioned a variable called \"wLastOAMAddress\". The \"DrawMetasprites\" function can be found in the \"src/main/utils/metasprites.asm\" file: include \"src/main/utils/constants.inc\"\nSECTION \"MetaSpriteVariables\", WRAM0 wMetaspriteAddress:: dw\nwMetaspriteX:: db\nwMetaspriteY::db SECTION \"MetaSprites\", ROM0 DrawMetasprites:: ; get the metasprite address ld a, [wMetaspriteAddress+0] ld l, a ld a, [wMetaspriteAddress+1] ld h, a ; Get the y position ld a, [hli] ld b, a ; stop if the y position is 128 ld a, b cp 128 ret z ld a, [wMetaspriteY] add b ld [wMetaspriteY], a ; Get the x position ld a, [hli] ld c, a ld a, [wMetaspriteX] add c ld [wMetaspriteX], a ; Get the tile position ld a, [hli] ld d, a ; Get the flag position ld a, [hli] ld e, a ; Get our offset address in hl ld a,[wLastOAMAddress+0] ld l, a ld a, HIGH(wShadowOAM) ld h, a ld a, [wMetaspriteY] ld [hli], a ld a, [wMetaspriteX] ld [hli], a ld a, d ld [hli], a ld a, e ld [hli], a call NextOAMSprite ; increase the wMetaspriteAddress ld a, [wMetaspriteAddress] add a, METASPRITE_BYTES_COUNT ld [wMetaspriteAddress], a ld a, [wMetaspriteAddress+1] adc 0 ld [wMetaspriteAddress+1], a jp DrawMetasprites 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\" NextOAMSprite:: ld a, [wSpritesUsed] inc a ld [wSpritesUsed], a ld a,[wLastOAMAddress] add sizeof_OAM_ATTRS ld [wLastOAMAddress], a ld a, HIGH(wShadowOAM) ld [wLastOAMAddress+1], a ret","breadcrumbs":"Gameplay » Sprites & Metasprites » Sprites & Metasprites","id":"105","title":"Sprites & Metasprites"},"106":{"body":"Galactic Armada will use \"object pools\" for bullets and enemies. A fixed amount of bytes representing a specific maximum amount of objects. Each pool is just a collection of bytes. The number of bytes per “pool” is the maximum number of objects in the pool, times the number of bytes needed for data for each object. Constants are also created for the size of each object, and what each byte is. These constants are in the “src/main/utils/constants.inc” file and utilize RGBDS offset constants (a really cool feature) ; from https://rgbds.gbdev.io/docs/v0.6.1/rgbasm.5#EXPRESSIONS\n; The RS group of commands is a handy way of defining structure offsets:\nRSRESET\nDEF bullet_activeByte RB 1\nDEF bullet_xByte RB 1\nDEF bullet_yLowByte RB 1\nDEF bullet_yHighByte RB 1\nDEF PER_BULLET_BYTES_COUNT RB 0 The two object types that we need to loop through are Enemies and Bullets. Bytes for an Enemy: Active - Are they active X - Position: horizontal coordinate Y (low) - The lower byte of their 16-bit (scaled) y position Y (high) - The higher byte of their 16-bit (scaled) y position Speed - How fast they move Health - How many bullets they can take ; Bytes: active, x , y (low), y (high), speed, health\nwEnemies:: ds MAX_ENEMY_COUNT*PER_ENEMY_BYTES_COUNT EnemyBytesVisualized.png Bytes for a Bullet: Active - Are they active X - Position: horizontal coordinate Y (low) - The lower byte of their 16-bit (scaled) y position Y (high) - The higher byte of their 16-bit (scaled) y position ; Bytes: active, x , y (low), y (high)\nwBullets:: ds MAX_BULLET_COUNT*PER_BULLET_BYTES_COUNT BulletBytesVisualized.png ⚠️ NOTE: Scaled integers are used for only the y positions of bullets and enemies. Scaled Integers are a way to provide smooth “sub-pixel” movement. They only move vertically, so the x position can be 8-bit. When looping through an object pool, we’ll check if an object is active. If it’s active, we’ll run the logic for that object. Otherwise, we’ll skip to the start of the next object’s bytes. Both bullets and enemies do similar things. They move vertically until they are off the screen. In addition, enemies will check against bullets when updating. If they are found to be colliding, the bullet is destroyed and so is the enemy.","breadcrumbs":"Gameplay » Object Pools » Object Pools","id":"106","title":"Object Pools"},"107":{"body":"To Activate a pooled object, we simply loop through each object. If the first byte, which tells us if it’s active or not, is 0: then we’ll add the new item at that location and set that byte to be 1. If we loop through all possible objects and nothing is inactive, nothing happens. Spawning Enemies.png","breadcrumbs":"Gameplay » Object Pools » “Activating” a pooled object","id":"107","title":"“Activating” a pooled object"},"108":{"body":"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 include \"src/main/utils/hardware.inc\"\ninclude \"src/main/utils/hardware.inc\"\ninclude \"src/main/utils/constants.inc\" SECTION \"PlayerVariables\", WRAM0 ; first byte is low, second is high (little endian)\nwPlayerPositionX:: dw\nwPlayerPositionY:: dw mPlayerFlash: dw 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: SECTION \"Player\", ROM0 playerShipTileData: INCBIN \"src/generated/sprites/player-ship.2bpp\"\nplayerShipTileDataEnd: playerTestMetaSprite:: .metasprite1 db 0,0,0,0 .metasprite2 db 0,8,2,0 .metaspriteEnd db 128","breadcrumbs":"Gameplay » The Player » The Player","id":"108","title":"The Player"},"109":{"body":"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: RSRESET\nDEF spriteTilesStart RB _VRAM\nDEF PLAYER_TILES_START RB 4*16\nDEF ENEMY_TILES_START RB 4*16\nDEF BULLET_TILES_START RB 0 Here's what our \"InitializePlayer\" function looks like. Recall, this was called when initiating the gameplay game state: InitializePlayer:: xor a ld [mPlayerFlash], a ld [mPlayerFlash+1], a ; Place in the middle of the screen xor a ld [wPlayerPositionX], a ld [wPlayerPositionY], a ld a, 5 ld [wPlayerPositionX+1], a ld [wPlayerPositionY+1], a CopyPlayerTileDataIntoVRAM: ; Copy the player's tile data into VRAM ld de, playerShipTileData ld hl, PLAYER_TILES_START ld bc, playerShipTileDataEnd - playerShipTileData call CopyDEintoMemoryAtHL ret","breadcrumbs":"Gameplay » The Player » Initializing the Player","id":"109","title":"Initializing the Player"},"11":{"body":"Any code editor is fine; I personally use Sublime Text with its RGBDS syntax package ; however, you can use any text editor, including Notepad, if you're crazy enough. Awesome GBDev has a section on syntax highlighting packages , see there if your favorite editor supports RGBDS.","breadcrumbs":"Setup » Code editor","id":"11","title":"Code editor"},"110":{"body":"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. UpdatePlayer:: UpdatePlayer_HandleInput: ld a, [wCurKeys] and PADF_UP call nz, MoveUp ld a, [wCurKeys] and PADF_DOWN call nz, MoveDown ld a, [wCurKeys] and PADF_LEFT call nz, MoveLeft ld a, [wCurKeys] and PADF_RIGHT call nz, MoveRight ld a, [wCurKeys] and PADF_A call nz, TryShoot 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. MoveUp: ; decrease the player's y position ld a, [wPlayerPositionY] sub PLAYER_MOVE_SPEED ld [wPlayerPositionY], a ld a, [wPlayerPositionY] sbc 0 ld [wPlayerPositionY], a ret MoveDown: ; increase the player's y position ld a, [wPlayerPositionY] add PLAYER_MOVE_SPEED ld [wPlayerPositionY], a ld a, [wPlayerPositionY+1] adc 0 ld [wPlayerPositionY+1], a ret MoveLeft: ; decrease the player's x position ld a, [wPlayerPositionX] sub PLAYER_MOVE_SPEED ld [wPlayerPositionX], a ld a, [wPlayerPositionX+1] sbc 0 ld [wPlayerPositionX+1], a ret MoveRight: ; increase the player's x position ld a, [wPlayerPositionX] add PLAYER_MOVE_SPEED ld [wPlayerPositionX], a ld a, [wPlayerPositionX+1] adc 0 ld [wPlayerPositionX+1], a ret 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\" TryShoot: ld a, [wLastKeys] and PADF_A ret nz jp FireNextBullet 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. ld a, [mPlayerFlash+0] ld b, a ld a, [mPlayerFlash+1] ld c, a UpdatePlayer_UpdateSprite_CheckFlashing: ld a, b or c jp z, UpdatePlayer_UpdateSprite ; decrease bc by 5 ld a, b sub 5 ld b, a ld a, c sbc 0 ld c, a UpdatePlayer_UpdateSprite_DecreaseFlashing: ld a, b ld [mPlayerFlash], a ld a, c ld [mPlayerFlash+1], a ; descale bc srl c rr b srl c rr b srl c rr b srl c rr b ld a, b cp 5 jp c, UpdatePlayer_UpdateSprite_StopFlashing bit 0, b jp z, UpdatePlayer_UpdateSprite UpdatePlayer_UpdateSprite_Flashing: ret\nUpdatePlayer_UpdateSprite_StopFlashing: xor a ld [mPlayerFlash],a ld [mPlayerFlash+1],a If we get past all of the \"wPlayerFlash\" logic, we'll draw our player using the \"DrawMetasprite\" function we previously discussed. UpdatePlayer_UpdateSprite: ; Get the unscaled player x position in b ld a, [wPlayerPositionX+0] ld b, a ld a, [wPlayerPositionX+1] ld d, a srl d rr b srl d rr b srl d rr b srl d rr b ; Get the unscaled player y position in c ld a, [wPlayerPositionY+0] ld c, a ld a, [wPlayerPositionY+1] ld e, a srl e rr c srl e rr c srl e rr c srl e rr c ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Drawing the palyer metasprite ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Save the address of the metasprite into the 'wMetaspriteAddress' variable ; Our DrawMetasprites functoin uses that variable ld a, LOW(playerTestMetaSprite) ld [wMetaspriteAddress+0], a ld a, HIGH(playerTestMetaSprite) ld [wMetaspriteAddress+1], a ; Save the x position ld a, b ld [wMetaspriteX], a ; Save the y position ld a, c ld [wMetaspriteY], a ; Actually call the 'DrawMetasprites function call DrawMetasprites; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ret 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. DamagePlayer:: xor a ld [mPlayerFlash], a inc a ld [mPlayerFlash+1], a ld a, [wLives] dec a ld [wLives], a ret That's everything for our player. Next, we'll go over bullets and then onto the enemies.","breadcrumbs":"Gameplay » The Player » Updating the Player","id":"110","title":"Updating the Player"},"111":{"body":"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. include \"src/main/utils/hardware.inc\"\ninclude \"src/main/utils/constants.inc\" SECTION \"BulletVariables\", WRAM0 wSpawnBullet: db ; how many bullets are currently active\nwActiveBulletCounter:: db ; how many bullet's we've updated\nwUpdateBulletsCounter: db ; Bytes: active, x , y (low), y (high)\nwBullets:: ds MAX_BULLET_COUNT*PER_BULLET_BYTES_COUNT SECTION \"Bullets\", ROM0 bulletMetasprite:: .metasprite1 db 0,0,8,0 .metaspriteEnd db 128 bulletTileData:: INCBIN \"src/generated/sprites/bullet.2bpp\"\nbulletTileDataEnd:: We'll need to loop through the bullet object pool in the following sections.","breadcrumbs":"Gameplay » Bullets » Bullets","id":"111","title":"Bullets"},"112":{"body":"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 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. InitializeBullets:: xor a ld [wSpawnBullet], a ; Copy the bullet tile data intto vram ld de, bulletTileData ld hl, BULLET_TILES_START ld bc, bulletTileDataEnd - bulletTileData call CopyDEintoMemoryAtHL ; Reset how many bullets are active to 0 xor a ld [wActiveBulletCounter],a ld b, a ld hl, wBullets ld [hl], a InitializeBullets_Loop: ; Increase the address ld a, l add PER_BULLET_BYTES_COUNT ld l, a ld a, h adc 0 ld h, a ; Increase how many bullets we have initailized ld a, b inc a ld b, a cp MAX_BULLET_COUNT ret z jp InitializeBullets_Loop","breadcrumbs":"Gameplay » Bullets » Initiating Bullets","id":"112","title":"Initiating Bullets"},"113":{"body":"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. UpdateBullets:: ; Make sure we have SOME active enemies ld a, [wSpawnBullet] ld b, a ld a, [wActiveBulletCounter] or b cp 0 ret z ; Reset our counter for how many bullets we have checked xor a ld [wUpdateBulletsCounter], a ; Get the address of the first bullet in hl ld a, LOW(wBullets) ld l, a ld a, HIGH(wBullets) ld h, a jp UpdateBullets_PerBullet 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 UpdateBullets_PerBullet: ; The first byte is if the bullet is active ; If it's NOT zero, it's active, go to the normal update section ld a, [hl] and a jp nz, UpdateBullets_PerBullet_Normal ; Do we need to spawn a bullet? ; If we dont, loop to the next enemy ld a, [wSpawnBullet] and a jp z, UpdateBullets_Loop UpdateBullets_PerBullet_SpawnDeactivatedBullet: ; reset this variable so we don't spawn anymore xor a ld [wSpawnBullet], a ; Increase how many bullets are active ld a, [wActiveBulletCounter] inc a ld [wActiveBulletCounter], a push hl ; Set the current bullet as active ld a, 1 ld [hli], a ; Get the unscaled player x position in b ld a, [wPlayerPositionX] ld b, a ld a, [wPlayerPositionX+1] ld d, a ; Descale the player's x position ; the result will only be in the low byt srl d rr b srl d rr b srl d rr b srl d rr b ; Set the x position to equal the player's x position ld a, b ld [hli], a ; Set the y position (low) ld a, [wPlayerPositionY] ld [hli], a ; Set the y position (high) ld a, [wPlayerPositionY+1] ld [hli], a pop hl UpdateBullets_PerBullet_Normal: ; Save our active byte push hl inc hl ; Get our x position ld a, [hli] ld b, a ; get our 16-bit y position ld a, [hl] sub BULLET_MOVE_SPEED ld [hli], a ld c, a ld a, [hl] sbc 0 ld [hl], a ld d, a pop hl; go to the active byte ; Descale our y position srl d rr c srl d rr c srl d rr c srl d rr c ; See if our non scaled low byte is above 160 ld a, c cp 178 ; If it's below 160, deactivate jp nc, UpdateBullets_DeActivateIfOutOfBounds","breadcrumbs":"Gameplay » Bullets » Updating Bullets","id":"113","title":"Updating Bullets"},"114":{"body":"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. push hl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Drawing a metasprite ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Save the address of the metasprite into the 'wMetaspriteAddress' variable ; Our DrawMetasprites functoin uses that variable ld a, LOW(bulletMetasprite) ld [wMetaspriteAddress], a ld a, HIGH(bulletMetasprite) ld [wMetaspriteAddress+1], a ; Save the x position ld a, b ld [wMetaspriteX], a ; Save the y position ld a, c ld [wMetaspriteY], a ; Actually call the 'DrawMetasprites function call DrawMetasprites ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pop hl jp UpdateBullets_Loop","breadcrumbs":"Gameplay » Bullets » Drawing the Bullets","id":"114","title":"Drawing the Bullets"},"115":{"body":"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. UpdateBullets_DeActivateIfOutOfBounds: ; if it's y value is grater than 160 ; Set as inactive xor a ld [hl], a ; Decrease counter ld a,[wActiveBulletCounter] dec a ld [wActiveBulletCounter], a jp UpdateBullets_Loop","breadcrumbs":"Gameplay » Bullets » Deactivating the Bullets","id":"115","title":"Deactivating the Bullets"},"116":{"body":"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. UpdateBullets_Loop: ; Check our counter, if it's zero ; Stop the function ld a, [wUpdateBulletsCounter] inc a ld [wUpdateBulletsCounter], a ; Check if we've already ld a, [wUpdateBulletsCounter] cp MAX_BULLET_COUNT ret nc ; Increase the bullet data our address is pointingtwo ld a, l add PER_BULLET_BYTES_COUNT ld l, a ld a, h adc 0 ld h, a","breadcrumbs":"Gameplay » Bullets » Updating the next bullet","id":"116","title":"Updating the next bullet"},"117":{"body":"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 FireNextBullet:: ; Make sure we don't have the max amount of enmies ld a, [wActiveBulletCounter] cp MAX_BULLET_COUNT ret nc ; Set our spawn bullet variable to true ld a, 1 ld [wSpawnBullet], a ret 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.","breadcrumbs":"Gameplay » Bullets » Firing New Bullets","id":"117","title":"Firing New Bullets"},"118":{"body":"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. include \"src/main/utils/hardware.inc\"\ninclude \"src/main/utils/constants.inc\" SECTION \"EnemyVariables\", WRAM0 wCurrentEnemyX:: db wCurrentEnemyY:: db wSpawnCounter: db wNextEnemyXPosition: db\nwActiveEnemyCounter::db\nwUpdateEnemiesCounter:db\nwUpdateEnemiesCurrentEnemyAddress::dw ; Bytes: active, x , y (low), y (high), speed, health\nwEnemies:: ds MAX_ENEMY_COUNT*PER_ENEMY_BYTES_COUNT Just like with bullets, we'll setup ROM data for our enemies tile data and metasprites. SECTION \"Enemies\", ROM0 enemyShipTileData:: INCBIN \"src/generated/sprites/enemy-ship.2bpp\"\nenemyShipTileDataEnd:: enemyShipMetasprite:: .metasprite1 db 0,0,4,0 .metasprite2 db 0,8,6,0 .metaspriteEnd db 128","breadcrumbs":"Gameplay » Enemies » Enemies","id":"118","title":"Enemies"},"119":{"body":"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. InitializeEnemies:: ld de, enemyShipTileData ld hl, ENEMY_TILES_START ld bc, enemyShipTileDataEnd - enemyShipTileData call CopyDEintoMemoryAtHL xor a ld [wSpawnCounter], a ld [wActiveEnemyCounter], a ld [wNextEnemyXPosition], a ld b, a ld hl, wEnemies InitializeEnemies_Loop: ; Set as inactive ld [hl], 0 ; Increase the address ld a, l add PER_ENEMY_BYTES_COUNT ld l, a ld a, h adc 0 ld h, a inc b ld a, b cp MAX_ENEMY_COUNT ret z jp InitializeEnemies_Loop","breadcrumbs":"Gameplay » Enemies » Initializing Enemies","id":"119","title":"Initializing Enemies"},"12":{"body":"Using an emulator to play games is one thing; using it to program games is another. The two aspects an emulator must fulfill to allow an enjoyable programming experience are: Debugging tools : When your code goes haywire on an actual console, it's very difficult to figure out why or how. There is no console output, no way to gdb the program, nothing. However, an emulator can provide debugging tools, allowing you to control execution, inspect memory, etc. These are vital if you want GB dev to be fun , trust me! Good accuracy : Accuracy means \"how faithful to the original console something is\". Using a bad emulator for playing games can work (to some extent, and even then...), but using it for developing a game makes it likely to accidentally render your game incompatible with the actual console. For more info, read this article on Ars Technica (especially the An emulator for every game section at the top of page 2). You can compare GB emulator accuracy on Daid's GB-emulator-shootout . The emulator I will be using for this tutorial is Emulicious . Users on all OSes can install the Java runtime to be able to run it. Other debugging emulators are available, such as Mesen2 , BGB (Windows/Wine only), SameBoy (graphical interface on macOS only); they should have similar capabilities, but accessed through different menu options.","breadcrumbs":"Setup » Emulator","id":"12","title":"Emulator"},"120":{"body":"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. UpdateEnemies:: call TryToSpawnEnemies ; Make sure we have active enemies ; or we want to spawn a new enemy ld a, [wNextEnemyXPosition] ld b, a ld a, [wActiveEnemyCounter] or b and a ret z xor a ld [wUpdateEnemiesCounter], a ld a, LOW(wEnemies) ld l, a ld a, HIGH(wEnemies) ld h, a jp UpdateEnemies_PerEnemy 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. UpdateEnemies_PerEnemy: ; The first byte is if the current object is active ; If it's not zero, it's active, go to the normal update section ld a, [hl] and a jp nz, UpdateEnemies_PerEnemy_Update UpdateEnemies_SpawnNewEnemy: ; If this enemy is NOT active ; Check If we want to spawn a new enemy ld a, [wNextEnemyXPosition] and a ; If we don't want to spawn a new enemy, we'll skip this (deactivated) enemy jp z, UpdateEnemies_Loop push hl ; If they are deactivated, and we want to spawn an enemy ; activate the enemy ld a, 1 ld [hli], a ; Put the value for our enemies x position ld a, [wNextEnemyXPosition] ld [hli], a ; Put the value for our enemies y position to equal 0 xor a ld [hli], a ld [hld], a ld [wNextEnemyXPosition], a pop hl ; Increase counter ld a, [wActiveEnemyCounter] inc a ld [wActiveEnemyCounter], a 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. UpdateEnemies_Loop: ; Check our coutner, if it's zero ; Stop the function ld a, [wUpdateEnemiesCounter] inc a ld [wUpdateEnemiesCounter], a ; Compare against the active count cp MAX_ENEMY_COUNT ret nc ; Increase the enemy data our address is pointingtwo ld a, l add PER_ENEMY_BYTES_COUNT ld l, a ld a, h adc 0 ld h, a 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. UpdateEnemies_PerEnemy_Update: ; Save our first bytye push hl ; Get our move speed in e ld bc, enemy_speedByte add hl, bc ld a, [hl] ld e, a ; Go back to the first byte ; put the address toe the first byte back on the stack for later pop hl push hl inc hl ; Get our x position ld a, [hli] ld b, a ld [wCurrentEnemyX], a ; get our 16-bit y position ; increase it (by e), but also save it ld a, [hl] add 10 ld [hli], a ld c, a ld a, [hl] adc 0 ld [hl], a ld d, a pop hl ; Descale the y psoition srl d rr c srl d rr c srl d rr c srl d rr c ld a, c ld [wCurrentEnemyY], a","breadcrumbs":"Gameplay » Enemies » Updating Enemies","id":"120","title":"Updating Enemies"},"121":{"body":"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. UpdateEnemies_PerEnemy_CheckPlayerCollision: push hl call CheckCurrentEnemyAgainstBullets call CheckEnemyPlayerCollision pop hl ld a, [wResult] and a jp z, UpdateEnemies_NoCollisionWithPlayer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; push hl call DamagePlayer call DrawLives pop hl jp UpdateEnemies_DeActivateEnemy 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. UpdateEnemies_NoCollisionWithPlayer:: ; See if our non scaled low byte is above 160 ld a, [wCurrentEnemyY] cp 160 jp nc, UpdateEnemies_DeActivateEnemy push hl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; call the 'DrawMetasprites function. setup variables and call ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Save the address of the metasprite into the 'wMetaspriteAddress' variable ; Our DrawMetasprites functoin uses that variable ld a, LOW(enemyShipMetasprite) ld [wMetaspriteAddress+0], a ld a, HIGH(enemyShipMetasprite) ld [wMetaspriteAddress+1], a ; Save the x position ld a, [wCurrentEnemyX] ld [wMetaspriteX], a ; Save the y position ld a, [wCurrentEnemyY] ld [wMetaspriteY], a ; Actually call the 'DrawMetasprites function call DrawMetasprites ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pop hl jp UpdateEnemies_Loop","breadcrumbs":"Gameplay » Enemies » Player & Bullet Collision","id":"121","title":"Player & Bullet Collision"},"122":{"body":"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. UpdateEnemies_DeActivateEnemy: ; Set as inactive xor a ld [hl], a ; Decrease counter ld a, [wActiveEnemyCounter] dec a ld [wActiveEnemyCounter], a jp UpdateEnemies_Loop","breadcrumbs":"Gameplay » Enemies » Deactivating Enemies","id":"122","title":"Deactivating Enemies"},"123":{"body":"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. 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 ENEMY_SPAWN_DELAY_MAX ret c ; Check our next enemy x position variable ; Stop if it's non zero ld a, [wNextEnemyXPosition] cp 0 ret nz ; Make sure we don't have the max amount of enmies ld a, [wActiveEnemyCounter] cp MAX_ENEMY_COUNT ret nc GetSpawnPosition: ; Generate a semi random value call rand ; make sure it's not above 150 ld a, b cp 150 ret nc ; make sure it's not below 24 ld a, b cp 24 ret c ; reset our spawn counter xor a ld [wSpawnCounter], a ld a, b ld [wNextEnemyXPosition], a ret","breadcrumbs":"Gameplay » Enemies » Spawning Enemies","id":"123","title":"Spawning Enemies"},"124":{"body":"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. [1] 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 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 Y position in the d register. We'll check it's value against the y value of the current enemy, which we have in a variable named wCurrentEnemyY. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Check the y distances. Jump to 'NoCollisionWithPlayer' on failure ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ld a, [wCurrentEnemyY] ld [wObject1Value], a ld a, d ld [wObject2Value], a ; Save if the minimum distance ld a, 16 ld [wSize], a call CheckObjectPositionDifference ld a, [wResult] and a jp z, NoCollisionWithPlayer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 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: include \"src/main/utils/constants.inc\"\ninclude \"src/main/utils/hardware.inc\" SECTION \"CollisionUtilsVariables\", WRAM0 wResult:: db\nwSize:: db\nwObject1Value:: db\nwObject2Value:: 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 d cp b ; no carry means no collision jp nc, CheckObjectPositionDifference_Failure ld a, 1 ld [wResult], a ret CheckObjectPositionDifference_Failure: ld a,0 ld [wResult], a ret; From mdn web docs - 2D collision detection","breadcrumbs":"Gameplay » Collision Detection » Collision Detection","id":"124","title":"Collision Detection"},"125":{"body":"Our enemy versus player collision detection starts with us getting our player's unscaled x position. We'll store that value in d. CheckEnemyPlayerCollision:: ; Get our player's unscaled x position in d ld a, [wPlayerPositionX] ld d, a ld a, [wPlayerPositionX+1] ld e, a srl e rr d srl e rr d srl e rr d srl e rr d With our player's x position in d, we'll compare it against a previously saved enemy x position variable. If they are more than 16 pixels apart, we'll jump to the \"NoCollisionWithPlayer\" label. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Check the x distances. Jump to 'NoCollisionWithPlayer' on failure ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ld a, [wCurrentEnemyX] ld [wObject1Value], a ld a, d ld [wObject2Value], a ; Save if the minimum distance ld a, 16 ld [wSize], a call CheckObjectPositionDifference ld a, [wResult] and a jp z, NoCollisionWithPlayer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; After checking the x axis, if the code gets this far there was an overlap. We'll do the same for the y axis next. We'll get the player's unscaled y position. We'll store that value in d for consistency. ; Get our player's unscaled y position in d ld a, [wPlayerPositionY+0] ld d, a ld a, [wPlayerPositionY+1] ld e, a srl e rr d srl e rr d srl e rr d srl e rr d Just like before, we'll compare our player's unscaled y position (stored in d) against a previously saved enemy y position variable. If they are more than 16 pixels apart, we'll jump to the \"NoCollisionWithPlayer\" label. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Check the y distances. Jump to 'NoCollisionWithPlayer' on failure ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ld a, [wCurrentEnemyY] ld [wObject1Value], a ld a, d ld [wObject2Value], a ; Save if the minimum distance ld a, 16 ld [wSize], a call CheckObjectPositionDifference ld a, [wResult] and a jp z, NoCollisionWithPlayer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; The \"NoCollisionWithPlayer\", just set's the \"wResult\" to 0 for failure. If overlap occurs on both axis, we'll isntead set 1 for success. ld a, 1 ld [wResult], a ret NoCollisionWithPlayer:: xor a ld [wResult], a ret That's the enemy-player collision logic. Callers of the function can simply check the \"wResult\" variable to determine if there was collision.","breadcrumbs":"Gameplay » Enemy-Player Collision » Enemy-Player Collision","id":"125","title":"Enemy-Player Collision"},"126":{"body":"When we are udating enemies, we'll call a function called \"CheckCurrentEnemyAgainstBullets\". This will check the current enemy against all active bullets. This fuction needs to loop through the bullet object pool, and check if our current enemy overlaps any bullet on both the x and y axis. If so, we'll deactivate the enemy and bullet. Our \"CheckCurrentEnemyAgainstBullets\" function starts off in a manner similar to how we updated enemies & bullets. This function expects \"hl\" points to the curent enemy. We'll save that in a variable for later usage. include \"src/main/utils/hardware.inc\"\ninclude \"src/main/utils/constants.inc\"\ninclude \"src/main/utils/hardware.inc\" SECTION \"EnemyBulletCollisionVariables\", WRAM0 wEnemyBulletCollisionCounter: db\nwBulletAddresses: dw SECTION \"EnemyBulletCollision\", ROM0 ; called from enemies.asm\nCheckCurrentEnemyAgainstBullets:: ld a, l ld [wUpdateEnemiesCurrentEnemyAddress], a ld a, h ld [wUpdateEnemiesCurrentEnemyAddress+1], a xor a ld [wEnemyBulletCollisionCounter], a ; Copy our bullets address into wBulletAddress ld a, LOW(wBullets) ld l, a ld a, HIGH(wBullets) ld h, a jp CheckCurrentEnemyAgainstBullets_PerBullet As we loop through the bullets, we need to make sure we only check active bullets. Inactive bullets will be skipped. CheckCurrentEnemyAgainstBullets_PerBullet: ld a, [hl] cp 1 jp nz, CheckCurrentEnemyAgainstBullets_Loop First, we need to check if the current enemy and current bullet are overlapping on the x axis. We'll get the enemy's x position in e, and the bullet's x position in b. From there, we'll again call our \"CheckObjectPositionDifference\" function. If it returns a failure (wResult=0), we'll start with the next bullet. We add an offset to the x coordinates so they measure from their centers. That offset is half it's respective object's width. CheckCurrentEnemyAgainstBullets_Check_X_Overlap: ; Save our first byte address push hl inc hl ; Get our x position ld a, [hli] add 4 ld b, a push hl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Start: Checking the absolute difference ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The first value ld a, b ld [wObject1Value], a ; The second value ld a, [wCurrentEnemyX] add 8 ld [wObject2Value], a ; Save if the minimum distance ld a, 12 ld [wSize], a call CheckObjectPositionDifference ld a, [wResult] and a jp z, CheckCurrentEnemyAgainstBullets_Check_X_Overlap_Fail pop hl jp CheckCurrentEnemyAgainstBullets_PerBullet_Y_Overlap CheckCurrentEnemyAgainstBullets_Check_X_Overlap_Fail: pop hl pop hl jp CheckCurrentEnemyAgainstBullets_Loop Next we restore our hl variable so we can get the y position of our current bullet. Once we have that y position, we'll get the current enemy's y position and check for an overlap on the y axis. If no overlap is found, we'll loop to the next bullet. Otherwise, we have a collision. CheckCurrentEnemyAgainstBullets_PerBullet_Y_Overlap: ; get our bullet 16-bit y position ld a, [hli] ld b, a ld a, [hli] ld c, a ; Descale our 16 bit y position srl c rr b srl c rr b srl c rr b srl c rr b ; preserve our first byte addresss pop hl push hl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Start: Checking the absolute difference ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The first value ld a, b ld [wObject1Value], a ; The second value ld a, [wCurrentEnemyY] ld [wObject2Value], a ; Save if the minimum distance ld a, 16 ld [wSize], a call CheckObjectPositionDifference pop hl ld a, [wResult] and a jp z, CheckCurrentEnemyAgainstBullets_Loop jp CheckCurrentEnemyAgainstBullets_PerBullet_Collision ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; End: Checking the absolute difference ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; If a collision was detected (overlap on x and y axis), we'll set the current active byte for that bullet to 0. Also , we'll set the active byte for the current enemy to zero. Before we end the function, we'll increase and redraw the score, and decrease how many bullets & enemies we have by one. CheckCurrentEnemyAgainstBullets_PerBullet_Collision: ; set the active byte and x value to 0 for bullets xor a ld [hli], a ld [hl], a ld a, [wUpdateEnemiesCurrentEnemyAddress+0] ld l, a ld a, [wUpdateEnemiesCurrentEnemyAddress+1] ld h, a ; set the active byte and x value to 0 for enemies xor a ld [hli], a ld [hl], a call IncreaseScore call DrawScore ; Decrease how many active enemies their are ld a, [wActiveEnemyCounter] dec a ld [wActiveEnemyCounter], a ; Decrease how many active bullets their are ld a, [wActiveBulletCounter] dec a ld [wActiveBulletCounter], a ret If no collision happened, we'll continue our loop through the enemy bullets. When we've checked all the bullets, we'll end the function. CheckCurrentEnemyAgainstBullets_Loop: ; increase our counter ld a, [wEnemyBulletCollisionCounter] inc a ld [wEnemyBulletCollisionCounter], a ; Stop if we've checked all bullets cp MAX_BULLET_COUNT ret nc ; Increase the data our address is pointing to ld a, l add PER_BULLET_BYTES_COUNT ld l, a ld a, h adc 0 ld h, a","breadcrumbs":"Gameplay » Enemy-Bullet Collision » Enemy-Bullet Collision","id":"126","title":"Enemy-Bullet Collision"},"127":{"body":"If you liked this tutorial, and you want to take things to the next level, here are some ideas: Add an options menu (for typewriter speed, difficulty, disable audio) Add Ship Select and different player ships Add the ability to upgrade your bullet type Add dialogue and \"waves\" of enemies Add different types of enemies Add a boss Add a level select","breadcrumbs":"Conclusion » Conclusion","id":"127","title":"Conclusion"},"128":{"body":"Oh. Well, you've reached the end of the tutorial... And yes, as you can see, it's not finished yet . We're actively working on new content (and improvement of the existing one). In the meantime, the best course of action is to peruse the resources in the next section, and experiment by yourself. Well, given that, it may be a good idea to ask around for advice. A lot of the problems and questions you will be encountering have already been solved, so others can—and will!—help you getting started faster. If you enjoyed the tutorial, please consider contributing , donating to our OpenCollective or simply share the link to this book.","breadcrumbs":"Where to go next » Where to go next","id":"128","title":"Where to go next"},"129":{"body":"The purpose of this page is to provide concise explanations and code snippets for common tasks. For extra depth, clarity, and understanding, it's recommended you read through the Hello World , Part II - Our first game , and Part III - Our second game tutorials. Assembly syntax & CPU Instructions will not be explained, for more information see the RGBDS Language Reference Is there something common you think is missing? Check the github repository to open an Issue or contribute to this page. Alternatively, you can reach out on one of the @gbdev community channels .","breadcrumbs":"Cheatsheet » RGBDS Cheatsheet","id":"129","title":"RGBDS Cheatsheet"},"13":{"body":"In this lesson, we will begin by assembling our first program. The rest of this chapter will be dedicated to explaining how and why it works. Note that we will need to type a lot of commands, so open a terminal now. It's a good idea to create a new directory (mkdir gb_hello_world, for example, then cd gb_hello_world to enter the new directory). Grab the following files (right-click each link, \"Save Link As...\"), and place them all in this new directory: hello-world.asm hardware.inc Then, still from a terminal within that directory, run the following three commands. ⚠️ To clarify where each individual command begins, I've added a $ before each command, but don't type them! $ rgbasm -o hello-world.o hello-world.asm\n$ rgblink -o hello-world.gb hello-world.o\n$ rgbfix -v -p 0xFF hello-world.gb ‼️ Be careful with arguments! Some options, such as -o here, use the argument after them as a parameter: rgbasm -o hello-world.asm hello-world.o won't work (and may corrupt hello-world.asm!) rgbasm hello-world.asm -o hello-world.o will work If you need whitespace within an argument, you must quote it: rgbasm -o hello world.o hello world.asm won't work rgbasm -o \"hello world.o\" \"hello world.asm\" will work It should look like this: (If you encounter an error you can't figure out by yourself, don't be afraid to ask us ! We'll sort it out.) Congrats! You just assembled your first Game Boy ROM! Now, we just need to run it; open Emulicious, then go \"File\", then \"Open File\", and load hello-world.gb. You could also take a flash cart (I use the EverDrive GB X5 , but there are plenty of alternatives), load up your ROM onto it, and run it on an actual console! Picture of the Hello World running on a physical DMG Well, now that we have something working, it's time to peel back the curtains...","breadcrumbs":"Hello World! » Hello World!","id":"13","title":"Hello World!"},"130":{"body":"RGBDS Cheatsheet Table of Contents Display Wait for the vertical blank phase Turn on/off the LCD Turn on/off the background Turn on/off the window Switch which tilemaps are used by the window and/or background Turn on/off sprites Turn on/off tall (8x16) sprites Backgrounds Put background/window tile data into VRAM Draw on the Background/Window Move the background Move the window Joypad Input Check if a button is down Check if a button was JUST pressed Wait for a button press HUD Draw text Draw a bottom HUD Sprites Put sprite tile data in VRAM Manipulate hardware OAM sprites Implement a Shadow OAM using @eievui5's Sprite Object Library Manipulate Shadow OAM OAM sprites Miscellaneous Save Data Generate random numbers","breadcrumbs":"Cheatsheet » Table of Contents","id":"130","title":"Table of Contents"},"131":{"body":"The rLCDC register controls all of the following: The screen The background The window Sprite objects For more information on LCD control, refer to the Pan Docs","breadcrumbs":"Cheatsheet » Display","id":"131","title":"Display"},"132":{"body":"To check for the vertical blank phase, use the rLY register. Compare that register's value against the height of the Game Boy screen in pixels: 144. WaitUntilVerticalBlankStart: ldh a, [rLY] cp 144 jp c, WaitUntilVerticalBlankStart","breadcrumbs":"Cheatsheet » Wait for the vertical blank phase","id":"132","title":"Wait for the vertical blank phase"},"133":{"body":"You can turn the LCD on and off by altering the most significant bit of the rLCDC register. hardware.inc a constant for this: LCDCF_ON . To turn the LCD on: ld a, LCDCF_ON\nldh [rLCDC], a To turn the LCD off: ⚠️ Do not turn the LCD off outside of the Vertical Blank Phase. See \" Wait for the vertical blank phase \". ; Turn the LCD off\nld a, LCDCF_OFF\nldh [rLCDC], a","breadcrumbs":"Cheatsheet » Turn on/off the LCD","id":"133","title":"Turn on/off the LCD"},"134":{"body":"To turn the background layer on and off, alter the least significant bit of the rLCDC register. You can use the LCDCF_BGON constant for this. To turn the background on: ; Turn the background on\nldh a, [rLCDC]\nor a, LCDCF_BGON\nldh [rLCDC], a To turn the background off: ; Turn the background off\nldh a, [rLCDC]\nand a, ~LCDCF_BGON\nldh [rLCDC], a","breadcrumbs":"Cheatsheet » Turn on/off the background","id":"134","title":"Turn on/off the background"},"135":{"body":"To turn the window layer on and off, alter the least significant bit of the rLCDC register. You can use the LCDCF_WINON and LCDCF_WINOFF constants for this. To turn the window on: ; Turn the window on\nldh a, [rLCDC]\nor a, LCDCF_WINON\nldh [rLCDC], a To turn the window off: ; Turn the window off\nldh a, [rLCDC]\nand a, LCDCF_WINOFF\nldh [rLCDC], a","breadcrumbs":"Cheatsheet » Turn on/off the window","id":"135","title":"Turn on/off the window"},"136":{"body":"By default, the window and background layer will use the same tilemap. For the window and background, there are 2 memory regions they can use: $9800 and $9C00. For more information, refer to the Pan Docs Which region the background uses is controlled by the 4th bit of the rLCDC register. Which region the window uses is controlled by the 7th bit. You can use one of the 4 constants to specify which layer uses which region: LCDCF_WIN9800 LCDCF_WIN9C00 LCDCF_BG9800 LCDCF_BG9C00 Note You still need to make sure the window and background are turned on when using these constants.","breadcrumbs":"Cheatsheet » Switch which tilemaps are used by the window and/or background","id":"136","title":"Switch which tilemaps are used by the window and/or background"},"137":{"body":"Sprites (or objects) can be toggled on and off using the 2nd bit of the rLCDC register. You can use the LCDCF_OBJON and LCDCF_OBJOFF constants for this. To turn sprite objects on: ; Turn the sprites on\nldh a, [rLCDC]\nor a, LCDCF_OBJON\nldh [rLCDC], a To turn sprite objects off: ; Turn the sprites off\nldh a, [rLCDC]\nand a, LCDCF_OBJOFF\nldh [rLCDC], a Sprites are in 8x8 mode by default.","breadcrumbs":"Cheatsheet » Turn on/off sprites","id":"137","title":"Turn on/off sprites"},"138":{"body":"Once sprites are enabled, you can enable tall sprites using the 3rd bit of the rLCDC register: LCDCF_OBJ16 You can not have some 8x8 sprites and some 8x16 sprites. All sprites must be of the same size. ; Turn tall sprites on\nldh a, [rLCDC]\nor a, LCDCF_OBJ16\nldh [rLCDC], a","breadcrumbs":"Cheatsheet » Turn on/off tall (8x16) sprites","id":"138","title":"Turn on/off tall (8x16) sprites"},"139":{"body":"","breadcrumbs":"Cheatsheet » Backgrounds","id":"139","title":"Backgrounds"},"14":{"body":"So, in the previous lesson, we built a nice little \"Hello World!\" ROM. Now, let's find out exactly what we did.","breadcrumbs":"The toolchain » The toolchain","id":"14","title":"The toolchain"},"140":{"body":"The region in VRAM dedicated for the background/window tilemaps is from $9000 to $97FF. hardware.inc defines a _VRAM9000 constant you can use for that. MyBackground: INCBIN \"src/path/to/my-background.2bpp\"\n.end CopyBackgroundWindowTileDataIntoVram: ; Copy the tile data ld de, myBackground ld hl, \\_VRAM ld bc, MyBackground.end - MyBackground\n.loop: ld a, [de] ld [hli], a inc de dec bc ld a, b or a, c jr nz, .Loop","breadcrumbs":"Cheatsheet » Put background/window tile data into VRAM","id":"140","title":"Put background/window tile data into VRAM"},"141":{"body":"The Game Boy has 2 32x32 tilemaps, one at $9800 and another at $9C00. Either can be used for the background or window. By default, they both use the tilemap at $9800. Drawing on the background or window is as simple as copying bytes starting at one of those addresses: CopyTilemapTo ; Copy the tilemap ld de, Tilemap ld hl, $9800 ld bc, TilemapEnd - Tilemap\nCopyTilemap: ld a, [de] ld [hli], a inc de dec bc ld a, b or a, c jp nz, CopyTilemap Make sure the layer you're targetting has been turned on. See \"Turn on/off the window\" and \"Turn on/off the background\" In terms of tiles, The background/window tilemaps are 32x32. The Game Boy's screen is 20x18. When copying tiles, understand that RGBDS or the Game Boy won't automatically jump to the next visible row after you've reached the 20th column.","breadcrumbs":"Cheatsheet » Draw on the Background/Window","id":"141","title":"Draw on the Background/Window"},"142":{"body":"You can move the background horizontally & vertically using the $FF43 and $FF42 registers, respectively. Hardware.inc defines two constants for that: rSCX and rSCY. How to change the background's X Position: ld a,64\nld [rSCX], a How to change the background's Y Position: ld a,64\nld [rSCY], a Check out the Pan Docs for more info on the Background viewport Y position, X position","breadcrumbs":"Cheatsheet » Move the background","id":"142","title":"Move the background"},"143":{"body":"Moving the window is the same as moving the background, except using the $FF4B and $FF4A registers. Hardware.inc defines two constants for that: rWX and rWY. The window layer has a -7 pixel horizontal offset. This means setting rWX to 7 places the window at the left side of the screen, and setting rWX to 87 places the window with its left side halfway across the screen. How to change the window's X Position: ld a,64\nld [rWX], a How to change the window's Y Position: ld a,64\nld [rWY], a Check out the Pan Docs for more info on the WY, WX: Window Y position, X position plus 7","breadcrumbs":"Cheatsheet » Move the window","id":"143","title":"Move the window"},"144":{"body":"Reading joypad input is not a trivial task. For more info see Tutorial #2 , or the Joypad Input Page in the Pan Docs. Paste this code somewhere in your project: UpdateKeys: ; 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\n.knownret ret Next setup 2 variables in working ram: SECTION \"Input Variables\", WRAM0\nwCurKeys: db\nwNewKeys: db Finally, during your game loop, be sure to call the UpdateKeys function during the Vertical Blank phase. ; Check the current keys every frame and move left or right.\ncall UpdateKeys","breadcrumbs":"Cheatsheet » Joypad Input","id":"144","title":"Joypad Input"},"145":{"body":"You can check if a button is down using any of the following constants from hardware.inc: PADF_DOWN PADF_UP PADF_LEFT PADF_RIGHT PADF_START PADF_SELECT PADF_B PADF_A You can check if the associataed button is down using the wCurKeys variable: ld a, [wCurKeys]\nand a, PADF_LEFT\njp nz, LeftIsPressedDown","breadcrumbs":"Cheatsheet » Check if a button is down","id":"145","title":"Check if a button is down"},"146":{"body":"You can tell if a button was JUST pressed using the wNewKeys variable ld a, [wNewKeys]\nand a, PADF_A\njp nz, AWasJustPressed","breadcrumbs":"Cheatsheet » Check if a button was JUST pressed","id":"146","title":"Check if a button was JUST pressed"},"147":{"body":"To wait indefinitely for a button press, create a loop where you: check if the button has JUST been pressed If not: Wait until the next vertical blank phase completes call the UpdateKeys function again Loop background to the beginning This will halt all other logic (outside of interrupts), be careful if you need any logic running simultaneously. WaitForAButtonToBePressed: ld a, [wNewKeys] and a, PADF_A ret nz\nWaitUntilVerticalBlankStart: ld a, [rLY] cp 144 jp nc, WaitUntilVerticalBlankStart\nWaitUntilVerticalBlankEnd: ld a, [rLY] cp 144 jp c, WaitUntilVerticalBlankEnd call UpdateKeys jp WaitForAButtonToBePressed","breadcrumbs":"Cheatsheet » Wait for a button press","id":"147","title":"Wait for a button press"},"148":{"body":"Heads Up Displays, or HUDs; are commonly used to present extra information to the player. Good examples are: Score, Health, and the current level. The window layer is drawn on top of the background, and cannot move like the background. For this reason, commonly the window layer is used for HUDs. See \"How to Draw on the Background/Window\" .","breadcrumbs":"Cheatsheet » HUD","id":"148","title":"HUD"},"149":{"body":"Drawing text on the window is essentially drawing tiles (with letters/numbers/punctuation on them) on the window and/or background layer. To simplify the process you can define constant strings. These constants end with a literal 255, which our code will read as the end of the string. SECTION \"Text ASM\", ROM0 wScoreText:: db \"score\", 255 RGBDS has a character map functionality. You can read more in the RGBDS Assembly Syntax Documentation . This functionality, tells the compiler how to map each letter: You need to have your text font tiles in VRAM at the locations specified in the map. See How to put background/window tile data in VRAM CHARMAP \" \", 0\nCHARMAP \".\", 24\nCHARMAP \"-\", 25\nCHARMAP \"a\", 26\nCHARMAP \"b\", 27\nCHARMAP \"c\", 28\nCHARMAP \"d\", 29\nCHARMAP \"e\", 30\nCHARMAP \"f\", 31\nCHARMAP \"g\", 32\nCHARMAP \"h\", 33\nCHARMAP \"i\", 34\nCHARMAP \"j\", 35\nCHARMAP \"k\", 36\nCHARMAP \"l\", 37\nCHARMAP \"m\", 38\nCHARMAP \"n\", 39\nCHARMAP \"o\", 40\nCHARMAP \"p\", 41\nCHARMAP \"q\", 42\nCHARMAP \"r\", 43\nCHARMAP \"s\", 44\nCHARMAP \"t\", 45\nCHARMAP \"u\", 46\nCHARMAP \"v\", 47\nCHARMAP \"w\", 48\nCHARMAP \"x\", 49\nCHARMAP \"y\", 50\nCHARMAP \"z\", 51 The above character mapping would convert (by the compiler) our wScoreText text to: s => 44 c => 28 o => 40 r => 43 e => 30 255 With that setup, we would loop though the bytes of wScoreText and copy each byte to the background/window layer. After we copy each byte, we'll increment where we will copy to, and which byte in wScoreText we are reading. When we read 255, our code will end. This example implies that your font tiles are located in VRAM at the locations specified in the character mapping. Drawing 'score' on the window DrawTextTiles:: ld hl, wScoreText ld de, $9C00 ; The window tilemap starts at $9C00 DrawTextTilesLoop:: ; 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 DrawTextTilesLoop","breadcrumbs":"Cheatsheet » Draw text","id":"149","title":"Draw text"},"15":{"body":"Let's begin by explaining what rgbasm and rgblink do. RGBASM is an assembler . It is responsible for reading the source code (in our case, hello-world.asm and hardware.inc), and generating blocks of code with some \"holes\". RGBASM does not always have enough information to produce a full ROM, so it does most of the work, and stores its intermediary results in what's known as object files (hence the .o extension). RGBLINK is a linker . Its job is taking object files (or, like in our case, just one), and \"linking\" them into a ROM, which is to say: filling the aforementioned \"holes\". RGBLINK's purpose may not be obvious with programs as simple as this Hello World, but it will become much clearer in Part Ⅱ. So: Source code → rgbasm → Object files → rgblink → ROM, right? Well, not exactly.","breadcrumbs":"The toolchain » RGBASM and RGBLINK","id":"15","title":"RGBASM and RGBLINK"},"150":{"body":"Enable the window (with a different tilemap than the background) Move the window downwards, so only 1 or 2 rows show at the bottom of the screen Draw your text, score, and icons on the top of the window layer. Sprites will still show over the window. To fully prevent that, you can use STAT interrupts to hide sprites where the bottom HUD will be shown.","breadcrumbs":"Cheatsheet » Draw a bottom HUD","id":"150","title":"Draw a bottom HUD"},"151":{"body":"","breadcrumbs":"Cheatsheet » Sprites","id":"151","title":"Sprites"},"152":{"body":"The region in VRAM dedicated for sprites is from $8000 to $87F0. Hardware.inc defines a _VRAM constant you can use for that. To copy sprite tile data into VRAM, you can use a loop to copy the bytes. mySprite: INCBIN \"src/path/to/my/sprite.2bpp\"\nmySpriteEnd: CopySpriteTileDataIntoVram: ; Copy the tile data ld de, Paddle ld hl, _VRAM ld bc, mySpriteEnd - mySprite\nCopySpriteTileDataIntoVram_Loop: ld a, [de] ld [hli], a inc de dec bc ld a, b or a, c jp nz, CopySpriteTileDataIntoVram_Loop","breadcrumbs":"Cheatsheet » Put sprite tile data in VRAM","id":"152","title":"Put sprite tile data in VRAM"},"153":{"body":"Each hardware sprite has 4 bytes: (in this order) Y position X Position Tile ID Flags/Props (priority, y flip, x flip, palette 0 [DMG], palette 1 [DMG], bank 0 [GBC], bank 1 [GBC]) Check out the Pan Docs page on Object Attribute Memory (OAM) for more info. The bytes controlling hardware OAM sprites start at $FE00, for which hardware.inc has defined a constant as _OAMRAM. Moving (the first) OAM sprite, one pixel downwards: ld a, [_OAMRAM]\ninc a\nld [_OAMRAM], a Moving (the first) OAM sprite, one pixel to the right: ld a, [_OAMRAM + 1]\ninc a\nld [_OAMRAM + 1], a Setting the tile for the first OAM sprite: ld a, 3\nld [_OAMRAM+2], a Moving (the fifth) OAM sprite, one pixel downwards: ld a, [_OAMRAM + 20]\ninc a\nld [_OAMRAM + 20], a TODO - Explanation on limitations of direct OAM manipulation. It's recommended that developers implement a shadow OAM, like @eievui5's Sprite Object Library","breadcrumbs":"Cheatsheet » Manipulate hardware OAM sprites","id":"153","title":"Manipulate hardware OAM sprites"},"154":{"body":"GitHub URL: 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. Usage The library is relatively simple to get set up. First, put the following in your initialization code: ; Initilize Sprite Object Library. call InitSprObjLib ; Reset hardware OAM xor a, a ld b, 160 ld hl, _OAMRAM\n.resetOAM ld [hli], a dec b jr nz, .resetOAM Then put a call to ResetShadowOAM at the beginning of your main loop. Finally, run the following code during VBlank: ld a, HIGH(wShadowOAM)\ncall hOAMDMA","breadcrumbs":"Cheatsheet » Implement a Shadow OAM using @eievui5's Sprite Object Library","id":"154","title":"Implement a Shadow OAM using @eievui5's Sprite Object Library"},"155":{"body":"Once you've set up @eievui5's Sprite Object Library, you can manipulate shadow OAM sprites the exact same way you would manipulate normal hardware OAM sprites. Except, this time you would use the library's wShadowOAM constant instead of the _OAMRAM register. Moving (the first) OAM sprite, one pixel downwards: ld a,LOW(wShadowOAM)\nld l, a\nld a, HIGH(wShadowOAM)\nld h, a ld a, [hl]\ninc a\nld [wShadowOAM], a","breadcrumbs":"Cheatsheet » Manipulate Shadow OAM OAM sprites","id":"155","title":"Manipulate Shadow OAM OAM sprites"},"156":{"body":"","breadcrumbs":"Cheatsheet » Miscellaneous","id":"156","title":"Miscellaneous"},"157":{"body":"If you want to save data in your game, your game's header needs to specify the correct MBC/cartridge type, and it needs to have a non-zero SRAM size. This should be done in your makefile by passing special parameters to rgbfix . Use the -m or --mbc-type parameters to set the mbc/cartidge type, 0x147, to a given value from 0 to 0xFF. More Info Use the -r or --ram-size parameters to set the RAM size, 0x149, to a given value from 0 to 0xFF. More Info . To save data you need to store variables in Static RAM. This is done by creating a new SRAM \"SECTION\". More Info SECTION \"SaveVariables\", SRAM wCurrentLevel:: db To access SRAM, you need to write CART_SRAM_ENABLE to the rRAMG register. When done, you can disable SRAM using the CART_SRAM_DISABLE constant. To enable read/write access to SRAM: ld a, CART_SRAM_ENABLE\nld [rRAMG], a To disable read/write access to SRAM: ld a, CART_SRAM_DISABLE\nld [rRAMG], a Initiating Save Data By default, save data for your game may or may not exist. When the save data does not exist, the value of the bytes dedicated for saving will be random. You can dedicate a couple bytes towards creating a pseudo-checksum. When these bytes have a very specific value, you can be somewhat sure the save data has been initialized. SECTION \"SaveVariables\", SRAM wCurrentLevel:: db\nwCheckSum1:: db\nwCheckSum2:: db\nwCheckSum3:: db When initializing your save data, you'll need to enable SRAM access set your checksum bytes give your other variables default values disable SRAM access ;; Setup our save data\nInitSaveData:: ld a, CART_SRAM_ENABLE ld [rRAMG], a ld a, 123 ld [wCheckSum1], a ld a, 111 ld [wCheckSum2], a ld a, 222 ld [wCheckSum3], a ld a, 0 ld [wCurrentLevel], a ld a, CART_SRAM_DISABLE ld [rRAMG], a ret Once your save file has been initialized, you can access any variable normally once SRAM is enabled. ;; Setup our save data\nStartNextLevel:: ld a, CART_SRAM_ENABLE ld [rRAMG], a ld a, [wCurrentLevel] cp a, 3 call z, StartLevel3 ld a, CART_SRAM_DISABLE ld [rRAMG], a ret","breadcrumbs":"Cheatsheet » Save Data","id":"157","title":"Save Data"},"158":{"body":"Random number generation is a complex task in software . What you can implement is a \"pseudorandom\" generator, giving you a very unpredictable sequence of values. Here's a rand function (from Damian Yerrick ) you can use. SECTION \"MathVariables\", WRAM0\nrandstate:: ds 4 SECTION \"Math\", ROM0 ;; From: https://github.com/pinobatch/libbet/blob/master/src/rand.z80#L34-L54\n; Generates a pseudorandom 16-bit integer in BC\n; using the LCG formula from cc65 rand():\n; x[i + 1] = x[i] * 0x01010101 + 0xB3B3B3B3\n; @return A=B=state bits 31-24 (which have the best entropy),\n; C=state bits 23-16, HL trashed\nrand:: ; 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","breadcrumbs":"Cheatsheet » Generate random numbers","id":"158","title":"Generate random numbers"},"159":{"body":"","breadcrumbs":"Resources » Resources","id":"159","title":"Resources"},"16":{"body":"RGBLINK does produces a ROM, but it's not quite usable yet. See, actual ROMs have what's called a header . It's a special area of the ROM that contains metadata about the ROM ; for example, the game's name, Game Boy Color compatibility, and more. For simplicity, we defaulted a lot of these values to 0 for the time being; we'll come back to them in Part Ⅱ. However, the header contains three crucial fields: The Nintendo logo , the ROM's size , and two checksums . When the console first starts up, it runs a little program known as the boot ROM , which reads and draws the logo from the cartridge, and displays the little boot animation. When the animation is finished, the console checks if the logo matches a copy that it stores internally; if there is a mismatch, it locks up! And, since it locks up, our game never gets to run... 😦 This was meant as an anti-piracy measure; however, that measure has since then been ruled as invalid , so don't worry, we are clear! 😄 Similarly, the boot ROM also computes a checksum of the header, supposedly to ensure that it isn't corrupted. The header also contains a copy of this checksum; if it doesn't match what the boot ROM computed, then the boot ROM also locks up! The header also contains a checksum over the whole ROM, but nothing ever uses it. It doesn't hurt to get it right, though. Finally, the header also contains the ROM's size, which is required by emulators and flash carts. RGBFIX's role is to fill in the header, especially these 3 fields, which are required for our ROM to be guaranteed to run fine. The -v option instructs RGBFIX to make the header v alid, by injecting the Nintendo logo and computing the two checksums. The -p 0xFF option instructs it to p ad the ROM to a valid size, and set the corresponding value in the \"ROM size\" header field. Alright! So the full story is: Source code → rgbasm → Object files → rgblink → \"Raw\" ROM → rgbfix → \"Fixed\" ROM. Good. You might be wondering why RGBFIX's functionality hasn't been included directly in RGBLINK. There are some historical reasons, but RGBLINK can also be used to produce things other than ROMs (especially via the -x option), and RGBFIX is sometimes used without RGBLINK anywhere in sight.","breadcrumbs":"The toolchain » RGBFIX","id":"16","title":"RGBFIX"},"160":{"body":"GBDev community home page and chat channels .","breadcrumbs":"Resources » Help channels","id":"160","title":"Help channels"},"161":{"body":"evie's interrupts tutorial should help you understand how to use interrupts, and what they are useful for. tbsp's \"Simple GB ASM examples\" is a collection of ROMs, each built from a single, fairly short source file. If you found this tutorial too abstract and/or want to get your feet wet, this is a good place to go to! GB assembly by example , Daid's collection of code snippets. Consider this a continuation of the tutorial, but without explanations; it's still useful to peruse them and ask about it, they are overall good quality.","breadcrumbs":"Resources » Other tutorials","id":"161","title":"Other tutorials"},"162":{"body":"Did you enjoy the tutorial or one of the above? The following should prove useful along the rest of your journey! RGBDS' online documentation is always useful! Notably, you'll find an instruction reference and the reference on RGBASM's syntax and features . Pan Docs are the reference for all Game Boy hardware. It's a good idea to consult it if you aare unsure how a register works, or if you're wondering how to do something. gb-optables is a more compact instruction table, it becomes more useful when you stop needing the instructions' descriptions.","breadcrumbs":"Resources » Complements","id":"162","title":"Complements"},"163":{"body":"Big thank you to Twoflower/Triad for making the Hello World graphic. I can't thank enough Chloé and many others for their continued support. Thanks to the GBDev community for being so nice throughout the years. You are all great. Thank you so very much. Thank you to the Rust language team for making mdBook , which powers this book (this honestly slick design is the stock one!!) Greets to AYCE, Phantasy, TPPDevs/RainbowDevs, Plutiedev, lft/kryo :) Shoutouts to Eievui , Rangi , MarkSixtyFour , ax6 , Baŝto , bbbbbr , and bitnenfer ! The Italian translation is curated by Antonio Guido Leoni , Antonio Vivace , Mattia Fortunati , Matilde Della Morte and Daniele Scasciafratte .","breadcrumbs":"Thanks » Special Thanks","id":"163","title":"Special Thanks"},"17":{"body":"Note that RGBDS does not care at all about the files' extensions. Some people call their source code .s, for example, or their object files .obj. The file names don't matter, either; it's just practical to keep the same name.","breadcrumbs":"The toolchain » File names","id":"17","title":"File names"},"18":{"body":"Before we talk about the code, a bit of background knowledge is in order. When programming at a low level, understanding of binary and hexadecimal is mandatory. Since you may already know about both of these, a summary of the RGBDS-specific information is available at the end of this lesson. So, what's binary? It's a different way to represent numbers, in what's called base 2 . We're used to counting in base 10 , so we have 10 digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9. Here's how digits work: 42 = 4 × 10 + 2 = 4 × 10^1 + 2 × 10^0 ↑ ↑ These tens come from us counting in base 10! 1024 = 1 × 1000 + 0 × 100 + 2 × 10 + 4 = 1 × 10^3 + 0 × 10^2 + 2 × 10^1 + 4 × 10^0 ↑ ↑ ↑ ↑\nAnd here we can see the digits that make up the number! ℹ️ ^ here means \"to the power of\", where X^N is equal to multiplying X with itself N times, and X ^ 0 = 1. Decimal digits form a unique decomposition of numbers in powers of 10 ( deci mal is base 10, remember?). But why stop at powers of 10? We could use other bases instead, such as base 2. (Why base 2 specifically will be explained later.) Binary is base 2, so there are only two digits, called bits : 0 and 1. Thus, we can generalize the principle outlined above, and write these two numbers in a similar way: 42 = 1 × 32 + 0 × 16 + 1 × 8 + 0 × 4 + 1 × 2 + 0 = 1 × 2^5 + 0 × 2^4 + 1 × 2^3 + 0 × 2^2 + 1 × 2^1 + 0 × 2^0 ↑ ↑ ↑ ↑ ↑ ↑ And since now we're counting in base 2, we're seeing twos instead of tens! 1024 = 1 × 1024 + 0 × 512 + 0 × 256 + 0 × 128 + 0 × 64 + 0 × 32 + 0 × 16 + 0 × 8 + 0 × 4 + 0 × 2 + 0 = 1 × 2^10 + 0 × 2^9 + 0 × 2^8 + 0 × 2^7 + 0 × 2^6 + 0 × 2^5 + 0 × 2^4 + 0 × 2^3 + 0 × 2^2 + 0 × 2^1 + 0 × 2^0 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ So, by applying the same principle, we can say that in base 2, 42 is written as 101010, and 1024 as 10000000000. Since you can't tell ten (decimal 10) and two (binary 10) apart, RGBDS assembly has binary numbers prefixed by a percent sign: 10 is ten, and %10 is two. Okay, but why base 2 specifically? Rather conveniently, a bit can only be 0 or 1, which are easy to represent as \"ON\" or \"OFF\", empty or full, etc! If you want, at home, to create a one-bit memory, just take a box. If it's empty, it stores a 0; if it contains something , it stores a 1. Computers thus primarily manipulate binary numbers, and this has a slew of implications, as we will see throughout this entire tutorial.","breadcrumbs":"Binary and hexadecimal » Binary and hexadecimal","id":"18","title":"Binary and hexadecimal"},"19":{"body":"To recap, decimal isn't practical for a computer to work with, instead relying on binary (base 2) numbers. Okay, but binary is really impractical to work with. Take %10000000000, aka 2048; when in decimal only 4 digits are required, binary instead needs 12! And, did you notice that I actually wrote one zero too few? Fortunately, hexadecimal is here to save the day! 🦸 Base 16 works just the same as every other base, but with 16 digits, called nibbles : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F. 42 = 2 × 16 + 10 = 2 × 16^1 + A × 16^0 1024 = 4 × 256 + 0 × 16 + 0 = 4 × 16^2 + 0 × 16^1 + 0 × 16^0 Like binary, we will use a prefix to denote hexadecimal, namely $. So, 42 = $2A, and 1024 = $400. This is much more compact than binary, and slightly more than decimal, too; but what makes hexadecimal very interesting is that one nibble corresponds exactly to 4 bits! Nibble Bits $0 %0000 $1 %0001 $2 %0010 $3 %0011 $4 %0100 $5 %0101 $6 %0110 $7 %0111 $8 %1000 $9 %1001 $A %1010 $B %1011 $C %1100 $D %1101 $E %1110 $F %1111 This makes it very easy to convert between binary and hexadecimal, while retaining a compact enough notation. Thus, hexadecimal is used a lot more than binary. And, don't worry, decimal can still be used 😜 (Side note: one could point that octal, i.e. base 8, would also work for this; however, we will primarily deal with units of 8 bits, for which hexadecimal works much better than octal. RGBDS supports octal via the & prefix, but I have yet to see it used.) 💡 If you're having trouble converting between decimal and binary/hexadecimal, check if your favorite calculator program doesn't have a \"programmer\" mode, or a way to convert between bases.","breadcrumbs":"Binary and hexadecimal » Hexadecimal","id":"19","title":"Hexadecimal"},"2":{"body":"The tutorial was written by Eldred \"ISSOtm\" Habert , Evie , Antonio Vivace , LaroldsJubilantJunkyard and other contributors .","breadcrumbs":"Home » Authors","id":"2","title":"Authors"},"20":{"body":"In RGBDS assembly, the hexadecimal prefix is $, and the binary prefix is %. Hexadecimal can be used as a \"compact binary\" notation. Using binary or hexadecimal is useful when individual bits matter; otherwise, decimal works just as well. For when numbers get a bit too long, RGBASM allows underscores between digits (123_465, %10_1010, $DE_AD_BE_EF, etc.)","breadcrumbs":"Binary and hexadecimal » Summary","id":"20","title":"Summary"},"21":{"body":"Alright! Now that we know what bits are, let's talk about how they're used. Don't worry, this is mostly prep work for the next section, where we will—finally!—look at the code 👀 First, if you opened Emulicious, you have been greeted with just the Game Boy screen. So, it's time we pop the debugger open! Go to \"Tools\", then click \"Debugger\", or press F1. Then in the debugger's menu, click \"View\", then click \"Show Addresses\" The debugger may look intimidating at first, but don't worry, soon we'll be very familiar with it! For now, let's focus on this small box near the top-right, the register viewer . Picture of the register viewer's location ⚠️ The register viewer shows both CPU registers and some hardware registers . This lesson will only deal with CPU registers, so that's why we will be ignoring some of these entries here. What are CPU registers? Well, imagine you're preparing a cake. You will be following a recipe, whose instructions may be \"melt 125g of chocolate and 125g of butter, blend with 2 eggs\" and so on. You will fetch some ingredients from the fridge as needed, but you don't cook inside the fridge; for that, you have a small workspace. Registers are pretty much the CPU's workspace. They are small, tiny chunks of memory embedded directly in the CPU (only 10 bytes for the Game Boy's CPU, and even modern CPUs have less than a kilobyte if you don't count SIMD registers). Operations are not performed directly on data stored in memory, which would be equivalent to breaking eggs directly inside our fridge, but they are performed on registers. ℹ️ There are exceptions to this rule, like many other \"rules\" I will give in this tutorial; I will paper over them to keep the mental complexity reasonable, but don't treat my word as gospel either.","breadcrumbs":"Registers » Registers","id":"21","title":"Registers"},"22":{"body":"CPU registers can be placed into two categories: general-purpose and special-purpose . A \"general-purpose\" register (GPR for short) can be used for storing arbitrary integer numbers. Some GPRs are special nonetheless, as we will see later; but the distinction is \"can I store arbitrary integers in it?\". I won't introduce special-purpose registers quite yet, as their purpose wouldn't make sense yet. Rather, they will be discussed as the relevant concepts are introduced. The Game Boy CPU has seven 8-bit GPRs: a, b, c, d, e, h, and l. \"8-bit\" means that, well, they store 8 bits. Thus, they can store integers from 0 to 255 (%1111_1111 aka $FF). a is the accumulator , and we will see later that it can be used in special ways. A special feature is that these registers, besides a, are paired up , and the pairs can be treated as the 16-bit registers bc, de, and hl. The pairs are not separate from the individual registers; for example, if d contains 192 ($C0) and e contains 222 ($DE), then de contains 49374 ($C0DE) = 192 × 256 + 222. The other pairs work similarly. Modifying de actually modifies both d and e at the same time, and modifying either individually also affects the pair. How do we modify registers? Let's see how, with our first assembly instructions!","breadcrumbs":"Registers » General-purpose registers","id":"22","title":"General-purpose registers"},"23":{"body":"Alright, now that we know what the tools do , let's see what language RGBASM speaks. I will take a short slice of the beginning of hello-world.asm, so that we agree on the line numbers, and you can get some syntax highlighting even if your editor doesn't support it. INCLUDE \"hardware.inc\" SECTION \"Header\", ROM0[$100] jp EntryPoint ds $150 - @, 0 ; Make room for the header EntryPoint: ; Shut down audio circuitry ld a, 0 ld [rNR52], a Let's analyze it. Note that I will be ignoring a lot of RGBASM's functionality; if you're curious to know more, you should wait until parts II and III, or read the docs .","breadcrumbs":"Assembly basics » Assembly basics","id":"23","title":"Assembly basics"},"24":{"body":"We'll start with line 10, which should appear gray above. Semicolons ; denote comments . Everything from a semicolon to the end of the line is ignored by RGBASM. As you can see on line 7, comments need not be on an otherwise empty line. Comments are a staple of every good programming language; they are useful to give context as to what code is doing. They're the difference between \"Pre-heat the oven at 180 °C\" and \"Pre-heat the oven at 180 °C, any higher and the cake would burn\", basically. In any language, good comments are very useful; in assembly, they play an even more important role, as many common semantic facilities are not available.","breadcrumbs":"Assembly basics » Comments","id":"24","title":"Comments"},"25":{"body":"Assembly is a very line-based language. Each line can contain one of two things: a directive , which instructs RGBASM to do something, or an instruction [1] , which is written directly into the ROM. We will talk about directives later, for now let's focus on instructions: for example, in the snippet above, we will ignore lines 1 (INCLUDE), 7 (ds), and 3 (SECTION). To continue the cake-baking analogy even further, instructions are like steps in a recipe. The console's processor (CPU) executes instructions one at a time, and that... eventually does something! Like baking a cake, drawing a \"Hello World\" image, or displaying a Game Boy programming tutorial! *wink* *wink* Instructions have a mnemonic , which is a name they are given, and operands , which indicate what they should act upon. For example, in \"melt the chocolate and butter in a saucepan\", the whole sentence would be the instruction, the verb \"melt\" would be the mnemonic, and \"chocolate\", \"butter\", and \"saucepan\" the operands, i.e. some kind of parameters to the operation. Let's discuss the most fundamental instruction, ld . ld stands for \"LoaD\", and its purpose is simply to copy data from its right operand ( \"RHS\" ) into its left operand ( \"LHS\" ). For example, take line 11's ld a, 0: it copies (\"loads\") the value 0 into the 8-bit register a [2] . If you look further in the file, line 33 has ld a, b, which causes the value in register b to be copied into register a. Instruction Mnemonic Effect Load ld Copies values around ℹ️ Due to CPU limitations, not all operand combinations are valid for ld and many other instructions; we will talk about this when writing our own code later. 🤔 RGBDS has an instruction reference worth bookmarking, and you can also consult it locally with man 7 gbz80 if RGBDS is installed on your machine (except Windows...). The descriptions there are more succinct, since they're intended as reminders, not as tutorials.","breadcrumbs":"Assembly basics » Instructions","id":"25","title":"Instructions"},"26":{"body":"In a way, instructions are destined to the console's CPU, and comments are destined to the programmer. But some lines are neither, and are instead sort of metadata destined to RGBDS itself. Those are called directives , and our Hello World actually contains three of those.","breadcrumbs":"Assembly basics » Directives","id":"26","title":"Directives"},"27":{"body":"INCLUDE \"hardware.inc\" Line 1 includes hardware.inc [3] . Including a file has the same effect as if you copy-pasted it, but without having to actually do that. It allows sharing code across files easily: for example, if two files a.asm and b.asm were to include hardware.inc, you would only need to modify hardware.inc once for the modifications to apply to both a.asm and b.asm. If you instead copy-pasted the contents manually, you would have to edit both copies in a.asm and b.asm to apply the changes, which is more tedious and error-prone. hardware.inc defines a bunch of constants related to interfacing with the hardware. Constants are basically names with a value attached, so when you write out their name, they are replaced with their value. This is useful because, for example, it is easier to remember the address of the LCD C ontrol register as rLCDC than $FF40. We will discuss constants in more detail in Part Ⅱ.","breadcrumbs":"Assembly basics » Including other files","id":"27","title":"Including other files"},"28":{"body":"Let's first explain what a \"section\" is, then we will see what line 3 does. A section represents a contiguous range of memory, and by default, ends up somewhere not known in advance. If you want to see where all the sections end up, you can ask RGBLINK to generate a \"map file\" with the -m flag: $ rgblink hello-world.o -m hello-world.map ...and we can see, for example, where the \"Tilemap\" section ended up: SECTION: $05a6-$07e5 ($0240 bytes) [\"Tilemap\"] Sections cannot be split by RGBDS, which is useful e.g. for code, since the processor executes instructions one right after the other (except jumps, as we will see later). There is a balance to be struck between too many and not enough sections, but it typically doesn't matter much until banking is introduced into the picture—and it won't be until much, much later. So, for now, let's just assume that one section should contain things that \"go together\" topically, and let's examine one of ours. SECTION \"Header\", ROM0[$100] So! What's happening here? Well, we are simply declaring a new section; all instructions and data after this line and until the next SECTION one will be placed in this newly-created section. Before the first SECTION directive, there is no \"active\" section, and thus generating code or data will be met with a Cannot output data outside of a SECTION error. The new section's name is \"Header\". Section names can contain any characters (and even be empty, if you want), and must be unique [4] . The ROM0 keyword indicates which \"memory type\" the section belongs to ( here is a list ). We will discuss them in Part Ⅱ. The [$100] part is more interesting, in that it is unique to this section. See, I said above that: a section [...] by default, ends up somewhere not known in advance. However, some memory locations are special, and so sometimes we need a specific section to span a specific range of memory. To enable this, RGBASM provides the [addr] syntax, which forces the section's starting address to be addr. In this case, the memory range $100–$14F is special, as it is the ROM's header . We will discuss the header in a couple lessons, but for now, just know that we need not to put any of our code or data in that space. How do we do that? Well, first, we begin a section at address $100, and then we need to reserve some space.","breadcrumbs":"Assembly basics » Sections","id":"28","title":"Sections"},"29":{"body":"jp EntryPoint ds $150 - @, 0 ; Make room for the header Line 7 claims to \"Make room for the header\", which I briefly mentioned just above. For now, let's focus on what ds actually does. ds is used for statically allocating memory. It simply reserves some amount of bytes, which are set to a given value. The first argument to ds, here $150 - @, is how many bytes to reserve . The second (optional) argument, here 0, is what value to set each reserved byte to [5] . We will see why these bytes must be reserved in a couple of lessons. It is worth mentioning that this first argument here is an expression . RGBDS (thankfully!) supports arbitrary expressions essentially anywhere. This expression is a simple subtraction: $150 minus @, which is a special symbol that stands for \"the current memory address\". A symbol is essentially \"a name attached to a value\", usually a number. We will explore the different types of symbols throughout the tutorial, starting with labels in the next section. A numerical symbol used in an expression evaluates to its value, which must be known when compiling the ROM—in particular, it can't depend on any register's contents. Oh, but you may be wondering what the \"memory addresses\" I keep mentioning are. Let's see about those! Technically, instructions in RGBASM are implemented as directives, basically writing their encoded form to the ROM; but the distinction between the instructions in the source code and those in the final ROM is not worth bringing up right now. The curious reader may ask where the value is copied from . The answer is simply that the \"immediate\" byte ($00 in this example) is stored in ROM just after the instruction's opcode byte, and it's what gets copied to a. We will come back to this when we talk about how instructions are encoded later on. hardware.inc itself contains more directives, in particular to define a lot of symbols. They will be touched upon much later, so we won't look into hardware.inc yet. Section names actually only need to be unique for \"plain\" sections, and function differently with \"unionized\" and \"fragment\" sections, which we will discuss much later. Actually, since RGBASM 0.5.0, ds can accept a list of bytes, and will repeat the pattern for as many bytes as specified. It just complicates the explanation slightly, so I omitted it for now. Also, if the argument is omitted, it defaults to what is specified using the -p option to RGBASM .","breadcrumbs":"Assembly basics » Reserving space","id":"29","title":"Reserving space"},"3":{"body":"You can provide feedback or send suggestions in the form of Issues on the GitHub repository . We're also looking for help for writing new lessons and improving the existing ones ! You can go through the Issues to see what needs to be worked on and send Pull Requests! You can also help translating the tutorial on Crowdin .","breadcrumbs":"Home » Contributing","id":"3","title":"Contributing"},"30":{"body":"🎉 Congrats, you have just finished the hardest lessons of the tutorial! Since you have the basics, from now on, we'll be looking at more and more concrete code. If we look at line 29, we see ld a, [de]. Given what we just learned, this copies a value into register a... but where from? What do these brackets mean? To answer that, we need to talk about memory .","breadcrumbs":"Memory » Memory","id":"30","title":"Memory"},"31":{"body":"The purpose of memory is to store information. On a piece of paper or a whiteboard, you can write letters to store the grocery list, for example. But what can you store in a computer memory? The answer to that question is current [1] . Computer memory is made of little cells that can store current. But, as we saw in the lesson about binary, the presence or absence of current can be used to encode binary numbers! tl;dr: memory stores numbers . In fact, memory is a long array of numbers, stored in cells. To uniquely identify each cell, it's given a number (what else!) called its address . Like street numbers! The first cell has address 0, then address 1, 2, and so on. On the Game Boy, each cell contains 8 bits , i.e. a byte . How many cells are there? Well, this is actually a trick question...","breadcrumbs":"Memory » What's a memory?","id":"31","title":"What's a memory?"},"32":{"body":"There are several memory chips in the Game Boy, but we can put them into two categories: ROM and RAM [2] . ROM simply designates memory that cannot be written to [3] , and RAM memory that can be written to. Due to how they work, the CPU, as well as the memory chips, can only use a single number for addresses. Let's go back to the \"street numbers\" analogy: each memory chip is a street, with its own set of numbers, but the CPU has no idea what a street is, it only deals with street numbers. To allow the CPU to talk to multiple chips, a sort of \"postal service\", the chip selector , is tasked with translating the CPU's street numbers into a street & street number. For example, let's say a convention is established where addresses 0 through 1999 go to chip A's addresses 0–1999, 2000–2999 to chip B's 0–999, and 3000–3999 to chip C's 0–999. Then, if the CPU asks for the byte at address 2791, the chip selector will ask chip B for the byte at its own address 791, and forward the reply to the CPU. Since addresses dealt with by the CPU do not directly correspond to the chips' addresses, we talk about logical addresses (here, the CPU's) versus physical addresses (here, the chips'), and the correspondence is called a memory map . Since we are programming the CPU, we will only be dealing with logical addresses, but it's crucial to keep in mind that different addresses may be backed by different memory chips, since each chip has unique characteristics. This may sound complicated, so here is a summary: Memory stores numbers, each 8-bit on the Game Boy. Memory is accessed byte by byte, and the cell being accessed is determined by an address , which is just a number. The CPU deals with all memory uniformly, but there are several memory chips each with their own characteristics.","breadcrumbs":"Memory » The many types of memory","id":"32","title":"The many types of memory"},"33":{"body":"Let's answer the question that introduced this section: how many memory cells are there on the Game Boy? Well, now, we can reframe this question as \"how many logical addresses are there?\" or \"how many physical addresses are there in total?\". Logical addresses, which again are just numbers, are 16-bit on the Game Boy. Therefore, there are 2^16 = 65536 logical addresses, from $0000 to $FFFF. How many physical addresses, though? Well, here is a memory map courtesy of Pan Docs (though I will simplify it a bit): Start End Name Description $0000 $7FFF ROM The game ROM, supplied by the cartridge. $8000 $9FFF VRAM Video RAM, where graphics are stored and arranged. $A000 $BFFF SRAM Save RAM, optionally supplied by the cartridge to save data to. $C000 $DFFF WRAM Work RAM, general-purpose RAM for the game to store things in. $FE00 $FE9F OAM Object Attribute Memory, where \"objects\" are stored. $FF00 $FF7F I/O Neither ROM nor RAM, but this is where you control the console. $FF80 $FFFE HRAM High RAM, a tiny bit of general-purpose RAM which can be accessed faster. $FFFF $FFFF IE A lone I/O byte that's separated from the rest for some reason. $8000 + $2000 + $2000 + $2000 + $A0 + $80 + $7F + 1 adds up to $E1A0, or 57760 bytes of memory that can be actually accessed. The curious reader will naturally ask, \"What about the remaining 7776 bytes? What happens when accessing them?\"; the answer is: \"It depends, it's complicated; avoid accessing them\".","breadcrumbs":"Memory » Game Boy memory map","id":"33","title":"Game Boy memory map"},"34":{"body":"Okay, memory addresses are nice, but you can't possibly expect me to keep track of all these addresses manually, right?? Well, fear not, for we have labels! Labels are symbols which basically allow attaching a name to a byte of memory. A label is declared like at line 9 (EntryPoint:): at the beginning of the line, write the label's name, followed by a colon, and it will refer to the byte right after itself. So, for example, EntryPoint refers to the ld a, 0 right below it (more accurately, the first byte of that instruction, but we will get there when we get there). If you peek inside hardware.inc, you will see that for example rNR52 is not defined as a label. That's because they are constants , which we will touch on later; since they can be used mostly like labels, we will conflate the two for now. Writing out a label's name is equivalent to writing the address of the byte it's referencing (with a few exceptions we will see in Part Ⅱ). For example, consider the ld de, Tiles at line 25. Tiles (line 64) is referring to the first byte of the tile data; if we assume that the tile data ends up being stored starting at $0193, then ld de, Tiles is equivalent to ld de, $0193!","breadcrumbs":"Memory » Labels","id":"34","title":"Labels"},"35":{"body":"Right, we came into this because we wanted to know what the brackets in ld a, [de] mean. Well, they can basically be read as \"at address...\". For example, ld a, b can be read as \"copy into a the value stored in b\"; ld a, [$5414] would be read as \"copy into a the value stored at address $5414\", and ld a, [de] would be read as \"copy into a the value stored at address de\". Wait, what does that mean? Well, if de contains the value $5414, then ld a, [de] will do the same thing as ld a, [$5414]. If you're familiar with C, these brackets are basically how the dereference operator is implemented.","breadcrumbs":"Memory » What's with the brackets?","id":"35","title":"What's with the brackets?"},"36":{"body":"An astute reader will have noticed the ld [hli], a just below the ld a, [de] we have just studied. [de] makes sense because it's one of the register pairs we saw a couple lessons ago, but [hli]? It's actually a special notation, which can also be written as [hl+]. It functions as [hl], but hl is incremented just after memory is accessed. [hld]/[hl-] is the mirror of this one, decrementing hl instead of incrementing it.","breadcrumbs":"Memory » hli","id":"36","title":"hli"},"37":{"body":"So, if we look at the first two instructions of CopyTiles: ld a, [de] ld [hli], a ...we can see that we're copying the byte in memory pointed to by de (that is, whose address is contained in de) into the byte pointed to by hl. Here, a serves as temporary storage, since the CPU is unable to perform ld [hl], [de] directly. While we're at this, let's examine the rest of .copyTiles in the following lessons! Actually, this depends a lot on the type of memory. A lot of memory nowadays uses magnetic storage, but to keep the explanation simple, and to parallel the explanation of binary given earlier, let's assume that current is being used. There are other types of memory, such as flash memory or EEPROM, but only Flash has been used on the Game Boy, and for only a handful of games; so we can mostly forget about them. No, really! Mask ROM is created by literally punching holes into a layer of silicon using acid, and e.g. the console's boot ROM is made of hard-wired transitors within the CPU die. Good luck writing to that! \"ROM\" is sometimes (mis)used to refer to \"persistent memory\" chips, such as flash memory, whose write functionality was disabled. Most bootleg / \"repro\" Game Boy cartridges you can find nowadays actually contain flash; this is why you can reflash them using specialized hardware, but original cartridges cannot be.","breadcrumbs":"Memory » An example","id":"37","title":"An example"},"38":{"body":"Let's go back to a certain line near the top of hello-world.asm. ds $150 - @, 0 ; Make room for the header What is this mysterious header, why are we making room for it, and more questions answered in this lesson!","breadcrumbs":"The header » Header","id":"38","title":"Header"},"39":{"body":"First order of business is explaining what the header is . It's the region of memory from $0104 to $014F (inclusive). It contains metadata about the ROM, such as its title, Game Boy Color compatibility, size, two checksums, and interestingly, the Nintendo logo that is displayed during the power-on animation. You can find this information and more in the Pan Docs . Interestingly, most of the information in the header does not matter on real hardware (the ROM's size is determined only by the capacity of the ROM chip in the cartridge, not the header byte). In fact, some prototype ROMs actually have incorrect header info! Most of the header was only used by Nintendo's manufacturing department to know what components to put in the cartridge when publishing a ROM. Thus, only ROMs sent to Nintendo had to have a fully correct header; ROMs used for internal testing only needed to pass the boot ROM's checks, explained further below. However, in our \"modern\" day and age, the header actually matters a lot. Emulators (including hardware emulators such as flashcarts) must emulate the hardware present in the cartridge. The header being the only source of information about what hardware the ROM's cartridge should contain, they rely on some of the values in the header.","breadcrumbs":"The header » What is the header?","id":"39","title":"What is the header?"},"4":{"body":"In short : Code within the tutorial is essentially public domain , meaning that you are allowed to copy it freely without restrictions. You are free to copy the tutorial's contents (prose, diagrams, etc.), modify them, and share that, but you must give credit and license any copies permissively. This site's source code can be freely copied, but you must give a license and copyright notice. Full details , please follow these links for more information on the respective licenses: All the code contained within the tutorial itself is licensed under CC0. To the extent possible under law, all copyright and related or neighboring rights to code presented within GB ASM Tutorial have been waived. The contents (prose, images, etc.) of this tutorial are licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. Code used to display and format the site is licensed under the MIT License unless otherwise specified.","breadcrumbs":"Home » Licensing","id":"4","title":"Licensing"},"40":{"body":"The header is intimately tied to what is called the boot ROM . The most observant and/or nostalgic of you may have noticed the lack of the boot-up animation and the Game Boy's signature \"ba-ding!\" in Emulicious. When the console powers up, the CPU does not begin executing instructions at address $0100 (where our ROM's entry point is), but at $0000. However, at that time, a small program called the boot ROM , burned within the CPU's silicon, is \"overlaid\" on top of our ROM! The boot ROM is responsible for the startup animation, but it also checks the ROM's header! Specifically, it verifies that the Nintendo logo and header checksums are correct; if either check fails, the boot ROM intentionally locks up , and our game never gets to run :( For the curious You can find a more detailed description of what the boot ROM does in the Pan Docs , as well as an explanation of the logo check. Beware that it is quite advanced, though. If you want to enable the boot ROMs in Emulicious, you must obtain a copy of the boot ROM(s), whose SHA256 checksums can be found in their disassembly for verification. If you wish, you can also compile SameBoy's boot ROMs and use those instead, as a free-software substitute. Then, in Emulicious' options, go to the Options tab, then Emulation→Game Boy, and choose which of GB and/or GBC boot roms you want to set. Finally, set the path(s) to the boot ROM(s) you wish to use, and click Open. Now, just reset the emulator, and voilà! A header is typically called \"valid\" if it would pass the boot ROM's checks, and \"invalid\" otherwise.","breadcrumbs":"The header » Boot ROM","id":"40","title":"Boot ROM"},"41":{"body":"RGBFIX is the third component of RGBDS, whose purpose is to write a ROM's header. It is separate from RGBLINK so that it can be used as a stand-alone tool. Its name comes from that RGBLINK typically does not produce a ROM with a valid header, so the ROM must be \"fixed\" before it's production-ready. RGBFIX has a bunch of options to set various parts of the header; but the only two that we are using here are -v, which produces a v alid header (so, correct Nintendo logo and checksums ), and -p 0xFF, which p ads the ROM to the next valid size (using $FF as the filler byte), and writes the appropriate value to the ROM size byte . If you look at other projects, you may find RGBFIX invocations with more options, but these two should almost always be present.","breadcrumbs":"The header » RGBFIX","id":"41","title":"RGBFIX"},"42":{"body":"Right! This line. ds $150 - @, 0 ; Make room for the header Well, let's see what happens if we remove it (or comment it out). $ rgbasm -L -o hello-world.o hello-world.asm\n$ rgblink -o hello-world.gb -n hello-world.sym hello-world.o (I am intentionally not running RGBFIX; we will see why in a minute.) \"This rom would not work on a real gameboy.\" As I explained, RGBFIX is responsible for writing the header, so we should use it to fix this exception. $ rgbfix -v -p 0xFF hello-world.gb\nwarning: Overwrote a non-zero byte in the Nintendo logo\nwarning: Overwrote a non-zero byte in the header checksum\nwarning: Overwrote a non-zero byte in the global checksum I'm sure these warnings are nothing to be worried about... (Depending on your version of RGBDS, you may have gotten different warnings, or none at all.) Let's run the ROM, click on Console on the debugger's bottom window, press F5 a few times, and... When the console reads \"Executing illegal instruction\", you might have screwed up somewhere. \"This is fine\" meme strip Okay, so, what happened? As we can see from the screenshot, PC is at $0105. What is it doing there? ...Oh, EntryPoint is at $0103. So the jp at $0100 went there, and started executing instructions (3E CE is the raw form of ld a, $CE), but then $ED does not encode any valid instruction, so the CPU locks up. But why is EntryPoint there? Well, as you may have figured out from the warnings RGBFIX printed, it overwrites the header area in the ROM. However, RGBLINK is not aware of the header (because RGBLINK is not only used to generate ROMs!), so you must explicitly reserve space for the header area. 🥴 Forgetting to reserve this space, and having a piece of code or data ending up there then overwritten, is a common beginner mistake that can be quite puzzling. Fortunately, RGBFIX since version 0.5.1 warns when it detects this mistake, as shown above. So, we prevent disaster like this: SECTION \"Header\", ROM0[$100] jp EntryPoint ds $150 - @, 0 ; Make room for the header The directive ds stands for \"define space\", and allows filling a range of memory. This specific line fills all bytes from $103 to $14F (inclusive) with the value $00. Since different pieces of code and/or data cannot overlap, this ensures that the header's memory range can safely be overwritten by RGBFIX, and that nothing else accidentally gets steamrolled instead. It may not be obvious how this ds ends up filling that specific memory range. The 3-byte jp covers memory addresses $100, $101, and $102. (We start at $100 because that's where the SECTION is hardcoded to be.) When RGBASM processes the ds directive, @ (which is a special symbol that evaluates to \"the current address\") thus has the value $103, so it fills $150 - $103 = $4D bytes with zeros, so $103, $104, ..., $14E, $14F.","breadcrumbs":"The header » So, what's the deal with that line?","id":"42","title":"So, what's the deal with that line?"},"43":{"body":"(This is not really linked to the header, but I need to explain it somewhere, and here is as good a place as any.) You may also be wondering what the point of the infinite loop at the end of the code is for. Done: jp Done Well, simply enough, the CPU never stops executing instructions; so when our little Hello World is done and there is nothing left to do, we must still give the CPU some busy-work: so we make it do nothing, forever. We cannot let the CPU just run off, as it would then start executing other parts of memory as code, possibly crashing. (See for yourself: remove or comment out these two lines, re- compile the ROM , and see what happens!)","breadcrumbs":"The header » Bonus: the infinite loop","id":"43","title":"Bonus: the infinite loop"},"44":{"body":"Alright, we know how to pass values around, but just copying numbers is no fun; we want to modify them! The GB CPU does not provide every operation under the sun (for example, there is no multiplication instruction), but we can just program those ourselves with what we have. Let's talk about some of the operations that it does have; I will be omitting some not used in the Hello World for now.","breadcrumbs":"Operations & flags » Operations & flags","id":"44","title":"Operations & flags"},"45":{"body":"The simplest arithmetic instructions the CPU supports are inc and dec, which INCrement and DECrement their operand, respectively. (If you aren't sure, \"to increment\" means \"to add 1\", and \"to decrement\" means \"to subtract 1\".) So for example, the dec bc at line 32 of hello-world.asm simply subtracts 1 from bc. Okay, cool! Can we go a bit faster, though? Sure we can, with add and sub! These respectively ADD and SUBtract arbitrary values (either a constant, or a register). Neither is used in the tutorial, but a sibling of sub's is: have you noticed little cp over at line 17? cp allows ComParing values. It works the same as sub, but it discards the result instead of writing it back. \"Wait, so it does nothing?\" you may ask; well, it does update the flags .","breadcrumbs":"Operations & flags » Arithmetic","id":"45","title":"Arithmetic"},"46":{"body":"The time has come to talk about the special-purpose register (remember those?) f, for, well, flags . The f register contains 4 bits, called \"flags\", which are updated depending on an operation's results. These 4 flags are: Name Description Z Zero flag N Addition/subtraction H Half-carry C Carry Yes, there is a flag called \"C\" and a register called \"c\", and they are different, unrelated things . This makes the syntax a bit confusing at the beginning, but they are always used in different contexts, so it's fine. We will forget about N and H for now; let's focus on Z and C. Z is the simplest flag: it gets set when an operation's result is 0, and gets reset otherwise. C is set when an operation overflows or underflows . What's an overflow? Let's take the simple instruction add a, 42. This simply adds 42 to the contents of register a, and writes the result back into a. ld a, 200 add a, 42 At the end of this snippet, a equals 200 + 42 = 242, great! But what if I write this instead? ld a, 220 add a, 42 Well, one could think that a would be equal to 220 + 42 = 262, but that would be incorrect. Remember, a is an 8-bit register, it can only store eight bits of information ! And if we were to write 262 in binary, we would get %100000110, which requires at least 9 bits... So what happens? Simply, that ninth bit is lost , and the value that we end up with is %00000110 = 6. This is called an overflow : after adding , we get a value smaller than what we started with. We can also do the opposite with sub, and—for example—subtract 42 from 6; as we know, for all X and Y, X + Y - Y = X, and we just saw that 220 + 42 = 6 (this is called modulo 256 arithmetic , by the way); so, 6 - 42 = (220 + 42) - 42 = 220. This is called an underflow : after subtracting , we get a value greater than what we started with. When an operation is performed, it sets the carry flag if an overflow or underflow occurred, and clears it otherwise. (We will see later that not all operations update the carry flag, though.) Summary We can add and subtract numbers. The Z flag lets us know if the result was 0. However, registers can only store a limited range of integers. Going outside this range is called an overflow or underflow , for addition and subtraction respectively. The C flag lets us know if either occurred.","breadcrumbs":"Operations & flags » Flags","id":"46","title":"Flags"},"47":{"body":"Now, let's talk more about how cp is used to compare numbers. Here is a refresher: cp subtracts its operand from a and updates flags accordingly, but doesn't write the result back. We can use flags to check properties about the values being compared, and we will see in the next lesson how to use the flags. The simplest interaction is with the Z flag. If it's set, we know that the subtraction yielded 0, i.e. a - operand == 0; therefore, a == operand! If it's not set, well, then we know that a != operand. Okay, checking for equality is nice, but we may also want to perform comparisons . Fret not, for the carry flag is here to do just that! See, when performing a subtraction, the carry flag gets set when the result goes below 0—but that's just a fancy way of saying \"becomes negative\"! So, when the carry flag gets set, we know that a - operand < 0, therefore that a < operand..! And, conversely, we know that if it's not set, a >= operand. Great!","breadcrumbs":"Operations & flags » Comparison","id":"47","title":"Comparison"},"48":{"body":"Instruction Mnemonic Effect Add add Adds values to a Subtract sub Subtracts values from a Compare cp Compares values with what's contained in a","breadcrumbs":"Operations & flags » Instruction summary","id":"48","title":"Instruction summary"},"49":{"body":"Once this lesson is done, we will be able to understand all of CopyTiles! So far, all the code we have seen was linear: it executes top to bottom. But this doesn't scale: sometimes, we need to perform certain actions depending on the result of others (\"if the crêpes start sticking, grease the pan again\"), and sometimes, we need to perform actions repeatedly (\"If there is some batter left, repeat from step 5\"). Both of these imply reading the recipe non-linearly. In assembly, this is achieved using jumps . The CPU has a special-purpose register called \"PC\", for Program Counter. It contains the address of the instruction currently being executed [1] , like how you'd keep in mind the number of the recipe step you're currently doing. PC increases automatically as the CPU reads instructions, so \"by default\" they are read sequentially; however, jump instructions allow writing a different value to PC, effectively jumping to another piece of the program. Hence the name. Okay, so, let's talk about those jump instructions, shall we? There are four of them: Instruction Mnemonic Effect Jump jp Jump execution to a location Jump Relative jr Jump to a location close by Call call Call a subroutine Return ret Return from a subroutine We will focus on jp for now. jp, such as the one line 5, simply sets PC to its argument, jumping execution there. In other words, after executing jp EntryPoint (line 5), the next instruction executed is the one below EntryPoint (line 16). 🤔 You may be wondering what is the point of that specific jp. Don't worry, we will see later why it's required.","breadcrumbs":"Jumps » Jumps","id":"49","title":"Jumps"},"5":{"body":"The tutorial is split into three parts. We strongly advise you go through the tutorial in order! In Part Ⅰ, we run our first \"Hello World!\" program, which we then dissect to learn what makes the Game Boy tick. In Part Ⅱ, we program our first game, a clone of Arkanoid ; we learn how to prod the hardware into having something we can call a \"game\". Along the way, we will make plenty of mistakes, so we can learn how to debug our code. And finally, Part Ⅲ is about \"advanced\" use of the hardware, where we learn how to make even better-looking games, and we program a Shoot 'Em Up! We hope this tutorial will work for you. But if it doesn't (the format may not work well for everyone, and that's okay), I encourage you to look at some other resources , which might work better for you. It's also fine to take a break from time to time ; feel free to read at your own pace, and to ask for clarifications if anything isn't clear to you. This tutorial is a work in progress.","breadcrumbs":"Roadmap » Roadmap","id":"5","title":"Roadmap"},"50":{"body":"Now to the really interesting part. Let's examine the loop responsible for copying tiles: ; Copy the tile data ld de, Tiles ld hl, $9000 ld bc, TilesEnd - Tiles\nCopyTiles: ld a, [de] ld [hli], a inc de dec bc ld a, b or a, c jp nz, CopyTiles Don't worry if you don't quite get all the following, as we'll see it live in action in the next lesson. If you're having trouble, try going to the next lesson, watch the code execute step by step; then, coming back here, it should make more sense. First, we copy Tiles, the address of the first byte of tile data, into de. Then, we set hl to $9000, which is the address where we will start copying the tile data to. ld bc, TilesEnd - Tiles sets bc to the length of the tile data: TilesEnd is the address of the first byte after the tile data, so subtracting Tiles to that yields the length. So, basically: de contains the address where data will be copied from; hl contains the address where data will be copied to; bc contains how many bytes we have to copy. Then we arrive at the main loop. We read one byte from the source (line 29), and write it to the destination (line 30). We increment the destination (via the implicit inc hl done by ld [hli], a) and source pointers (line 31), so the following loop iteration processes the next byte. Here's the interesting part: since we've just copied one byte, that means we have one less to go, so we dec bc. (We have seen dec two lessons ago; as a refresher, it simply decreases the value stored in bc by one.) Since bc contains the amount of bytes that still need to be copied, it's trivial to see that we should simply repeat the operation if bc != 0. 😓 dec usually updates flags, but unfortunately dec bc doesn't, so we must check if bc reached 0 manually. ld a, b and or a, c \"bitwise OR\" b and c together; it's enough to know for now that it leaves 0 in a if and only if bc == 0. And or updates the Z flag! So, after line 34, the Z flag is set if and only if bc == 0, that is, if we should exit the loop. And this is where conditional jumps come into the picture! See, it's possible to conditionally \"take\" a jump depending on the state of the flags. There are four \"conditions\": Name Mnemonic Description Zero z Z is set (last operation had a result of 0) Non-zero nz Z is not set (last operation had a non-zero result) Carry c C is set (last operation overflowed) No carry nc C is not set (last operation did not overflow) Thus, jp nz, CopyTiles can be read as \"if the Z flag is not set, then jump to CopyTiles\". Since we're jumping backwards , we will repeat the instructions again: we have just created a loop ! Okay, we've been talking about the code a lot, and we have seen it run, but we haven't really seen how it runs. Let's watch the magic unfold in slow-motion in the next lesson! Not exactly; instructions may be several bytes long, and PC increments after reading each byte. Notably, this means that when an instruction finishes executing, PC is pointing to the following instruction. Still, it's pretty much \"where the CPU is currently reading from\", but it's better to keep it simple and avoid mentioning instruction encoding for now.","breadcrumbs":"Jumps » Conditional jumps","id":"50","title":"Conditional jumps"},"51":{"body":"Ever dreamed of being a wizard? Well, this won't give you magical powers, but let's see how emulators can be used to control time! First, make sure to focus the debugger window. Let's first explain the debugger's layout: Top-left is the code viewer, bottom-left is the data viewer, top-right are some registers (as we saw in the registers lesson ), and bottom-right is the stack viewer. What's the stack? We will answer that question a bit later... in Part Ⅱ 😅","breadcrumbs":"Tracing » Tracing","id":"51","title":"Tracing"},"52":{"body":"For now, let's focus on the code viewer. As Emulicious can load our source code, our code's labels and comments are automatically shown in the debugger. As we have seen a couple of lessons ago, labels are merely a convenience provided by RGBASM, but they are not part of the ROM itself. In other emulators, it is very much inconvenient to debug without them, and so sym files (for \" sym bols\") have been developed. Let's run RGBLINK to generate a sym file for our ROM: $ rgblink -n hello-world.sym hello-world.o ‼️ The file names matter! When looking for a ROM's sym file, emulators take the ROM's file name, strip the extension (here, .gb), replace it with .sym, and look for a file in the same directory with that name.","breadcrumbs":"Tracing » Setup","id":"52","title":"Setup"},"53":{"body":"When pausing execution, the debugger will automatically focus on the instruction the CPU is about to execute, as indicated by the line highlighted in blue. Screenshot of the debugger showing that the highlighted line corresponds to PC ℹ️ The instruction highlighted in blue is always what the CPU is about to execute , not what it just executed . Keep this in mind. If we want to watch execution from the beginning, we need to reset the emulator. Go into the emulator's \"File\" menu, and select \"Reset\", or press Ctrl+Backspace. The blue line should automatically move to address $0100 [1] , and now we're ready to trace! All the commands for that are in the \"Run\" menu. \"Resume\" simply unpauses the emulator. \"Step Into\" and \"Step Over\" advance the emulator by one instruction. They only really differ on the call instruction, interrupts, and when encountering a conditional jump, neither of which we are using here, so we will use \"Step Into\". The other options are not relevant for now. We will have to \"Step Into\" a bunch of times, so it's a good idea to use the key shortcut. If we press F5 once, the jp EntryPoint is executed. And if we press it a few more times, can see the instructions being executed, one by one! Now, you may notice the WaitVBlank loop runs a lot of times, but what we are interested in is the CopyTiles loop. We can easily skip over it in several ways; this time, we will use a breakpoint . We will place the breakpoint on the ld de, Tiles at 00:0162; either double-click on that line, or select it and press Ctrl+Shift+B. Debugger screenshot showcasing the breakpoint Then you can resume execution by pressing F8. Whenever Emulicious is running, and the (emulated) CPU is about to execute an instruction a breakpoint was placed on, it automatically pauses. Debugger screenshot showcasing execution paused on the breakpoint You can see where execution is being paused both from the green arrow and the value of PC. If we trace the next three instructions, we can see the three arguments to the CopyTiles loop getting loaded into registers. The state of some registers at the beginning of the CopyTiles loop For fun, let's watch the tiles as they're being copied. For that, obviously, we will use the Memory Editor, and position it at the destination. As we can see from the image above, that would be $9000! Click on \"Memory\" on the bottom window, then \"VRAM\", and press Ctrl+G (for \"Goto\"). Awesome, right?","breadcrumbs":"Tracing » Stepping","id":"53","title":"Stepping"},"54":{"body":"Congrats, you have just learned how to use a debugger! We have only scratched the surface, though; we will use more of Emulicious' tools to illustrate the next parts. Don't worry, from here on, lessons will go with a lot more images—you've made it through the hardest part! Why does execution start at $0100? That's because it's where the boot ROM hands off control to our game once it's done.","breadcrumbs":"Tracing » What next?","id":"54","title":"What next?"},"55":{"body":"💭 \"Tiles\" were called differently in documentation of yore. They were usually called \"patterns\" or \"characters\", the latter giving birth to the \"CHR\" abbreviation which is sometimes used to refer to tiles. For example, on the NES, tile data is usually provided by the cartridge in either CHR ROM or CHR RAM . The term \"CHR\" is typically not used on the Game Boy, though exchanges between communities cause terms to \"leak\", so some refer to the area of VRAM where tiles are stored as \"CHR RAM\" or \"CHR VRAM\", for example. As with all such jargon whose meaning may depend on who you are talking to, I will stick to \"tiles\" across this entire tutorial for consistency, being what is the most standard in the GB dev community now. Well, copying this data blindly is fine and dandy, but why exactly is the data \"graphics\"? Ah, yes, pixels. Let's see about that!","breadcrumbs":"Graphics » Tiles » Tiles","id":"55","title":"Tiles"},"56":{"body":"Now, figuring out the format with an explanation alone is going to be very confusing; but fortunately, Emulicious got us covered thanks to its Tile Viewer . You can open it either by selecting \"Tools\" then \"Tile Viewer\", or by clicking on the grid of colored tiles in the debugger's toolbar. Screenshot of the Tile Viewer You can combine the various VRAM viewers by going to \"View\", then \"Combine Video Viewers\". We will come to the other viewers in due time. This one shows the tiles present in the Game Boy's video memory (or \"VRAM\"). 🤔 I encourage you to experiment with the VRAM viewer, hover over things, tick and untick checkboxes, see by yourself what's what. Any questions you might have will be answered in due time, don't worry! And if what you're seeing later on doesn't match my screenshots, ensure that the checkboxes match mine. Don't mind the \"®\" icon in the top-left; we did not put it there ourselves, and we will see why it's there later.","breadcrumbs":"Graphics » Tiles » Helpful hand","id":"56","title":"Helpful hand"},"57":{"body":"You may have heard of tiles before, especially as they were really popular in 8-bit and 16-bit systems. That's no coincidence: tiles are very useful. Instead of storing every on-screen pixel (144 × 160 pixels × 2 bits/pixel = 46080 bits = 5760 bytes, compared to the console's 8192 bytes of VRAM), pixels are grouped into tiles, and then tiles are assembled in various ways to produce the final image. In particular, tiles can be reused very easily and at basically no cost, saving a lot of memory! In addition, manipulating whole tiles at once is much cheaper than manipulating the individual pixels, so this spares processing time as well. The concept of a \"tile\" is very general, but on the Game Boy, tiles are always 8 by 8 pixels. Often, hardware tiles are grouped to manipulate them as larger tiles (often 16×16); to avoid the confusion, those are referred to as meta-tiles .","breadcrumbs":"Graphics » Tiles » Short primer","id":"57","title":"Short primer"},"58":{"body":"You may be wondering where that \"2 bits/pixel\" figure earlier came from... This is something called \"bit depth\". See, colors are not stored in the tiles themselves! Instead, it works like a coloring book: the tile itself contains 8 by 8 indices , not colors; you give the hardware a tile and a set of colors—a palette —and it colorizes them! (This is also why color swaps were very common back then: you could create enemy variations by storing tiny palettes instead of large different graphics.) Anyway, as it is, Game Boy palettes are 4 colors large. [1] This means that the indices into those palettes, stored in the tiles, can be represented in only two bits ! This is called \"2 bits per pixel\", noted \"2bpp\". With that in mind, we are ready to explain how these bytes turn into pixels!","breadcrumbs":"Graphics » Tiles » \"bpp\"?","id":"58","title":"\"bpp\"?"},"59":{"body":"As I explained, each pixel takes up 2 bits. Since there are 8 bits in a byte, you might expect each byte to contain 4 pixels... and you would be neither entirely right, nor entirely wrong. See, each row of 8 pixels is stored in 2 bytes, but neither of these bytes contains the info for 4 pixels. (Think of it like a 10 € banknote torn in half: neither half is worth anything, but the full bill is worth, well, 10 €.) For each pixel, the least significant bit of its index is stored in the first byte, and the most significant bit is stored in the second byte. Since each byte is a collection of one of the bits for each pixel, it's called a bitplane . The leftmost pixel is stored in the leftmost bit of both bytes, the pixel to its right in the second leftmost bit, and so on. The first pair of bytes stores the topmost row, the second byte the row below that, and so on. Here is a more visual demonstration: This encoding may seem a little weird at first, and it can be; it's made to be more convenient for the hardware to decode, keeping the circuitry simple and low-power. It even makes a few cool tricks possible, as we will see (much) later! You can read up more about the encoding in the Pan Docs and ShantyTown's site . In the next lesson, we shall see how colors are applied! Other consoles can have varying bit depths; for example, the SNES has 2bpp, 4bpp, and 8bpp depending on the graphics mode and a few other parameters.","breadcrumbs":"Graphics » Tiles » Encoding","id":"59","title":"Encoding"},"6":{"body":"If you are stuck in a certain part of the tutorial, want some advice, or just wish to chat with us, the GBDev community chat is the place to go! The authors actively participate there so don't be afraid to ask questions! (The \"ASM\" channel should be the most appropriate to discuss the tutorial, by the way.) If you prefer email, you can reach us at tutorial@