diff --git a/assets/bg/title.png b/assets/bg/title.png new file mode 100644 index 0000000..4cb5ac0 Binary files /dev/null and b/assets/bg/title.png differ diff --git a/assets/tiles.png b/assets/tiles.png deleted file mode 100644 index 97f5df9..0000000 Binary files a/assets/tiles.png and /dev/null differ diff --git a/src/actor.c b/src/actor.c index 8e4649f..4e51cfa 100644 --- a/src/actor.c +++ b/src/actor.c @@ -9,6 +9,9 @@ #define MAX_ACTORS 5 #define NUM_OAM_ENTRIES 40 #define TILE_WIDTH 8 +#define FRAMES_PER_ANIM_FRAME 20 + +static actor_anim_state_t ANIM_TOTAL_FRAMES[] = {2}; static uint8_t mob_ids[MAX_UNIQUE_MOBS]; static sprite16_mob mob_anim_data[MAX_UNIQUE_MOBS]; @@ -54,12 +57,31 @@ actor_t *actor_create_mob(uint8_t id) { void actor_remove(actor_t *a) { a->active = 0; } +void actor_update(void) { + actor_t *a = &all_actors[0]; + + for (uint8_t i = 0; i < ARRSIZE(all_actors); ++i) { + if (!a->active) { + continue; + } + + a->frame_counter++; + if (a->frame_counter == FRAMES_PER_ANIM_FRAME) { + a->frame_counter = 0; + a->frame_idx = (a->frame_idx + 1) % ANIM_TOTAL_FRAMES[a->anim]; + } + + a++; + } +} + void actor_flush_oam(void) { struct oam_entry *oam_ptr = &shadow_oam[0]; actor_t *a = &all_actors[0]; for (uint8_t i = 0; i < ARRSIZE(all_actors); ++i) { if (!a->active) { + a++; continue; } @@ -116,4 +138,4 @@ static uint8_t actor_load_mob_anim_data(uint8_t mob_id) { } return mob_anim_idx; -} \ No newline at end of file +} diff --git a/src/actor.h b/src/actor.h index 7773ce7..e9f24f9 100644 --- a/src/actor.h +++ b/src/actor.h @@ -16,6 +16,7 @@ typedef enum { typedef struct { uint8_t active; + uint8_t dirty; uint8_t mob_anim_idx; actor_anim_state_t anim; uint8_t frame_idx; @@ -54,6 +55,11 @@ actor_t *actor_create_mob(mob_id_t id); */ void actor_remove(actor_t *a); +/** + * @brief Update the internal state of each actor. + */ +void actor_update(void); + /** * @brief Flush actor sprite data to OAM. */ diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..5ffe29f --- /dev/null +++ b/src/game.h @@ -0,0 +1,47 @@ +#ifndef IS_GAME_H_ +#define IS_GAME_H_ + +#define _VRAM 0x8000 +#define _VRAM8000 _VRAM +#define _VRAM8800 (_VRAM + 0x800) +#define _VRAM9000 (_VRAM + 0x1000) +#define _SCRN0 0x9800 +#define _SCRN1 0x9c00 + +#define SCRN_X 160 +#define SCRN_Y 144 +#define SCRN_X_B 20 +#define SCRN_Y_B 18 + +#define SCRN_VX 256 +#define SCRN_VY 256 +#define SCRN_VX_B 32 +#define SCRN_VX_Y 32 + +#define SCREEN_HEIGHT_TILES 18 +#define SCREEN_WIDTH_TILES 20 + +typedef enum { + GAME_STATE_TITLE, + GAME_STATE_LEVEL, + GAME_STATE_MENU, +} game_state_t; + +extern game_state_t game_state; + +/** + * @brief Render the title screen. + */ +void title(void); + +/** + * @brief Enable interrupts. + */ +void interrupts_enable(void) __preserves_regs(a, b, c, d, e, h, l); + +/** + * @brief Disable interrupts. + */ +void interrupts_disable(void) __preserves_regs(a, b, c, d, e, h, l); + +#endif diff --git a/src/helpers.asm b/src/helpers.asm new file mode 100644 index 0000000..df4ff77 --- /dev/null +++ b/src/helpers.asm @@ -0,0 +1,14 @@ +SECTION "vblank", ROM0[$40] + +handle_vblank: + reti + +SECTION "interrupt routines", ROM0 + +_interrupts_disable:: + di + ret + +_interrupts_enable:: + ei + ret diff --git a/src/main.c b/src/main.c index 05ab2c3..8e8894d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,80 +1,34 @@ #include #include "actor.h" -#include "sdk/assets.h" +#include "game.h" #include "sdk/hardware.h" -#include "sdk/joypad.h" #include "sdk/oam.h" -#include "sdk/video.h" -ASSET(tiles, "tiles.2bpp"); - -uint8_t cursor_x = 0; -uint8_t cursor_y = 0; +game_state_t game_state; void main() { - // Load our initial vram (this is slow, but always works, even outside of - // vblank) - vram_memcpy(0x8000, tiles, tiles_end - tiles); - vram_memcpy(0x9000, tiles, tiles_end - tiles); + game_state = GAME_STATE_TITLE; + // Setup the OAM for sprite drawing oam_init(); - // Set some default DMG palette data + // Set up background & sprites rBGP = 0b11100100; rOBP0 = 0b11100100; rOBP1 = 0b11100100; - - // Put some sprites on the screen - shadow_oam[0].y = 0x20; - shadow_oam[0].x = 0x20; - shadow_oam[0].tile = 0x04; - shadow_oam[0].attr = 0x00; - shadow_oam[1].y = 0x24; - shadow_oam[1].x = 0x24; - shadow_oam[1].tile = 0x02; - shadow_oam[1].attr = 0x00; - - // Make sure sprites and the background are drawn - rLCDC = LCDC_ON | LCDC_OBJON | LCDC_BGON; + rLCDC = LCDC_OBJON | LCDC_BGON | LCDC_BG8000; // Setup the VBLANK interrupt, but we don't actually enable interrupt - // handling. - // We only do this, so HALT waits for VBLANK. + // handling. We only do this so HALT waits for VBLANK. rIF = 0; rIE = IE_VBLANK; - vram_memset(0x9800, 0x05, 32 * 18); - for (uint8_t x = 0; x < 20; x++) { - vram_set(0x9800 + x, 0x07); - vram_set(0x9A20 + x, 0x07); - } - for (uint8_t y = 1; y < 18; y++) { - vram_set(0x9800 + y * 0x20, 0x07); - vram_set(0x9800 + 19 + y * 0x20, 0x07); - } - vram_set(0x9800 + 3 + 3 * 0x20, 0x01); - while (1) { - joypad_update(); - if (joypad_state & PAD_LEFT) cursor_x--; - if (joypad_state & PAD_RIGHT) cursor_x++; - if (joypad_state & PAD_UP) cursor_y--; - if (joypad_state & PAD_DOWN) cursor_y++; - if (joypad_state & (PAD_A | PAD_B)) { - vram_set(0x9800 + cursor_x / 8 + cursor_y / 8 * 0x20, 0x03); + switch (game_state) { + case GAME_STATE_TITLE: + title(); + break; } - - // Screen to OAM coordinates - shadow_oam[0].y = cursor_y + 16; - shadow_oam[0].x = cursor_x + 8; - - // Wait for VBLANK - HALT(); - rIF = 0; // As global interrupts are not enabled, we need to clear the - // interrupt flag. - - // Copy the sprites into OAM memory - oam_dma_copy(); } } diff --git a/src/title.c b/src/title.c new file mode 100644 index 0000000..13e2276 --- /dev/null +++ b/src/title.c @@ -0,0 +1,44 @@ +#include + +#include "game.h" +#include "sdk/assets.h" +#include "sdk/hardware.h" +#include "sdk/joypad.h" +#include "sdk/oam.h" +#include "sdk/video.h" + +ASSET(bg_tiles, "bg/title.2bpp"); +ASSET(bg_map, "bg/title.map"); + +void title(void) { + // NOTE: Replace with lcdc_off() if necessary + rLCDC &= ~LCDC_ON; + + // Copy title screen to VRAM + memcpy((uint8_t *)_VRAM, &bg_tiles[0], bg_tiles_end - bg_tiles); + // vram_memcpy(_VRAM, &bg_tiles[0], bg_tiles_end - bg_tiles); + + uint8_t *vram_ptr = (uint8_t *)_SCRN0; + const uint8_t *map_ptr = &bg_map[0]; + + for (uint8_t y = 0; y < SCRN_Y_B; ++y) { + memcpy(vram_ptr, map_ptr, SCRN_X_B); + vram_ptr += SCRN_VX_B; + map_ptr += SCRN_X_B; + } + + rLCDC |= LCDC_ON; + interrupts_enable(); + + while (1) { + joypad_update(); + if (joypad_state & PAD_START) { + game_state = GAME_STATE_LEVEL; + break; + } + + // Wait for VBLANK. Clear IF since global interrupts are disabled. + HALT(); + rIF = 0; + } +}