+11
README.md
+11
README.md
···
1
+
# HonkBird?
2
+
3
+
Idk i'm still working on the name. A gba game written in rust
4
+
with [agb-rs](https://agbrs.dev/). Heavily wip, just need it in a git repo so I can hack on it on another computer.
5
+
6
+
If you're really wanting to run this, check the [agb book out](https://agbrs.dev/book/), first couple chapters will get
7
+
you going
8
+
9
+
Special thanks
10
+
11
+
- The goose is from [here](https://duckhive.itch.io/goose). Just scaled it down to 16x16 and animated it in aseprite
+132
src/game.rs
+132
src/game.rs
···
1
+
use crate::{Fixed, goose_sprites};
2
+
use agb::display::object::Object;
3
+
use agb::display::{GraphicsFrame, HEIGHT, Priority, WIDTH};
4
+
use agb::fixnum::{Num, Vector2D, num, vec2};
5
+
use agb::input::Button;
6
+
7
+
pub enum GameState {
8
+
Start,
9
+
Playing,
10
+
GameOver,
11
+
}
12
+
13
+
pub struct GameLoop {
14
+
pub score: u16,
15
+
pub game_state: GameState,
16
+
}
17
+
18
+
impl GameLoop {
19
+
pub fn new() -> Self {
20
+
Self {
21
+
score: 0,
22
+
game_state: GameState::Start,
23
+
}
24
+
}
25
+
26
+
pub fn run(&mut self, gba: &mut agb::Gba) {
27
+
let mut gfx = gba.graphics.get();
28
+
29
+
let mut button_controller = agb::input::ButtonController::new();
30
+
let mut goose = Goose::new();
31
+
32
+
loop {
33
+
button_controller.update();
34
+
let mut frame = gfx.frame();
35
+
36
+
let just_pressed = button_controller.is_just_pressed(Button::A);
37
+
goose.move_goose(just_pressed);
38
+
goose.show(&mut frame);
39
+
frame.commit();
40
+
}
41
+
}
42
+
}
43
+
44
+
/// I really don't have a better name for these
45
+
#[derive(Copy, Clone)]
46
+
pub enum GooseState {
47
+
Zero = 0,
48
+
One = 1,
49
+
Two = 2,
50
+
Three = 3,
51
+
}
52
+
53
+
pub struct Goose {
54
+
pos: Vector2D<Fixed>,
55
+
velocity: Vector2D<Fixed>,
56
+
current_state: GooseState,
57
+
//Should increase every game play loop and reset at 64
58
+
tick_counter: u8,
59
+
}
60
+
61
+
impl Goose {
62
+
pub fn new() -> Self {
63
+
Self {
64
+
pos: vec2(num!(5), num!(5)),
65
+
velocity: vec2(num!(0.20), num!(0)),
66
+
current_state: GooseState::Zero,
67
+
tick_counter: 0,
68
+
}
69
+
}
70
+
71
+
pub fn show(&mut self, frame: &mut GraphicsFrame) {
72
+
let _ = Object::new(goose_sprites::GOOSE.sprite(self.current_state as usize))
73
+
.set_pos(self.pos.round())
74
+
.set_priority(Priority::P1)
75
+
.show(frame);
76
+
}
77
+
78
+
fn animate(&mut self) {
79
+
// Advance animation every 8 ticks
80
+
self.tick_counter = self.tick_counter.wrapping_add(1);
81
+
if self.tick_counter % 8 == 0 {
82
+
self.current_state = match self.current_state {
83
+
GooseState::Zero => GooseState::One,
84
+
GooseState::One => GooseState::Two,
85
+
GooseState::Two => GooseState::Three,
86
+
GooseState::Three => GooseState::Zero,
87
+
}
88
+
}
89
+
//Just a catch to not let that number get too big
90
+
if self.tick_counter >= 64 {
91
+
self.tick_counter = 0;
92
+
}
93
+
}
94
+
95
+
fn reset(&mut self) {
96
+
self.pos = vec2(num!(5), num!(5));
97
+
self.velocity = vec2(num!(0.20), num!(0));
98
+
}
99
+
100
+
pub fn move_goose(&mut self, button_pressed: bool) {
101
+
//If the button is pressed, go up a bit
102
+
if button_pressed {
103
+
self.velocity.y = num!(-1);
104
+
} else {
105
+
self.velocity.y += num!(0.02);
106
+
}
107
+
108
+
self.pos += self.velocity;
109
+
110
+
//Right now just cycling through may change animations around
111
+
self.animate();
112
+
113
+
//Keep in frame right now. Will later do the scrolling map thing
114
+
if self.pos.y < num!(0) {
115
+
self.pos.y = num!(0);
116
+
self.velocity.y = num!(0);
117
+
}
118
+
if self.pos.y >= num!(HEIGHT) {
119
+
//TODO call game end here later
120
+
self.reset();
121
+
}
122
+
123
+
// Keep X within screen horizontally (optional: clamp)
124
+
if self.pos.x < num!(0) {
125
+
self.pos.x = num!(0);
126
+
}
127
+
let max_x = num!(WIDTH - 1);
128
+
if self.pos.x > max_x {
129
+
self.pos.x = max_x;
130
+
}
131
+
}
132
+
}
+11
-152
src/main.rs
+11
-152
src/main.rs
···
11
11
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
12
12
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
13
13
14
+
mod game;
15
+
14
16
// By default no_std crates don't get alloc, so you won't be able to use things like Vec
15
17
// until you declare the extern crate. `agb` provides an allocator so it will all work
16
18
extern crate alloc;
17
19
18
-
use agb::display::{GraphicsFrame, Priority, Rgb15};
20
+
use crate::game::{GameLoop, GameState};
19
21
use agb::display::font::{AlignmentKind, Font, Layout, RegularBackgroundTextRenderer};
20
-
use agb::display::object::Object;
21
22
use agb::display::tiled::{RegularBackground, RegularBackgroundSize, TileFormat, VRAM_MANAGER};
22
-
use agb::fixnum::{num, vec2, FixedNum, Num, Vector2D};
23
+
use agb::display::{Priority, Rgb15};
24
+
use agb::fixnum::Num;
25
+
use agb::input::Button;
23
26
use agb::{include_aseprite, include_font};
24
-
use agb::input::Button;
25
-
26
-
const SCREEN_W: i32 = 240;
27
-
const SCREEN_H: i32 = 160;
28
27
29
28
include_aseprite!(
30
29
mod goose_sprites,
31
30
"gfx/flap_animated.aseprite"
32
31
);
33
32
34
-
static FONT: Font = include_font!("fnt/Born2bSportyV2.ttf", 20);
33
+
static FONT: Font = include_font!("fnt/Born2bSportyV2.ttf", 24);
35
34
36
35
type Fixed = Num<i32, 8>;
37
-
38
36
39
37
#[agb::entry]
40
38
fn main(mut gba: agb::Gba) -> ! {
···
47
45
TileFormat::FourBpp,
48
46
);
49
47
50
-
51
48
let mut text_layout = Layout::new(
52
-
"Hello, this is some text that I want to display!",
49
+
"HonkBird\nPress Start to play",
53
50
&FONT,
54
51
AlignmentKind::Left,
55
52
32,
56
53
200,
57
54
);
58
55
59
-
let mut text_renderer = RegularBackgroundTextRenderer::new((4, 0));
56
+
let mut text_renderer = RegularBackgroundTextRenderer::new((4, 50));
60
57
let mut button_controller = agb::input::ButtonController::new();
61
-
62
58
loop {
63
59
match game.game_state {
64
60
GameState::Start => {
···
67
63
if let Some(letter) = text_layout.next() {
68
64
text_renderer.show(&mut bg, &letter);
69
65
}
70
-
if button_controller.is_just_pressed(Button::A) {
66
+
if button_controller.is_just_pressed(Button::START) {
71
67
game.game_state = GameState::Playing;
72
68
}
73
-
// Scope the graphics borrow to this arm only
74
69
let mut gfx = gba.graphics.get();
75
70
let mut frame = gfx.frame();
76
71
···
79
74
frame.commit();
80
75
}
81
76
GameState::Playing => {
82
-
game.run(&mut gba);
77
+
game.run(&mut gba);
83
78
}
84
79
GameState::GameOver => {}
85
80
}
86
-
87
81
}
88
82
}
89
-
90
-
91
-
pub enum GameState {
92
-
Start,
93
-
Playing,
94
-
GameOver,
95
-
}
96
-
97
-
struct GameLoop {
98
-
still_going: bool,
99
-
score: u16,
100
-
game_state: GameState
101
-
}
102
-
103
-
impl GameLoop {
104
-
pub fn new() -> Self {
105
-
Self {
106
-
still_going: true,
107
-
score: 0,
108
-
game_state: GameState::Start,
109
-
}
110
-
}
111
-
112
-
pub fn run(&mut self, gba: &mut agb::Gba) {
113
-
let mut gfx = gba.graphics.get();
114
-
115
-
let mut button_controller = agb::input::ButtonController::new();
116
-
let mut goose = Goose::new();
117
-
118
-
loop {
119
-
button_controller.update();
120
-
let mut frame = gfx.frame();
121
-
122
-
let just_pressed = button_controller.is_just_pressed(Button::A);
123
-
goose.move_goose(just_pressed);
124
-
goose.show(&mut frame);
125
-
frame.commit();
126
-
}
127
-
}
128
-
}
129
-
130
-
131
-
132
-
133
-
/// I really don't have a better name for these
134
-
#[derive(Copy, Clone)]
135
-
pub enum GooseState {
136
-
Zero = 0,
137
-
One = 1,
138
-
Two = 2,
139
-
Three = 3,
140
-
}
141
-
142
-
143
-
struct Goose {
144
-
pos: Vector2D<Fixed>,
145
-
velocity: Vector2D<Fixed>,
146
-
current_state: GooseState,
147
-
frame_counter: u16,
148
-
}
149
-
150
-
impl Goose {
151
-
pub fn new() -> Self {
152
-
Self {
153
-
pos: vec2(num!(5), num!(5)),
154
-
velocity: vec2(num!(0.20), num!(0)),
155
-
current_state: GooseState::Zero,
156
-
frame_counter: 0,
157
-
}
158
-
}
159
-
160
-
pub fn show(&mut self, frame: &mut GraphicsFrame) {
161
-
let _ = Object::new(goose_sprites::GOOSE.sprite(self.current_state as usize))
162
-
.set_pos(self.pos.round())
163
-
.set_priority(Priority::P1)
164
-
.show(frame);
165
-
166
-
}
167
-
168
-
fn animate(&mut self) {
169
-
// Advance animation every 8 frames
170
-
self.frame_counter = self.frame_counter.wrapping_add(1);
171
-
if self.frame_counter % 8 == 0 {
172
-
self.current_state = match self.current_state {
173
-
GooseState::Zero => GooseState::One,
174
-
GooseState::One => GooseState::Two,
175
-
GooseState::Two => GooseState::Three,
176
-
GooseState::Three => GooseState::Zero,
177
-
}
178
-
}
179
-
}
180
-
181
-
fn reset(&mut self) {
182
-
self.pos = vec2(num!(5), num!(5));
183
-
self.velocity = vec2(num!(0), num!(0));
184
-
}
185
-
186
-
pub fn move_goose(&mut self, button_pressed: bool) {
187
-
// Flap impulse
188
-
if button_pressed {
189
-
// Give an upward kick similar to Flappy Bird
190
-
self.velocity.y = num!(-1);
191
-
}else{
192
-
// Gravity pulls down constantly
193
-
self.velocity.y += num!(0.02);
194
-
}
195
-
196
-
197
-
// Apply velocity to position
198
-
self.pos += self.velocity;
199
-
200
-
// Animate sprite
201
-
self.animate();
202
-
203
-
// Screen bounds checking (top-left is 0,0; bottom-right is 240,160)
204
-
// Loop/reset when going out of vertical bounds
205
-
if self.pos.y < num!(0) {
206
-
self.pos.y = num!(0);
207
-
self.velocity.y = num!(0);
208
-
}
209
-
if self.pos.y >= Num::<i32, 8>::from_raw((SCREEN_H as i32) << 8) {
210
-
// Off the bottom of the screen — reset to start like a flappy-bird loop
211
-
self.reset();
212
-
}
213
-
214
-
// Keep X within screen horizontally (optional: clamp)
215
-
if self.pos.x < num!(0) {
216
-
self.pos.x = num!(0);
217
-
}
218
-
let max_x = Num::<i32, 8>::from_raw((SCREEN_W as i32 - 1) << 8);
219
-
if self.pos.x > max_x {
220
-
self.pos.x = max_x;
221
-
}
222
-
}
223
-
}