This commit is contained in:
t. boddy 2026-02-16 17:00:35 -05:00
parent 1702a06d9f
commit 364a34ce33
20 changed files with 453 additions and 64 deletions

View file

@ -2,7 +2,7 @@
// #define FADE_TOP_I BG_I + 64
// #define FADE_BOTTOM_I FADE_TOP_I + 64
#define BG_OFF 112
#define BG_OFF 24
void loadBackground(){
VDP_loadTileSet(sky.tileset, BG_I, DMA);
@ -11,7 +11,7 @@ void loadBackground(){
// VDP_loadTileSet(fadeBottom.tileset, FADE_BOTTOM_I, DMA);
for(u8 y = 0; y < 4; y++){
for(u8 x = 0; x < 16; x++){
VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, BG_I), x * 8, y * 8, 8, 8);
VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, BG_I + (y > 2 ? 64 : 0)), x * 8, y * 8, 8, 8);
}
}
// for(u8 x = 0; x < 5; x++){
@ -22,5 +22,5 @@ void loadBackground(){
void updateBackground(){
VDP_setHorizontalScroll(BG_B, fix32ToInt(-player.camera));
VDP_setVerticalScroll(BG_B, (fix32ToInt(player.pos.y) - BG_OFF) >> 2);
VDP_setVerticalScroll(BG_B, (fix32ToInt(player.pos.y) - BG_OFF) >> 3);
}

View file

@ -57,6 +57,7 @@ static void doBulletRotation(u8 i){
}
void spawnBullet(struct bulletSpawner spawner, void(*updater)){
if(player.recoveringClock > 0 && !spawner.player) return;
// Don't spawn if offscreen
fix32 dx = getWrappedDelta(spawner.x, player.pos.x);
bool offScreenX = (dx < -CULL_LIMIT || dx > CULL_LIMIT);
@ -124,6 +125,7 @@ static void collideWithEnemy(u8 i){
deltaX >= -BULLET_CHECK && deltaX <= BULLET_CHECK){
bulletDist = getApproximatedDistance(fix32ToInt(deltaX), fix32ToInt(deltaY));
if(bulletDist <= bullets[i].dist){
score += (enemies[j].ints[3] >= 0) ? 200 : 100;
killBullet(i, TRUE);
killEnemy(j);
sfxExplosion();
@ -134,6 +136,7 @@ static void collideWithEnemy(u8 i){
}
static void collideWithPlayer(u8 i){
if(player.recoveringClock > 0) return;
fix32 deltaX = getWrappedDelta(bullets[i].pos.x, player.pos.x);
fix32 deltaY = bullets[i].pos.y - player.pos.y;
@ -143,8 +146,13 @@ static void collideWithPlayer(u8 i){
if(dist <= 4){
killBullet(i, TRUE);
sfxExplosion();
// player.lives--;
// if(player.lives <= 0) gameOver = TRUE;
player.lives--;
if(player.lives == 0){
gameOver = TRUE;
} else {
player.recoveringClock = 120;
killBullets = TRUE;
}
}
}
@ -195,7 +203,7 @@ static void updateBullet(u8 i){
}
if(bullets[i].clock > 0) bullets[i].updater(i);
if(bullets[i].player) collideWithEnemy(i);
else collideWithPlayer(i);
else if(!gameOver) collideWithPlayer(i);
if(bullets[i].active){
s16 sx = getScreenX(bullets[i].pos.x, player.camera);
s16 sy = fix32ToInt(bullets[i].pos.y);
@ -209,6 +217,11 @@ static void updateBullet(u8 i){
void updateBullets(){
bulletCount = 0;
if(killBullets){
killBullets = FALSE;
for(s16 i = 0; i < BULLET_COUNT; i++)
if(bullets[i].active && !bullets[i].player) killBullet(i, TRUE);
}
for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active)
updateBullet(i);
// intToStr(bulletCount, debugStr, 1);

View file

@ -2,18 +2,55 @@
#define MAP_TILE TILE_ATTR_FULL(PAL1, 1, 0, 0, MAP_I)
#define MAP_PLAYER_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 1)
#define MAP_ENEMY_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 2)
#define MAP_BORDER_X_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 3)
#define MAP_HUMAN_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 3)
#define MAP_BORDER_X_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 4)
#define FONT_BIG_I 256
void bigText(char* str, u16 x, u16 y, bool shadow){
for(u8 i = 0; i < strlen(str); i++){
if(str[i] >= 48){
VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + str[i] - 48), x + i, y);
VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + 16 + str[i] - 48), x + i, y + 1);
}
}
}
char scoreStr[SCORE_LENGTH];
u32 lastScore;
s16 scoreLength;
#define SCORE_X 1
#define SCORE_Y 5
#define LIFE_I (FONT_BIG_I + 64)
#define LIVES_X 38
#define LIVES_Y 5
s16 lastLives;
static void drawLives(){
VDP_clearTileMapRect(BG_A, LIVES_X, LIVES_Y, 1, 16);
for(u8 i = 0; i < (player.lives - 1); i++)
VDP_fillTileMapRectInc(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, LIFE_I + (i > 0 ? 2 : 0)), LIVES_X, LIVES_Y + i, 1, 2);
lastLives = player.lives;
}
// previous map positions: -1 means not drawn
s16 mapEnemyCol[ENEMY_COUNT], mapEnemyRow[ENEMY_COUNT];
s16 mapHumanCol[HUMAN_COUNT], mapHumanRow[HUMAN_COUNT];
s16 mapPlayerRow;
static void drawScore(){
uintToStr(score, scoreStr, 1);
VDP_drawText(scoreStr, 1, 5);
if(lastScore < 10) scoreLength = 1;
else if(lastScore < 100) scoreLength = 2;
else if(lastScore < 1000) scoreLength = 3;
else if(lastScore < 10000) scoreLength = 4;
else if(lastScore < 100000) scoreLength = 5;
else if(lastScore < 1000000) scoreLength = 6;
else if(lastScore < 10000000) scoreLength = 7;
else scoreLength = 8;
uintToStr(lastScore, scoreStr, scoreLength);
bigText(scoreStr, SCORE_X, SCORE_Y, FALSE);
}
// load map when stage does so we have all enemies + player
@ -29,17 +66,24 @@ void loadMap(){
mapEnemyCol[i] = -1;
mapEnemyRow[i] = -1;
}
for(s16 i = 0; i < HUMAN_COUNT; i++){
mapHumanCol[i] = -1;
mapHumanRow[i] = -1;
}
mapPlayerRow = -1;
}
// temp arrays for new positions
s16 mapNewCol[ENEMY_COUNT], mapNewRow[ENEMY_COUNT];
s16 mapNewHumanCol[HUMAN_COUNT], mapNewHumanRow[HUMAN_COUNT];
static bool mapTileOccupied(s16 col, s16 row, s16 pRow){
// player always at center column
if(col == MAP_W / 2 && row == pRow) return TRUE;
for(s16 i = 0; i < ENEMY_COUNT; i++)
if(mapNewCol[i] == col && mapNewRow[i] == row) return TRUE;
for(s16 i = 0; i < HUMAN_COUNT; i++)
if(mapNewHumanCol[i] == col && mapNewHumanRow[i] == row) return TRUE;
return FALSE;
}
@ -67,6 +111,24 @@ static void updateMap(){
mapNewRow[i] = row;
}
// compute new human positions
for(s16 i = 0; i < HUMAN_COUNT; i++){
if(!humans[i].active || humans[i].image == NULL){
mapNewHumanCol[i] = -1;
mapNewHumanRow[i] = -1;
continue;
}
fix32 dx = getWrappedDelta(humans[i].pos.x, player.pos.x);
s16 col = fix32ToInt(dx) / 54 + MAP_W / 2;
if(col < 0) col = 0;
if(col >= MAP_W) col = MAP_W - 1;
s16 row = fix32ToInt(humans[i].pos.y) / 75;
if(row < 0) row = 0;
if(row >= MAP_H) row = MAP_H - 1;
mapNewHumanCol[i] = col;
mapNewHumanRow[i] = row;
}
// clear old player tile if it moved and nothing new occupies it
if(mapPlayerRow >= 0 && mapPlayerRow != pRow)
if(!mapTileOccupied(MAP_W / 2, mapPlayerRow, pRow))
@ -80,6 +142,23 @@ static void updateMap(){
VDP_setTileMapXY(BG_A, MAP_TILE, MAP_X + mapEnemyCol[i], MAP_Y + mapEnemyRow[i]);
}
// clear old human tiles that moved or disappeared
for(s16 i = 0; i < HUMAN_COUNT; i++){
if(mapHumanCol[i] < 0) continue;
if(mapHumanCol[i] == mapNewHumanCol[i] && mapHumanRow[i] == mapNewHumanRow[i]) continue;
if(!mapTileOccupied(mapHumanCol[i], mapHumanRow[i], pRow))
VDP_setTileMapXY(BG_A, MAP_TILE, MAP_X + mapHumanCol[i], MAP_Y + mapHumanRow[i]);
}
// draw human dots (skip if player occupies same tile)
for(s16 i = 0; i < HUMAN_COUNT; i++){
mapHumanCol[i] = mapNewHumanCol[i];
mapHumanRow[i] = mapNewHumanRow[i];
if(mapNewHumanCol[i] < 0) continue;
if(mapNewHumanCol[i] == MAP_W / 2 && mapNewHumanRow[i] == pRow) continue;
VDP_setTileMapXY(BG_A, MAP_HUMAN_TILE, MAP_X + mapNewHumanCol[i], MAP_Y + mapNewHumanRow[i]);
}
// draw enemy dots (skip if player occupies same tile)
for(s16 i = 0; i < ENEMY_COUNT; i++){
mapEnemyCol[i] = mapNewCol[i];
@ -95,18 +174,42 @@ static void updateMap(){
}
void loadChrome(){
VDP_loadTileSet(imageFontBig.tileset, FONT_BIG_I, DMA);
VDP_loadTileSet(imageFontBigShadow.tileset, FONT_BIG_I + 32, DMA);
VDP_loadTileSet(imageChromeLife.tileset, LIFE_I, DMA);
VDP_loadTileSet(imageChromeLife2.tileset, LIFE_I + 2, DMA);
VDP_loadTileSet(mapIndicator.tileset, MAP_I, DMA);
lastScore = 1;
drawScore();
drawLives();
}
bool didGameOver;
static void doGameOver(){
didGameOver = TRUE;
for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) SPR_setPalette(bullets[i].image, PAL1);
for(s16 i = 0; i < ENEMY_COUNT; i++) if(enemies[i].active) SPR_setPalette(enemies[i].image, PAL1);
for(s16 i = 0; i < HUMAN_COUNT; i++) if(humans[i].active) SPR_setPalette(humans[i].image, PAL1);
SPR_releaseSprite(player.image);
// clear minimap
VDP_clearTileMapRect(BG_A, MAP_X, MAP_Y, MAP_W, MAP_H);
// clear score
VDP_clearTileMapRect(BG_A, SCORE_X, SCORE_Y, SCORE_LENGTH, 2);
// clear lives
VDP_clearTileMapRect(BG_A, LIVES_X, LIVES_Y, 1, 16);
VDP_drawText("GAME OVER", 15, 13);
VDP_drawText("PRESS ANY BUTTON", 12, 14);
}
void updateChrome(){
score++;
if(score > 99999999) score = 0;
if(gameOver && !didGameOver) doGameOver();
if(didGameOver) return;
if(lastScore != score){
lastScore = score;
drawScore();
}
if(lastLives != player.lives) drawLives();
if(clock % 4 == 0) updateMap();
// VDP_clearText(1, 26, 4);
// VDP_drawText(debugStr, 1, 26);
}

View file

@ -34,7 +34,7 @@ void spawnEnemy(u8 type, u8 zone){
do {
// Random X within zone: zoneStart + random(0-511)
randX = zoneStart + FIX32(random() % 512);
randY = FIX32(16 + (random() % 192));
randY = FIX32(16 + (random() % 128));
attempts++;
} while(!isValidEnemyPosition(randX, randY) && attempts < 100);
@ -61,8 +61,22 @@ void spawnEnemy(u8 type, u8 zone){
}
static void boundsEnemy(u8 i){
if(enemies[i].pos.y >= GAME_H_F - FIX32(enemies[i].off) || enemies[i].pos.y <= FIX32(enemies[i].off))
enemies[i].vel.y *= -1;
if(enemies[i].ints[3] >= 0){
// carrying: only check for reaching the top
if(enemies[i].pos.y <= FIX32(0)){
s16 h = enemies[i].ints[3];
if(humans[h].active) killHuman(h);
enemies[i].ints[3] = -1;
humanBeingCarried = FALSE;
// TODO: spawn mutant here
killEnemy(i);
return;
}
} else {
// not carrying: bounce off top and bottom
if(enemies[i].pos.y >= GAME_H_F - FIX32(enemies[i].off) || enemies[i].pos.y <= FIX32(enemies[i].off))
enemies[i].vel.y *= -1;
}
if(enemies[i].pos.x >= GAME_WRAP){
enemies[i].pos.x -= GAME_WRAP;
@ -73,7 +87,6 @@ static void boundsEnemy(u8 i){
}
static void updateEnemy(u8 i){
boundsEnemy(i);
enemies[i].pos.x += enemies[i].vel.x;
enemies[i].pos.y += enemies[i].vel.y;
@ -83,6 +96,9 @@ static void updateEnemy(u8 i){
break;
}
boundsEnemy(i);
if(!enemies[i].active) return;
s16 sx = getScreenX(enemies[i].pos.x, player.camera);
s16 sy = fix32ToInt(enemies[i].pos.y);
fix32 dx = getWrappedDelta(enemies[i].pos.x, player.pos.x);

View file

@ -1,18 +1,81 @@
void loadEnemyOne(u8 i){
enemies[i].ints[0] = random() % 40;
enemies[i].ints[0] = random() % 60;
enemies[i].ints[2] = -1; // target human index
enemies[i].ints[3] = -1; // carried human index
enemies[i].angle = ((random() % 4) * 256) + 128;
enemies[i].speed = FIX32(2);
}
void updateEnemyOne(u8 i){
if(enemies[i].clock % 40 == enemies[i].ints[0] && enemies[i].onScreen){
enemies[i].clock % 80 == enemies[i].ints[0] ? sfxEnemyShotB() : sfxEnemyShotA();
// carrying behavior: move upward, skip shooting
if(enemies[i].ints[3] >= 0){
// enemies[i].vel.x = (enemies[i].vel.x > 0) ? FIX32(0.3) : FIX32(-0.3);
// enemies[i].vel.y = FIX32(-1.5);
enemies[i].angle = 704 + (random() % 128);
enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed);
enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed);
return;
}
// cancel any target if a human is already being carried
if(humanBeingCarried && enemies[i].ints[2] >= 0){
enemies[i].ints[2] = -1;
}
// seeking behavior: periodically look for a human to grab
if(!humanBeingCarried && enemies[i].clock % 30 == (u32)(enemies[i].ints[0]) % 30){
s16 bestHuman = -1;
fix32 bestDist = FIX32(9999);
for(s16 j = 0; j < HUMAN_COUNT; j++){
if(!humans[j].active || humans[j].state != HUMAN_WALKING) continue;
fix32 dx = getWrappedDelta(enemies[i].pos.x, humans[j].pos.x);
fix32 dy = enemies[i].pos.y - humans[j].pos.y;
fix32 dist = (dx < 0 ? -dx : dx) + (dy < 0 ? -dy : dy);
if(dist < bestDist && dist < FIX32(256)){
bestDist = dist;
bestHuman = j;
}
}
enemies[i].ints[2] = bestHuman;
}
// steer toward target human
if(enemies[i].ints[2] >= 0){
s16 t = enemies[i].ints[2];
if(!humans[t].active || humans[t].state != HUMAN_WALKING){
enemies[i].ints[2] = -1;
} else {
fix32 dx = getWrappedDelta(humans[t].pos.x, enemies[i].pos.x);
fix32 dy = humans[t].pos.y - enemies[i].pos.y;
// hone toward human's current position at base speed
s16 angle = honeAngle(
fix32ToFix16(enemies[i].pos.x), fix32ToFix16(humans[t].pos.x),
fix32ToFix16(enemies[i].pos.y), fix32ToFix16(humans[t].pos.y));
enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(angle)), enemies[i].speed);
enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(angle)), enemies[i].speed);
// grab check: within 16px
fix32 adx = dx < 0 ? -dx : dx;
fix32 ady = dy < 0 ? -dy : dy;
if(adx < FIX32(16) && ady < FIX32(16)){
enemies[i].ints[3] = t;
enemies[i].ints[2] = -1;
humanBeingCarried = TRUE;
humans[t].state = HUMAN_CARRIED;
humans[t].carriedBy = i;
return;
}
}
}
// normal shooting
if(enemies[i].clock % 60 == enemies[i].ints[0] && enemies[i].onScreen){
enemies[i].clock % 120 == enemies[i].ints[0] ? sfxEnemyShotB() : sfxEnemyShotA();
struct bulletSpawner spawner = {
.x = enemies[i].pos.x,
.y = enemies[i].pos.y,
.anim = 3 + (random() % 3),
// .anim = 6,
// .frame = 1,
.speed = FIX32(2) + FIX16(random() % 4),
.angle = random() % 128,
};
@ -26,16 +89,3 @@ void updateEnemyOne(u8 i){
}
}
}
void updateLander(u8 i){}
void updateMutant(u8 i){}
void updateSwarmer(u8 i){}
void updatePod(u8 i){}
void updateBomber(u8 i){}
void updateBaiter(u8 i){}

View file

@ -50,15 +50,16 @@ void updateControls(u16 joy, u16 changed, u16 state){
struct playerStruct {
Vect2D_f32 pos, vel;
s16 shotAngle;
u8 lives;
u8 lives, recoveringClock;
fix32 camera, yCamera;
Sprite* image;
};
struct playerStruct player;
bool killBullets;
// bullets
#define BULLET_COUNT 64
#define BULLET_COUNT 70
struct bulletSpawner {
fix32 x, y, speed;
@ -92,6 +93,31 @@ struct enemy {
};
struct enemy enemies[ENEMY_COUNT];
// humans
#define HUMAN_COUNT 8
#define HUMAN_WALKING 0
#define HUMAN_CARRIED 1
#define HUMAN_FALLING 2
struct human {
bool active;
u8 state;
s16 carriedBy;
Vect2D_f32 pos, vel;
Sprite* image;
};
struct human humans[HUMAN_COUNT];
bool humanBeingCarried;
void killHuman(u8 i){
if(humans[i].state == HUMAN_CARRIED && humans[i].carriedBy >= 0){
enemies[humans[i].carriedBy].ints[3] = -1;
humanBeingCarried = FALSE;
}
humans[i].active = FALSE;
SPR_releaseSprite(humans[i].image);
}
void killBullet(u8 i, bool explode){
if(explode){
if(bullets[i].player){
@ -120,6 +146,16 @@ void killBullet(u8 i, bool explode){
}
void killEnemy(u8 i){
if(enemies[i].ints[3] >= 0){
s16 h = enemies[i].ints[3];
if(humans[h].active){
humans[h].state = HUMAN_FALLING;
humans[h].carriedBy = -1;
humans[h].vel.x = 0;
humans[h].vel.y = FIX32(2);
}
humanBeingCarried = FALSE;
}
enemies[i].active = FALSE;
SPR_releaseSprite(enemies[i].image);
}
@ -142,4 +178,31 @@ static s16 getScreenX(fix32 worldX, fix32 camera) {
screenX -= GAME_WRAP;
}
return fix32ToInt(screenX);
}
// homing
#define PI_MOD 2.84444444444
#define PI_F FIX16(3.14159265358 * PI_MOD)
#define PI_F_2 FIX16(1.57079632679 * PI_MOD)
#define PI_F_4 FIX16(0.78539816339 * PI_MOD)
fix16 arctan(fix16 x) {
return fix16Mul(PI_F_4, x) - fix16Mul(fix16Mul(x, (abs(x) - 1)), (FIX16(0.245) + fix16Mul(FIX16(0.066), abs(x))));
}
fix16 arctan2(fix16 y, fix16 x) {
return x >= 0 ?
(y >= 0 ? (y < x ? arctan(fix16Div(y, x)) : PI_F_2 - arctan(fix16Div(x, y))) : (-y < x ? arctan(fix16Div(y, x)) : -PI_F_2 - arctan(fix16Div(x, y)))) :
(y >= 0 ? (y < -x ? arctan(fix16Div(y, x)) + PI_F : PI_F_2 - arctan(fix16Div(x, y))) : (-y < -x ? arctan(fix16Div(y, x)) - PI_F : -PI_F_2 - arctan(fix16Div(x, y))));
}
s16 arcAngle;
s16 honeAngle(fix16 x1, fix16 x2, fix16 y1, fix16 y2){
arcAngle = arctan2(y2 - y1, x2 - x1);
if(arcAngle >= 128) arcAngle -= 32;
if(arcAngle >= 384) arcAngle -= 32;
if(arcAngle < 0){
arcAngle = 1024 + arcAngle;
if(arcAngle < 896) arcAngle += 32;
if(arcAngle < 640) arcAngle += 32;
}
return arcAngle;
}

96
src/humans.h Normal file
View file

@ -0,0 +1,96 @@
void spawnHuman(u8 zone){
s16 i = -1;
for(s16 j = 0; j < HUMAN_COUNT; j++) if(!humans[j].active) { i = j; break; }
if(i == -1) return;
humans[i].active = TRUE;
humans[i].state = HUMAN_WALKING;
humans[i].carriedBy = -1;
fix32 zoneStart = FIX32(zone * 512);
humans[i].pos.x = zoneStart + FIX32(random() % 512);
humans[i].pos.y = GAME_H_F - FIX32(24);
fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) };
humans[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3];
humans[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1);
humans[i].image = SPR_addSprite(&humanSprite,
getScreenX(humans[i].pos.x, player.camera), fix32ToInt(humans[i].pos.y),
TILE_ATTR(PAL0, 0, 0, 0));
if(!humans[i].image){
humans[i].active = FALSE;
return;
}
}
static void updateHuman(u8 i){
switch(humans[i].state){
case HUMAN_WALKING:
// Y bounce: bob 4px around ground level
if(humans[i].pos.y >= GAME_H_F - FIX32(20) || humans[i].pos.y <= GAME_H_F - FIX32(28))
humans[i].vel.y *= -1;
// X wrap
if(humans[i].pos.x >= GAME_WRAP)
humans[i].pos.x -= GAME_WRAP;
if(humans[i].pos.x < 0)
humans[i].pos.x += GAME_WRAP;
humans[i].pos.x += humans[i].vel.x;
humans[i].pos.y += humans[i].vel.y;
break;
case HUMAN_CARRIED:
// follow carrier enemy position
if(humans[i].carriedBy >= 0 && enemies[humans[i].carriedBy].active){
humans[i].pos.x = enemies[humans[i].carriedBy].pos.x;
humans[i].pos.y = enemies[humans[i].carriedBy].pos.y + FIX32(16);
} else {
// carrier died (shouldn't normally reach here, killEnemy handles it)
humans[i].state = HUMAN_FALLING;
humans[i].carriedBy = -1;
humans[i].vel.x = 0;
humans[i].vel.y = FIX32(2);
humanBeingCarried = FALSE;
}
break;
case HUMAN_FALLING:
humans[i].pos.y += humans[i].vel.y;
// land on ground
if(humans[i].pos.y >= GAME_H_F - FIX32(24)){
humans[i].pos.y = GAME_H_F - FIX32(24);
humans[i].state = HUMAN_WALKING;
fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) };
humans[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3];
humans[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1);
}
break;
}
// collect: check overlap with player (walking or falling only)
fix32 dx = getWrappedDelta(humans[i].pos.x, player.pos.x);
if(humans[i].state != HUMAN_CARRIED){
fix32 dy = humans[i].pos.y - player.pos.y;
if(dx >= FIX32(-24) && dx <= FIX32(24) && dy >= FIX32(-24) && dy <= FIX32(24)){
score += (humans[i].state == HUMAN_FALLING) ? 2000 : 1000;
sfxPickup();
killHuman(i);
return;
}
}
s16 sx = getScreenX(humans[i].pos.x, player.camera);
s16 sy = fix32ToInt(humans[i].pos.y);
bool visible = (dx >= -CULL_LIMIT && dx <= CULL_LIMIT);
SPR_setVisibility(humans[i].image, visible ? VISIBLE : HIDDEN);
SPR_setPosition(humans[i].image, sx, sy);
}
void updateHumans(){
for(s16 i = 0; i < HUMAN_COUNT; i++)
if(humans[i].active)
updateHuman(i);
}

View file

@ -5,6 +5,7 @@
#include "background.h"
#include "bullets.h"
#include "enemies.h"
#include "humans.h"
#include "player.h"
#include "stage.h"
#include "chrome.h"
@ -35,6 +36,8 @@ static void updateGame(){
updateSfx();
if(clock % 2 == 0){
updateEnemies();
if(!gameOver && enemyCount == 0) gameOver = TRUE;
updateHumans();
} else {
updateBackground();
updateBullets();

View file

@ -9,8 +9,8 @@
#define PLAYER_BOUND_Y FIX32(PLAYER_OFF)
#define PLAYER_BOUND_H FIX32(224 - PLAYER_OFF)
#define CAMERA_X FIX32(80)
#define CAMERA_W FIX32(240)
#define CAMERA_X FIX32(96)
#define CAMERA_W FIX32(224)
#define SHOT_INTERVAL 15
@ -106,7 +106,10 @@ static void shootPlayer(){
.angle = player.shotAngle,
.player = TRUE
};
spawnBullet(spawner, EMPTY);
void updater(s16 i){
if(bullets[i].clock == 5) killBullet(i, TRUE);
}
spawnBullet(spawner, updater);
sfxPlayerShot();
shotClock = SHOT_INTERVAL;
} else if(shotClock > 0) shotClock--;
@ -127,16 +130,21 @@ void loadPlayer(){
}
void updatePlayer(){
movePlayer();
boundsPlayer();
cameraPlayer();
shootPlayer();
s16 sx = getScreenX(player.pos.x, player.camera);
s16 sy = fix32ToInt(player.pos.y);
SPR_setPosition(player.image, sx - PLAYER_OFF, sy - PLAYER_OFF);
intToStr(fix32ToInt(player.pos.x), debugStr, 1);
if(!gameOver){
if(player.recoveringClock > 0){
if(player.recoveringClock % 10 == 1)
SPR_setVisibility(player.image, player.recoveringClock % 20 == 1 ? VISIBLE : HIDDEN);
player.recoveringClock--;
if(player.recoveringClock == 0)
SPR_setVisibility(player.image, VISIBLE);
}
movePlayer();
boundsPlayer();
cameraPlayer();
shootPlayer();
s16 sx = getScreenX(player.pos.x, player.camera);
s16 sy = fix32ToInt(player.pos.y);
SPR_setPosition(player.image, sx - PLAYER_OFF, sy - PLAYER_OFF);
intToStr(fix32ToInt(player.pos.x), debugStr, 1);
}
}

View file

@ -1,10 +1,12 @@
static s16 sfxShotClock;
static u16 sfxShotFreq;
#define SFX_VOL 2
void sfxPlayerShot(){
sfxShotClock = 4;
sfxShotFreq = 150;
PSG_setEnvelope(2, 2);
PSG_setEnvelope(2, SFX_VOL);
PSG_setFrequency(2, sfxShotFreq);
}
@ -14,43 +16,56 @@ static u8 sfxEnemyShotType;
// high sharp zap - quick descending chirp
void sfxEnemyShotA(){
if(player.recoveringClock > 0) return;
sfxEnemyShotClock = 3;
sfxEnemyShotFreq = 1200;
sfxEnemyShotType = 0;
PSG_setEnvelope(1, 3);
PSG_setEnvelope(1, SFX_VOL);
PSG_setFrequency(1, sfxEnemyShotFreq);
}
// mid buzzy pulse - sits in the midrange
void sfxEnemyShotB(){
if(player.recoveringClock > 0) return;
sfxEnemyShotClock = 5;
sfxEnemyShotFreq = 400;
sfxEnemyShotType = 1;
PSG_setEnvelope(1, 3);
PSG_setEnvelope(1, SFX_VOL);
PSG_setFrequency(1, sfxEnemyShotFreq);
}
// quick rising ping - sweeps upward
void sfxEnemyShotC(){
if(player.recoveringClock > 0) return;
sfxEnemyShotClock = 4;
sfxEnemyShotFreq = 300;
sfxEnemyShotType = 2;
PSG_setEnvelope(1, 3);
PSG_setEnvelope(1, SFX_VOL);
PSG_setFrequency(1, sfxEnemyShotFreq);
}
static s16 sfxPickupClock;
static u16 sfxPickupFreq;
void sfxPickup(){
sfxPickupClock = 12;
sfxPickupFreq = 800;
PSG_setEnvelope(0, SFX_VOL);
PSG_setFrequency(0, sfxPickupFreq);
}
static s16 sfxExpClock;
void sfxExplosion(){
sfxExpClock = 18;
PSG_setNoise(PSG_NOISE_TYPE_WHITE, PSG_NOISE_FREQ_CLOCK2);
PSG_setEnvelope(3, 0);
PSG_setEnvelope(3, SFX_VOL);
}
void updateSfx(){
if(sfxExpClock > 0){
sfxExpClock--;
PSG_setEnvelope(3, (18 - sfxExpClock) * 15 / 18);
PSG_setEnvelope(3, SFX_VOL + (18 - sfxExpClock) * (15 - SFX_VOL) / 18);
if(sfxExpClock == 0){
PSG_setEnvelope(3, 15);
}
@ -61,18 +76,28 @@ void updateSfx(){
else if(sfxEnemyShotType == 1) sfxEnemyShotFreq -= 50;
else sfxEnemyShotFreq += 150;
PSG_setFrequency(1, sfxEnemyShotFreq);
PSG_setEnvelope(1, 3 + (sfxEnemyShotType == 0 ? (3 - sfxEnemyShotClock) * 4 :
sfxEnemyShotType == 1 ? (5 - sfxEnemyShotClock) * 2 :
(4 - sfxEnemyShotClock) * 3));
PSG_setEnvelope(1, SFX_VOL + (sfxEnemyShotType == 0 ? (3 - sfxEnemyShotClock) * (15 - SFX_VOL) / 3 :
sfxEnemyShotType == 1 ? (5 - sfxEnemyShotClock) * (15 - SFX_VOL) / 5 :
(4 - sfxEnemyShotClock) * (15 - SFX_VOL) / 4));
if(sfxEnemyShotClock == 0){
PSG_setEnvelope(1, 15);
}
}
if(sfxPickupClock > 0){
sfxPickupClock--;
// rising staircase: jump up every 3 frames
if(sfxPickupClock % 3 == 0) sfxPickupFreq += 200;
PSG_setFrequency(0, sfxPickupFreq);
PSG_setEnvelope(0, SFX_VOL);
if(sfxPickupClock == 0){
PSG_setEnvelope(0, 15);
}
}
if(sfxShotClock > 0){
sfxShotClock--;
sfxShotFreq -= 30;
PSG_setFrequency(2, sfxShotFreq);
PSG_setEnvelope(2, 2 + (4 - sfxShotClock) * 3);
PSG_setEnvelope(2, SFX_VOL + (4 - sfxShotClock) * (15 - SFX_VOL) / 4);
if(sfxShotClock == 0){
PSG_setEnvelope(2, 15);
}

View file

@ -1,9 +1,15 @@
void loadStage(){
// Spawn 2 enemies per zone (4 zones = 8 total)
// Spawn 3 enemies per zone (4 zones = 12 total)
for(u8 zone = 0; zone < 4; zone++){
for(u8 i = 0; i < 3; i++){
spawnEnemy(0, zone);
}
}
// Spawn 2 humans per zone (4 zones = 8 total)
for(u8 zone = 0; zone < 4; zone++){
for(u8 i = 0; i < 2; i++){
spawnHuman(zone);
}
}
loadMap();
}