Why are sprites not rendered after being moved to struct? #525
-
I'm trying to figure out how to organize my graphics code and I hit a big problem when moving my sprite rendering code from the main function to a struct in a separate file, inspired by how the code was organized in the example "The Purple Night". Initially, my code looked this (irrelevant parts truncated): main.rs use agb::{println, display::{
tiled::{RegularBackgroundSize, TileSetting, TiledMap},
Priority, object::{Graphics, TagMap},
}, input::{ButtonController, Button}, fixnum::Rect};
use szo_advance::{triToInt, Direction, incOrWrap, Entity, TAGS};
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
game_main(gba) // Use a separate function since agb::entry macro breaks rust-analyzer
}
fn game_main(mut gba: agb::Gba) -> ! {
let vblank = agb::interrupt::VBlank::get();
let (gfx, mut vram) = gba.display.video.tiled0();
let objCtrl = gba.display.object.get_managed();
let animTagPlayerIdleDown = TAGS.get("p_idle");
// definitions of anim tags for up, left and right idle sprites omitted
let mut player = Entity::new(&objCtrl, Rect::new((0, 1).into(), (5, 10).into()));
let sprite = animTagPlayerIdleDown.sprite(0);
let mut obj = objCtrl.object_sprite(sprite);
player.updatePosition(0, 0);
obj.set_position((player.position.x, player.position.y).into());
obj.show();
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32, tileset.format());
let mut input = ButtonController::new();
// background gfx setup omitted
let movtSpeed: i16 = 2;
let mut frame: usize = 0;
let mut playerSpriteOffset = 0;
let mut lastDirection = Direction::Down;
loop {
vblank.wait_for_vblank();
input.update(); // call update every loop
let (xDiff, yDiff) = (triToInt(input.x_tri()), triToInt(input.y_tri()));
// does bounds checking and returns new coords
let (newX, newY) = handlePlayerMovement(player.position.x + xDiff * movtSpeed, player.position.y + yDiff * movtSpeed);
// updates player.position, but not sprite position
player.updatePosition(newX, newY);
let playerIdle = xDiff == 0 && yDiff == 0;
if frame % 15 == 0 && playerIdle {
if playerSpriteOffset == 0 {
playerSpriteOffset = 1;
} else {
playerSpriteOffset = 0;
}
let idleAnimTag = match lastDirection {
Direction::Down => animTagPlayerIdleDown,
Direction::Up => animTagPlayerIdleUp,
Direction::Left | Direction::Right => animTagPlayerIdleHoriz,
};
obj.set_sprite(objCtrl.sprite(idleAnimTag.sprite(playerSpriteOffset)));
obj.set_hflip(lastDirection == Direction::Left);
}
obj.set_position((player.position.x, player.position.y).into());
// draw sprites
objCtrl.commit();
frame = frame.wrapping_add(1);
}
} lib.rs const SPRITESHEET: &Graphics = agb::include_aseprite!("gfx/main_spritesheet.aseprite");
pub const TAGS: &TagMap = SPRITESHEET.tags();
#[derive(Debug, PartialEq, Eq)]
pub enum Direction {
Up, Down, Left, Right
}
pub struct Entity<'a> {
sprite: Object<'a>,
pub position: Vector2D<i16>,
velocity: Vector2D<u16>,
collision_mask: Rect<i32>,
visible: bool,
}
impl<'a> Entity<'a> {
pub fn new(object_controller: &'a OamManaged, collision_mask: Rect<i32>) -> Self {
let mut sprite = object_controller.object_sprite(TAGS.get("p_idle").sprite(0));
sprite.set_priority(Priority::P1);
Entity {
sprite,
collision_mask,
position: Vector2D { x: 0, y: 0 },
velocity: (0u16, 0u16).into(),
visible: true,
}
}
pub fn updatePosition(&mut self, x: i16, y: i16) {
self.position = (x,y).into();
}
} The above code works, but I'm not yet using the player.sprite field. The next step was to move the updates of sprite position, sprite frame and flipping into the struct impl: main.rs #[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
game_main(gba) // Use a separate function since agb::entry macro breaks rust-analyzer
}
fn game_main(mut gba: agb::Gba) -> ! {
let vblank = agb::interrupt::VBlank::get();
let (gfx, mut vram) = gba.display.video.tiled0();
let objCtrl = gba.display.object.get_managed();
let animTagPlayerIdleDown = TAGS.get("p_idle");
// rest of anim tags omitted
let mut player = Entity::new(&objCtrl, Rect::new((0, 1).into(), (5, 10).into()));
player.updatePosition(0, 0);
player.updateSprite(&objCtrl, (false, false));
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32, tileset.format());
let mut input = ButtonController::new();
let movtSpeed: i16 = 2;
let mut frame: usize = 0;
let mut playerSpriteOffset = 0;
let mut lastDirection = Direction::Down;
loop {
vblank.wait_for_vblank();
input.update(); // call update every loop
let (xDiff, yDiff) = (triToInt(input.x_tri()), triToInt(input.y_tri()));
let (newX, newY) = handlePlayerMovement(player.position.x + xDiff * movtSpeed, player.position.y + yDiff * movtSpeed);
player.updatePosition(newX, newY);
let playerIdle = xDiff == 0 && yDiff == 0;
if frame % 15 == 0 && playerIdle {
if playerSpriteOffset == 0 {
playerSpriteOffset = 1;
} else {
playerSpriteOffset = 0;
}
let idleAnimTag = match lastDirection {
Direction::Down => animTagPlayerIdleDown,
Direction::Up => animTagPlayerIdleUp,
Direction::Left | Direction::Right => animTagPlayerIdleHoriz,
};
player.updateSprite(&objCtrl, (lastDirection == Direction::Left, false));
}
// draw sprites
objCtrl.commit();
frame = frame.wrapping_add(1);
// syscall::halt();
}
} lib.rs const SPRITESHEET: &Graphics = agb::include_aseprite!("gfx/main_spritesheet.aseprite");
pub const TAGS: &TagMap = SPRITESHEET.tags();
#[derive(Debug, PartialEq, Eq)]
pub enum Direction {
Up, Down, Left, Right
}
pub struct Entity<'a> {
pub sprite: Object<'a>,
pub position: Vector2D<i16>,
velocity: Vector2D<u16>,
collision_mask: Rect<i32>,
visible: bool,
}
impl<'a> Entity<'a> {
pub fn new(object_controller: &'a OamManaged, collision_mask: Rect<i32>) -> Self {
let mut sprite = object_controller.object_sprite(TAGS.get("p_idle").sprite(0));
sprite.set_priority(Priority::P1);
Entity {
sprite,
collision_mask,
position: Vector2D { x: 0, y: 0 },
velocity: (0u16, 0u16).into(),
visible: true,
}
}
pub fn updatePosition(&mut self, x: i16, y: i16) {
self.position = (x,y).into();
}
pub fn updateSprite(&mut self, object_controller: &'a OamManaged, flips: (bool, bool)) {
self.sprite.set_sprite(object_controller.sprite(TAGS.get("p_idle").sprite(0)))
.set_hflip(flips.0)
.set_vflip(flips.1)
.show()
.set_position((self.position.x, self.position.y).into());
}
} This code compiles and runs, but surprisingly the player sprite is no longer rendered. No error messages are shown in the mgba logs either. The background still renders, and the input works. Logging the player.position values shows that it's being updated according to input. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
I see you're using |
Beta Was this translation helpful? Give feedback.
-
Hah, yeah, that was silly of me 😅 Thanks for all the help! |
Beta Was this translation helpful? Give feedback.
I see you're using
set_priority(Priority::P1)
, it should be thought of that a higher priority means it is rendered first, ie. behind that of lower priorities. So I think in this case your object is behind your background.