···11+# HonkBird?
22+33+Idk i'm still working on the name. A gba game written in rust
44+with [agb-rs](https://agbrs.dev/). Heavily wip, just need it in a git repo so I can hack on it on another computer.
55+66+If you're really wanting to run this, check the [agb book out](https://agbrs.dev/book/), first couple chapters will get
77+you going
88+99+Special thanks
1010+1111+- 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
···11+use crate::{Fixed, goose_sprites};
22+use agb::display::object::Object;
33+use agb::display::{GraphicsFrame, HEIGHT, Priority, WIDTH};
44+use agb::fixnum::{Num, Vector2D, num, vec2};
55+use agb::input::Button;
66+77+pub enum GameState {
88+ Start,
99+ Playing,
1010+ GameOver,
1111+}
1212+1313+pub struct GameLoop {
1414+ pub score: u16,
1515+ pub game_state: GameState,
1616+}
1717+1818+impl GameLoop {
1919+ pub fn new() -> Self {
2020+ Self {
2121+ score: 0,
2222+ game_state: GameState::Start,
2323+ }
2424+ }
2525+2626+ pub fn run(&mut self, gba: &mut agb::Gba) {
2727+ let mut gfx = gba.graphics.get();
2828+2929+ let mut button_controller = agb::input::ButtonController::new();
3030+ let mut goose = Goose::new();
3131+3232+ loop {
3333+ button_controller.update();
3434+ let mut frame = gfx.frame();
3535+3636+ let just_pressed = button_controller.is_just_pressed(Button::A);
3737+ goose.move_goose(just_pressed);
3838+ goose.show(&mut frame);
3939+ frame.commit();
4040+ }
4141+ }
4242+}
4343+4444+/// I really don't have a better name for these
4545+#[derive(Copy, Clone)]
4646+pub enum GooseState {
4747+ Zero = 0,
4848+ One = 1,
4949+ Two = 2,
5050+ Three = 3,
5151+}
5252+5353+pub struct Goose {
5454+ pos: Vector2D<Fixed>,
5555+ velocity: Vector2D<Fixed>,
5656+ current_state: GooseState,
5757+ //Should increase every game play loop and reset at 64
5858+ tick_counter: u8,
5959+}
6060+6161+impl Goose {
6262+ pub fn new() -> Self {
6363+ Self {
6464+ pos: vec2(num!(5), num!(5)),
6565+ velocity: vec2(num!(0.20), num!(0)),
6666+ current_state: GooseState::Zero,
6767+ tick_counter: 0,
6868+ }
6969+ }
7070+7171+ pub fn show(&mut self, frame: &mut GraphicsFrame) {
7272+ let _ = Object::new(goose_sprites::GOOSE.sprite(self.current_state as usize))
7373+ .set_pos(self.pos.round())
7474+ .set_priority(Priority::P1)
7575+ .show(frame);
7676+ }
7777+7878+ fn animate(&mut self) {
7979+ // Advance animation every 8 ticks
8080+ self.tick_counter = self.tick_counter.wrapping_add(1);
8181+ if self.tick_counter % 8 == 0 {
8282+ self.current_state = match self.current_state {
8383+ GooseState::Zero => GooseState::One,
8484+ GooseState::One => GooseState::Two,
8585+ GooseState::Two => GooseState::Three,
8686+ GooseState::Three => GooseState::Zero,
8787+ }
8888+ }
8989+ //Just a catch to not let that number get too big
9090+ if self.tick_counter >= 64 {
9191+ self.tick_counter = 0;
9292+ }
9393+ }
9494+9595+ fn reset(&mut self) {
9696+ self.pos = vec2(num!(5), num!(5));
9797+ self.velocity = vec2(num!(0.20), num!(0));
9898+ }
9999+100100+ pub fn move_goose(&mut self, button_pressed: bool) {
101101+ //If the button is pressed, go up a bit
102102+ if button_pressed {
103103+ self.velocity.y = num!(-1);
104104+ } else {
105105+ self.velocity.y += num!(0.02);
106106+ }
107107+108108+ self.pos += self.velocity;
109109+110110+ //Right now just cycling through may change animations around
111111+ self.animate();
112112+113113+ //Keep in frame right now. Will later do the scrolling map thing
114114+ if self.pos.y < num!(0) {
115115+ self.pos.y = num!(0);
116116+ self.velocity.y = num!(0);
117117+ }
118118+ if self.pos.y >= num!(HEIGHT) {
119119+ //TODO call game end here later
120120+ self.reset();
121121+ }
122122+123123+ // Keep X within screen horizontally (optional: clamp)
124124+ if self.pos.x < num!(0) {
125125+ self.pos.x = num!(0);
126126+ }
127127+ let max_x = num!(WIDTH - 1);
128128+ if self.pos.x > max_x {
129129+ self.pos.x = max_x;
130130+ }
131131+ }
132132+}
+11-152
src/main.rs
···1111#![cfg_attr(test, reexport_test_harness_main = "test_main")]
1212#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
13131414+mod game;
1515+1416// By default no_std crates don't get alloc, so you won't be able to use things like Vec
1517// until you declare the extern crate. `agb` provides an allocator so it will all work
1618extern crate alloc;
17191818-use agb::display::{GraphicsFrame, Priority, Rgb15};
2020+use crate::game::{GameLoop, GameState};
1921use agb::display::font::{AlignmentKind, Font, Layout, RegularBackgroundTextRenderer};
2020-use agb::display::object::Object;
2122use agb::display::tiled::{RegularBackground, RegularBackgroundSize, TileFormat, VRAM_MANAGER};
2222-use agb::fixnum::{num, vec2, FixedNum, Num, Vector2D};
2323+use agb::display::{Priority, Rgb15};
2424+use agb::fixnum::Num;
2525+use agb::input::Button;
2326use agb::{include_aseprite, include_font};
2424-use agb::input::Button;
2525-2626-const SCREEN_W: i32 = 240;
2727-const SCREEN_H: i32 = 160;
28272928include_aseprite!(
3029 mod goose_sprites,
3130 "gfx/flap_animated.aseprite"
3231);
33323434-static FONT: Font = include_font!("fnt/Born2bSportyV2.ttf", 20);
3333+static FONT: Font = include_font!("fnt/Born2bSportyV2.ttf", 24);
35343635type Fixed = Num<i32, 8>;
3737-38363937#[agb::entry]
4038fn main(mut gba: agb::Gba) -> ! {
···4745 TileFormat::FourBpp,
4846 );
49475050-5148 let mut text_layout = Layout::new(
5252- "Hello, this is some text that I want to display!",
4949+ "HonkBird\nPress Start to play",
5350 &FONT,
5451 AlignmentKind::Left,
5552 32,
5653 200,
5754 );
58555959- let mut text_renderer = RegularBackgroundTextRenderer::new((4, 0));
5656+ let mut text_renderer = RegularBackgroundTextRenderer::new((4, 50));
6057 let mut button_controller = agb::input::ButtonController::new();
6161-6258 loop {
6359 match game.game_state {
6460 GameState::Start => {
···6763 if let Some(letter) = text_layout.next() {
6864 text_renderer.show(&mut bg, &letter);
6965 }
7070- if button_controller.is_just_pressed(Button::A) {
6666+ if button_controller.is_just_pressed(Button::START) {
7167 game.game_state = GameState::Playing;
7268 }
7373- // Scope the graphics borrow to this arm only
7469 let mut gfx = gba.graphics.get();
7570 let mut frame = gfx.frame();
7671···7974 frame.commit();
8075 }
8176 GameState::Playing => {
8282- game.run(&mut gba);
7777+ game.run(&mut gba);
8378 }
8479 GameState::GameOver => {}
8580 }
8686-8781 }
8882}
8989-9090-9191-pub enum GameState {
9292- Start,
9393- Playing,
9494- GameOver,
9595-}
9696-9797-struct GameLoop {
9898- still_going: bool,
9999- score: u16,
100100- game_state: GameState
101101-}
102102-103103-impl GameLoop {
104104- pub fn new() -> Self {
105105- Self {
106106- still_going: true,
107107- score: 0,
108108- game_state: GameState::Start,
109109- }
110110- }
111111-112112- pub fn run(&mut self, gba: &mut agb::Gba) {
113113- let mut gfx = gba.graphics.get();
114114-115115- let mut button_controller = agb::input::ButtonController::new();
116116- let mut goose = Goose::new();
117117-118118- loop {
119119- button_controller.update();
120120- let mut frame = gfx.frame();
121121-122122- let just_pressed = button_controller.is_just_pressed(Button::A);
123123- goose.move_goose(just_pressed);
124124- goose.show(&mut frame);
125125- frame.commit();
126126- }
127127- }
128128-}
129129-130130-131131-132132-133133-/// I really don't have a better name for these
134134-#[derive(Copy, Clone)]
135135-pub enum GooseState {
136136- Zero = 0,
137137- One = 1,
138138- Two = 2,
139139- Three = 3,
140140-}
141141-142142-143143-struct Goose {
144144- pos: Vector2D<Fixed>,
145145- velocity: Vector2D<Fixed>,
146146- current_state: GooseState,
147147- frame_counter: u16,
148148-}
149149-150150-impl Goose {
151151- pub fn new() -> Self {
152152- Self {
153153- pos: vec2(num!(5), num!(5)),
154154- velocity: vec2(num!(0.20), num!(0)),
155155- current_state: GooseState::Zero,
156156- frame_counter: 0,
157157- }
158158- }
159159-160160- pub fn show(&mut self, frame: &mut GraphicsFrame) {
161161- let _ = Object::new(goose_sprites::GOOSE.sprite(self.current_state as usize))
162162- .set_pos(self.pos.round())
163163- .set_priority(Priority::P1)
164164- .show(frame);
165165-166166- }
167167-168168- fn animate(&mut self) {
169169- // Advance animation every 8 frames
170170- self.frame_counter = self.frame_counter.wrapping_add(1);
171171- if self.frame_counter % 8 == 0 {
172172- self.current_state = match self.current_state {
173173- GooseState::Zero => GooseState::One,
174174- GooseState::One => GooseState::Two,
175175- GooseState::Two => GooseState::Three,
176176- GooseState::Three => GooseState::Zero,
177177- }
178178- }
179179- }
180180-181181- fn reset(&mut self) {
182182- self.pos = vec2(num!(5), num!(5));
183183- self.velocity = vec2(num!(0), num!(0));
184184- }
185185-186186- pub fn move_goose(&mut self, button_pressed: bool) {
187187- // Flap impulse
188188- if button_pressed {
189189- // Give an upward kick similar to Flappy Bird
190190- self.velocity.y = num!(-1);
191191- }else{
192192- // Gravity pulls down constantly
193193- self.velocity.y += num!(0.02);
194194- }
195195-196196-197197- // Apply velocity to position
198198- self.pos += self.velocity;
199199-200200- // Animate sprite
201201- self.animate();
202202-203203- // Screen bounds checking (top-left is 0,0; bottom-right is 240,160)
204204- // Loop/reset when going out of vertical bounds
205205- if self.pos.y < num!(0) {
206206- self.pos.y = num!(0);
207207- self.velocity.y = num!(0);
208208- }
209209- if self.pos.y >= Num::<i32, 8>::from_raw((SCREEN_H as i32) << 8) {
210210- // Off the bottom of the screen — reset to start like a flappy-bird loop
211211- self.reset();
212212- }
213213-214214- // Keep X within screen horizontally (optional: clamp)
215215- if self.pos.x < num!(0) {
216216- self.pos.x = num!(0);
217217- }
218218- let max_x = Num::<i32, 8>::from_raw((SCREEN_W as i32 - 1) << 8);
219219- if self.pos.x > max_x {
220220- self.pos.x = max_x;
221221- }
222222- }
223223-}