diff --git a/src/animation.odin b/src/animation.odin new file mode 100644 index 0000000..b4f8c8f --- /dev/null +++ b/src/animation.odin @@ -0,0 +1,58 @@ +package game + +import rl "vendor:raylib" + +Animation :: struct { + texture: rl.Texture2D, + num_frames: int, + frame_timer: f32, + current_frame: int, + frame_length: f32, + flip: bool, + loop: bool, + done: bool, + offset: rl.Vector2, +} + +update_animation :: proc(a: ^Animation) { + a.frame_timer += rl.GetFrameTime() + + if a.frame_timer > a.frame_length && !a.done { + a.current_frame += 1 + a.frame_timer = 0 + + if a.current_frame == a.num_frames { + if a.loop { + a.current_frame = 0 + } else { + a.current_frame += -1 + a.done = true + } + } + } +} + +draw_animation :: proc(a: Animation, pos: rl.Vector2) { + width := f32(a.texture.width) + height := f32(a.texture.height) + + source := rl.Rectangle { + x = f32(a.current_frame) * width / f32(a.num_frames), + y = 0, + width = width / f32(a.num_frames), + height = height, + } + + if a.flip { + source.width = -source.width + } + + dest := rl.Rectangle { + x = pos.x + a.offset.x, + y = pos.y + a.offset.y, + width = width / f32(a.num_frames), + height = height, + } + + rl.DrawTexturePro(a.texture, source, dest, {dest.width / 2, dest.height}, 0, rl.WHITE) +} diff --git a/src/constants.odin b/src/constants.odin new file mode 100644 index 0000000..c80f632 --- /dev/null +++ b/src/constants.odin @@ -0,0 +1,5 @@ +package game + +GAME_SCREEN_WIDTH: f32 = 480 +GAME_SCREEN_HEIGHT: f32 = 270 +TILE_SIZE :: 16 diff --git a/src/entity.odin b/src/entity.odin new file mode 100644 index 0000000..c5674f2 --- /dev/null +++ b/src/entity.odin @@ -0,0 +1,144 @@ +package game + +import "core:math" +import rl "vendor:raylib" + +EntityKind :: enum { + Player, + Tile, + Item, +} + +PlayerData :: struct { + animation: Animation, +} + +ItemData :: struct { + id: int, + count: int, + texture: rl.Texture2D, +} + +TileData :: struct { + has_plant: bool, + is_watered: bool, + plant_id: int, + animation: Animation, + plant_grown: bool, +} + +EntityData :: union { + PlayerData, + TileData, + ItemData, +} + +Entity :: struct { + pos: rl.Vector2, + kind: EntityKind, + handle: EntityHandle, + flags: bit_set[EntityFlags], + data: EntityData, +} +latest_entity_handle: int + +EntityHandle :: int +EntityFlags :: enum { + nil, + Allocated, +} + +create_entity :: proc(entity: Entity) -> ^Entity { + for &e in entities { + if .Allocated not_in e.flags { + e = entity + e.flags = {.Allocated} + latest_entity_handle += 1 + return &e + } + } + assert(false, "Failed to allocate entity, not enough space!!!!") + return nil +} + +destroy_entity :: proc(e: ^Entity) { + e^ = Entity{} +} + +handle_to_entity :: proc(handle: EntityHandle) -> ^Entity { + for &e in entities { + if handle == e.handle && .Allocated in e.flags { + return &e + } + } + return nil +} + +update_entities :: proc() { + for &e in entities { + if .Allocated not_in e.flags {continue} + + #partial switch &data in e.data { + case TileData: + update_tile(e, &data) + case PlayerData: + update_animation(&data.animation) + case ItemData: + //rl.DrawTextureV(data.texture, e.pos + {0, t * 3}, rl.WHITE) + } + } +} + +update_tile :: proc(e: Entity, data: ^TileData) { + update_animation(&data.animation) + if data.has_plant && data.animation.done && data.plant_grown == false { + data.plant_grown = true + data.has_plant = false + rl.PlaySound(pickup_sound) + item := create_entity( + Entity { + pos = e.pos, + kind = .Item, + data = ItemData{id = data.plant_id, count = 3, texture = tomato_texture}, + }, + ) + } +} + +draw_entities :: proc() { + for e in entities { + if .Allocated not_in e.flags {continue} + + switch &data in e.data { + case TileData: + draw_tile(e) + case PlayerData: + draw_player(e) + case ItemData: + draw_item(e) + } + } +} + +entity_to_handle :: proc(e: Entity) -> EntityHandle { + return e.handle +} + +draw_player :: proc(e: Entity) { + data, ok := e.data.(PlayerData) + draw_animation(data.animation, e.pos) +} +draw_tile :: proc(e: Entity) { + data, ok := e.data.(TileData) + //rl.DrawRectangle(auto_cast e.pos.x, auto_cast e.pos.y, TILE_SIZE, TILE_SIZE, rl.BLACK) + rl.DrawTextureV(tile_dirt_dry_texture, e.pos, rl.WHITE) + if data.has_plant { + draw_animation(data.animation, e.pos) + } +} +draw_item :: proc(e: Entity) { + data, ok := e.data.(ItemData) + t := f32(math.sin(rl.GetTime() * 5)) + rl.DrawTextureV(data.texture, e.pos + {0, t * 3}, rl.WHITE) + //draw_animation(data.animation, e.pos + {0, t * 3}) +} diff --git a/src/globals.odin b/src/globals.odin new file mode 100644 index 0000000..e903b61 --- /dev/null +++ b/src/globals.odin @@ -0,0 +1,29 @@ +package game + +import t "../tween" +import rl "vendor:raylib" + +SELL_BUTTON_POS :: rl.Vector2{432, 16} + +game_camera := rl.Camera2D { + zoom = 1, +} +ui_camera := rl.Camera2D { + zoom = 1, +} +game_render_buffer: rl.RenderTexture2D +screen_game_area: rl.Rectangle +game_render_buffer_area := rl.Rectangle{0, 0, GAME_SCREEN_WIDTH, -GAME_SCREEN_HEIGHT} +screen_scale: f32 + +world_mouse: rl.Vector2 + +player_handle: EntityHandle + +tiles: [dynamic]Tile +animations: [dynamic]^Animation + +entities: [256]Entity + +tween_ctx_f32: t.TweenContext(f32) +tween_ctx_vec2: t.TweenContext([2]f32) diff --git a/src/main.odin b/src/main.odin index 95a8d04..095e130 100644 --- a/src/main.odin +++ b/src/main.odin @@ -8,75 +8,11 @@ import "core:math/linalg" import "core:slice" import rl "vendor:raylib" -GAME_SCREEN_WIDTH: f32 = 480 -GAME_SCREEN_HEIGHT: f32 = 270 - -SELL_BUTTON_POS :: rl.Vector2{432, 16} - -TILE_SIZE :: 16 -game_render_buffer: rl.RenderTexture2D -game_camera := rl.Camera2D { - zoom = 1, -} -ui_camera := rl.Camera2D { - zoom = 1, -} - -screen_scale: f32 -world_mouse: rl.Vector2 -screen_game_area: rl.Rectangle -game_render_buffer_area := rl.Rectangle{0, 0, GAME_SCREEN_WIDTH, -GAME_SCREEN_HEIGHT} - -pickup_sound: rl.Sound -pickup_sound_data: []u8 : #load("../assets/sounds/pickup_sound.mp3") - -player_spritesheet_sprite: []u8 : #load("../assets/player.png") -tomato_spritesheet_sprite: []u8 : #load("../assets/tomato.png") -tomato_item_sprite: []u8 : #load("../assets/tomato_item.png") -sell_button_sprite: []u8 : #load("../assets/sell_button.png") -background_sprite: []u8 : #load("../assets/background.png") -tile_dirt_dry_sprite: []u8 : #load("../assets/tile_dirt_dry.png") -tile_dirt_wet_sprite: []u8 : #load("../assets/tile_dirt_wet.png") - -load_texture_from_memory :: proc(data: []u8) -> (texture: rl.Texture2D) { - img := rl.LoadImageFromMemory(".png", raw_data(data), auto_cast len(data)) - defer rl.UnloadImage(img) - texture = rl.LoadTextureFromImage(img) - return texture -} - -load_sound_from_memory :: proc( - data: []u8, - sound_extension: cstring = ".mp3", -) -> ( - sound: rl.Sound, -) { - wav := rl.LoadWaveFromMemory(sound_extension, raw_data(data), auto_cast len(data)) - defer rl.UnloadWave(wav) - sound = rl.LoadSoundFromWave(wav) - return -} - -sell_button_texture: rl.Texture2D -background_texture: rl.Texture2D -tile_dirt_dry_texture: rl.Texture2D -tile_dirt_wet_texture: rl.Texture2D -tomato_texture: rl.Texture2D - -player_animation: Animation -tomato_animation: Animation - -player_handle: EntityHandle - -tiles: [dynamic]Tile -animations: [dynamic]^Animation - -entities: [256]Entity - -tween_ctx_f32: t.TweenContext(f32) -tween_ctx_vec2: t.TweenContext([2]f32) - main :: proc() { + run() +} + +run :: proc() { rl.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .MSAA_4X_HINT}) rl.InitWindow(1280, 720, "Raylib Minimal") rl.SetTargetFPS(rl.GetMonitorRefreshRate(rl.GetCurrentMonitor())) @@ -88,57 +24,17 @@ main :: proc() { fmt.println("Audio borked!") } - pickup_sound = load_sound_from_memory(pickup_sound_data) - - game_render_buffer = rl.LoadRenderTexture( - auto_cast GAME_SCREEN_WIDTH, - auto_cast GAME_SCREEN_HEIGHT, - ) - rl.SetTextureFilter(game_render_buffer.texture, rl.TextureFilter.POINT) // Texture scale filter to use - - tween_ctx_f32 = t.context_init(f32) - tween_ctx_vec2 = t.context_init([2]f32) - defer t.context_destroy(tween_ctx_f32) - defer t.context_destroy(tween_ctx_vec2) - - player_animation = Animation { - texture = load_texture_from_memory(player_spritesheet_sprite), - num_frames = 2, - frame_length = 0.5, - loop = true, - } - tomato_animation = Animation { - texture = load_texture_from_memory(tomato_spritesheet_sprite), - num_frames = 4, - frame_length = 0.1, - loop = false, - offset = {TILE_SIZE / 2, TILE_SIZE}, - } - tomato_texture = load_texture_from_memory(tomato_item_sprite) - sell_button_texture = load_texture_from_memory(sell_button_sprite) - background_texture = load_texture_from_memory(background_sprite) - tile_dirt_dry_texture = load_texture_from_memory(tile_dirt_dry_sprite) - tile_dirt_wet_texture = load_texture_from_memory(tile_dirt_wet_sprite) - - e := create_entity( - Entity { - pos = {GAME_SCREEN_WIDTH / 2, GAME_SCREEN_HEIGHT / 2}, - kind = .Player, - data = PlayerData{animation = player_animation}, - }, - ) - player_handle = entity_to_handle(e^) - + load_resources() + rl.SetSoundVolume(pickup_sound, 0.1) rl.PlaySound(pickup_sound) - if true {for !rl.WindowShouldClose() { - free_all(context.temp_allocator) - update() - draw() - } - + for !rl.WindowShouldClose() { + free_all(context.temp_allocator) + update() + draw() } - rl.UnloadRenderTexture(game_render_buffer) // Unload render texture + + free_resources() rl.CloseAudioDevice() rl.CloseWindow() } @@ -232,227 +128,59 @@ draw :: proc() { rl.EndDrawing() } -Animation :: struct { - texture: rl.Texture2D, - num_frames: int, - frame_timer: f32, - current_frame: int, - frame_length: f32, - flip: bool, - loop: bool, - done: bool, - offset: rl.Vector2, -} +load_resources :: proc() { + tween_ctx_f32 = t.context_init(f32) + tween_ctx_vec2 = t.context_init([2]f32) -update_animation :: proc(a: ^Animation) { - a.frame_timer += rl.GetFrameTime() + game_render_buffer = rl.LoadRenderTexture( + auto_cast GAME_SCREEN_WIDTH, + auto_cast GAME_SCREEN_HEIGHT, + ) + rl.SetTextureFilter(game_render_buffer.texture, rl.TextureFilter.POINT) // Texture scale filter to use - if a.frame_timer > a.frame_length && !a.done { - a.current_frame += 1 - a.frame_timer = 0 - - if a.current_frame == a.num_frames { - if a.loop { - a.current_frame = 0 - } else { - a.current_frame += -1 - a.done = true - } - } + player_animation = Animation { + texture = load_texture_from_memory(player_spritesheet_sprite), + num_frames = 2, + frame_length = 0.5, + loop = true, } -} - -draw_animation :: proc(a: Animation, pos: rl.Vector2) { - width := f32(a.texture.width) - height := f32(a.texture.height) - - source := rl.Rectangle { - x = f32(a.current_frame) * width / f32(a.num_frames), - y = 0, - width = width / f32(a.num_frames), - height = height, + tomato_animation = Animation { + texture = load_texture_from_memory(tomato_spritesheet_sprite), + num_frames = 4, + frame_length = 0.1, + loop = false, + offset = {TILE_SIZE / 2, TILE_SIZE}, } - if a.flip { - source.width = -source.width - } + tomato_texture = load_texture_from_memory(tomato_item_sprite) + sell_button_texture = load_texture_from_memory(sell_button_sprite) + background_texture = load_texture_from_memory(background_sprite) + tile_dirt_dry_texture = load_texture_from_memory(tile_dirt_dry_sprite) + tile_dirt_wet_texture = load_texture_from_memory(tile_dirt_wet_sprite) + pickup_sound = load_sound_from_memory(pickup_sound_data) - dest := rl.Rectangle { - x = pos.x + a.offset.x, - y = pos.y + a.offset.y, - width = width / f32(a.num_frames), - height = height, - } - rl.DrawTexturePro(a.texture, source, dest, {dest.width / 2, dest.height}, 0, rl.WHITE) + e := create_entity( + Entity { + pos = {GAME_SCREEN_WIDTH / 2, GAME_SCREEN_HEIGHT / 2}, + kind = .Player, + data = PlayerData{animation = player_animation}, + }, + ) + player_handle = entity_to_handle(e^) } -Tile :: struct { - id: int, - pos: rl.Vector2, - animation: Animation, -} +free_resources :: proc() { + t.context_destroy(tween_ctx_f32) + t.context_destroy(tween_ctx_vec2) -spawn_tile_under_mouse :: proc() { - tile_position := get_tile_position(world_mouse) - old_tile: bool - for e in entities { - if .Allocated not_in e.flags {continue} - if e.kind == .Tile && e.pos == tile_position { - old_tile = true - } - } - if !old_tile { - create_entity( - Entity { - pos = tile_position, - kind = .Tile, - data = TileData{has_plant = true, plant_id = 0, animation = tomato_animation}, - }, - ) - } -} + rl.UnloadSound(pickup_sound) -get_tile_position :: proc(wpos: rl.Vector2) -> rl.Vector2 { - return {math.floor(wpos.x / TILE_SIZE) * TILE_SIZE, math.floor(wpos.y / TILE_SIZE) * TILE_SIZE} -} + rl.UnloadTexture(tomato_texture) + rl.UnloadTexture(sell_button_texture) + rl.UnloadTexture(background_texture) + rl.UnloadTexture(tile_dirt_dry_texture) + rl.UnloadTexture(tile_dirt_wet_texture) -EntityKind :: enum { - Player, - Tile, - Item, -} - -PlayerData :: struct { - animation: Animation, -} - -ItemData :: struct { - id: int, - count: int, - texture: rl.Texture2D, -} - -TileData :: struct { - has_plant: bool, - is_watered: bool, - plant_id: int, - animation: Animation, - plant_grown: bool, -} - -EntityData :: union { - PlayerData, - TileData, - ItemData, -} - -Entity :: struct { - pos: rl.Vector2, - kind: EntityKind, - handle: EntityHandle, - flags: bit_set[EntityFlags], - data: EntityData, -} -latest_entity_handle: int - -EntityHandle :: int -EntityFlags :: enum { - nil, - Allocated, -} - -create_entity :: proc(entity: Entity) -> ^Entity { - for &e in entities { - if .Allocated not_in e.flags { - e = entity - e.flags = {.Allocated} - latest_entity_handle += 1 - return &e - } - } - assert(false, "Failed to allocate entity, not enough space!!!!") - return nil -} - -destroy_entity :: proc(e: ^Entity) { - e^ = Entity{} -} - -handle_to_entity :: proc(handle: EntityHandle) -> ^Entity { - for &e in entities { - if handle == e.handle && .Allocated in e.flags { - return &e - } - } - return nil -} - -update_entities :: proc() { - for &e in entities { - if .Allocated not_in e.flags {continue} - - #partial switch &data in e.data { - case TileData: - update_tile(e, &data) - case PlayerData: - update_animation(&data.animation) - case ItemData: - //rl.DrawTextureV(data.texture, e.pos + {0, t * 3}, rl.WHITE) - } - } -} - -update_tile :: proc(e: Entity, data: ^TileData) { - update_animation(&data.animation) - if data.has_plant && data.animation.done && data.plant_grown == false { - data.plant_grown = true - data.has_plant = false - rl.PlaySound(pickup_sound) - item := create_entity( - Entity { - pos = e.pos, - kind = .Item, - data = ItemData{id = data.plant_id, count = 3, texture = tomato_texture}, - }, - ) - } -} - -draw_entities :: proc() { - for e in entities { - if .Allocated not_in e.flags {continue} - - switch &data in e.data { - case TileData: - draw_tile(e) - case PlayerData: - draw_player(e) - case ItemData: - draw_item(e) - } - } -} - -entity_to_handle :: proc(e: Entity) -> EntityHandle { - return e.handle -} - -draw_player :: proc(e: Entity) { - data, ok := e.data.(PlayerData) - draw_animation(data.animation, e.pos) -} -draw_tile :: proc(e: Entity) { - data, ok := e.data.(TileData) - //rl.DrawRectangle(auto_cast e.pos.x, auto_cast e.pos.y, TILE_SIZE, TILE_SIZE, rl.BLACK) - rl.DrawTextureV(tile_dirt_dry_texture, e.pos, rl.WHITE) - if data.has_plant { - draw_animation(data.animation, e.pos) - } -} -draw_item :: proc(e: Entity) { - data, ok := e.data.(ItemData) - t := f32(math.sin(rl.GetTime() * 5)) - rl.DrawTextureV(data.texture, e.pos + {0, t * 3}, rl.WHITE) - //draw_animation(data.animation, e.pos + {0, t * 3}) + rl.UnloadRenderTexture(game_render_buffer) // Unload render texture } diff --git a/src/resources.odin b/src/resources.odin new file mode 100644 index 0000000..7b2d339 --- /dev/null +++ b/src/resources.odin @@ -0,0 +1,43 @@ +package game + +import rl "vendor:raylib" + +pickup_sound_data: []u8 : #load("../assets/sounds/pickup_sound.mp3") + +player_spritesheet_sprite: []u8 : #load("../assets/player.png") +tomato_spritesheet_sprite: []u8 : #load("../assets/tomato.png") +tomato_item_sprite: []u8 : #load("../assets/tomato_item.png") +sell_button_sprite: []u8 : #load("../assets/sell_button.png") +background_sprite: []u8 : #load("../assets/background.png") +tile_dirt_dry_sprite: []u8 : #load("../assets/tile_dirt_dry.png") +tile_dirt_wet_sprite: []u8 : #load("../assets/tile_dirt_wet.png") + +load_texture_from_memory :: proc(data: []u8) -> (texture: rl.Texture2D) { + img := rl.LoadImageFromMemory(".png", raw_data(data), auto_cast len(data)) + defer rl.UnloadImage(img) + texture = rl.LoadTextureFromImage(img) + return texture +} + +load_sound_from_memory :: proc( + data: []u8, + sound_extension: cstring = ".mp3", +) -> ( + sound: rl.Sound, +) { + wav := rl.LoadWaveFromMemory(sound_extension, raw_data(data), auto_cast len(data)) + defer rl.UnloadWave(wav) + sound = rl.LoadSoundFromWave(wav) + return +} + +pickup_sound: rl.Sound + +sell_button_texture: rl.Texture2D +background_texture: rl.Texture2D +tile_dirt_dry_texture: rl.Texture2D +tile_dirt_wet_texture: rl.Texture2D +tomato_texture: rl.Texture2D + +player_animation: Animation +tomato_animation: Animation diff --git a/src/tile.odin b/src/tile.odin new file mode 100644 index 0000000..4927c3f --- /dev/null +++ b/src/tile.odin @@ -0,0 +1,34 @@ +package game + +import "core:math" +import rl "vendor:raylib" + +Tile :: struct { + id: int, + pos: rl.Vector2, + animation: Animation, +} + +spawn_tile_under_mouse :: proc() { + tile_position := get_tile_position(world_mouse) + old_tile: bool + for e in entities { + if .Allocated not_in e.flags {continue} + if e.kind == .Tile && e.pos == tile_position { + old_tile = true + } + } + if !old_tile { + create_entity( + Entity { + pos = tile_position, + kind = .Tile, + data = TileData{has_plant = true, plant_id = 0, animation = tomato_animation}, + }, + ) + } +} + +get_tile_position :: proc(wpos: rl.Vector2) -> rl.Vector2 { + return {math.floor(wpos.x / TILE_SIZE) * TILE_SIZE, math.floor(wpos.y / TILE_SIZE) * TILE_SIZE} +}