2018-11-12 08:56:11 +11:00
<!DOCTYPE HTML>
< html lang = "en" class = "sidebar-visible no-js" >
< head >
<!-- Book generated using mdBook -->
< meta charset = "UTF-8" >
2018-11-13 10:29:17 +11:00
< title > light_cycle - Rust GBA Tutorials< / title >
2018-11-12 08:56:11 +11:00
< meta content = "text/html; charset=utf-8" http-equiv = "Content-Type" >
< meta name = "description" content = "" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "theme-color" content = "#ffffff" / >
< link rel = "shortcut icon" href = "../favicon.png" >
< link rel = "stylesheet" href = "../css/variables.css" >
< link rel = "stylesheet" href = "../css/general.css" >
< link rel = "stylesheet" href = "../css/chrome.css" >
< link rel = "stylesheet" href = "../css/print.css" media = "print" >
<!-- Fonts -->
< link rel = "stylesheet" href = "../FontAwesome/css/font-awesome.css" >
< link href = "https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel = "stylesheet" type = "text/css" >
< link href = "https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel = "stylesheet" type = "text/css" >
<!-- Highlight.js Stylesheets -->
< link rel = "stylesheet" href = "../highlight.css" >
< link rel = "stylesheet" href = "../tomorrow-night.css" >
< link rel = "stylesheet" href = "../ayu-highlight.css" >
<!-- Custom theme stylesheets -->
< / head >
< body class = "light" >
<!-- Provide site root to javascript -->
< script type = "text/javascript" > var path _to _root = "../" ; < / script >
<!-- Work around some values being stored in localStorage wrapped in quotes -->
< script type = "text/javascript" >
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') & & theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') & & sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
< / script >
<!-- Set the theme before any content is loaded, prevents flash -->
< script type = "text/javascript" >
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = 'light'; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
< / script >
<!-- Hide / unhide sidebar before it is displayed -->
< script type = "text/javascript" >
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
< / script >
< nav id = "sidebar" class = "sidebar" aria-label = "Table of contents" >
2018-11-13 10:29:17 +11:00
< ol class = "chapter" > < li > < a href = "../introduction.html" > < strong aria-hidden = "true" > 1.< / strong > Introduction< / a > < / li > < li > < a href = "../ch0/index.html" > < strong aria-hidden = "true" > 2.< / strong > Ch 0: Development Setup< / a > < / li > < li > < a href = "../ch1/index.html" > < strong aria-hidden = "true" > 3.< / strong > Ch 1: Hello GBA< / a > < / li > < li > < ol class = "section" > < li > < a href = "../ch1/hello1.html" > < strong aria-hidden = "true" > 3.1.< / strong > hello1< / a > < / li > < li > < a href = "../ch1/io_registers.html" > < strong aria-hidden = "true" > 3.2.< / strong > IO Registers< / a > < / li > < li > < a href = "../ch1/the_display_control_register.html" > < strong aria-hidden = "true" > 3.3.< / strong > The Display Control Register< / a > < / li > < li > < a href = "../ch1/video_memory_intro.html" > < strong aria-hidden = "true" > 3.4.< / strong > Video Memory Intro< / a > < / li > < li > < a href = "../ch1/hello2.html" > < strong aria-hidden = "true" > 3.5.< / strong > hello2< / a > < / li > < / ol > < / li > < li > < a href = "../ch2/index.html" > < strong aria-hidden = "true" > 4.< / strong > Ch 2: User Input< / a > < / li > < li > < ol class = "section" > < li > < a href = "../ch2/the_key_input_register.html" > < strong aria-hidden = "true" > 4.1.< / strong > The Key Input Register< / a > < / li > < li > < a href = "../ch2/the_vcount_register.html" > < strong aria-hidden = "true" > 4.2.< / strong > The VCount Register< / a > < / li > < li > < a href = "../ch2/light_cycle.html" class = "active" > < strong aria-hidden = "true" > 4.3.< / strong > light_cycle< / a > < / li > < / ol > < / li > < / ol >
2018-11-12 08:56:11 +11:00
< / nav >
< div id = "page-wrapper" class = "page-wrapper" >
< div class = "page" >
< div id = "menu-bar" class = "menu-bar" >
< div id = "menu-bar-sticky-container" >
< div class = "left-buttons" >
< button id = "sidebar-toggle" class = "icon-button" type = "button" title = "Toggle Table of Contents" aria-label = "Toggle Table of Contents" aria-controls = "sidebar" >
< i class = "fa fa-bars" > < / i >
< / button >
< button id = "theme-toggle" class = "icon-button" type = "button" title = "Change theme" aria-label = "Change theme" aria-haspopup = "true" aria-expanded = "false" aria-controls = "theme-list" >
< i class = "fa fa-paint-brush" > < / i >
< / button >
< ul id = "theme-list" class = "theme-popup" aria-label = "Themes" role = "menu" >
< li role = "none" > < button role = "menuitem" class = "theme" id = "light" > Light < span class = "default" > (default)< / span > < / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "rust" > Rust< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "coal" > Coal< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "navy" > Navy< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "ayu" > Ayu< / button > < / li >
< / ul >
< button id = "search-toggle" class = "icon-button" type = "button" title = "Search. (Shortkey: s)" aria-label = "Toggle Searchbar" aria-expanded = "false" aria-keyshortcuts = "S" aria-controls = "searchbar" >
< i class = "fa fa-search" > < / i >
< / button >
< / div >
< h1 class = "menu-title" > Rust GBA Tutorials< / h1 >
< div class = "right-buttons" >
< a href = "../print.html" title = "Print this book" aria-label = "Print this book" >
< i id = "print-button" class = "fa fa-print" > < / i >
< / a >
< / div >
< / div >
< / div >
< div id = "search-wrapper" class = "hidden" >
< form id = "searchbar-outer" class = "searchbar-outer" >
< input type = "search" name = "search" id = "searchbar" name = "searchbar" placeholder = "Search this book ..." aria-controls = "searchresults-outer" aria-describedby = "searchresults-header" >
< / form >
< div id = "searchresults-outer" class = "searchresults-outer hidden" >
< div id = "searchresults-header" class = "searchresults-header" > < / div >
< ul id = "searchresults" >
< / ul >
< / div >
< / div >
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
< script type = "text/javascript" >
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
< / script >
< div id = "content" class = "content" >
< main >
2018-11-13 10:29:17 +11:00
< a class = "header" href = "#light_cycle" id = "light_cycle" > < h1 > light_cycle< / h1 > < / a >
< p > Now let's make a game of " light_cycle" with our new knowledge.< / p >
< a class = "header" href = "#gameplay" id = "gameplay" > < h2 > Gameplay< / h2 > < / a >
< p > < code > light_cycle< / code > 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.< / p >
< a class = "header" href = "#operations" id = "operations" > < h2 > Operations< / h2 > < / a >
< p > We need some better drawing operations this time around.< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" >
# #![allow(unused_variables)]
#fn main() {
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()
}
#}< / code > < / pre > < / pre >
< p > The draw pixel and read pixel are both pretty obvious. What's new is the clear
screen operation. It changes the < code > u16< / code > color into a < code > u32< / code > and then packs the
value in twice. Then we write out < code > u32< / code > 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.< / p >
< p > Now we just have to fill in the main function:< / p >
< pre > < pre class = "playpen" > < code class = "language-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();
}
}
< / code > < / pre > < / pre >
< p > Oh that's a lot more than before!< / p >
< p > First we set Mode 3 and Background 2, we know about that.< / p >
< p > 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.< / p >
< p > 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.< / p >
< p > 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.< / p >
< p > 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.< / p >
< p > Finally, if the player is in bounds and they haven't crashed, we write their color into memory at this position.< / p >
< p > Regardless of how it worked out, we hold here until vdraw starts before going to
the next loop.< / p >
2018-11-12 08:56:11 +11:00
< / main >
< nav class = "nav-wrapper" aria-label = "Page navigation" >
<!-- Mobile navigation buttons -->
< a rel = "prev" href = "../ch2/the_vcount_register.html" class = "mobile-nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
< i class = "fa fa-angle-left" > < / i >
< / a >
< div style = "clear: both" > < / div >
< / nav >
< / div >
< / div >
< nav class = "nav-wide-wrapper" aria-label = "Page navigation" >
< a href = "../ch2/the_vcount_register.html" class = "nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
< i class = "fa fa-angle-left" > < / i >
< / a >
< / nav >
< / div >
< script src = "../elasticlunr.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../mark.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../searcher.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../clipboard.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../highlight.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../book.js" type = "text/javascript" charset = "utf-8" > < / script >
<!-- Custom JS scripts -->
< / body >
< / html >