mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-26 04:01:31 +11:00
597 lines
29 KiB
HTML
597 lines
29 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Regular Objects - Rust GBA Guide</title>
|
|
<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">
|
|
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch00/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch01/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch01/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch01/volatile.html"><strong aria-hidden="true">3.2.</strong> Volatile</a></li><li><a href="../ch01/io_registers.html"><strong aria-hidden="true">3.3.</strong> IO Registers</a></li><li><a href="../ch01/the_display_control_register.html"><strong aria-hidden="true">3.4.</strong> The Display Control Register</a></li><li><a href="../ch01/video_memory_intro.html"><strong aria-hidden="true">3.5.</strong> Video Memory Intro</a></li><li><a href="../ch01/hello2.html"><strong aria-hidden="true">3.6.</strong> hello2</a></li></ol></li><li><a href="../ch02/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch02/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch02/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch02/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li><li><a href="../ch03/index.html"><strong aria-hidden="true">5.</strong> Ch 3: Memory and Objects</a></li><li><ol class="section"><li><a href="../ch03/gba_memory_mapping.html"><strong aria-hidden="true">5.1.</strong> GBA Memory Mapping</a></li><li><a href="../ch03/tile_data.html"><strong aria-hidden="true">5.2.</strong> Tile Data</a></li><li><a href="../ch03/regular_backgrounds.html"><strong aria-hidden="true">5.3.</strong> Regular Backgrounds</a></li><li><a href="../ch03/regular_objects.html" class="active"><strong aria-hidden="true">5.4.</strong> Regular Objects</a></li><li><a href="../ch03/gba_prng.html"><strong aria-hidden="true">5.5.</strong> GBA PRNG</a></li><li><a href="../ch03/memory_game.html"><strong aria-hidden="true">5.6.</strong> memory_game</a></li></ol></li></ol>
|
|
</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 Guide</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>
|
|
<a class="header" href="#regular-objects" id="regular-objects"><h1>Regular Objects</h1></a>
|
|
<p>As with backgrounds, objects can be used in both an affine and non-affine way.
|
|
For this section we'll focus on the non-affine elements, and then we'll do all
|
|
the affine stuff in a later chapter.</p>
|
|
<a class="header" href="#objects-vs-sprites" id="objects-vs-sprites"><h2>Objects vs Sprites</h2></a>
|
|
<p>As <a href="https://www.coranac.com/tonc/text/regobj.htm">TONC</a> helpfully reminds us
|
|
(and then proceeds to not follow its own advice), we should always try to think
|
|
in terms of <em>objects</em>, not <em>sprites</em>. A sprite is a logical / software concern,
|
|
perhaps a player concern, whereas an object is a hardware concern.</p>
|
|
<p>What's more, a given sprite that the player sees might need more than one object
|
|
to display. Objects must be either square or rectangular (so sprite bits that
|
|
stick out probably call for a second object), and can only be from 8x8 to 64x64
|
|
(so anything bigger has to be two objects lined up to appear as one).</p>
|
|
<a class="header" href="#general-object-info" id="general-object-info"><h2>General Object Info</h2></a>
|
|
<p>Unlike with backgrounds, you can enable the object layer in any video mode.
|
|
There's space for 128 object definitions in OAM.</p>
|
|
<p>The display gets a number of cycles per scanline to process objects: 1210 by
|
|
default, but only 954 if you enable the "HBlank interval free" setting in the
|
|
display control register. The <a href="http://problemkaputt.de/gbatek.htm#lcdobjoverview">cycle cost per
|
|
object</a> depends on the
|
|
object's size and if it's using affine or regular mode, so enabling the HBlank
|
|
interval free setting doesn't cut the number of objects displayable by an exact
|
|
number of objects. The objects are processed in order of their definitions and
|
|
if you run out of cycles then the rest just don't get shown. If there's a
|
|
concern that you might run out of cycles you can place important objects (such
|
|
as the player) at the start of the list and then less important animation
|
|
objects later on.</p>
|
|
<a class="header" href="#ready-the-palette" id="ready-the-palette"><h2>Ready the Palette</h2></a>
|
|
<p>Objects use the palette the same as the background does. The only difference is
|
|
that the palette data for objects starts at <code>0x500_0200</code>.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const PALRAM_OBJECT_BASE: VolatilePtr<u16> = VolatilePtr(0x500_0200 as *mut u16);
|
|
|
|
pub fn object_palette(slot: usize) -> u16 {
|
|
assert!(slot < 256);
|
|
unsafe { PALRAM_OBJECT_BASE.offset(slot as isize).read() }
|
|
}
|
|
|
|
pub fn set_object_palette(slot: usize, color: u16) {
|
|
assert!(slot < 256);
|
|
unsafe { PALRAM_OBJECT_BASE.offset(slot as isize).write(color) }
|
|
}
|
|
#}</code></pre></pre>
|
|
<a class="header" href="#ready-the-tiles" id="ready-the-tiles"><h2>Ready the Tiles</h2></a>
|
|
<p>Objects, as with backgrounds, are composed of 8x8 tiles, and if you want
|
|
something bigger than 8x8 you have to use more than one tile put together.
|
|
Object tiles go into the final two charblocks of VRAM (indexes 4 and 5). Because
|
|
there's only two of them, they are sometimes called the lower block
|
|
(<code>0x601_0000</code>) and the higher/upper block (<code>0x601_4000</code>).</p>
|
|
<p>Tile indexes for sprites always offset from the base of the lower block, and
|
|
they always go 32 bytes at a time, regardless of if the object is set for 4bpp
|
|
or 8bpp. From this we can determine that there's 512 tile slots in each of the
|
|
two object charblocks. However, in video modes 3, 4, and 5 the space for the
|
|
background cuts into the lower charblock, so you can only safely use the upper
|
|
charblock.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub fn obj_tile_4bpp(tile_index: usize) -> Tile4bpp {
|
|
assert!(tile_index < 512);
|
|
let address = VRAM + size_of::<Charblock4bpp>() * 4 + 32 * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
|
}
|
|
|
|
pub fn set_obj_tile_4bpp(tile_index: usize, tile: Tile4bpp) {
|
|
assert!(tile_index < 512);
|
|
let address = VRAM + size_of::<Charblock4bpp>() * 4 + 32 * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
|
}
|
|
|
|
pub fn obj_tile_8bpp(tile_index: usize) -> Tile8bpp {
|
|
assert!(tile_index < 512);
|
|
let address = VRAM + size_of::<Charblock8bpp>() * 4 + 32 * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
|
}
|
|
|
|
pub fn set_obj_tile_8bpp(tile_index: usize, tile: Tile8bpp) {
|
|
assert!(tile_index < 512);
|
|
let address = VRAM + size_of::<Charblock8bpp>() * 4 + 32 * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile8bpp).write(tile) }
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>With backgrounds you picked every single tile individually with a bunch of
|
|
screen entry values. Objects don't do that at all. Instead you pick a base tile,
|
|
size, and shape, then it figures out the rest from there. However, you may
|
|
recall back with the display control register something about an "object memory
|
|
1d" bit. This is where that comes into play.</p>
|
|
<ul>
|
|
<li>If object memory is set to be 2d (the default) then each charblock is treated
|
|
as 32 tiles by 32 tiles square. Each object has a base tile and dimensions,
|
|
and that just extracts directly from the charblock picture as if you were
|
|
selecting an area. This mode probably makes for the easiest image editing.</li>
|
|
<li>If object memory is set to be 1d then the tiles are loaded sequentially from
|
|
the starting point, enough to fill in the object's dimensions. This most
|
|
probably makes it the easiest to program with about things, since programming
|
|
languages are pretty good at 1d things.</li>
|
|
</ul>
|
|
<p>I'm not sure I explained that well, here's a picture:</p>
|
|
<p><img src="obj_memory_2d1d.jpg" alt="2d1d-diagram" /></p>
|
|
<p>In 2d mode, a new row of tiles starts every 32 tile indexes.</p>
|
|
<p>Of course, the mode that you actually end up using is not particularly
|
|
important, since it should be the job of your image conversion routine to get
|
|
everything all lined up and into place anyway.</p>
|
|
<a class="header" href="#set-the-object-attributes" id="set-the-object-attributes"><h2>Set the Object Attributes</h2></a>
|
|
<p>The final step is to assign the correct attributes to an object. Each object has
|
|
three <code>u16</code> values that make up its overall attributes.</p>
|
|
<p>Before we go into the details, I want to bring up that the hardware will attempt
|
|
to process every single object every single frame if the object layer is
|
|
enabled, and also that all of the GBA's object memory is cleared to 0 at
|
|
startup. Why do these two things matter right now? As you'll see in a second an
|
|
"all zero" set of object attributes causes an 8x8 object to appear at 0,0 using
|
|
object tile index 0. This is usually <em>not</em> what you want your unused objects to
|
|
do. When your game first starts you should take a moment to mark any objects you
|
|
won't be using as objects to not render.</p>
|
|
<a class="header" href="#objectattributesattr0" id="objectattributesattr0"><h3>ObjectAttributes.attr0</h3></a>
|
|
<ul>
|
|
<li>8 bits for row coordinate (marks the top of the sprite)</li>
|
|
<li>2 bits for object rendering: 0 = Normal, 1 = Affine, 2 = Disabled, 3 = Affine with double rendering area</li>
|
|
<li>2 bits for object mode: 0 = Normal, 1 = Alpha Blending, 2 = Object Window, 3 = Forbidden</li>
|
|
<li>1 bit for mosaic enabled</li>
|
|
<li>1 bit 8bpp color enabled</li>
|
|
<li>2 bits for shape: 0 = Square, 1 = Horizontal, 2 = Vertical, 3 = Forbidden</li>
|
|
</ul>
|
|
<p>If an object is 128 pixels big at Y > 128 you'll get a strange looking result
|
|
where it acts like Y > -128 and then displays partly off screen to the top.</p>
|
|
<a class="header" href="#objectattributesattr1" id="objectattributesattr1"><h3>ObjectAttributes.attr1</h3></a>
|
|
<ul>
|
|
<li>9 bit for column coordinate (marks the left of the sprite)</li>
|
|
<li>Either:
|
|
<ul>
|
|
<li>3 empty bits, 1 bit for horizontal flip, 1 bit for vertical flip (non-affine)</li>
|
|
<li>5 bits for affine index (affine)</li>
|
|
</ul>
|
|
</li>
|
|
<li>2 bits for size.</li>
|
|
</ul>
|
|
<table><thead><tr><th align="center"> Size </th><th align="center"> Square </th><th align="center"> Horizontal </th><th align="center"> Vertical</th></tr></thead><tbody>
|
|
<tr><td align="center"> 0 </td><td align="center"> 8x8 </td><td align="center"> 16x8 </td><td align="center"> 8x16 </td></tr>
|
|
<tr><td align="center"> 1 </td><td align="center"> 16x16 </td><td align="center"> 32x8 </td><td align="center"> 8x32 </td></tr>
|
|
<tr><td align="center"> 2 </td><td align="center"> 32x32 </td><td align="center"> 32x16 </td><td align="center"> 16x32 </td></tr>
|
|
<tr><td align="center"> 3 </td><td align="center"> 64x64 </td><td align="center"> 64x32 </td><td align="center"> 32x64 </td></tr>
|
|
</tbody></table>
|
|
<a class="header" href="#objectattributesattr2" id="objectattributesattr2"><h3>ObjectAttributes.attr2</h3></a>
|
|
<ul>
|
|
<li>10 bits for the base tile index</li>
|
|
<li>2 bits for priority</li>
|
|
<li>4 bits for the palbank index (4bpp mode only, ignored in 8bpp)</li>
|
|
</ul>
|
|
<a class="header" href="#objectattributes-summary" id="objectattributes-summary"><h3>ObjectAttributes summary</h3></a>
|
|
<p>So I said in the GBA memory mapping section that C people would tell you that
|
|
the object attributes should look like this:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[repr(C)]
|
|
pub struct ObjectAttributes {
|
|
attr0: u16,
|
|
attr1: u16,
|
|
attr2: u16,
|
|
filler: i16,
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Except that:</p>
|
|
<ol>
|
|
<li>It's wasteful when we store object attributes on their own outside of OAM
|
|
(which we definitely might want to do).</li>
|
|
<li>In Rust we can't access just one field through a volatile pointer (our
|
|
pointers aren't actually volatile to begin with, just the ops we do with them
|
|
are). We have to read or write the whole pointer's value at a time.
|
|
Similarly, we can't do things like <code>|=</code> and <code>&=</code> with volatile in Rust. So in
|
|
rust we can't have a volatile pointer to an ObjectAttributes and then write
|
|
to just the three "real" values and not touch the filler field. Having the
|
|
filler value in there just means we have to dance around it more, not less.</li>
|
|
<li>We want to newtype this whole thing to prevent accidental invalid states from
|
|
being written into memory.</li>
|
|
</ol>
|
|
<p>So we will not be using that representation. At the same time we want to have no
|
|
overhead, so we will stick to three <code>u16</code> values. We could newtype each
|
|
individual field to be its own type (<code>ObjectAttributesAttr0</code> or something silly
|
|
like that), since there aren't actual dependencies between two different fields
|
|
such that a change in one can throw another into a forbidden state. The worst
|
|
that can happen is if we disable or enable affine mode (<code>attr0</code>) it can change
|
|
the meaning of <code>attr1</code>. The changed meaning isn't actually in invalid state
|
|
though, so we <em>could</em> make each field its own type if we wanted.</p>
|
|
<p>However, when you think about it, I can't imagine a common situation where we do
|
|
something like make an <code>attr0</code> value that we then want to save on its own and
|
|
apply to several different <code>ObjectAttributes</code> that we make during a game. That
|
|
just doesn't sound likely to me. So, we'll go the route where <code>ObjectAttributes</code>
|
|
is just a big black box to the outside world and we don't need to think about
|
|
the three fields internally as being separate.</p>
|
|
<p>First we make it so that we can get and set object attributes from memory:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const OAM: usize = 0x700_0000;
|
|
|
|
pub fn object_attributes(slot: usize) -> ObjectAttributes {
|
|
assert!(slot < 128);
|
|
let ptr = VolatilePtr((OAM + slot * (size_of::<u16>() * 4)) as *mut u16);
|
|
unsafe {
|
|
ObjectAttributes {
|
|
attr0: ptr.read(),
|
|
attr1: ptr.offset(1).read(),
|
|
attr2: ptr.offset(2).read(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn set_object_attributes(slot: usize, obj: ObjectAttributes) {
|
|
assert!(slot < 128);
|
|
let ptr = VolatilePtr((OAM + slot * (size_of::<u16>() * 4)) as *mut u16);
|
|
unsafe {
|
|
ptr.write(obj.attr0);
|
|
ptr.offset(1).write(obj.attr1);
|
|
ptr.offset(2).write(obj.attr2);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub struct ObjectAttributes {
|
|
attr0: u16,
|
|
attr1: u16,
|
|
attr2: u16,
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Then we add a billion methods to the <code>ObjectAttributes</code> type so that we can
|
|
actually set all the different values that we want to set.</p>
|
|
<p>This code block is the last thing on this page so if you don't wanna scroll past
|
|
the whole thing you can just go to the next page.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectRenderMode {
|
|
Normal,
|
|
Affine,
|
|
Disabled,
|
|
DoubleAreaAffine,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectMode {
|
|
Normal,
|
|
AlphaBlending,
|
|
ObjectWindow,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectShape {
|
|
Square,
|
|
Horizontal,
|
|
Vertical,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectOrientation {
|
|
Normal,
|
|
HFlip,
|
|
VFlip,
|
|
BothFlip,
|
|
Affine(u8),
|
|
}
|
|
|
|
impl ObjectAttributes {
|
|
pub fn row(&self) -> u16 {
|
|
self.attr0 & 0b1111_1111
|
|
}
|
|
pub fn column(&self) -> u16 {
|
|
self.attr1 & 0b1_1111_1111
|
|
}
|
|
pub fn rendering(&self) -> ObjectRenderMode {
|
|
match (self.attr0 >> 8) & 0b11 {
|
|
0 => ObjectRenderMode::Normal,
|
|
1 => ObjectRenderMode::Affine,
|
|
2 => ObjectRenderMode::Disabled,
|
|
3 => ObjectRenderMode::DoubleAreaAffine,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
pub fn mode(&self) -> ObjectMode {
|
|
match (self.attr0 >> 0xA) & 0b11 {
|
|
0 => ObjectMode::Normal,
|
|
1 => ObjectMode::AlphaBlending,
|
|
2 => ObjectMode::ObjectWindow,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
pub fn mosaic(&self) -> bool {
|
|
((self.attr0 << 3) as i16) < 0
|
|
}
|
|
pub fn two_fifty_six_colors(&self) -> bool {
|
|
((self.attr0 << 2) as i16) < 0
|
|
}
|
|
pub fn shape(&self) -> ObjectShape {
|
|
match (self.attr0 >> 0xE) & 0b11 {
|
|
0 => ObjectShape::Square,
|
|
1 => ObjectShape::Horizontal,
|
|
2 => ObjectShape::Vertical,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
pub fn orientation(&self) -> ObjectOrientation {
|
|
if (self.attr0 >> 8) & 1 > 0 {
|
|
ObjectOrientation::Affine((self.attr1 >> 9) as u8 & 0b1_1111)
|
|
} else {
|
|
match (self.attr1 >> 0xC) & 0b11 {
|
|
0 => ObjectOrientation::Normal,
|
|
1 => ObjectOrientation::HFlip,
|
|
2 => ObjectOrientation::VFlip,
|
|
3 => ObjectOrientation::BothFlip,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
}
|
|
pub fn size(&self) -> u16 {
|
|
self.attr1 >> 0xE
|
|
}
|
|
pub fn tile_index(&self) -> u16 {
|
|
self.attr2 & 0b11_1111_1111
|
|
}
|
|
pub fn priority(&self) -> u16 {
|
|
self.attr2 >> 0xA
|
|
}
|
|
pub fn palbank(&self) -> u16 {
|
|
self.attr2 >> 0xC
|
|
}
|
|
//
|
|
pub fn set_row(&mut self, row: u16) {
|
|
self.attr0 &= !0b1111_1111;
|
|
self.attr0 |= row & 0b1111_1111;
|
|
}
|
|
pub fn set_column(&mut self, col: u16) {
|
|
self.attr1 &= !0b1_1111_1111;
|
|
self.attr2 |= col & 0b1_1111_1111;
|
|
}
|
|
pub fn set_rendering(&mut self, rendering: ObjectRenderMode) {
|
|
const RENDERING_MASK: u16 = 0b11 << 8;
|
|
self.attr0 &= !RENDERING_MASK;
|
|
self.attr0 |= (rendering as u16) << 8;
|
|
}
|
|
pub fn set_mode(&mut self, mode: ObjectMode) {
|
|
const MODE_MASK: u16 = 0b11 << 0xA;
|
|
self.attr0 &= MODE_MASK;
|
|
self.attr0 |= (mode as u16) << 0xA;
|
|
}
|
|
pub fn set_mosaic(&mut self, bit: bool) {
|
|
const MOSAIC_BIT: u16 = 1 << 0xC;
|
|
if bit {
|
|
self.attr0 |= MOSAIC_BIT
|
|
} else {
|
|
self.attr0 &= !MOSAIC_BIT
|
|
}
|
|
}
|
|
pub fn set_two_fifty_six_colors(&mut self, bit: bool) {
|
|
const COLOR_MODE_BIT: u16 = 1 << 0xD;
|
|
if bit {
|
|
self.attr0 |= COLOR_MODE_BIT
|
|
} else {
|
|
self.attr0 &= !COLOR_MODE_BIT
|
|
}
|
|
}
|
|
pub fn set_shape(&mut self, shape: ObjectShape) {
|
|
self.attr0 &= 0b0011_1111_1111_1111;
|
|
self.attr0 |= (shape as u16) << 0xE;
|
|
}
|
|
pub fn set_orientation(&mut self, orientation: ObjectOrientation) {
|
|
const AFFINE_INDEX_MASK: u16 = 0b1_1111 << 9;
|
|
self.attr1 &= !AFFINE_INDEX_MASK;
|
|
let bits = match orientation {
|
|
ObjectOrientation::Affine(index) => (index as u16) << 9,
|
|
ObjectOrientation::Normal => 0,
|
|
ObjectOrientation::HFlip => 1 << 0xC,
|
|
ObjectOrientation::VFlip => 1 << 0xD,
|
|
ObjectOrientation::BothFlip => 0b11 << 0xC,
|
|
};
|
|
self.attr1 |= bits;
|
|
}
|
|
pub fn set_size(&mut self, size: u16) {
|
|
self.attr1 &= 0b0011_1111_1111_1111;
|
|
self.attr1 |= size << 14;
|
|
}
|
|
pub fn set_tile_index(&mut self, index: u16) {
|
|
self.attr2 &= !0b11_1111_1111;
|
|
self.attr2 |= 0b11_1111_1111 & index;
|
|
}
|
|
pub fn set_priority(&mut self, priority: u16) {
|
|
self.attr2 &= !0b0000_1100_0000_0000;
|
|
self.attr2 |= (priority & 0b11) << 0xA;
|
|
}
|
|
pub fn set_palbank(&mut self, palbank: u16) {
|
|
self.attr2 &= !0b1111_0000_0000_0000;
|
|
self.attr2 |= (palbank & 0b1111) << 0xC;
|
|
}
|
|
}
|
|
#}</code></pre></pre>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
<a rel="prev" href="../ch03/regular_backgrounds.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
<a rel="next" href="../ch03/gba_prng.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
<a href="../ch03/regular_backgrounds.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
<a href="../ch03/gba_prng.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></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>
|