Sprite animation system , state machine

This commit is contained in:
Joseph Ferano 2024-09-01 10:53:13 +07:00
parent eed7ee178f
commit ea806f6b22
4 changed files with 143 additions and 12 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/assetsviewer
/topdown
/top-down-shooter

70
sprites.odin Normal file
View File

@ -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)
}

View File

@ -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() {