From 7b19fbcf533acb0045dd7a3486d8eb61628d1239 Mon Sep 17 00:00:00 2001 From: chris bell Date: Sun, 15 Feb 2026 16:55:22 -0600 Subject: [PATCH] Sprites and Animations --- src/main.odin | 5 +- src/player.odin | 80 ++++++++++++++++++++++++---- src/sprite.odin | 134 +++++++++++++++++++++++++++++++++++++++++++++++ src/tilemap.odin | 34 ++++++++++++ 4 files changed, 241 insertions(+), 12 deletions(-) create mode 100644 src/sprite.odin create mode 100644 src/tilemap.odin diff --git a/src/main.odin b/src/main.odin index 218581e..663093a 100644 --- a/src/main.odin +++ b/src/main.odin @@ -14,12 +14,15 @@ main :: proc() { player = { position = {0, 0}, camera = { - zoom = 2, + zoom = 4, offset = {f32(raylib.GetScreenWidth()) / 2, f32(raylib.GetScreenHeight()) / 2}, target = {player.position.x + (32 / 2), player.position.y + (32 / 2)}, }, + sprite = load_sprite(PLAYER_SPRITE_PATH, PLAYER_WIDTH, PLAYER_HEIGHT), } + player.state = .WALKING + for (!raylib.WindowShouldClose()) { delta := raylib.GetFrameTime() diff --git a/src/player.odin b/src/player.odin index 0c52e45..b40809f 100644 --- a/src/player.odin +++ b/src/player.odin @@ -1,20 +1,40 @@ package main import "core:fmt" +import "core:strings" import "vendor:raylib" -PLAYER_SPEED :: 5 +PLAYER_SPEED :: 2 +PLAYER_WIDTH :: 32 +PLAYER_HEIGHT :: 32 +PLAYER_SPRITE_PATH :: "assets/player/player.png" + +spritesheet: raylib.Texture2D +framesX: i32 +framesY: i32 Player :: struct { + sprite: Sprite, health: int, current_world: string, camera: raylib.Camera2D, position: raylib.Vector2, + animator: SpriteAnimator, + state: PlayerState, + facing_left: bool, +} + +PlayerState :: enum { + IDLE, + WALKING, } @(private = "file") handle_player_camera :: proc(p: ^Player, delta: f32) { - player_center := raylib.Vector2{p.position.x + 16, p.position.y + 16} + player_center := raylib.Vector2 { + p.position.x + (PLAYER_WIDTH / 2), + p.position.y + (PLAYER_HEIGHT / 2), + } smooth_speed :: 10.0 @@ -24,12 +44,6 @@ handle_player_camera :: proc(p: ^Player, delta: f32) { if (raylib.IsWindowResized()) { p.camera.offset = {f32(raylib.GetScreenWidth()) * 0.5, f32(raylib.GetScreenHeight()) * 0.5} } - - // p.camera.target = {p.position.x + (32 / 2), p.position.y + (32 / 2)} - - // if raylib.IsWindowResized() { - // p.camera.offset = {f32(raylib.GetScreenWidth()) / 2, f32(raylib.GetScreenHeight()) / 2} - // } } @(private = "file") @@ -41,20 +55,64 @@ handle_player_input :: proc(p: ^Player, delta: f32) { if raylib.IsKeyDown(.A) do dir.x -= 1 if raylib.IsKeyDown(.D) do dir.x += 1 - if dir.x != 0 || dir.y != 0 { + is_moving := dir.x != 0 || dir.y != 0 + + if (is_moving) { dir = raylib.Vector2Normalize(dir) dir = dir * PLAYER_SPEED p.position = p.position + dir + + if dir.x < 0 { + p.facing_left = true + } + + if dir.x > 0 { + p.facing_left = false + } + + p.state = .WALKING + } else { + p.state = .IDLE } + + +} + + +idle_animation: SpriteAnimation = { + start_frame = 0, + end_frame = 5, + fps = 6, + loop = true, +} + +player_walk_anim: SpriteAnimation = { + start_frame = 6, + end_frame = 11, + fps = 6, + loop = true, } draw_player :: proc(p: ^Player) { - raylib.DrawRectangle(i32(p.position.x), i32(p.position.y), 32, 32, raylib.BLACK) + // raylib.DrawRectangle(i32(p.position.x), i32(p.position.y), 32, 32, raylib.BLACK) + + // draw_sprite_frame(&p.sprite, {0, 0}, p.position, raylib.WHITE) + + draw_sprite_animated(&p.sprite, &p.animator, p.position, p.facing_left, false, raylib.WHITE) } update_player :: proc(p: ^Player, delta: f32) { handle_player_input(p, delta) handle_player_camera(p, delta) - // fmt.println(p.position) + + if (p.state == .IDLE) { + set_sprite_animation(&p.animator, &idle_animation) + } + + if (p.state == .WALKING) { + set_sprite_animation(&p.animator, &player_walk_anim) + } + + update_animator(&p.animator, delta) } diff --git a/src/sprite.odin b/src/sprite.odin new file mode 100644 index 0000000..59ef46f --- /dev/null +++ b/src/sprite.odin @@ -0,0 +1,134 @@ +package main + +import "vendor:raylib" + +Sprite :: struct { + texture: raylib.Texture2D, + width: i32, + height: i32, + framesX: i32, + framesY: i32, +} + +SpriteAnimation :: struct { + start_frame: i32, + end_frame: i32, + fps: i32, + loop: bool, +} + +SpriteAnimator :: struct { + anim: ^SpriteAnimation, + current_frame: i32, + timer: f32, +} + +load_sprite :: proc(image_path: cstring, w: i32, h: i32) -> Sprite { + new_texture: raylib.Texture2D = raylib.LoadTexture(image_path) + + new_sprite: Sprite = { + texture = new_texture, + width = w, + height = h, + framesX = new_texture.width / w, + framesY = new_texture.height / h, + } + + return new_sprite +} + +draw_sprite_frame :: proc( + sprite: ^Sprite, + frame_pos: raylib.Vector2, + draw_pos: raylib.Vector2, + flipX: bool, + flipY: bool, + color: raylib.Color, +) { + widthf := f32(sprite.width) + heightf := f32(sprite.height) + + src_width := widthf + src_height := heightf + src_x := frame_pos.x * widthf + src_y := frame_pos.y * heightf + + if flipX { + src_width = -src_width + src_x += widthf + } + + if flipY { + src_height = -src_height + src_y += heightf + } + + source_rect := raylib.Rectangle { + x = src_x, + y = src_y, + width = src_width, + height = src_height, + } + + dest_rect := raylib.Rectangle { + x = draw_pos.x, + y = draw_pos.y, + width = widthf, + height = heightf, + } + + // origin := raylib.Vector2{widthf * 0.5, heightf * 0.5} + // dest_rect.x = draw_pos.x + widthf * 0.5 + // dest_rect.y = draw_pos.y + heightf * 0.5 + + origin := raylib.Vector2{0, 0} + + raylib.DrawTexturePro(sprite.texture, source_rect, dest_rect, origin, 0.0, color) +} + +set_sprite_animation :: proc(animator: ^SpriteAnimator, new_anim: ^SpriteAnimation) { + if (animator.anim == new_anim) do return + + animator.anim = new_anim + animator.timer = 0 + animator.current_frame = new_anim.start_frame +} + +draw_sprite_animated :: proc( + sprite: ^Sprite, + animator: ^SpriteAnimator, + draw_pos: raylib.Vector2, + flipX: bool, + flipY: bool, + color: raylib.Color, +) { + frame_pos := frame_to_xy(sprite, animator.current_frame) + draw_sprite_frame(sprite, frame_pos, draw_pos, flipX, flipY, color) +} + +update_animator :: proc(a: ^SpriteAnimator, delta: f32) { + if a.anim == nil do return + + frame_time := 1.0 / f32(a.anim.fps) + a.timer += delta + + if (a.timer >= frame_time) { + a.timer -= frame_time + a.current_frame += 1 + + if (a.current_frame > a.anim.end_frame) { + if (a.anim.loop) { + a.current_frame = a.anim.start_frame + } else { + a.current_frame = a.anim.end_frame + } + } + } +} + +frame_to_xy :: proc(sprite: ^Sprite, frame: i32) -> raylib.Vector2 { + x := frame % sprite.framesX + y := frame / sprite.framesX + return raylib.Vector2{f32(x), f32(y)} +} + diff --git a/src/tilemap.odin b/src/tilemap.odin new file mode 100644 index 0000000..c94c2bf --- /dev/null +++ b/src/tilemap.odin @@ -0,0 +1,34 @@ +package main + +import rl "vendor:raylib" + +TILE_SIZE :: 16 +TILE_IMAGE_PATH :: "assets/tiles.png" + +tilemap_image: rl.Texture2D + +tilesX: i32 +tilesY: i32 + +load_tilemap :: proc() { + tilemap_image = rl.LoadTexture(TILE_IMAGE_PATH) + + tilesX = tilemap_image.width / TILE_SIZE + tilesY = tilemap_image.height / TILE_SIZE +} + +unload_tilemap :: proc() { + rl.UnloadTexture(tilemap_image) +} + +draw_tile :: proc(tilemap_pos: rl.Vector2, draw_pos: rl.Vector2, color: rl.Color) { + source_rect := rl.Rectangle { + x = tilemap_pos.x * TILE_SIZE, + y = tilemap_pos.y * TILE_SIZE, + width = TILE_SIZE, + height = TILE_SIZE, + } + + rl.DrawTextureRec(tilemap_image, source_rect, draw_pos, color) +} +