From 553a4bea089b037d9faca1505645aa0b6e272e74 Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 1 Aug 2022 22:26:32 +0100 Subject: [PATCH 1/6] update the sprites page of the book --- book/games/pong/gfx/sprites.aseprite | Bin 1366 -> 1184 bytes book/games/pong/src/main.rs | 33 +++++-- book/src/SUMMARY.md | 14 +-- book/src/pong/03_sprites.md | 129 ++++++++++----------------- book/src/pong/ball.aseprite | Bin 0 -> 1184 bytes book/src/pong/sprites.aseprite | Bin 0 -> 1184 bytes book/src/pong/sprites.png | Bin 438 -> 420 bytes 7 files changed, 79 insertions(+), 97 deletions(-) create mode 100644 book/src/pong/ball.aseprite create mode 100644 book/src/pong/sprites.aseprite diff --git a/book/games/pong/gfx/sprites.aseprite b/book/games/pong/gfx/sprites.aseprite index 098e25512c326d01709f38aacf12d7ab7bb574d8..ec9761f90d711122ab5ab308e990ff5db5f6164b 100644 GIT binary patch delta 492 zcmcb{wSbdp0n0?DI^K9D28LfB*%(q77#P?#_QoFtET>LTI?8Z)QsAWIk4D<>UkhRw1QT|34;fQWRR9t|@E_=Q2;gB5V5pb_vt^t%n+kTFvr|_k6m~GE~ zkFydz7q9-Wp2uG%$I2QEij@D2m&^Y(Z>s|Yy1ojhC6&FyR$?B08Vi1761SM diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 456b7856..218caa25 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -10,33 +10,50 @@ // which won't be a particularly clear error message. #![no_main] -use agb::display::object::{Graphics, Tag}; -use agb::Gba; +use agb::{ + display::object::{Graphics, ObjectController, Tag}, + include_aseprite, +}; -const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite"); +// Import the sprites in to this constant. This holds the sprite +// and palette data in a way that is manageable by agb. +const GRAPHICS: &Graphics = include_aseprite!("gfx/sprites.aseprite"); + +// We define some easy ways of referencing the sprites +const PADDLE_END: &Tag = GRAPHICS.tags().get("Paddle End"); +const PADDLE_MID: &Tag = GRAPHICS.tags().get("Paddle Mid"); +const BALL: &Tag = GRAPHICS.tags().get("Ball"); // The main function must take 0 arguments and never return. The agb::entry decorator // ensures that everything is in order. `agb` will call this after setting up the stack // and interrupt handlers correctly. #[agb::entry] -fn main(mut gba: Gba) -> ! { +fn main(mut gba: agb::Gba) -> ! { + // Get the OAM manager let object = gba.display.object.get(); - const BALL: &Tag = GRAPHICS.tags().get("Ball"); - let ball_sprite = object.sprite(BALL.sprite(0)); - let mut ball = object.object(ball_sprite); + // Create an object with the ball sprite + let mut ball = object.object_sprite(BALL.sprite(0)); + // Place this at some point on the screen, (50, 50) for example ball.set_x(50).set_y(50).show(); + // Now commit the object controller so this change is reflected on the screen, + // this should normally be done in vblank but it'll work just fine here for now + object.commit(); + let mut ball_x = 50; let mut ball_y = 50; let mut x_velocity = 1; let mut y_velocity = 1; loop { + // This will calculate the new position and enforce the position + // of the ball remains within the screen ball_x = (ball_x + x_velocity).clamp(0, agb::display::WIDTH - 16); ball_y = (ball_y + y_velocity).clamp(0, agb::display::HEIGHT - 16); + // We check if the ball reaches the edge of the screen and reverse it's direction if ball_x == 0 || ball_x == agb::display::WIDTH - 16 { x_velocity = -x_velocity; } @@ -45,8 +62,10 @@ fn main(mut gba: Gba) -> ! { y_velocity = -y_velocity; } + // Set the position of the ball to match our new calculated position ball.set_x(ball_x as u16).set_y(ball_y as u16); + // Wait for vblank, then commit the objects to the screen agb::display::busy_wait_for_vblank(); object.commit(); } diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index eb4d55de..be3bdd75 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -3,11 +3,11 @@ - [Introduction](./introduction/introduction.md) - [The Game Boy Advance hardware](./hardware/hardware.md) - [Running an example](./setup/getting_started.md) - - [Environment setup](./setup/setup.md) - - [Linux setup](./setup/linux.md) - - [Windows setup]() - - [Mac OS setup]() - - [Building the game](./setup/building.md) + - [Environment setup](./setup/setup.md) + - [Linux setup](./setup/linux.md) + - [Windows setup]() + - [Mac OS setup]() + - [Building the game](./setup/building.md) - [Learn agb part I - pong](./pong/01_introduction.md) - - [The Gba struct](./pong/02_the_gba_struct.md) - \ No newline at end of file + - [The Gba struct](./pong/02_the_gba_struct.md) + - [Sprites](./pong/03_sprites.md) diff --git a/book/src/pong/03_sprites.md b/book/src/pong/03_sprites.md index e4ce29a9..57216144 100644 --- a/book/src/pong/03_sprites.md +++ b/book/src/pong/03_sprites.md @@ -38,11 +38,10 @@ For now, we will only be dealing with regular sprites. # Import the sprite -As mentioned above, we'll need to convert the sprite data into a format that the Game Boy Advance will be able to understand (so palette information and tile data). -Once we've converted it, we'll need to import this tile data into the Game Boy Advance's memory on start up and then create a sprite in the OAM. - Firstly, you're going to need to import the sprites into your project. -Save the following image into a new folder called `gfx` in your project: +`agb` has great support for the [aseprite](https://www.aseprite.org/) sprite editor which can be bought for $20 or you can compile it yourself for free. +Aseprite files can be natively imported by `agb` for use on the Game Boy Advance. +Here is the sprite sheet we will use as a png, but you should [download the aseprite file](sprites.aseprite) and place it in `gfx/sprites.aseprite`. ![pong sprites](sprites.png) @@ -50,89 +49,48 @@ This contains 5 `16x16px` sprites. The first is the end cap for the paddle. The second is the centre part of the paddle, which could potentially be repeated a few times. The third until the fifth is the ball, with various squashed states. -The background is a lovely shade of `#ff0044` which we will use for the transparency. - -`agb` needs to know what the tile size is, and also what the transparent colour is so that it can properly produce data that the Game Boy Advance can understand. -So you need to create a manifest file for the image, which is declared in a toml file. - -In the same `gfx` folder as the `sprites.png` file, also create a `sprites.toml` file with the following content: - -```toml -version = "1.0" - -[image.sprites] -filename = "sprites.png" -tile_size = "16x16" -transparent_colour = "ff0044" -``` - -Now let's create a module in the `main.rs` file which imports the sprite sheet and loads it into memory. -Anything sprite related is managed by the [`ObjectControl` struct](https://docs.rs/agb/0.8.0/agb/display/object/struct.ObjectControl.html). -So we use that to load the sprite tile map and palette data. +The aseprite file defines tags for these sprites, being "Paddle End", "Paddle Mid", and "Ball". ```rust -// Put all the graphics related code in the gfx module -mod gfx { - use agb::display::object::ObjectControl; +use agb::{include_aseprite, + display::object::{Graphics, ObjectController, Tag} +}; - // Import the sprites into this module. This will create a `sprites` module - // and within that will be a constant called `sprites` which houses all the - // palette and tile data. - agb::include_gfx!("gfx/sprites.toml"); +// Import the sprites in to this constant. This holds the sprite +// and palette data in a way that is manageable by agb. +const GRAPHICS: &Graphics = include_aseprite!("gfx/sprites.aseprite"); - // Loads the sprites tile data and palette data into VRAM - pub fn load_sprite_data(object: &mut ObjectControl) { - object.set_sprite_palettes(sprites::sprites.palettes); - object.set_sprite_tilemap(sprites::sprites.tiles); - } +// We define some easy ways of referencing the sprites +const PADDLE_END: &Tag = GRAPHICS.tags().get("Paddle End"); +const PADDLE_MID: &Tag = GRAPHICS.tags().get("Paddle Mid"); +const BALL: &Tag = GRAPHICS.tags().get("Ball"); +``` + +This uses the `include_aseprite` macro to include the sprites in the given aseprite file. +Now, let's put this on screen by firstly creating the object manager and then creating an object, this will also involve the creation of the main entry function using the `entry` macro. +The signature of this function takes the `Gba` struct and has the never return type, this means Rust will enforce that this function never returns, for now we will achieve this using a busy loop. +Using the `Gba` struct we get the [`ObjectController` struct](https://docs.rs/agb/0.10.0/agb/display/object/struct.ObjectController.html) which manages loading and unloading sprites and objects. + +```rust +#[agb::entry] +fn main(gba: mut agb::Gba) -> ! { + // Get the OAM manager + let object = gba.display.object.get(); + + // Create an object with the ball sprite + let mut ball = object.object_sprite(BALL.sprite(0)); + + // Place this at some point on the screen, (50, 50) for example + ball.set_x(50).set_y(50).show(); + + // Now commit the object controller so this change is reflected on the screen, + // this should normally be done in vblank but it'll work just fine here for now + object.commit(); + + loop {} } ``` -This uses the `include_gfx!` macro which loads the sprite information file and grabs the relevant tile data from there. - -Now, let's put this on screen by firstly creating the object manager and loading the sprite data from above: - -```rust -let mut gba = Gba::new(); - -// set background mode to mode 0 (we'll cover this in more detail later) -// for now, this is required in order for anything to show up on screen at all. -let _tiled = gba.display.video.tiled0(); - -// Get the OAM manager -let mut object = gba.display.object.get(); -gfx::load_sprite_data(&mut object); -object.enable(); -``` - -Then, let's create the ball object and put it at 50, 50 on the screen: - -```rust -// continued from above: -let mut ball = object.get_object_standard(); // (1) - -ball.set_x(50) // (2) - .set_y(50) - .set_sprite_size(Size::S16x16) - .set_tile_id(4 * 2) - .show(); - -ball.commit(); // (3) -``` - -There are a few new things introduced here, so lets go through these 1 by 1. - -1. The first thing we're doing is creating a 'standard' object. -These are of non-affine type. -2. We now set some properties on the ball. -The position and size are self explanatory. -Interesting here is that the tile id is 4 * 2. -This is because the tile id is calculated in 8x8 pixel chunks, and in our example we have 16x16 sprites so each sprite takes 4 tiles and this is the tile in position 2 on the tilesheet above (0 indexed). -The final call to `.show()` will make it actually visible as sprites are hidden by default. -3. The call to `.commit()` actually makes the change to object memory. -Until `.commit()` is called, no changes you made will actually be visible. -This is very handy because we might want to change sprite positions etc while the frame is rendering, and then move them in the short space of time we have before the next frame starts rendering (vblank). - If you run this you should now see the ball for this pong game somewhere in the top left of the screen. # Making the sprite move @@ -144,7 +102,7 @@ You shouldn't use this is a real game (we'll do it properly later on), but for n Making the sprite move 1 pixel every frame (so approximately 60 pixels per second) can be done as follows: ```rust -// replace the call to ball.commit() with the following: +// replace the call to object.commit() with the following: let mut ball_x = 50; let mut ball_y = 50; @@ -152,9 +110,12 @@ let mut x_velocity = 1; let mut y_velocity = 1; loop { + // This will calculate the new position and enforce the position + // of the ball remains within the screen ball_x = (ball_x + x_velocity).clamp(0, agb::display::WIDTH - 16); ball_y = (ball_y + y_velocity).clamp(0, agb::display::HEIGHT - 16); + // We check if the ball reaches the edge of the screen and reverse it's direction if ball_x == 0 || ball_x == agb::display::WIDTH - 16 { x_velocity = -x_velocity; } @@ -163,13 +124,15 @@ loop { y_velocity = -y_velocity; } + // Set the position of the ball to match our new calculated position ball.set_x(ball_x as u16).set_y(ball_y as u16); + // Wait for vblank, then commit the objects to the screen agb::display::busy_wait_for_vblank(); - ball.commit(); + object.commit(); } ``` # What we did -In this section, we covered why sprites are important, how to create and manage them using the `ObjectControl` in `agb` and make a ball bounce around the screen. \ No newline at end of file +In this section, we covered why sprites are important, how to create and manage them using the `ObjectController` in `agb` and make a ball bounce around the screen. \ No newline at end of file diff --git a/book/src/pong/ball.aseprite b/book/src/pong/ball.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..83fa3e61afb767ed823f96f0ed5da3b934887c69 GIT binary patch literal 1184 zcmcJOYeHtOTDR5EE|ayz<4A5LY||)nPFB(WPUT=E>9H0 z>;}_flfkk?IoPgF0$&+-fb+d&;F9es`29-)XkEwx^>fNjTFUXKlkJ?GA#gH2!u(1$z3VGU*YLKddbgd+rD2Q_#>3P#ZJxbQqIpn!aY zBN?&CL?F@-CG#XPB5Zqd9j_Z(RrNZ-l3T&i*d1`J8<&o`?(`rbi-T*7gw$6R6C8< zvloX3WO@7Uw*(DvRgUmnw}?Wgd(4d+Br1O^5sLq(?-Vb|MFQoBsdM?OVms>|n-yD( zpSG$~R`MS`;p@8tr#>$A=cdG+%pP{P-VBNA``mZSRHM3XDzcN!{I(MxFDY)Sj9m|}TvPpT#F3L!X zK$oF{3QM$4l_glo!Y@rR=z?8Ry5@dEoqi&pGdagy_|< zl%nxtNJK;i%(a0%-=@F5w)^i^r9ng1lOi9nB2Fv`&z45o4> zquL-rE9*v}c(BUQfQpn^E%Sl1ecxTtLbVK|I@6yYHT1vd^*X>Ufy5&htsFfc} z+&gv48CSqr?;e4Z$Ui-T-5JR)^-W5pj{9a?V!m6x`^0-Lr12N&CJZEonwdQ&UJ^S5J LMPAY*6qbGmdQDcP literal 0 HcmV?d00001 diff --git a/book/src/pong/sprites.png b/book/src/pong/sprites.png index 9964bf0b6400be2a60e1778c6ee65dbb0f91467f..360634d1a9218d16efa30ae90dd6b511ec96bede 100644 GIT binary patch delta 380 zcmV-?0fYXw1Ed3xF@Ii3L_t(oh3%FxN&`U@hCfXponTZD8zB&E5>P8e8*>LQpx_Y% zJwlEU$O&?XU?T{M3Tvk*X@a08#Lhz2VjY&5WHXV>+YK^*wd}t4=40o*e+IJFocUsP zxg3s5czu7=a^{^8sFiDn#{g_?SG2&p+Xn#Jt)@mYH9__T@_!fCPa(khS$(2}(&j<* za8Kxjz!&fZd;wp;7nr32Gk7}RY^(#YwD$}^VW+4itep%rlIhPzwQ{|jO|*S=2~|)p0{G)QPr!IPHA` z4m&aXX|#5txpkr`_q*_N^bN#5V9vjDf)Reu*8;l1ene7@?#Aiug3Sly2$FpumlACaI0t9b;iL zHo*W5c_CQnPYif{U-CqtDtGn{09adJ(HtM{pJKnWQ;o!$pnv=Xjmz6(0K%i5Q9!u5 z13>V2;fA0}P$j4mR0*mC|0zLDo)5}@JKrtG73Q~|0cdWtw1A-7*GTLMROMc4cCzPY z8-T${q>;EkBT4(Z83NEG%DnH7;Nv<3l5QxH#Oj_1&fi7=bQb3{lC(MM!tX4YJ`ab( zPr&7qs1_1o=8p zFNi>rB$hoR_5^YY<(HG3fA<7xk*5dVr!jRtkS5Oy#m$1LKKT2CVX6=QRase{KKLh? trVqvKL7qOiC-|#A_}c?heXu6@0SQNyXlYPJ`Tzg`00>D%PDHLkV1miOx?%tT From 41f22f825b5f523616539a323e6cefcc77827583 Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 1 Aug 2022 22:50:22 +0100 Subject: [PATCH 2/6] update link to use latest on docs.rs --- book/src/pong/03_sprites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/pong/03_sprites.md b/book/src/pong/03_sprites.md index 57216144..b97cd526 100644 --- a/book/src/pong/03_sprites.md +++ b/book/src/pong/03_sprites.md @@ -69,7 +69,7 @@ const BALL: &Tag = GRAPHICS.tags().get("Ball"); This uses the `include_aseprite` macro to include the sprites in the given aseprite file. Now, let's put this on screen by firstly creating the object manager and then creating an object, this will also involve the creation of the main entry function using the `entry` macro. The signature of this function takes the `Gba` struct and has the never return type, this means Rust will enforce that this function never returns, for now we will achieve this using a busy loop. -Using the `Gba` struct we get the [`ObjectController` struct](https://docs.rs/agb/0.10.0/agb/display/object/struct.ObjectController.html) which manages loading and unloading sprites and objects. +Using the `Gba` struct we get the [`ObjectController` struct](https://docs.rs/agb/latest/agb/display/object/struct.ObjectController.html) which manages loading and unloading sprites and objects. ```rust #[agb::entry] From 595d64912ef694adfbfb5a99946cbb63b9cd8328 Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 1 Aug 2022 22:50:31 +0100 Subject: [PATCH 3/6] remove unused import --- book/src/pong/03_sprites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/pong/03_sprites.md b/book/src/pong/03_sprites.md index b97cd526..77df0726 100644 --- a/book/src/pong/03_sprites.md +++ b/book/src/pong/03_sprites.md @@ -53,7 +53,7 @@ The aseprite file defines tags for these sprites, being "Paddle End", "Paddle Mi ```rust use agb::{include_aseprite, - display::object::{Graphics, ObjectController, Tag} + display::object::{Graphics, Tag} }; // Import the sprites in to this constant. This holds the sprite From f4bd6e330df1f3adff54ebd66999d6f237ecbe95 Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 1 Aug 2022 22:50:37 +0100 Subject: [PATCH 4/6] satisfy linter --- book/games/pong/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 218caa25..73916baa 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -11,7 +11,7 @@ #![no_main] use agb::{ - display::object::{Graphics, ObjectController, Tag}, + display::object::{Graphics, Tag}, include_aseprite, }; @@ -20,7 +20,9 @@ use agb::{ const GRAPHICS: &Graphics = include_aseprite!("gfx/sprites.aseprite"); // We define some easy ways of referencing the sprites +#[allow(dead_code)] const PADDLE_END: &Tag = GRAPHICS.tags().get("Paddle End"); +#[allow(dead_code)] const PADDLE_MID: &Tag = GRAPHICS.tags().get("Paddle Mid"); const BALL: &Tag = GRAPHICS.tags().get("Ball"); From 6c957f9e3f50f53980e86f373092728467130ae9 Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 1 Aug 2022 23:00:50 +0100 Subject: [PATCH 5/6] resolve multiple aseprite files --- book/src/pong/ball.aseprite | Bin 1184 -> 0 bytes book/src/pong/sprites.aseprite | Bin 1184 -> 1184 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 book/src/pong/ball.aseprite diff --git a/book/src/pong/ball.aseprite b/book/src/pong/ball.aseprite deleted file mode 100644 index 83fa3e61afb767ed823f96f0ed5da3b934887c69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1184 zcmcJOYeHtOTDR5EE|ayz<4A5LY||)nPFB(WPUT=E>9H0 z>;}_flfkk?IoPgF0$&+-fb+d&;F9es`29-)XkEwx^>fNjTFUXKlkJ?GA#gH2!u(1$z3VGU*YLKddbgd+rD2Q_#>3P#ZJxbQqIpn!aY zBN?&CL?F@-CG#XPB5Zqd9j_Z(RrNZ-l3T&i*d1`J8<&o`?(`rbi-T*7gw$6R6C8< zvloX3WO@7Uw*(DvRgUmnw}?Wgd(4d+Br1O^5sLq(?-Vb|MFQoBsdM?OVms>|n-yD( zpSG$~R`MS`;p@8tr#>$A=cdG+%pP{P-VBNA``mZSRHM3XDzcN!{I($iF=A|$)Fis9&k`iZPU|~Q3EDTPGIXRQxGj9}Z a1xoz-$jFcaq#1#@bn*u#(ajA^@r(dCHxkAG delta 102 zcmZ3$xqx#*17rNg##xLKObjT1g~2H?Cx@AVd9nEw;fqMIF=;~51qfU16dWMoJI(u_bnIg4o{0CI>DPXGV_ From 7e2787384d253871250e760c77920d40eb0c5754 Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 1 Aug 2022 23:02:23 +0100 Subject: [PATCH 6/6] reorder sprites --- book/games/pong/gfx/sprites.aseprite | Bin 1184 -> 1184 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/book/games/pong/gfx/sprites.aseprite b/book/games/pong/gfx/sprites.aseprite index ec9761f90d711122ab5ab308e990ff5db5f6164b..83fa3e61afb767ed823f96f0ed5da3b934887c69 100644 GIT binary patch delta 85 zcmZ3$xqx#*1EbT%##xLLZTKh0F>$iF=A|$)Fis9&k`iZPU|~Q3EDTPGIXRQxGj9}Z a1xoz-$jFcaq#1#@bn*u#(ajA^@r(dCHxkAG delta 102 zcmZ3$xqx#*17rNg##xLKObjT1g~2H?Cx@AVd9nEw;fqMIF=;~51qfU16dWMoJI(u_bnIg4o{0CI>DPXGV_