mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-24 19:21:30 +11:00
120 lines
3.9 KiB
Markdown
120 lines
3.9 KiB
Markdown
|
# light_cycle
|
||
|
|
||
|
Now let's make a game of "light_cycle" with our new knowledge.
|
||
|
|
||
|
## Gameplay
|
||
|
|
||
|
`light_cycle` is pretty simple, and very obvious if you've ever seen Tron. The
|
||
|
player moves around the screen with a trail left behind them. They die if they
|
||
|
go off the screen or if they touch their own trail.
|
||
|
|
||
|
## Operations
|
||
|
|
||
|
We need some better drawing operations this time around.
|
||
|
|
||
|
```rust
|
||
|
pub unsafe fn mode3_clear_screen(color: u16) {
|
||
|
let color = color as u32;
|
||
|
let bulk_color = color << 16 | color;
|
||
|
let mut ptr = VRAM as *mut u32;
|
||
|
for _ in 0..SCREEN_HEIGHT {
|
||
|
for _ in 0..(SCREEN_WIDTH / 2) {
|
||
|
ptr.write_volatile(bulk_color);
|
||
|
ptr = ptr.offset(1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
||
|
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
|
||
|
}
|
||
|
|
||
|
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
|
||
|
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile()
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The draw pixel and read pixel are both pretty obvious. What's new is the clear
|
||
|
screen operation. It changes the `u16` color into a `u32` and then packs the
|
||
|
value in twice. Then we write out `u32` values the whole way through screen
|
||
|
memory. This means we have to do less write operations overall, and so the
|
||
|
screen clear is twice as fast.
|
||
|
|
||
|
Now we just have to fill in the main function:
|
||
|
|
||
|
```rust
|
||
|
#[start]
|
||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||
|
unsafe {
|
||
|
DISPCNT.write_volatile(MODE3 | BG2);
|
||
|
}
|
||
|
|
||
|
let mut px = SCREEN_WIDTH / 2;
|
||
|
let mut py = SCREEN_HEIGHT / 2;
|
||
|
let mut color = rgb16(31, 0, 0);
|
||
|
|
||
|
loop {
|
||
|
// read the input for this frame
|
||
|
let this_frame_keys = read_key_input();
|
||
|
|
||
|
// adjust game state and wait for vblank
|
||
|
px += 2 * this_frame_keys.column_direction() as isize;
|
||
|
py += 2 * this_frame_keys.row_direction() as isize;
|
||
|
wait_until_vblank();
|
||
|
|
||
|
// draw the new game and wait until the next frame starts.
|
||
|
unsafe {
|
||
|
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
|
||
|
// out of bounds, reset the screen and position.
|
||
|
mode3_clear_screen(0);
|
||
|
color = color.rotate_left(5);
|
||
|
px = SCREEN_WIDTH / 2;
|
||
|
py = SCREEN_HEIGHT / 2;
|
||
|
} else {
|
||
|
let color_here = mode3_read_pixel(px, py);
|
||
|
if color_here != 0 {
|
||
|
// crashed into our own line, reset the screen
|
||
|
mode3_clear_screen(0);
|
||
|
color = color.rotate_left(5);
|
||
|
} else {
|
||
|
// draw the new part of the line
|
||
|
mode3_draw_pixel(px, py, color);
|
||
|
mode3_draw_pixel(px, py + 1, color);
|
||
|
mode3_draw_pixel(px + 1, py, color);
|
||
|
mode3_draw_pixel(px + 1, py + 1, color);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
wait_until_vdraw();
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Oh that's a lot more than before!
|
||
|
|
||
|
First we set Mode 3 and Background 2, we know about that.
|
||
|
|
||
|
Then we're going to store the player's x and y, along with a color value for
|
||
|
their light cycle. Then we enter the core loop.
|
||
|
|
||
|
We read the keys for input, and then do as much as we can without touching video
|
||
|
memory. Since we're using video memory as the place to store the player's light
|
||
|
trail, we can't do much, we just update their position and wait for VBlank to
|
||
|
start. The player will be a 2x2 square, so the arrows will move you 2 pixels per
|
||
|
frame.
|
||
|
|
||
|
Once we're in VBlank we check to see what kind of drawing we're doing. If the
|
||
|
player has gone out of bounds, we clear the screen, rotate their color, and then
|
||
|
reset their position. Why rotate the color? Just because it's fun to have
|
||
|
different colors.
|
||
|
|
||
|
Next, if the player is in bounds we read the video memory for their position. If
|
||
|
it's not black that means we've been here before and the player has crashed into
|
||
|
their own line. In this case, we reset the game without moving them to a new
|
||
|
location.
|
||
|
|
||
|
Finally, if the player is in bounds and they haven't crashed, we write their color into memory at this position.
|
||
|
|
||
|
Regardless of how it worked out, we hold here until vdraw starts before going to
|
||
|
the next loop.
|