From c1d93b812801122970e854da0d109fe180e281f5 Mon Sep 17 00:00:00 2001 From: LaroldsJubilantJunkyard Date: Sat, 16 Dec 2023 01:43:40 -0500 Subject: [PATCH] started on collision detection between objects --- .../main/states/gameplay/objects/enemies.asm | 15 +- ...ect-collision.asm => object-collision.asm} | 235 +++++++++--------- src/SUMMARY.md | 8 +- src/part3/enemies.md | 76 +++--- src/part3/enemy-bullet-collision.md | 1 - src/part3/enemy-collision.md | 1 + src/part3/enemy-collisions.md | 14 ++ src/part3/enemy-player-collision.md | 1 - src/part3/enemy-spawning.md | 1 + src/part3/object-collision-detection.md | 1 + src/part3/object-collisions.md | 42 ++++ 11 files changed, 224 insertions(+), 171 deletions(-) rename galactic-armada/src/main/states/gameplay/objects/{collision/object-object-collision.asm => object-collision.asm} (86%) delete mode 100644 src/part3/enemy-bullet-collision.md create mode 100644 src/part3/enemy-collision.md create mode 100644 src/part3/enemy-collisions.md delete mode 100644 src/part3/enemy-player-collision.md create mode 100644 src/part3/enemy-spawning.md create mode 100644 src/part3/object-collision-detection.md create mode 100644 src/part3/object-collisions.md diff --git a/galactic-armada/src/main/states/gameplay/objects/enemies.asm b/galactic-armada/src/main/states/gameplay/objects/enemies.asm index b28132f3..c76c9a2d 100644 --- a/galactic-armada/src/main/states/gameplay/objects/enemies.asm +++ b/galactic-armada/src/main/states/gameplay/objects/enemies.asm @@ -5,7 +5,7 @@ include "src/main/includes/constants.inc" SECTION "Enemies", ROM0 ; ANCHOR_END: enemies-start -; ANCHOR: enemies-update +; ANCHOR: enemies-update1 UpdateEnemy:: ; get the start of our object back in hl @@ -37,6 +37,9 @@ UpdateEnemy:: cp a, 10 jp nc, DeactivateEnemy +; ANCHOR_END: enemies-update1 +; ANCHOR: enemies-update2 + push hl ; Check for collision for current enemy @@ -52,14 +55,16 @@ UpdateEnemy:: cp a, ENEMY_COLLISION_DAMAGED jp z, DamageEnemy ret -; ANCHOR_END: enemies-update +; ANCHOR_END: enemies-update2 ; ANCHOR: enemies-damage DamageEnemy: - + + ; Save our pointers push hl - ; Decrease the enemies health byte push de + + ; Move to the health byte ld de, object_healthByte add hl, de ld a, [hl] @@ -81,7 +86,7 @@ DamageEnemy: pop de ; Set as damaged for 128 frames - ld a, 128 + ld a, 32 ld [hl], a ; Move to the next diff --git a/galactic-armada/src/main/states/gameplay/objects/collision/object-object-collision.asm b/galactic-armada/src/main/states/gameplay/objects/object-collision.asm similarity index 86% rename from galactic-armada/src/main/states/gameplay/objects/collision/object-object-collision.asm rename to galactic-armada/src/main/states/gameplay/objects/object-collision.asm index df35a657..903aff10 100644 --- a/galactic-armada/src/main/states/gameplay/objects/collision/object-object-collision.asm +++ b/galactic-armada/src/main/states/gameplay/objects/object-collision.asm @@ -1,115 +1,120 @@ - -; ANCHOR: enemy-bullet-collision-start -include "src/main/includes/hardware.inc" -include "src/main/includes/constants.inc" -include "src/main/includes/hardware.inc" - - -SECTION "ObjectObjectCollisionVariables", WRAM0 -wSizeY:: db -wSizeX:: db -wCheckByte: db - -SECTION "ObjectObjectCollision", ROM0 - -CheckCollisionWithObjectsInHL_andDE:: - - ; SAve original values for y axis - push de - push hl - -XAxis: - - ; Save which byte we are checking - ld a, object_xLowByte - ld [wCheckByte], a - - ; Save if the minimum distance - ld a, [wSizeX] - ld [wSize], a - - call CheckObjectBytesOfObjects_InDE_AndHL - - ; Restore original vaues just in case - pop hl - pop de - - jp nz, YAxis - - ld a,0 - and a - ret - -YAxis: - - ; Save which byte we are checking - ld a, object_yLowByte - ld [wCheckByte], a - - ; Save if the minimum distance - ld a, [wSizeY] - ld [wSize], a - - call CheckObjectBytesOfObjects_InDE_AndHL - - ; Normal return with the z/c flags as-is - ret - - -CheckObjectBytesOfObjects_InDE_AndHL:: - - ; put de in hl so we can get the x bytes (for the de object) in bc and descale just to c - - push hl - - ; Offset de by the check byte - ld a, [wCheckByte] - add a,e - ld e,a - - ; copy the low byte to c - ld a, [de] - ld c, a - - ; move to the high byte - inc de - - ; copy the high byte to b - ld a, [de] - ld b, a - - ; Descale - REPT 4 - srl b - rr c - ENDR - - ld a, c - ld [wObject1Value], a - - pop hl - - ; get the bytes (for the hl object) in bc and descale just to c - ld a, [wCheckByte] - add a, l - ld l, a - - ; move to the high byte - ld a, [hli] - ld c, a - - ; copy the high byte to b - ld a, [hl] - ld b, a - - ; Descale - REPT 4 - srl b - rr c - ENDR - - ld a, c - ld [wObject2Value], a - - call CheckObjectPositionDifference - ret \ No newline at end of file + +; ANCHOR: object-collision-start +include "src/main/includes/hardware.inc" +include "src/main/includes/constants.inc" +include "src/main/includes/hardware.inc" + + +SECTION "ObjectObjectCollisionVariables", WRAM0 +wSizeY:: db +wSizeX:: db +wCheckByte: db + +SECTION "ObjectObjectCollision", ROM0 + +; ANCHOR_END: object-collision-start +; ANCHOR: object-collision-x + +CheckCollisionWithObjectsInHL_andDE:: + + ; SAve original values for y axis + push de + push hl +XAxis: + + ; Save which byte we are checking + ld a, object_xLowByte + ld [wCheckByte], a + + ; Save if the minimum distance + ld a, [wSizeX] + ld [wSize], a + + call CheckObjectBytesOfObjects_InDE_AndHL + + ; Restore original vaues just in case + pop hl + pop de + + jp nz, YAxis + + ld a,0 + and a + ret +; ANCHOR_END: object-collision-x +; ANCHOR: object-collision-y +YAxis: + + ; Save which byte we are checking + ld a, object_yLowByte + ld [wCheckByte], a + + ; Save if the minimum distance + ld a, [wSizeY] + ld [wSize], a + + call CheckObjectBytesOfObjects_InDE_AndHL + + ; Normal return with the z/c flags as-is + ret +; ANCHOR_END: object-collision-y + +; ANCHOR: object-collision-check-bytes +CheckObjectBytesOfObjects_InDE_AndHL:: + + ; put de in hl so we can get the x bytes (for the de object) in bc and descale just to c + + push hl + + ; Offset de by the check byte + ld a, [wCheckByte] + add a,e + ld e,a + + ; copy the low byte to c + ld a, [de] + ld c, a + + ; move to the high byte + inc de + + ; copy the high byte to b + ld a, [de] + ld b, a + + ; Descale + REPT 4 + srl b + rr c + ENDR + + ld a, c + ld [wObject1Value], a + + pop hl + + ; get the bytes (for the hl object) in bc and descale just to c + ld a, [wCheckByte] + add a, l + ld l, a + + ; move to the high byte + ld a, [hli] + ld c, a + + ; copy the high byte to b + ld a, [hl] + ld b, a + + ; Descale + REPT 4 + srl b + rr c + ENDR + + ld a, c + ld [wObject2Value], a + + call CheckObjectPositionDifference + ret +; ANCHOR_END: object-collision-check-bytes \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f846c5e5..f8d82d64 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -42,22 +42,22 @@ - [Drawing Text](part3/drawing-text.md) - [Included Utilities](part3/utilities.md) - [Compilation](part3/compilation.md) + - [Collision Detection](part3/collision.md) - [Game State Management](part3/game-state-management.md) - [Title Screen](part3/title-screen.md) - [Story Screen](part3/story-screen.md) - [Gameplay](part3/gameplay.md) - [Object Pools](part3/object-pools.md) + - [Object Collision Detection](part3/object-collisions.md) - [Scrolling Background](part3/scrolling-background.md) - [Heads-Up Interface](part3/heads-up-interface.md) - [The Player](part3/the-player.md) - [Bullets](part3/bullets.md) - [Enemies](part3/enemies.md) - - [Collision Detection](part3/collision.md) - - [Enemy Collisions](part3/collision.md) + - [Spawning Enemies](part3/enemy-spawning.md) + - [Enemy Collisions](part3/enemy-collision.md) - [Conclusion](part3/conclusion.md) - - [Where to go next](next.md) [Resources](resources.md) [Thanks](thanks.md) diff --git a/src/part3/enemies.md b/src/part3/enemies.md index c9a89a4f..9c916a1d 100644 --- a/src/part3/enemies.md +++ b/src/part3/enemies.md @@ -1,75 +1,61 @@ # Enemies -Enemies in SHMUPS often come in a variety of types, and travel also in a variety of patterns. To keep things simple for this tutorial, we'll have one enemy that flys straight downward. Because of this decision, the logic for enemies is going to be similar to bullets in a way. They both travel vertically and disappear when off screeen. Some differences to point out are: +Enemies in Shoot-em-ups 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 single enemy type. That single type of enemy will only fly 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. +- Enemies must check for collision against bullets -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. +**Create a new file called `enemies.asm`:** ```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-start}} {{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-start}} ``` -Just like with bullets, we'll setup ROM data for our enemies tile data and metasprites. - -## Initializing Enemies - -When initializing the enemies (at the start of gameplay), we'll copy the enemy tile data into VRAM. Also, like with bullets, we'll loop through and make sure each enemy is set to inactive. +Our enemies will be a part of the object pool we [previously](#object-pools) setup ## Updating Enemies -When "UpdateEnemies" is called from gameplay, the first thing we try to do is spawn new enemies. After that, if we have no active enemies (and are not trying to spawn a new enemy), we stop the "UpdateEnemies" function. From here, like with bullets, we'll save the address of our first enemy in hl and start looping through. - -When we are looping through our enemy object pool, let's check if the current enemy is active. If it's active, we'll update it like normal. If it isn't active, the game checks if we want to spawn a new enemy. We specify we want to spawn a new enemy by setting 'wNextEnemyXPosition' to a non-zero value. If we don't want to spawn a new enemy, we'll move on to the next enemy. - -If we want to spawn a new enemy, we'll set the current inactive enemy to active. Afterwards, we'll set it's y position to zero, and it's x position to whatever was in the 'wNextEnemyXPosition' variable. After that, we'll increase our active enemy counter, and go on to update the enemy like normal. - - -When We are done updating a single enemy, we'll jump to the "UpdateEnemies_Loop" label. Here we'll increase how many enemies we've updated, and end if we've done them all. If we still have more enemies left, we'll increase the address stored in hl by 6 and update the next enemy. - -> The "hl" registers should always point to the current enemies first byte when this label is reached. +When updating a single enemy, we first get the pointer to our enemies object. We copy this from `bc`. With that said, we'll increase the y bytes to move the enemy downward. +Like with [bullets](#bullets), if the y high byte is above 10, we'll consider the enemy off screen. -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. +**Create an `UpdateEnemy` function the following code:** -```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update-per-enemy2}} -{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update-per-enemy2}} +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update1}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update1}} ``` -## Player & Bullet Collision +If the enemy is still on screen we want to check for collisions. We'll do this using a function called `CheckCollisionForCurrentEnemy`. We'll define that in the next page. This function will set a result in the `a` register. +- `ENEMY_COLLISION_NOTHING` - No collisions have occured +- `ENEMY_COLLISION_DAMAGED` - The enemy has been damaged by a bullet +- `ENEMY_COLLISION_END` - The enemy should be deactivated -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. +>**Note:** These constants are already defined in constants.inc -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. +We'll finish the `UpdateEnemy` exection based on the response for the function. -> 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. +**Finish the `UpdateEnemy` function, with the following code:** +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update2}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-update2}} +``` -If there is no collision with the player, we'll draw the enemies. This is done just as we did the player and bullets, with the "DrawMetasprites" function. - - -## Deactivating Enemies - -Deactivating an enemy is just like with bullets. We'll set it's first byte to 0, and decrease our counter variable. - -> Here, we can just use the current address in HL. This is the second reason we wanted to keep the address of our first byte on the stack. +When the enemy has been damaged, we'll jump to their health byte and decrement it. If it becomes zero, we'll kill the enemy. If the enemy still has health remaining, we'll set it as damaged for small time. +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-damage}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-damage}} +``` -## Spawning Enemies +When an enemy is killed, we simply increase the score and deactivate it. -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. +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-kill}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-kill}} +``` -> All enemies are spawned with y position of 0, so we only need to get the x position. +To deactivate any object in our object pool, we simply set the first byte to 0. -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. +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-deactivate}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/enemies.asm:enemies-deactivate}} +``` diff --git a/src/part3/enemy-bullet-collision.md b/src/part3/enemy-bullet-collision.md deleted file mode 100644 index 77196d13..00000000 --- a/src/part3/enemy-bullet-collision.md +++ /dev/null @@ -1 +0,0 @@ -# Enemy-Bullet Collision diff --git a/src/part3/enemy-collision.md b/src/part3/enemy-collision.md new file mode 100644 index 00000000..2b3203ad --- /dev/null +++ b/src/part3/enemy-collision.md @@ -0,0 +1 @@ +# Enemy Collisions diff --git a/src/part3/enemy-collisions.md b/src/part3/enemy-collisions.md new file mode 100644 index 00000000..443d4cc7 --- /dev/null +++ b/src/part3/enemy-collisions.md @@ -0,0 +1,14 @@ +# Enemy Collision + +In the previous page, we used a function called `CheckCollisionForCurrentEnemy`. We'll explain and define that function on this page. + +There are two parts to enemy collision detection: +- Collision Detection against the player +- Collision Detection against bullets + +## Against the Player + +Checking agains the player is really simple + + +## Against bullets \ No newline at end of file diff --git a/src/part3/enemy-player-collision.md b/src/part3/enemy-player-collision.md deleted file mode 100644 index 99606872..00000000 --- a/src/part3/enemy-player-collision.md +++ /dev/null @@ -1 +0,0 @@ -# Enemy-Player Collision diff --git a/src/part3/enemy-spawning.md b/src/part3/enemy-spawning.md new file mode 100644 index 00000000..8d96ce57 --- /dev/null +++ b/src/part3/enemy-spawning.md @@ -0,0 +1 @@ +# Spawning Enemies diff --git a/src/part3/object-collision-detection.md b/src/part3/object-collision-detection.md new file mode 100644 index 00000000..3f106b20 --- /dev/null +++ b/src/part3/object-collision-detection.md @@ -0,0 +1 @@ +# Object Collision Detection diff --git a/src/part3/object-collisions.md b/src/part3/object-collisions.md new file mode 100644 index 00000000..982023e7 --- /dev/null +++ b/src/part3/object-collisions.md @@ -0,0 +1,42 @@ +# Object Collision Detection + +For collision detection between objects in our object pool, we'll setup a universal function. This function, called `CheckCollisionWithObjectsInHL_andDE`, will have 4 requirements: +- A pointer to Object A in `hl` +- A pointer to Object B in `de` +- The minimum allowed distance on the x axis in `wSizeX` +- The minimum allowed distance on the y-axis in `wSizeY` + +**Create a file called `object-collision.asm` and add the following code:** + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-start}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-start}} +``` + +The logic for checking the distance on the x & y axes is identical. For that reason, we've isolated it into a function called `CheckObjectBytesOfObjects_InDE_AndHL`. + +All of our object's data share the same order and structure. For collision detection, we want to check the same bytes (the 2 x bytes , or the 2 y bytes) for 2 different objects. For this, we've created the function called `CheckObjectBytesOfObjects_InDE_AndHL`. This function has 3 requirements: + +- A pointer to Object A in `hl` +- A pointer to Object B in `de` +- Which byte to check in `wCheckByte` + +This function uses the `CheckObjectPositionDifference` function that comes with the starter. Our x & y bytes are Q12.4 fixed point integers. Before we can use them, we need to descale them. After descaling them, we'll call the `CheckObjectPositionDifference` function and use it's result as our own. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-check-bytes}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-check-bytes}} +``` + + +The x-axis is up first. In a nutshell, we simply pass which byte we and distance we want to check to the `CheckObjectBytesOfObjects_InDE_AndHL` function. If it returns a value of zero, there is no overlap on that axis. Otherwise, we'll proceed on to check the y-axis + + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-x}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-x}} +``` + +After checking the x-axis, we'll do the same thing for the y-axis. After the `CheckObjectBytesOfObjects_InDE_AndHL` function is called, we'll return from the `CheckCollisionWithObjectsInHL_andDE` function. The result flags of the previous function call will be used. + +```rgbasm,linenos,start={{#line_no_of "" ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-y}} +{{#include ../../galactic-armada/src/main/states/gameplay/objects/object-collision.asm:object-collision-y}} +``` +