mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 19:01:30 +11:00
keyinput section
This commit is contained in:
parent
44abf95841
commit
cdb9be13d6
|
@ -11,3 +11,9 @@ Also, we will need a way to keep the program from running "too fast". On a
|
||||||
modern computer or console you do this with vsync info from the GPU and Monitor,
|
modern computer or console you do this with vsync info from the GPU and Monitor,
|
||||||
and on the GBA we'll be using vsync info from an IO register that tracks what
|
and on the GBA we'll be using vsync info from an IO register that tracks what
|
||||||
the display hardware is doing.
|
the display hardware is doing.
|
||||||
|
|
||||||
|
For this chapter we'll make a copy of `hello2.rs` named `snake.rs` and then fill
|
||||||
|
it in as we go. Normally you might not place the entire program into a single
|
||||||
|
source file, but since these are examples it's slightly better to have them be
|
||||||
|
completely self contained than it is to have them be "properly organized" for
|
||||||
|
the long term.
|
||||||
|
|
|
@ -1,3 +1,210 @@
|
||||||
# The Key Input Register
|
# The Key Input Register
|
||||||
|
|
||||||
TODO: describe all the stuff about key input
|
The Key Input Register is our next IO register. Its shorthand name is
|
||||||
|
[KEYINPUT](http://problemkaputt.de/gbatek.htm#gbakeypadinput) and it's a `u16`
|
||||||
|
at `0x4000130`. The entire register is obviosuly read only, you can't tell the
|
||||||
|
GBA what buttons are pressed.
|
||||||
|
|
||||||
|
Each button is exactly one bit:
|
||||||
|
|
||||||
|
| Bit | Button |
|
||||||
|
|:---:|:------:|
|
||||||
|
| 0 | A |
|
||||||
|
| 1 | B |
|
||||||
|
| 2 | Select |
|
||||||
|
| 3 | Start |
|
||||||
|
| 4 | Right |
|
||||||
|
| 5 | Left |
|
||||||
|
| 6 | Up |
|
||||||
|
| 7 | Down |
|
||||||
|
| 8 | R |
|
||||||
|
| 9 | L |
|
||||||
|
|
||||||
|
The higher bits above are not used at all.
|
||||||
|
|
||||||
|
Similar to other old hardware devices, the convention here is that a button's
|
||||||
|
bit is **clear when pressed, active when released**. In other words, when the
|
||||||
|
user is not touching the device at all the KEYINPUT value will read
|
||||||
|
`0b0000_0011_1111_1111`. There's similar values for when the user is pressing as
|
||||||
|
many buttons as possible, but since the left/right and up/down keys are on an
|
||||||
|
arrow pad the value can never be 0 since you can't ever press every single key
|
||||||
|
at once.
|
||||||
|
|
||||||
|
When dealing with key input, the register always shows the exact key values at
|
||||||
|
any moment you read it. Obviously that's what it should do, but what it means to
|
||||||
|
you as a programmer is that you should usually gather input once at the top of a
|
||||||
|
game frame and then use that single input poll as the input values across the
|
||||||
|
whole game frame.
|
||||||
|
|
||||||
|
Of course, you might want to know if a user's key state changed from frame to
|
||||||
|
frame. That's fairly easy too: We just store the last frame keys as well as the
|
||||||
|
current frame keys (it's only a `u16`) and then we can xor the two values.
|
||||||
|
Anything that shows up in the xor result is a key that changed. If it's changed
|
||||||
|
and it's now down, that means it was pushed this frame. If it's changed and it's
|
||||||
|
now up, that means it was released this frame.
|
||||||
|
|
||||||
|
The other major thing you might frequently want is to know "which way" the arrow
|
||||||
|
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
|
||||||
|
Except that often time we'll have situations where the direction just needs to
|
||||||
|
be multiplied by a speed and applied as a delta to a position. We want to
|
||||||
|
support that as well as we can too.
|
||||||
|
|
||||||
|
## Key Input Code
|
||||||
|
|
||||||
|
Let's get down to some code. First we want to make a way to read the address as
|
||||||
|
a `u16` and then wrap that in our newtype which will implement methods for
|
||||||
|
reading and writing the key bits.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
||||||
|
|
||||||
|
/// A newtype over the key input state of the GBA.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct KeyInputSetting(u16);
|
||||||
|
|
||||||
|
pub fn read_key_input() -> KeyInputSetting {
|
||||||
|
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we want a way to check if a key is _being pressed_, since that's normally
|
||||||
|
how we think of things as a game designer and even as a player. That is, usually
|
||||||
|
you'd say "if you press A, then X happens" instead of "if you don't press A,
|
||||||
|
then X does not happen".
|
||||||
|
|
||||||
|
Normally we'd pick a constant for the bit we want, `&` it with our value, and
|
||||||
|
then check for `val != 0`. Since the bit we're looking for is `0` in the "true"
|
||||||
|
state we still pick the same constant and we still do the `&`, but we test with
|
||||||
|
`== 0`. Practically the same, right? Well, since I'm asking a rhetorical
|
||||||
|
question like that you can probably already guess that it's not the same. I was
|
||||||
|
shocked to learn this too.
|
||||||
|
|
||||||
|
All we have to do is ask our good friend
|
||||||
|
[Godbolt](https://rust.godbolt.org/z/d-8oCe) what's gonna happen when the code
|
||||||
|
compiles. The link there has the page set for the `stable` 1.30 compiler just so
|
||||||
|
that the link results stay consistent if you read this book in a year or
|
||||||
|
something. Also, we've set the target to `thumbv6m-none-eabi`, which is a
|
||||||
|
slightly later version of ARM than the actual GBA, but it's close enough for
|
||||||
|
just checking. Of course, in a full program small functions like these will
|
||||||
|
probably get inlined into the calling code and disappear entirely as they're
|
||||||
|
folded and refolded by the compiler, and so on. Still, we can see that in the
|
||||||
|
simple case when the compiler doesn't know any extra context, the `!=0` test is
|
||||||
|
4 instructions and the `==0` test is six instructions. Since we want to get
|
||||||
|
saving where we can, we'll always use a `!=0` test and we'll adjust how we
|
||||||
|
initially read the register to compensate.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn read_key_input() -> KeyInputSetting {
|
||||||
|
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we add a method for seeing if a key is pressed. In the full library there's
|
||||||
|
a more advanced version of this that's built up via macro, but for this example
|
||||||
|
we'll just name a bunch of `const` values and then have a method that takes a
|
||||||
|
value and says if that bit is on.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const KEY_A: u16 = 1 << 0;
|
||||||
|
pub const KEY_B: u16 = 1 << 1;
|
||||||
|
pub const KEY_SELECT: u16 = 1 << 2;
|
||||||
|
pub const KEY_START: u16 = 1 << 3;
|
||||||
|
pub const KEY_RIGHT: u16 = 1 << 4;
|
||||||
|
pub const KEY_LEFT: u16 = 1 << 5;
|
||||||
|
pub const KEY_UP: u16 = 1 << 6;
|
||||||
|
pub const KEY_DOWN: u16 = 1 << 7;
|
||||||
|
pub const KEY_R: u16 = 1 << 8;
|
||||||
|
pub const KEY_L: u16 = 1 << 9;
|
||||||
|
|
||||||
|
impl KeyInputSetting {
|
||||||
|
pub fn contains(&self, key: u16) -> bool {
|
||||||
|
(self.0 & key) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Because each key is a unique bit you can even check for more than one key at
|
||||||
|
once by just adding two key values together.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
|
||||||
|
```
|
||||||
|
|
||||||
|
And we wanted to save the state of an old frame and compare it to the current
|
||||||
|
frame to see what was different:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
||||||
|
KeyInputSetting(self.0 ^ other.0)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Anything that's "in" the difference output is a key that _changed_, and then if
|
||||||
|
the key reads as pressed this frame that means it was just pressed. The exact
|
||||||
|
mechanics of all the ways you might care to do something based on new key
|
||||||
|
presses is obviously quite varied, but it might be something like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let this_frame_diff = this_frame_input.difference(last_frame_input);
|
||||||
|
|
||||||
|
if this_frame_diff.contains(KEY_B) && this_frame_input.contains(KEY_B) {
|
||||||
|
// the user just pressed B, react in some way
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And for the arrow pad, we'll make an enum that easily casts into `i32`. Whenever
|
||||||
|
we're working with stuff we can try to use `i32` / `isize` as often as possible
|
||||||
|
just because it's easier on the GBA's CPU if we stick to its native number size.
|
||||||
|
Having it be an enum lets us use `match` and be sure that we've covered all our
|
||||||
|
cases.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum TriBool {
|
||||||
|
Minus = -1,
|
||||||
|
Neutral = 0,
|
||||||
|
Plus = +1,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, how do we determine _which way_ is plus or minus? Well... I don't know.
|
||||||
|
Really. I'm not sure what the best one is because the GBA really wants the
|
||||||
|
origin at 0,0 with higher rows going down and higher cols going right. On the
|
||||||
|
other hand, all the normal math you and I learned in school is oriented with
|
||||||
|
increasing Y being upward on the page. So, at least for this demo, we're going
|
||||||
|
to go with what the GBA wants us to do and give it a try. If we don't end up
|
||||||
|
confusing ourselves then we can stick with that. Maybe we can cover it over
|
||||||
|
somehow later on.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn column_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_RIGHT) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_LEFT) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_DOWN) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_UP) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So then every frame we can check for `column_direction` and `row_direction` and
|
||||||
|
then apply those to the snake's current position to make it move around the
|
||||||
|
screen.
|
||||||
|
|
||||||
|
With that I think we're all done with user input for now. There's some other
|
||||||
|
things to eventually know about like key interrupts that you can set and stuff,
|
||||||
|
but we'll cover that later on because it's not necessary right now.
|
||||||
|
|
|
@ -146,6 +146,11 @@ SNES had. As you can guess, we get key state info from an IO register.</p>
|
||||||
modern computer or console you do this with vsync info from the GPU and Monitor,
|
modern computer or console you do this with vsync info from the GPU and Monitor,
|
||||||
and on the GBA we'll be using vsync info from an IO register that tracks what
|
and on the GBA we'll be using vsync info from an IO register that tracks what
|
||||||
the display hardware is doing.</p>
|
the display hardware is doing.</p>
|
||||||
|
<p>For this chapter we'll make a copy of <code>hello2.rs</code> named <code>snake.rs</code> and then fill
|
||||||
|
it in as we go. Normally you might not place the entire program into a single
|
||||||
|
source file, but since these are examples it's slightly better to have them be
|
||||||
|
completely self contained than it is to have them be "properly organized" for
|
||||||
|
the long term.</p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,202 @@
|
||||||
<div id="content" class="content">
|
<div id="content" class="content">
|
||||||
<main>
|
<main>
|
||||||
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
|
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
|
||||||
<p>TODO: describe all the stuff about key input</p>
|
<p>The Key Input Register is our next IO register. Its shorthand name is
|
||||||
|
<a href="http://problemkaputt.de/gbatek.htm#gbakeypadinput">KEYINPUT</a> and it's a <code>u16</code>
|
||||||
|
at <code>0x4000130</code>. The entire register is obviosuly read only, you can't tell the
|
||||||
|
GBA what buttons are pressed.</p>
|
||||||
|
<p>Each button is exactly one bit:</p>
|
||||||
|
<table><thead><tr><th align="center"> Bit </th><th align="center"> Button </th></tr></thead><tbody>
|
||||||
|
<tr><td align="center"> 0 </td><td align="center"> A </td></tr>
|
||||||
|
<tr><td align="center"> 1 </td><td align="center"> B </td></tr>
|
||||||
|
<tr><td align="center"> 2 </td><td align="center"> Select </td></tr>
|
||||||
|
<tr><td align="center"> 3 </td><td align="center"> Start </td></tr>
|
||||||
|
<tr><td align="center"> 4 </td><td align="center"> Right </td></tr>
|
||||||
|
<tr><td align="center"> 5 </td><td align="center"> Left </td></tr>
|
||||||
|
<tr><td align="center"> 6 </td><td align="center"> Up </td></tr>
|
||||||
|
<tr><td align="center"> 7 </td><td align="center"> Down </td></tr>
|
||||||
|
<tr><td align="center"> 8 </td><td align="center"> R </td></tr>
|
||||||
|
<tr><td align="center"> 9 </td><td align="center"> L </td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
<p>The higher bits above are not used at all.</p>
|
||||||
|
<p>Similar to other old hardware devices, the convention here is that a button's
|
||||||
|
bit is <strong>clear when pressed, active when released</strong>. In other words, when the
|
||||||
|
user is not touching the device at all the KEYINPUT value will read
|
||||||
|
<code>0b0000_0011_1111_1111</code>. There's similar values for when the user is pressing as
|
||||||
|
many buttons as possible, but since the left/right and up/down keys are on an
|
||||||
|
arrow pad the value can never be 0 since you can't ever press every single key
|
||||||
|
at once.</p>
|
||||||
|
<p>When dealing with key input, the register always shows the exact key values at
|
||||||
|
any moment you read it. Obviously that's what it should do, but what it means to
|
||||||
|
you as a programmer is that you should usually gather input once at the top of a
|
||||||
|
game frame and then use that single input poll as the input values across the
|
||||||
|
whole game frame.</p>
|
||||||
|
<p>Of course, you might want to know if a user's key state changed from frame to
|
||||||
|
frame. That's fairly easy too: We just store the last frame keys as well as the
|
||||||
|
current frame keys (it's only a <code>u16</code>) and then we can xor the two values.
|
||||||
|
Anything that shows up in the xor result is a key that changed. If it's changed
|
||||||
|
and it's now down, that means it was pushed this frame. If it's changed and it's
|
||||||
|
now up, that means it was released this frame.</p>
|
||||||
|
<p>The other major thing you might frequently want is to know "which way" the arrow
|
||||||
|
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
|
||||||
|
Except that often time we'll have situations where the direction just needs to
|
||||||
|
be multiplied by a speed and applied as a delta to a position. We want to
|
||||||
|
support that as well as we can too.</p>
|
||||||
|
<a class="header" href="#key-input-code" id="key-input-code"><h2>Key Input Code</h2></a>
|
||||||
|
<p>Let's get down to some code. First we want to make a way to read the address as
|
||||||
|
a <code>u16</code> and then wrap that in our newtype which will implement methods for
|
||||||
|
reading and writing the key bits.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
||||||
|
|
||||||
|
/// A newtype over the key input state of the GBA.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct KeyInputSetting(u16);
|
||||||
|
|
||||||
|
pub fn read_key_input() -> KeyInputSetting {
|
||||||
|
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Now we want a way to check if a key is <em>being pressed</em>, since that's normally
|
||||||
|
how we think of things as a game designer and even as a player. That is, usually
|
||||||
|
you'd say "if you press A, then X happens" instead of "if you don't press A,
|
||||||
|
then X does not happen".</p>
|
||||||
|
<p>Normally we'd pick a constant for the bit we want, <code>&</code> it with our value, and
|
||||||
|
then check for <code>val != 0</code>. Since the bit we're looking for is <code>0</code> in the "true"
|
||||||
|
state we still pick the same constant and we still do the <code>&</code>, but we test with
|
||||||
|
<code>== 0</code>. Practically the same, right? Well, since I'm asking a rhetorical
|
||||||
|
question like that you can probably already guess that it's not the same. I was
|
||||||
|
shocked to learn this too.</p>
|
||||||
|
<p>All we have to do is ask our good friend
|
||||||
|
<a href="https://rust.godbolt.org/z/d-8oCe">Godbolt</a> what's gonna happen when the code
|
||||||
|
compiles. The link there has the page set for the <code>stable</code> 1.30 compiler just so
|
||||||
|
that the link results stay consistent if you read this book in a year or
|
||||||
|
something. Also, we've set the target to <code>thumbv6m-none-eabi</code>, which is a
|
||||||
|
slightly later version of ARM than the actual GBA, but it's close enough for
|
||||||
|
just checking. Of course, in a full program small functions like these will
|
||||||
|
probably get inlined into the calling code and disappear entirely as they're
|
||||||
|
folded and refolded by the compiler, and so on. Still, we can see that in the
|
||||||
|
simple case when the compiler doesn't know any extra context, the <code>!=0</code> test is
|
||||||
|
4 instructions and the <code>==0</code> test is six instructions. Since we want to get
|
||||||
|
saving where we can, we'll always use a <code>!=0</code> test and we'll adjust how we
|
||||||
|
initially read the register to compensate.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub fn read_key_input() -> KeyInputSetting {
|
||||||
|
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Now we add a method for seeing if a key is pressed. In the full library there's
|
||||||
|
a more advanced version of this that's built up via macro, but for this example
|
||||||
|
we'll just name a bunch of <code>const</code> values and then have a method that takes a
|
||||||
|
value and says if that bit is on.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub const KEY_A: u16 = 1 << 0;
|
||||||
|
pub const KEY_B: u16 = 1 << 1;
|
||||||
|
pub const KEY_SELECT: u16 = 1 << 2;
|
||||||
|
pub const KEY_START: u16 = 1 << 3;
|
||||||
|
pub const KEY_RIGHT: u16 = 1 << 4;
|
||||||
|
pub const KEY_LEFT: u16 = 1 << 5;
|
||||||
|
pub const KEY_UP: u16 = 1 << 6;
|
||||||
|
pub const KEY_DOWN: u16 = 1 << 7;
|
||||||
|
pub const KEY_R: u16 = 1 << 8;
|
||||||
|
pub const KEY_L: u16 = 1 << 9;
|
||||||
|
|
||||||
|
impl KeyInputSetting {
|
||||||
|
pub fn contains(&self, key: u16) -> bool {
|
||||||
|
(self.0 & key) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Because each key is a unique bit you can even check for more than one key at
|
||||||
|
once by just adding two key values together.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>And we wanted to save the state of an old frame and compare it to the current
|
||||||
|
frame to see what was different:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
||||||
|
KeyInputSetting(self.0 ^ other.0)
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Anything that's "in" the difference output is a key that <em>changed</em>, and then if
|
||||||
|
the key reads as pressed this frame that means it was just pressed. The exact
|
||||||
|
mechanics of all the ways you might care to do something based on new key
|
||||||
|
presses is obviously quite varied, but it might be something like this:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
let this_frame_diff = this_frame_input.difference(last_frame_input);
|
||||||
|
|
||||||
|
if this_frame_diff.contains(KEY_B) && this_frame_input.contains(KEY_B) {
|
||||||
|
// the user just pressed B, react in some way
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>And for the arrow pad, we'll make an enum that easily casts into <code>i32</code>. Whenever
|
||||||
|
we're working with stuff we can try to use <code>i32</code> / <code>isize</code> as often as possible
|
||||||
|
just because it's easier on the GBA's CPU if we stick to its native number size.
|
||||||
|
Having it be an enum lets us use <code>match</code> and be sure that we've covered all our
|
||||||
|
cases.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum TriBool {
|
||||||
|
Minus = -1,
|
||||||
|
Neutral = 0,
|
||||||
|
Plus = +1,
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Now, how do we determine <em>which way</em> is plus or minus? Well... I don't know.
|
||||||
|
Really. I'm not sure what the best one is because the GBA really wants the
|
||||||
|
origin at 0,0 with higher rows going down and higher cols going right. On the
|
||||||
|
other hand, all the normal math you and I learned in school is oriented with
|
||||||
|
increasing Y being upward on the page. So, at least for this demo, we're going
|
||||||
|
to go with what the GBA wants us to do and give it a try. If we don't end up
|
||||||
|
confusing ourselves then we can stick with that. Maybe we can cover it over
|
||||||
|
somehow later on.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub fn column_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_RIGHT) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_LEFT) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_DOWN) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_UP) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>So then every frame we can check for <code>column_direction</code> and <code>row_direction</code> and
|
||||||
|
then apply those to the snake's current position to make it move around the
|
||||||
|
screen.</p>
|
||||||
|
<p>With that I think we're all done with user input for now. There's some other
|
||||||
|
things to eventually know about like key interrupts that you can set and stuff,
|
||||||
|
but we'll cover that later on because it's not necessary right now.</p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
202
docs/print.html
202
docs/print.html
|
@ -789,8 +789,208 @@ SNES had. As you can guess, we get key state info from an IO register.</p>
|
||||||
modern computer or console you do this with vsync info from the GPU and Monitor,
|
modern computer or console you do this with vsync info from the GPU and Monitor,
|
||||||
and on the GBA we'll be using vsync info from an IO register that tracks what
|
and on the GBA we'll be using vsync info from an IO register that tracks what
|
||||||
the display hardware is doing.</p>
|
the display hardware is doing.</p>
|
||||||
|
<p>For this chapter we'll make a copy of <code>hello2.rs</code> named <code>snake.rs</code> and then fill
|
||||||
|
it in as we go. Normally you might not place the entire program into a single
|
||||||
|
source file, but since these are examples it's slightly better to have them be
|
||||||
|
completely self contained than it is to have them be "properly organized" for
|
||||||
|
the long term.</p>
|
||||||
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
|
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
|
||||||
<p>TODO: describe all the stuff about key input</p>
|
<p>The Key Input Register is our next IO register. Its shorthand name is
|
||||||
|
<a href="http://problemkaputt.de/gbatek.htm#gbakeypadinput">KEYINPUT</a> and it's a <code>u16</code>
|
||||||
|
at <code>0x4000130</code>. The entire register is obviosuly read only, you can't tell the
|
||||||
|
GBA what buttons are pressed.</p>
|
||||||
|
<p>Each button is exactly one bit:</p>
|
||||||
|
<table><thead><tr><th align="center"> Bit </th><th align="center"> Button </th></tr></thead><tbody>
|
||||||
|
<tr><td align="center"> 0 </td><td align="center"> A </td></tr>
|
||||||
|
<tr><td align="center"> 1 </td><td align="center"> B </td></tr>
|
||||||
|
<tr><td align="center"> 2 </td><td align="center"> Select </td></tr>
|
||||||
|
<tr><td align="center"> 3 </td><td align="center"> Start </td></tr>
|
||||||
|
<tr><td align="center"> 4 </td><td align="center"> Right </td></tr>
|
||||||
|
<tr><td align="center"> 5 </td><td align="center"> Left </td></tr>
|
||||||
|
<tr><td align="center"> 6 </td><td align="center"> Up </td></tr>
|
||||||
|
<tr><td align="center"> 7 </td><td align="center"> Down </td></tr>
|
||||||
|
<tr><td align="center"> 8 </td><td align="center"> R </td></tr>
|
||||||
|
<tr><td align="center"> 9 </td><td align="center"> L </td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
<p>The higher bits above are not used at all.</p>
|
||||||
|
<p>Similar to other old hardware devices, the convention here is that a button's
|
||||||
|
bit is <strong>clear when pressed, active when released</strong>. In other words, when the
|
||||||
|
user is not touching the device at all the KEYINPUT value will read
|
||||||
|
<code>0b0000_0011_1111_1111</code>. There's similar values for when the user is pressing as
|
||||||
|
many buttons as possible, but since the left/right and up/down keys are on an
|
||||||
|
arrow pad the value can never be 0 since you can't ever press every single key
|
||||||
|
at once.</p>
|
||||||
|
<p>When dealing with key input, the register always shows the exact key values at
|
||||||
|
any moment you read it. Obviously that's what it should do, but what it means to
|
||||||
|
you as a programmer is that you should usually gather input once at the top of a
|
||||||
|
game frame and then use that single input poll as the input values across the
|
||||||
|
whole game frame.</p>
|
||||||
|
<p>Of course, you might want to know if a user's key state changed from frame to
|
||||||
|
frame. That's fairly easy too: We just store the last frame keys as well as the
|
||||||
|
current frame keys (it's only a <code>u16</code>) and then we can xor the two values.
|
||||||
|
Anything that shows up in the xor result is a key that changed. If it's changed
|
||||||
|
and it's now down, that means it was pushed this frame. If it's changed and it's
|
||||||
|
now up, that means it was released this frame.</p>
|
||||||
|
<p>The other major thing you might frequently want is to know "which way" the arrow
|
||||||
|
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
|
||||||
|
Except that often time we'll have situations where the direction just needs to
|
||||||
|
be multiplied by a speed and applied as a delta to a position. We want to
|
||||||
|
support that as well as we can too.</p>
|
||||||
|
<a class="header" href="#key-input-code" id="key-input-code"><h2>Key Input Code</h2></a>
|
||||||
|
<p>Let's get down to some code. First we want to make a way to read the address as
|
||||||
|
a <code>u16</code> and then wrap that in our newtype which will implement methods for
|
||||||
|
reading and writing the key bits.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
||||||
|
|
||||||
|
/// A newtype over the key input state of the GBA.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct KeyInputSetting(u16);
|
||||||
|
|
||||||
|
pub fn read_key_input() -> KeyInputSetting {
|
||||||
|
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Now we want a way to check if a key is <em>being pressed</em>, since that's normally
|
||||||
|
how we think of things as a game designer and even as a player. That is, usually
|
||||||
|
you'd say "if you press A, then X happens" instead of "if you don't press A,
|
||||||
|
then X does not happen".</p>
|
||||||
|
<p>Normally we'd pick a constant for the bit we want, <code>&</code> it with our value, and
|
||||||
|
then check for <code>val != 0</code>. Since the bit we're looking for is <code>0</code> in the "true"
|
||||||
|
state we still pick the same constant and we still do the <code>&</code>, but we test with
|
||||||
|
<code>== 0</code>. Practically the same, right? Well, since I'm asking a rhetorical
|
||||||
|
question like that you can probably already guess that it's not the same. I was
|
||||||
|
shocked to learn this too.</p>
|
||||||
|
<p>All we have to do is ask our good friend
|
||||||
|
<a href="https://rust.godbolt.org/z/d-8oCe">Godbolt</a> what's gonna happen when the code
|
||||||
|
compiles. The link there has the page set for the <code>stable</code> 1.30 compiler just so
|
||||||
|
that the link results stay consistent if you read this book in a year or
|
||||||
|
something. Also, we've set the target to <code>thumbv6m-none-eabi</code>, which is a
|
||||||
|
slightly later version of ARM than the actual GBA, but it's close enough for
|
||||||
|
just checking. Of course, in a full program small functions like these will
|
||||||
|
probably get inlined into the calling code and disappear entirely as they're
|
||||||
|
folded and refolded by the compiler, and so on. Still, we can see that in the
|
||||||
|
simple case when the compiler doesn't know any extra context, the <code>!=0</code> test is
|
||||||
|
4 instructions and the <code>==0</code> test is six instructions. Since we want to get
|
||||||
|
saving where we can, we'll always use a <code>!=0</code> test and we'll adjust how we
|
||||||
|
initially read the register to compensate.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub fn read_key_input() -> KeyInputSetting {
|
||||||
|
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Now we add a method for seeing if a key is pressed. In the full library there's
|
||||||
|
a more advanced version of this that's built up via macro, but for this example
|
||||||
|
we'll just name a bunch of <code>const</code> values and then have a method that takes a
|
||||||
|
value and says if that bit is on.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub const KEY_A: u16 = 1 << 0;
|
||||||
|
pub const KEY_B: u16 = 1 << 1;
|
||||||
|
pub const KEY_SELECT: u16 = 1 << 2;
|
||||||
|
pub const KEY_START: u16 = 1 << 3;
|
||||||
|
pub const KEY_RIGHT: u16 = 1 << 4;
|
||||||
|
pub const KEY_LEFT: u16 = 1 << 5;
|
||||||
|
pub const KEY_UP: u16 = 1 << 6;
|
||||||
|
pub const KEY_DOWN: u16 = 1 << 7;
|
||||||
|
pub const KEY_R: u16 = 1 << 8;
|
||||||
|
pub const KEY_L: u16 = 1 << 9;
|
||||||
|
|
||||||
|
impl KeyInputSetting {
|
||||||
|
pub fn contains(&self, key: u16) -> bool {
|
||||||
|
(self.0 & key) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Because each key is a unique bit you can even check for more than one key at
|
||||||
|
once by just adding two key values together.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>And we wanted to save the state of an old frame and compare it to the current
|
||||||
|
frame to see what was different:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
||||||
|
KeyInputSetting(self.0 ^ other.0)
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Anything that's "in" the difference output is a key that <em>changed</em>, and then if
|
||||||
|
the key reads as pressed this frame that means it was just pressed. The exact
|
||||||
|
mechanics of all the ways you might care to do something based on new key
|
||||||
|
presses is obviously quite varied, but it might be something like this:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
let this_frame_diff = this_frame_input.difference(last_frame_input);
|
||||||
|
|
||||||
|
if this_frame_diff.contains(KEY_B) && this_frame_input.contains(KEY_B) {
|
||||||
|
// the user just pressed B, react in some way
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>And for the arrow pad, we'll make an enum that easily casts into <code>i32</code>. Whenever
|
||||||
|
we're working with stuff we can try to use <code>i32</code> / <code>isize</code> as often as possible
|
||||||
|
just because it's easier on the GBA's CPU if we stick to its native number size.
|
||||||
|
Having it be an enum lets us use <code>match</code> and be sure that we've covered all our
|
||||||
|
cases.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum TriBool {
|
||||||
|
Minus = -1,
|
||||||
|
Neutral = 0,
|
||||||
|
Plus = +1,
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>Now, how do we determine <em>which way</em> is plus or minus? Well... I don't know.
|
||||||
|
Really. I'm not sure what the best one is because the GBA really wants the
|
||||||
|
origin at 0,0 with higher rows going down and higher cols going right. On the
|
||||||
|
other hand, all the normal math you and I learned in school is oriented with
|
||||||
|
increasing Y being upward on the page. So, at least for this demo, we're going
|
||||||
|
to go with what the GBA wants us to do and give it a try. If we don't end up
|
||||||
|
confusing ourselves then we can stick with that. Maybe we can cover it over
|
||||||
|
somehow later on.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
pub fn column_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_RIGHT) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_LEFT) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_DOWN) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_UP) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>So then every frame we can check for <code>column_direction</code> and <code>row_direction</code> and
|
||||||
|
then apply those to the snake's current position to make it move around the
|
||||||
|
screen.</p>
|
||||||
|
<p>With that I think we're all done with user input for now. There's some other
|
||||||
|
things to eventually know about like key interrupts that you can set and stuff,
|
||||||
|
but we'll cover that later on because it's not necessary right now.</p>
|
||||||
<a class="header" href="#the-vcount-register" id="the-vcount-register"><h1>The VCount Register</h1></a>
|
<a class="header" href="#the-vcount-register" id="the-vcount-register"><h1>The VCount Register</h1></a>
|
||||||
<p>TODO: describe all the stuff about vcount</p>
|
<p>TODO: describe all the stuff about vcount</p>
|
||||||
<p>TODO: mention vblank and hblank</p>
|
<p>TODO: mention vblank and hblank</p>
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
95
examples/snake.rs
Normal file
95
examples/snake.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
#![feature(start)]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[start]
|
||||||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
unsafe {
|
||||||
|
DISPCNT.write_volatile(MODE3 | BG2);
|
||||||
|
mode3_pixel(120, 80, rgb16(31, 0, 0));
|
||||||
|
mode3_pixel(136, 80, rgb16(0, 31, 0));
|
||||||
|
mode3_pixel(120, 96, rgb16(0, 0, 31));
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
|
||||||
|
pub const MODE3: u16 = 3;
|
||||||
|
pub const BG2: u16 = 0b100_0000_0000;
|
||||||
|
|
||||||
|
pub const VRAM: usize = 0x06000000;
|
||||||
|
pub const SCREEN_WIDTH: isize = 240;
|
||||||
|
|
||||||
|
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
||||||
|
blue << 10 | green << 5 | red
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
|
||||||
|
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
||||||
|
|
||||||
|
/// A newtype over the key input state of the GBA.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct KeyInputSetting(u16);
|
||||||
|
|
||||||
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum TriBool {
|
||||||
|
Minus = -1,
|
||||||
|
Neutral = 0,
|
||||||
|
Plus = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_key_input() -> KeyInputSetting {
|
||||||
|
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const KEY_A: u16 = 1 << 0;
|
||||||
|
pub const KEY_B: u16 = 1 << 1;
|
||||||
|
pub const KEY_SELECT: u16 = 1 << 2;
|
||||||
|
pub const KEY_START: u16 = 1 << 3;
|
||||||
|
pub const KEY_RIGHT: u16 = 1 << 4;
|
||||||
|
pub const KEY_LEFT: u16 = 1 << 5;
|
||||||
|
pub const KEY_UP: u16 = 1 << 6;
|
||||||
|
pub const KEY_DOWN: u16 = 1 << 7;
|
||||||
|
pub const KEY_R: u16 = 1 << 8;
|
||||||
|
pub const KEY_L: u16 = 1 << 9;
|
||||||
|
|
||||||
|
impl KeyInputSetting {
|
||||||
|
pub fn contains(&self, key: u16) -> bool {
|
||||||
|
(self.0 & key) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
||||||
|
KeyInputSetting(self.0 ^ other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_RIGHT) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_LEFT) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_direction(&self) -> TriBool {
|
||||||
|
if self.contains(KEY_DOWN) {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.contains(KEY_UP) {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
error_on_line_overflow = false
|
error_on_line_overflow = false
|
||||||
fn_args_density = "Compressed"
|
fn_args_density = "Compressed"
|
||||||
reorder_imported_names = true
|
merge_imports = true
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
reorder_imports_in_group = true
|
|
||||||
use_try_shorthand = true
|
use_try_shorthand = true
|
||||||
write_mode = "Overwrite"
|
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
max_width = 150
|
max_width = 150
|
||||||
color = "Never"
|
color = "Never"
|
||||||
|
|
|
@ -383,6 +383,16 @@ pub const SIODATA8: VolatilePtr<u16> = VolatilePtr(0x400012A as *mut u16);
|
||||||
/// Key Status
|
/// Key Status
|
||||||
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(0x4000130 as *mut u16);
|
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(0x4000130 as *mut u16);
|
||||||
|
|
||||||
|
/// A newtype over the key input state of the GBA.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct KeyInputSetting(u16);
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
impl KeyInputSetting {
|
||||||
|
register_bit!(A_BIT, u16, 0b1, a_pressed, read_write);
|
||||||
|
}
|
||||||
|
|
||||||
/// Key Interrupt Control
|
/// Key Interrupt Control
|
||||||
pub const KEYCNT: VolatilePtr<u16> = VolatilePtr(0x4000132 as *mut u16);
|
pub const KEYCNT: VolatilePtr<u16> = VolatilePtr(0x4000132 as *mut u16);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue