mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
gb: added presets
This commit is contained in:
1 parent
602e56db58
commit
20e9827210
9 files changed
+672
-11
No files matched your search
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
Color Game Boy demo.
|
||||
|
||||
A CGB has 8 background and 8 sprite palettes of 4 colors each, chosen
|
||||
from a 15-bit RGB palette, plus a double-speed CPU mode. This demo checks
|
||||
_cpu to see which machine it is running on. On a CGB it:
|
||||
|
||||
- pulses one background palette entry smoothly with
|
||||
set_bkg_palette_entry(),
|
||||
- toggles between vivid color and the DMG-compatible grayscale palettes
|
||||
with cgb_compatibility() (press A),
|
||||
- switches the CPU between normal and double speed with cpu_slow() and
|
||||
cpu_fast() (press START), and shows the difference with a busy-loop
|
||||
counter that counts how many iterations fit between two VBlanks.
|
||||
|
||||
On a monochrome Game Boy it falls back to the four-shade DMG palettes.
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/cgb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
// 2x2 checkerboard tile (colors 1 and 2)
|
||||
static const uint8_t checker_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0xCC,0xCC,0x33,0x33,0xCC,0xCC,0x33,0x33,
|
||||
0x33,0x33,0xCC,0xCC,0x33,0x33,0xCC,0xCC
|
||||
/*;;*/
|
||||
};
|
||||
|
||||
// background palette. color 1 is replaced each frame by pulse().
|
||||
palette_color_t colors[4] = {
|
||||
RGB_BLACK, RGB_RED, RGB_DARKBLUE, RGB_WHITE
|
||||
};
|
||||
|
||||
// smooth pastel rainbow: three triangle waves 120 degrees apart
|
||||
static uint16_t pulse(uint8_t t) {
|
||||
uint8_t r = (t + 0) & 63;
|
||||
uint8_t g = (t + 21) & 63;
|
||||
uint8_t b = (t + 42) & 63;
|
||||
if (r >= 32) r = 63 - r;
|
||||
if (g >= 32) g = 63 - g;
|
||||
if (b >= 32) b = 63 - b;
|
||||
return RGB(r, g, b);
|
||||
}
|
||||
|
||||
volatile uint8_t vbl_flag;
|
||||
void vbl_handler(void) NONBANKED { vbl_flag++; }
|
||||
|
||||
uint8_t cgb_mode;
|
||||
uint8_t color_mode = 1;
|
||||
uint8_t fast_mode = 0;
|
||||
|
||||
void main(void) {
|
||||
uint8_t keys, last_keys = 0;
|
||||
uint8_t frame = 0;
|
||||
char buf[6];
|
||||
|
||||
cgb_mode = DEVICE_SUPPORTS_COLOR;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = OBP0_REG = OBP1_REG = 0xE4;
|
||||
font_init();
|
||||
set_bkg_data(0, 1, checker_tile);
|
||||
fill_bkg_rect(0, 0, 32, 32, 0);
|
||||
|
||||
put_str(1, 0, cgb_mode ? "GAME BOY COLOR" : "GAME BOY");
|
||||
put_str(1, 2, "A: PALETTE");
|
||||
put_str(1, 3, "START: SPEED");
|
||||
put_str(1, 5, "SPEED: NORMAL");
|
||||
put_str(1, 7, "WORK: 00000");
|
||||
|
||||
if (cgb_mode)
|
||||
set_bkg_palette(0, 1, colors);
|
||||
|
||||
add_VBL(vbl_handler);
|
||||
set_interrupts(VBL_IFLAG);
|
||||
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
uint8_t start;
|
||||
uint16_t work;
|
||||
int8_t b;
|
||||
|
||||
wait_vbl_done();
|
||||
keys = joypad();
|
||||
|
||||
// toggle color vs. DMG-compatible grayscale palettes
|
||||
if ((keys & J_A) && !(last_keys & J_A)) {
|
||||
color_mode = !color_mode;
|
||||
if (cgb_mode) {
|
||||
if (color_mode)
|
||||
set_bkg_palette(0, 1, colors);
|
||||
else
|
||||
cgb_compatibility();
|
||||
}
|
||||
}
|
||||
|
||||
// toggle double speed
|
||||
if ((keys & J_START) && !(last_keys & J_START)) {
|
||||
fast_mode = !fast_mode;
|
||||
if (cgb_mode) {
|
||||
if (fast_mode) cpu_fast(); else cpu_slow();
|
||||
}
|
||||
put_str(8, 5, fast_mode ? "DOUBLE" : "NORMAL");
|
||||
}
|
||||
last_keys = keys;
|
||||
|
||||
// benchmark: count loop iterations between two VBlanks
|
||||
start = vbl_flag;
|
||||
work = 0;
|
||||
while (vbl_flag == start) work++;
|
||||
buf[5] = '\0';
|
||||
for (b = 4; b >= 0; b--) {
|
||||
buf[b] = '0' + (work % 10);
|
||||
work /= 10;
|
||||
}
|
||||
put_str(7, 7, buf);
|
||||
|
||||
// pulse palette entry 1 (CGB only)
|
||||
if (cgb_mode && color_mode)
|
||||
set_bkg_palette_entry(0, 1, pulse(frame));
|
||||
|
||||
frame++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Interrupts on Game Boy.
|
||||
|
||||
Three interrupts matter in most games: the VBlank at ~60 Hz, the
|
||||
programmable timer, and the LCD status line (see raster.c). GBDK installs
|
||||
a VBlank handler by default; add_VBL()/add_TIM() append your own handler
|
||||
to an interrupt's chain, and remove_*() takes it back out.
|
||||
|
||||
This demo counts VBlanks and timer overflows in two handlers. The timer
|
||||
runs at 65536 Hz, so with TMA=0 it overflows about 256 times a second,
|
||||
much faster than the VBlank. Press START to remove both handlers, then
|
||||
press it again to add them back.
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
// interrupt counters (volatile: written by the handlers)
|
||||
volatile uint8_t vbl_count;
|
||||
volatile uint16_t tim_count;
|
||||
uint8_t handlers_installed = 1;
|
||||
|
||||
// VBlank handler: called once per frame
|
||||
void vbl_handler(void) NONBANKED {
|
||||
vbl_count++;
|
||||
}
|
||||
|
||||
// timer handler: called when TIMA_REG overflows
|
||||
void tim_handler(void) NONBANKED {
|
||||
tim_count++;
|
||||
}
|
||||
|
||||
// write a zero-padded 5-digit decimal number to the tile map
|
||||
static void put_u16(uint8_t x, uint8_t y, uint16_t v) {
|
||||
char buf[6];
|
||||
int8_t i;
|
||||
buf[5] = '\0';
|
||||
for (i = 4; i >= 0; i--) {
|
||||
buf[i] = '0' + (v % 10);
|
||||
v /= 10;
|
||||
}
|
||||
put_str(x, y, buf);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t keys, last_keys = 0;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = OBP0_REG = OBP1_REG = 0xE4;
|
||||
font_init();
|
||||
|
||||
put_str(4, 0, "INTERRUPTS");
|
||||
put_str(2, 3, "VBLANK");
|
||||
put_str(2, 4, "00000");
|
||||
put_str(2, 7, "TIMER");
|
||||
put_str(2, 8, "00000");
|
||||
put_str(2, 12, "START: PAUSE");
|
||||
|
||||
// timer overflows every 256 ticks at 65536 Hz
|
||||
TMA_REG = 0x00;
|
||||
TAC_REG = 0x06;
|
||||
|
||||
// install our handlers and enable their interrupts
|
||||
add_VBL(vbl_handler);
|
||||
add_TIM(tim_handler);
|
||||
set_interrupts(VBL_IFLAG | TIM_IFLAG);
|
||||
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
uint16_t t;
|
||||
wait_vbl_done();
|
||||
|
||||
// edge-detect START to add/remove the handlers
|
||||
keys = joypad();
|
||||
if ((keys & J_START) && !(last_keys & J_START)) {
|
||||
handlers_installed = !handlers_installed;
|
||||
if (handlers_installed) {
|
||||
add_VBL(vbl_handler);
|
||||
add_TIM(tim_handler);
|
||||
put_str(2, 12, "START: PAUSE");
|
||||
} else {
|
||||
remove_VBL(vbl_handler);
|
||||
remove_TIM(tim_handler);
|
||||
put_str(2, 12, "START: GO");
|
||||
}
|
||||
}
|
||||
last_keys = keys;
|
||||
|
||||
// read the 16-bit timer count without a torn read
|
||||
CRITICAL { t = tim_count; }
|
||||
put_u16(2, 4, vbl_count);
|
||||
put_u16(2, 8, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Joypad input on Game Boy.
|
||||
|
||||
waitpad() blocks until the chosen buttons are held, and waitpadup() waits
|
||||
for them to be released again -- handy for title screens. For gameplay
|
||||
you poll joypad() once per frame and compare against the previous frame
|
||||
to find edges: a button that is down now but was up before has just been
|
||||
pressed.
|
||||
|
||||
This demo waits for START, then moves a sprite with the d-pad while
|
||||
counting A and B presses with edge detection.
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
// an 8x8 ball sprite (2bpp)
|
||||
static const uint8_t ball_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0x3C,0x3C,0x42,0x7E,0x99,0xFF,0xA9,0xFF,
|
||||
0x89,0xFF,0x89,0xFF,0x42,0x7E,0x3C,0x3C
|
||||
/*;;*/
|
||||
};
|
||||
|
||||
uint8_t a_count, b_count;
|
||||
|
||||
// write a zero-padded 3-digit decimal number
|
||||
static void put_u8(uint8_t x, uint8_t y, uint8_t v) {
|
||||
char buf[4];
|
||||
buf[3] = '\0';
|
||||
buf[2] = '0' + (v % 10);
|
||||
buf[1] = '0' + (v / 10) % 10;
|
||||
buf[0] = '0' + (v / 100) % 10;
|
||||
put_str(x, y, buf);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t keys, last_keys;
|
||||
uint8_t x = 80, y = 72;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = OBP0_REG = OBP1_REG = 0xE4;
|
||||
|
||||
font_init();
|
||||
set_sprite_data(0, 1, ball_tile);
|
||||
set_sprite_tile(0, 0);
|
||||
|
||||
put_str(4, 6, "PRESS START");
|
||||
|
||||
SHOW_BKG;
|
||||
SHOW_SPRITES;
|
||||
DISPLAY_ON;
|
||||
|
||||
// block until START is pressed, then wait for it to be released
|
||||
waitpad(J_START);
|
||||
waitpadup();
|
||||
|
||||
// replace the title message with the HUD
|
||||
fill_bkg_rect(4, 6, 11, 1, ' ');
|
||||
put_str(1, 0, "D-PAD: MOVE");
|
||||
put_str(1, 1, "A:000 B:000");
|
||||
a_count = b_count = 0;
|
||||
|
||||
move_sprite(0, x, y);
|
||||
|
||||
last_keys = 0;
|
||||
|
||||
while (1) {
|
||||
uint8_t pressed;
|
||||
|
||||
wait_vbl_done();
|
||||
keys = joypad();
|
||||
|
||||
// a button "just pressed" is down now and was up last frame
|
||||
pressed = keys & ~last_keys;
|
||||
if (pressed & J_A) {
|
||||
a_count++;
|
||||
put_u8(3, 1, a_count);
|
||||
}
|
||||
if (pressed & J_B) {
|
||||
b_count++;
|
||||
put_u8(11, 1, b_count);
|
||||
}
|
||||
|
||||
// dpad is read as held keys, so movement repeats while held
|
||||
if (keys & J_LEFT) x--;
|
||||
if (keys & J_RIGHT) x++;
|
||||
if (keys & J_UP) y--;
|
||||
if (keys & J_DOWN) y++;
|
||||
|
||||
// keep the sprite on screen
|
||||
if (x < 8) x = 8;
|
||||
if (x > 152) x = 152;
|
||||
if (y < 16) y = 16;
|
||||
if (y > 136) y = 136;
|
||||
|
||||
move_sprite(0, x, y);
|
||||
last_keys = keys;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
Metasprites combine several hardware sprites to make a larger
|
||||
sprite. Our demo uses 4 hardware sprites in a 2x2 pattern,
|
||||
forming 16x16 pixel sprites.
|
||||
forming 16x16 pixel sprites. Actors alternate between the normal
|
||||
draw, a horizontal flip and a vertical flip.
|
||||
|
||||
Ported from presets/nes/metasprites.c, using GBDK (gb/metasprites.h).
|
||||
The Game Boy OAM holds 40 sprites, so 10 actors of 4 sprites each
|
||||
@@ -41,6 +42,8 @@ uint8_t actor_y[NUM_ACTORS];
|
||||
int8_t actor_dx[NUM_ACTORS];
|
||||
int8_t actor_dy[NUM_ACTORS];
|
||||
|
||||
const int8_t deltas[4] = { -2, -1, 1, 2 };
|
||||
|
||||
// setup LCD and tile data
|
||||
void setup_graphics(void) {
|
||||
DISPLAY_OFF;
|
||||
@@ -60,20 +63,28 @@ void main(void) {
|
||||
setup_graphics();
|
||||
|
||||
// initialize actors with random values
|
||||
srand(0);
|
||||
for (i=0; i<NUM_ACTORS; i++) {
|
||||
actor_x[i] = rand();
|
||||
actor_y[i] = rand();
|
||||
actor_dx[i] = (rand() & 3) - 1;
|
||||
actor_dy[i] = (rand() & 3) - 1;
|
||||
actor_x[i] = rand() + i * 8;
|
||||
actor_y[i] = rand() + i * 8;
|
||||
actor_dx[i] = deltas[rand() & 3];
|
||||
actor_dy[i] = deltas[rand() & 3];
|
||||
}
|
||||
|
||||
// loop forever
|
||||
while (1) {
|
||||
uint8_t base = 0; // first free sprite ID
|
||||
// draw and move all actors
|
||||
// draw and move all actors, flipping some of them
|
||||
for (i=0; i<NUM_ACTORS; i++) {
|
||||
base += move_metasprite(metasprite, 0x41, base,
|
||||
actor_x[i], actor_y[i]);
|
||||
if (i < 4)
|
||||
base += move_metasprite(metasprite, 0x41, base,
|
||||
actor_x[i], actor_y[i]);
|
||||
else if (i < 7)
|
||||
base += move_metasprite_hflip(metasprite, 0x41, base,
|
||||
actor_x[i], actor_y[i]);
|
||||
else
|
||||
base += move_metasprite_vflip(metasprite, 0x41, base,
|
||||
actor_x[i], actor_y[i]);
|
||||
actor_x[i] += actor_dx[i];
|
||||
actor_y[i] += actor_dy[i];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
STAT/LYC raster effects on Game Boy.
|
||||
|
||||
The LCD status (STAT) interrupt can fire when the raster reaches a
|
||||
programmed scanline, that is when LY_REG == LYC_REG. From inside the
|
||||
handler we can point LYC at a new line and write SCX_REG, changing the
|
||||
scroll register in the middle of a frame -- an effect the tile engine
|
||||
cannot do by itself.
|
||||
|
||||
This demo waves the background left and right. The handler runs once every
|
||||
eight scanlines, sets SCX_REG from a sine table, then arms LYC for the
|
||||
next interrupt. The table phase advances every two frames, so the wave travels
|
||||
across the screen.
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
// 32-step wave, +/- 20 pixels
|
||||
static const int8_t wave[32] = {
|
||||
0, 6, 12, 16, 19, 20, 19, 16,
|
||||
12, 6, 0, -6, -12, -16, -19, -20,
|
||||
-19, -16, -12, -6, 0, 6, 12, 16,
|
||||
19, 20, 19, 16, 12, 6, 0, -6
|
||||
};
|
||||
|
||||
// diagonal stripe tile so the per-line offset is easy to see
|
||||
static const uint8_t stripe_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0xF0,0xE1,0xC3,0x87,0x0F,0x1E,0x3C,0x78,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
/*;;*/
|
||||
};
|
||||
|
||||
volatile uint8_t phase;
|
||||
|
||||
// LCD handler: called once per 8 visible scanlines (LY == LYC)
|
||||
void lcd_handler(void) NONBANKED {
|
||||
uint8_t ly = LY_REG;
|
||||
if (ly < 143) {
|
||||
// offset this line and arm the interrupt for the next one
|
||||
SCX_REG = 248 + (int8_t)wave[(ly + phase) & 31];
|
||||
LYC_REG = ly + 8;
|
||||
} else {
|
||||
// last line: reset so the next frame starts at the top
|
||||
SCX_REG = 128;
|
||||
LYC_REG = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = OBP0_REG = OBP1_REG = 0xE4;
|
||||
|
||||
font_init();
|
||||
set_bkg_data(0, 1, stripe_tile);
|
||||
fill_bkg_rect(0, 0, 32, 32, 0);
|
||||
put_str(2, 6, "GAME BOY RASTER");
|
||||
put_str(2, 8, "LYC WAVE");
|
||||
|
||||
// enable the LY==LYC interrupt and install the handler
|
||||
LYC_REG = 0;
|
||||
STAT_REG = STATF_LYC;
|
||||
add_LCD(lcd_handler);
|
||||
set_interrupts(VBL_IFLAG | LCD_IFLAG);
|
||||
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
wait_vbl_done();
|
||||
wait_vbl_done();
|
||||
phase++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
VRAM and OAM access windows on Game Boy.
|
||||
|
||||
The CPU shares VRAM and OAM with the LCD controller. While the LCD is
|
||||
off, both are freely accessible. While it is on, the CPU can only touch
|
||||
them during VBlank -- writes at other times are ignored or corrupt the
|
||||
display.
|
||||
|
||||
This demo exercises the low-level access helpers:
|
||||
- vmemset() fills a VRAM range while the LCD is off, clearing the map.
|
||||
- vmemcpy() and vmemset() copy a moving bar into the tile map, once per
|
||||
VBlank.
|
||||
- hide_sprite() hides the objects outside a moving four-sprite window.
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
#define TILE_BAR 0x01U // custom tile (font lives at 0x20 and up)
|
||||
|
||||
// solid bar tile
|
||||
static const uint8_t bar_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
/*;;*/
|
||||
};
|
||||
|
||||
// one full row of bar tiles, copied with vmemcpy()
|
||||
static const uint8_t bar_row[32] = {
|
||||
TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,
|
||||
TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,
|
||||
TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,
|
||||
TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR,TILE_BAR
|
||||
};
|
||||
|
||||
// an 8x8 ball sprite (2bpp)
|
||||
static const uint8_t ball_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0x3C,0x3C,0x42,0x7E,0x99,0xFF,0xA9,0xFF,
|
||||
0x89,0xFF,0x89,0xFF,0x42,0x7E,0x3C,0x3C
|
||||
/*;;*/
|
||||
};
|
||||
|
||||
void main(void) {
|
||||
uint8_t i;
|
||||
uint8_t bar_y = 3, bar_dy = 1;
|
||||
uint8_t spot = 0, spot_dx = 1;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = OBP0_REG = OBP1_REG = 0xE4;
|
||||
|
||||
font_init();
|
||||
set_bkg_data(TILE_BAR, 1, bar_tile);
|
||||
set_sprite_data(0, 1, ball_tile);
|
||||
for (i = 0; i < 8; i++) {
|
||||
set_sprite_tile(i, 0);
|
||||
hide_sprite(i);
|
||||
}
|
||||
|
||||
// LCD is off: fill the whole background map at once.
|
||||
// (0x20 is the space tile loaded by font_init.)
|
||||
vmemset(_SCRN0, 0x20, 32 * 32);
|
||||
put_str(1, 0, "VRAM / OAM WINDOW");
|
||||
|
||||
SHOW_BKG;
|
||||
SHOW_SPRITES;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
wait_vbl_done();
|
||||
|
||||
// erase the old bar and draw the new one -- only safe in VBlank
|
||||
vmemset(_SCRN0 + bar_y * 32, 0x20, 32);
|
||||
bar_y += bar_dy;
|
||||
if (bar_y <= 2 || bar_y >= 16) bar_dy = -bar_dy;
|
||||
vmemcpy(_SCRN0 + bar_y * 32, (uint8_t*)bar_row, 32);
|
||||
|
||||
// show four sprites and hide the rest
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (i >= spot && i < spot + 4)
|
||||
move_sprite(i, 16 + i * 16, 136);
|
||||
else
|
||||
hide_sprite(i);
|
||||
}
|
||||
spot += spot_dx;
|
||||
if (spot == 0 || spot == 4) spot_dx = -spot_dx;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
The Game Boy window layer as a status bar.
|
||||
|
||||
The window is a second tile map that the PPU draws on top of the
|
||||
background. Unlike the background it does not scroll; instead WX_REG and
|
||||
WY_REG set where its top-left corner appears, so it makes a natural HUD.
|
||||
|
||||
This demo builds a status bar with init_win() and fill_win_rect(), stamps
|
||||
a row of icons with set_win_submap(), then slides the whole bar in from
|
||||
the left with scroll_win() while the background scrolls behind it.
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
#define TILE_BLOCK 0
|
||||
#define TILE_STRIPE 1
|
||||
#define TILE_CHECK 2
|
||||
|
||||
// solid, diagonal-stripe and checkerboard 2bpp tiles
|
||||
static const uint8_t block_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
/*;;*/
|
||||
};
|
||||
static const uint8_t stripe_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0xF0,0xE1,0xC3,0x87,0x0F,0x1E,0x3C,0x78,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
/*;;*/
|
||||
};
|
||||
static const uint8_t checker_tile[] = {
|
||||
/*;;{w:8,h:8,bpp:1,count:1,brev:1,np:2,pofs:1,sl:2};;*/
|
||||
0xCC,0xCC,0x33,0x33,0xCC,0xCC,0x33,0x33,
|
||||
0x33,0x33,0xCC,0xCC,0x33,0x33,0xCC,0xCC
|
||||
/*;;*/
|
||||
};
|
||||
|
||||
// one row of alternating icons for set_win_submap()
|
||||
static const uint8_t icon_map[] = { TILE_CHECK, TILE_BLOCK, TILE_CHECK, TILE_BLOCK,
|
||||
TILE_CHECK, TILE_BLOCK, TILE_CHECK, TILE_BLOCK };
|
||||
|
||||
// write a string into the window tile map
|
||||
static void put_win_str(uint8_t x, uint8_t y, const char* s) {
|
||||
set_win_tiles(x, y, strlen(s), 1, (uint8_t*)s);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
int8_t win_x = 0;
|
||||
uint8_t scroll = 0;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = OBP0_REG = OBP1_REG = 0xE4;
|
||||
|
||||
// put the window map at 0x9C00, separate from the background map.
|
||||
// set LCDC before loading tiles: GBDK picks the tile-data region
|
||||
// (0x8000 or 0x9000) from the LCDC bit in effect at the call.
|
||||
LCDC_REG = LCDCF_OFF | LCDCF_WIN9C00 | LCDCF_BG8000 | LCDCF_BGON | LCDCF_WINON;
|
||||
|
||||
// load the font plus our three pattern tiles
|
||||
font_init();
|
||||
set_bkg_data(TILE_BLOCK, 1, block_tile);
|
||||
set_bkg_data(TILE_STRIPE, 1, stripe_tile);
|
||||
set_bkg_data(TILE_CHECK, 1, checker_tile);
|
||||
|
||||
// background: stripes, so the scrolling is easy to see
|
||||
fill_bkg_rect(0, 0, 32, 32, TILE_STRIPE);
|
||||
|
||||
// build the status bar (2 rows tall)
|
||||
init_win(TILE_BLOCK); // clear the whole window map
|
||||
fill_win_rect(0, 0, 20, 2, TILE_BLOCK); // solid 20x2 bar
|
||||
set_win_submap(12, 0, 8, 1, icon_map, 8);// icon row from a submap
|
||||
put_win_str(1, 0, "STATUS BAR");
|
||||
put_win_str(1, 1, "SCORE 000000");
|
||||
|
||||
// anchor the bar to the bottom two rows, hidden off the left edge
|
||||
// (the window always extends from WY down to the bottom of the screen)
|
||||
move_win(0, 128);
|
||||
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
wait_vbl_done();
|
||||
|
||||
// WX=7 puts the window's left edge at screen x=0
|
||||
if (win_x < 7) {
|
||||
scroll_win(1, 0);
|
||||
win_x++;
|
||||
}
|
||||
|
||||
// scroll the background
|
||||
scroll++;
|
||||
SCX_REG = scroll;
|
||||
SCY_REG = scroll >> 1;
|
||||
}
|
||||
}
|
||||
@@ -1171,6 +1171,22 @@ export class GameBoyMachine extends BasicScanlineMachine {
|
||||
}
|
||||
|
||||
// CGB speed switch (executed during STOP instruction)
|
||||
//
|
||||
// TODO: double speed is only partially implemented. performSpeedSwitch()
|
||||
// toggles the flag, but nothing calls it and advanceCPU() does not account
|
||||
// for the faster CPU. Plan for fixing:
|
||||
// 1. Hook STOP: in advanceCPU(), snapshot the PC before advanceInsn() and
|
||||
// call performSpeedSwitch() when the executed opcode is 0x10.
|
||||
// 2. Decouple CPU from hardware cycles: at double speed one CPU T-cycle is
|
||||
// half a hardware T-cycle. Accumulate the fractional remainder (the CPU
|
||||
// clock is 8 MHz vs the 4 MHz PPU/timer/APU) and pass only the hardware
|
||||
// T-cycles to updatePPUMode(), the timer and the APU, while returning
|
||||
// those hardware cycles so advanceFrame() fits ~2x instructions per
|
||||
// scanline. Keep the full tCycles for probe.logClocks().
|
||||
// 3. The timer/APU paths currently take M-cycles; convert them to a
|
||||
// T-cycle entry point (or pass the halved value) so they stay at 4 MHz.
|
||||
// 4. Once done, presets/gb/gbc.c's WORK counter should roughly double when
|
||||
// SPEED is DOUBLE, and DIV/timer rates should be unchanged.
|
||||
performSpeedSwitch(): void {
|
||||
if (this.cgbMode && this.speedSwitchArmed) {
|
||||
this.doubleSpeed = !this.doubleSpeed;
|
||||
|
||||
+9
-3
@@ -7,22 +7,28 @@ const GB_PRESETS: Preset[] = [
|
||||
{ id: 'hello.c', name: 'Hello World', category: 'C' },
|
||||
{ id: 'typewriter.c', name: 'Typewriter Text' },
|
||||
{ id: 'testdrawing.c', name: 'Drawing Routines' },
|
||||
{ id: 'testphys.c', name: 'Joypad Sprite' },
|
||||
{ id: 'joypad.c', name: 'Joypad Input' },
|
||||
{ id: 'testphys.c', name: 'Sprite Movement' },
|
||||
{ id: 'sprites.c', name: 'Multiple Sprites' },
|
||||
{ id: 'metasprites.c', name: 'Metasprites' },
|
||||
{ id: 'paint.c', name: 'Paint Program' },
|
||||
{ id: 'scroll.c', name: 'Scrolling' },
|
||||
{ id: 'large_map.c', name: 'Large Scrolling Map' },
|
||||
{ id: 'window.c', name: 'Window Status Bar' },
|
||||
{ id: 'galaxy.c', name: 'Window + Sprites' },
|
||||
{ id: 'raster.c', name: 'STAT/LYC Raster Effects' },
|
||||
{ id: 'interrupts.c', name: 'VBlank + Timer Interrupts' },
|
||||
{ id: 'vram.c', name: 'VRAM/OAM Access' },
|
||||
{ id: 'gbc.c', name: 'Game Boy Color' },
|
||||
{ id: 'gbdecompress.c', name: 'Decompression' },
|
||||
{ id: 'music.c', name: 'Music Player' },
|
||||
{ id: 'clock.c', name: 'MBC3 Real-Time Clock' },
|
||||
{ id: 'banking.c', name: 'ROM Bank Switching' },
|
||||
{ id: 'siegegame.c', name: 'Siege Game' },
|
||||
{ id: 'siegegame.c', name: 'Siege Game', category: 'Games' },
|
||||
{ id: 'climber.c', name: 'Climber Game' },
|
||||
{ id: 'chase.c', name: "Shiru's Chase Game" },
|
||||
{ id: 'pakupaku.c', name: 'Paku Paku' },
|
||||
{ id: 'hello.sgb', name: 'Hello World (ASM)', category: 'assembler' },
|
||||
{ id: 'hello.sgb', name: 'Hello World (ASM)', category: 'Assembler' },
|
||||
{ id: 'main.wiz', name: 'Snake Game (Wiz)' },
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user