diff --git a/.gitignore b/.gitignore index 9ac9953..d8b295f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /assetsviewer /topdown +/top-down-shooter diff --git a/assetsviewer.odin b/assetviewer/assetsviewer.odin similarity index 100% rename from assetsviewer.odin rename to assetviewer/assetsviewer.odin diff --git a/sprites.odin b/sprites.odin new file mode 100644 index 0000000..f130eee --- /dev/null +++ b/sprites.odin @@ -0,0 +1,70 @@ +package topdown + +import rl "vendor:raylib" +import "core:fmt" + +SpriteAnimation :: struct { + speed: f32, + width: u16, + height: u16, + total_width: u16, + row: u16, + loop: bool, + total_frames: u8, +} + +SpriteAnimationPlayer :: struct { + curr_anim: ^SpriteAnimation, + time_elapsed: f32, + curr_frame: u8, + skip_frame: bool +} + +anim_player_start :: proc(player: ^SpriteAnimationPlayer, anim: ^SpriteAnimation) { + player.curr_anim = anim + player.time_elapsed = 0 + player.curr_frame = 0 + player.skip_frame = true +} + +get_sprite_rect :: proc(player: ^SpriteAnimationPlayer) -> Rect { + return Rect{ + f32(u16(player.curr_frame) * player.curr_anim.width), + f32(player.curr_anim.row), + f32(player.curr_anim.width), + f32(player.curr_anim.height) + } +} + +anim_player_tick :: proc(player: ^SpriteAnimationPlayer) { + // fmt.println(player.curr_anim) + finished := !player.curr_anim.loop && player.curr_frame == player.curr_anim.total_frames + if finished || player.skip_frame { + player.skip_frame = false + return + } + player.time_elapsed += rl.GetFrameTime() + // TODO: We want to subtract to advance a frame in case we dropped frames + if player.time_elapsed >= player.curr_anim.speed { + player.time_elapsed = 0 + player.curr_frame += 1 + if player.curr_frame >= player.curr_anim.total_frames { + if player.curr_anim.loop { + player.curr_frame = 0 + } + } + } +} + +draw_sprite :: proc(texture: Tex, player: ^SpriteAnimationPlayer, pos: Vec2, scale: f32, flip: bool) { + src := get_sprite_rect(player) + if flip { + src.width *= -1 + } + w := f32(player.curr_anim.width) * scale + h := f32(player.curr_anim.height) * scale + dst := Rect{pos.x, pos.y, w, h} + // fmt.println(src) + // fmt.println(dst) + rl.DrawTexturePro(texture, src, dst, {0,0}, 0, rl.WHITE) +} diff --git a/topdown.odin b/topdown.odin index bc0069a..288b2eb 100644 --- a/topdown.odin +++ b/topdown.odin @@ -16,9 +16,21 @@ SCREEN_HEIGHT : i32 = 800 BW := f32(SCREEN_WIDTH) BH := f32(SCREEN_HEIGHT) +IdleState :: struct {} + +MovingState :: struct { + velocity: Vec2, +} + +SoldierState :: union #no_nil { + IdleState, + MovingState, +} + Soldier :: struct { position: Vec2, - velocity: Vec2, + state: SoldierState, + anim_player: SpriteAnimationPlayer, } GameState :: struct { @@ -27,7 +39,8 @@ GameState :: struct { reticle: Tex, idle: Tex, run: Tex, - player: Soldier, + soldier: Soldier, + animations: [dynamic]SpriteAnimation, } init_state :: proc() -> ^GameState { @@ -44,34 +57,81 @@ init_state :: proc() -> ^GameState { keymap["Up"] = .W keymap["Down"] = .S + animations := make([dynamic]SpriteAnimation) + idle_anim := SpriteAnimation { + speed = 1 / 2.0, + width = 16, + height = 16, + total_width = 32, + row = 0, + loop = true, + total_frames = 2, + } + run_anim := SpriteAnimation { + speed = 1 / 9.0, + width = 16, + height = 16, + total_width = 64, + row = 0, + loop = true, + total_frames = 4, + } + append(&animations, idle_anim) + append(&animations, run_anim) + soldier := Soldier { + position = {100, 100}, + anim_player = SpriteAnimationPlayer{}, + } + anim_player_start(&soldier.anim_player, &animations[0]) + state := GameState { reticle = rl.LoadTextureFromImage(reticle), idle = rl.LoadTextureFromImage(idle), run = rl.LoadTextureFromImage(run), - keymap = keymap + keymap = keymap, + soldier = soldier, + animations = animations } - return new_clone(state) + cloned := new_clone(state) + return cloned } player_input :: proc(s: ^GameState) { horizontal := rl.IsKeyDown(s.keymap["Left"]) ? -1 : rl.IsKeyDown(s.keymap["Right"]) ? 1 : 0 vertical := rl.IsKeyDown(s.keymap["Up"]) ? -1 : rl.IsKeyDown(s.keymap["Down"]) ? 1 : 0 - dir := rl.Vector2Normalize({f32(horizontal), f32(vertical)}) - s.player.position += dir * (250 * s.dt) + moving := horizontal != 0 || vertical != 0 + if moving { + dir := rl.Vector2Normalize({f32(horizontal), f32(vertical)}) + velocity := dir * (250 * s.dt) + moving_state := MovingState { velocity = velocity } + if _, ok := s.soldier.state.(IdleState); ok { + anim_player_start(&s.soldier.anim_player, &s.animations[1]) + } + s.soldier.state = moving_state + s.soldier.position += velocity + } else if _, ok := s.soldier.state.(MovingState); ok { + anim_player_start(&s.soldier.anim_player, &s.animations[0]) + s.soldier.state = IdleState {} + } } update :: proc(s: ^GameState) { + anim_player_tick(&s.soldier.anim_player) } draw2d :: proc(s: ^GameState) { - pos := rl.GetMousePosition() - {f32(s.reticle.width) / 2, f32(s.reticle.height) / 2} + mpos := rl.GetMousePosition() - {f32(s.reticle.width) / 2, f32(s.reticle.height) / 2} // rl.DrawTextureEx(s.idle, s.player.position, 0, 4, rl.WHITE) - w :f32 = 16 - src := Rect{0, 0, pos.x < s.player.position.x ? -w + 1 : w, w} - dst := Rect{s.player.position.x, s.player.position.y, w * 4, w * 4} - rl.DrawTexturePro(s.idle, src, dst, {0,0}, 0, rl.WHITE) + flip := mpos.x < s.soldier.position.x + sprite :Tex + if _, ok := s.soldier.state.(IdleState); ok { + sprite = s.idle + } else { + sprite = s.run + } + draw_sprite(sprite, &s.soldier.anim_player, s.soldier.position, 4, flip) - rl.DrawTextureV(s.reticle, pos, rl.WHITE); + rl.DrawTextureV(s.reticle, mpos, rl.WHITE); } main :: proc() {