From 9504812ed52223605af638a047d60a1dec6ac638 Mon Sep 17 00:00:00 2001 From: Stefan Stefanov Date: Tue, 31 Dec 2024 17:06:06 +0200 Subject: [PATCH] added scythe --- Makefile | 5 +- assets/assets.aseprite | Bin 0 -> 13418 bytes assets/hoe.png | Bin 0 -> 203 bytes assets/scythe.png | Bin 0 -> 237 bytes atlas_packer/atlas_packer.odin | 14 +++++ src/entity.odin | 39 +++---------- src/globals.odin | 85 ++++++++++++++++++++++++++++ src/main.odin | 99 ++++++++++++--------------------- src/resources.odin | 82 ++++++++++++++++++++++++--- src/tile.odin | 97 ++++++++++++++++++++++++++++---- 10 files changed, 305 insertions(+), 116 deletions(-) create mode 100644 assets/assets.aseprite create mode 100644 assets/hoe.png create mode 100644 assets/scythe.png create mode 100644 atlas_packer/atlas_packer.odin diff --git a/Makefile b/Makefile index 982bac1..5671082 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ run: - odin run src -out:game.exe -debug + odin run src -out:game.exe -debug -use-separate-modules -vet build: odin build src -out:game.exe -debug -use-separate-modules +check: + odin check src -vet -debug + clean: rm *.exe rm -rf *.dSYM/ diff --git a/assets/assets.aseprite b/assets/assets.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..0e3fb385ed02ff7e47ff5c1d82b7a6af9b48e2ca GIT binary patch literal 13418 zcmeHN4Ny~87Cs?=P%yYt%Frs9?iPzy0Ts!1L9ksZB3gbDaH(Yp@)Mw$q^rbWAQGiW zWu{ek=_+6ss;fd{Rq!WUFcN>dL&sHYg#v0~w!2HqYc*_b2!x;PdshO1lGW|b;>^x{ znVg(C_uO;tJNM1IU(PvsAppk1EHKhIG%nBpAd~z~Tnm9l31K$*`CnXqPXJi|$w6K@ zqyumA%jP1N4({G2c?Pw*g(UM+Zb(!>c=n4+b|;73`OW<9+U$Fms)oJ`t^7xP-uYl& zg(NmNC^7cArQt6|M&<_#cWjXSHKW>=cJ=R9ZVlUPw(5^GZ*42f&C5$kPTcZx`LU%- zyVqxQ7B=nPx29Yg?BH}7_Gi>OCaum!O59;%dd3@~O;VX8GuRKQ&>bdIDlB+YoW3<{ z)3ei#xEGhLD&JZIYJbjXo9owzw;Ykp4F0yY4XOU@=Qo6Cb=a^uNO zFF0_wqv4GH6)(mGK|#(EsND2k@#>Axxazx>^SX+Jh?8`2v8}(e<<8VSu^Sox9oP`)ARZ*teA$_^Ro3|=ZxU7^|A{4RvY z>5m^(Tj<7*iJdDFv`>R6-tz4N;(aLC*YrkQyp*8}>rgfyGq6;a59i2}wC-R^mft(5 zcoIU}watGtxTt((_m9`fK^Uvox)ZaYOJIpBr2R_3i>IhOz8F!8*C1YloX{jo0`iqe&E-83MXN%Za;Frn7L97AQvP(a#`UL+I+LS2o$$_ogq8Cx zC=UN2O`2@EgpAT;3#5dMA+;V$0yV0ch&CyAlu())B#|Q>ImMH^M(Oa7goh*!yuiM{ z$o`}q|8Wwup0THhvS5FrNS-f8&(&lJr6WiBi1K-ecc6UBiR79{s3t}`i2H0IU*GbY zaumcL4y{+W%~P$A4F%TmKp3Y#kDhG4#{;FLWi!P2~zU zoNd=PA2iS&b=ZmVhxoP+Pg(IgnJv#eNIzMBFYTjBPnU2>IW98eqfoDJn%@;s;1&I)?r%LSooa9XG(PKclh7M=i#%RY+Ycz287Z2 zbFsu?=t|(3suZ5+jd#RAS6S2>#@~dEiaB6VOKxYGL*X1>ula^hLc!4{DSCA_x2G1C zE1n0#A<4}f?S-_yBH5Kc)EPWfB6V1Qrg;m5^Ytfqbv%YKg^*}twEoZ*Zz0|x*3tf? zmbBmM7;W*0Nl{{r`xfa%O$Lw$R?swT1r3_LMe(IWABij4QPLl`evNBGeW)Azph->ybhTwpsKb+Ez#HnOskne}R_ts$K?7xpP$ zw>Es5dcsh;W7iVTR8N0Jj4WvRMRuw$tgUP43_^D3uoLPX?9hQu+NmB4F-Fp#+3)66 zz3Vv+95%S-WFd71?1c{;)MvpgHzxcgwpfV+8RqrP~^D?!UyGqvu zEl~7z#{`^f=#F-8?(2Qlf88E6n*nzVUb~j(N})q0LWfM!FNQG~#!$!H1e_S>$Ki)S zgJH}Vu%SQ;hA|k%U>Jj840(EzDV)axV$8jkJ}-CZ`| zd9n3NZbai;I!7h(ZuEmvfzPKif@)M4Y2btjFOfnK)1eFW=XwlvV5C7t11C%#@}C_i zOzi0woG`%&lbK4G2*!}cTX4uB1L6v92x$m~`%%)wi+d`)D1U1a@!2Ph{Z9*9FI`?q zyuCMieaRxvzg@a~z58p$$@*XN=A>{y^g(RYjNp%Qj6X2`!1%+0G8cAA)vSZD>p@|B)=o96uLIIJ-R&&8v0k@gT$C?*W^Go8K#*d8C;X8QluW;!ES;a}pk=|=zn literal 0 HcmV?d00001 diff --git a/assets/hoe.png b/assets/hoe.png new file mode 100644 index 0000000000000000000000000000000000000000..81ae0093bbc32c4bd327bb749975eeb0b0b6f45e GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|T0LDHLo9le z6Beij1R4BXza-(4{Cp|3npuf&OuU`vv^_9kkbF~l?o7jJ)t(nHugd)d<^?(RKPAk}r9dvo9X^pu2zgc)|ZQhv@Gq#ATDOyG4i<5`_nvvA(b zsjcQwAKfI)8XmC*nFi-rcX4ZMnj3I{H;;i~R*IqjyH|GJKnF2+y85}Sb4q9e08F|{ ArvLx| literal 0 HcmV?d00001 diff --git a/assets/scythe.png b/assets/scythe.png new file mode 100644 index 0000000000000000000000000000000000000000..06e114beb597d9202f5417f5acc2771d94603be2 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|mV3H5hFJ6_ zCoB*R2r~G&eo4Zo`HhW@if1oA%f6xF3QqofY-rxCFB#-q6&xtdqe)Z3>+L)#G iglUi2 \nProvided args: %v", + os.args[1:], + ) + } +} diff --git a/src/entity.odin b/src/entity.odin index c5674f2..133ec80 100644 --- a/src/entity.odin +++ b/src/entity.odin @@ -14,15 +14,16 @@ PlayerData :: struct { } ItemData :: struct { - id: int, + id: InventoryItem, count: int, texture: rl.Texture2D, } TileData :: struct { + is_tiled: bool, has_plant: bool, is_watered: bool, - plant_id: int, + plant_id: InventoryItem, animation: Animation, plant_grown: bool, } @@ -88,23 +89,6 @@ update_entities :: proc() { } } } - -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} @@ -125,20 +109,13 @@ entity_to_handle :: proc(e: Entity) -> EntityHandle { } 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 { + data, ok := e.data.(PlayerData);if ok { 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}) + data, ok := e.data.(ItemData);if ok { + t := f32(math.sin(rl.GetTime() * 5)) + rl.DrawTextureV(data.texture, e.pos + {0, t * 3}, rl.WHITE) + } } diff --git a/src/globals.odin b/src/globals.odin index e903b61..7b3a04b 100644 --- a/src/globals.odin +++ b/src/globals.odin @@ -1,9 +1,11 @@ package game import t "../tween" +import "core:time" import rl "vendor:raylib" SELL_BUTTON_POS :: rl.Vector2{432, 16} +TOOL_POS :: rl.Vector2{32, 32} game_camera := rl.Camera2D { zoom = 1, @@ -25,5 +27,88 @@ animations: [dynamic]^Animation entities: [256]Entity +current_tick: time.Tick + tween_ctx_f32: t.TweenContext(f32) tween_ctx_vec2: t.TweenContext([2]f32) + +player_inventory: [InventoryItem]int + +InventoryItem :: enum { + //Tools + hoe, + scythe, + watering_can, + pickaxe, + axe, + //Plants + tomato, + tomato_seed, + tomato_seedling, + //Other +} + +ITEM_TEXTURES := [InventoryItem]^rl.Texture2D { + .hoe = &hoe_texture, + .scythe = &scythe_texture, + .watering_can = &tomato_texture, + .pickaxe = &tomato_texture, + .axe = &tomato_texture, + .tomato = &tomato_texture, + .tomato_seed = &tomato_texture, + .tomato_seedling = &tomato_texture, +} + +HotbarKind :: enum { + tools, + seeds, +} + +Hotbar :: struct { + kind: HotbarKind, + tool_slots: []InventoryItem, + seed_slots: []InventoryItem, + current_slot: int, +} +hotbar := Hotbar { + tool_slots = {.hoe, .scythe, .watering_can}, + seed_slots = {.tomato_seed}, +} + +TOOL_SLOTS := len(hotbar.tool_slots) + +update_hotbar :: proc() { + if rl.IsKeyPressed(.E) { + total_slots := hotbar.kind == .tools ? len(hotbar.tool_slots) : len(hotbar.seed_slots) + hotbar.current_slot += 1 + if hotbar.current_slot >= total_slots { + hotbar.current_slot = 0 + } + } + if rl.IsKeyPressed(.TAB) { + // TODO: preserve position for each hotbar slot + hotbar.kind = hotbar.kind == .seeds ? .tools : .seeds + hotbar.current_slot = 0 + } +} + +draw_hotbar :: proc() { + rl.DrawRectangleRec({TOOL_POS.x, TOOL_POS.y, 32, 32}, rl.WHITE) + slots := hotbar.kind == .tools ? hotbar.tool_slots : hotbar.seed_slots + hotbar_item_texture := ITEM_TEXTURES[slots[hotbar.current_slot]]^ + rl.DrawTexturePro( + hotbar_item_texture, + {0, 0, 16, 16}, + {TOOL_POS.x, TOOL_POS.y, 32, 32}, + {}, + 0, + rl.WHITE, + ) + +} + +get_current_hand_item :: proc() -> InventoryItem { + return( + hotbar.kind == .tools ? hotbar.tool_slots[hotbar.current_slot] : hotbar.seed_slots[hotbar.current_slot] \ + ) +} diff --git a/src/main.odin b/src/main.odin index 095e130..aa25840 100644 --- a/src/main.odin +++ b/src/main.odin @@ -1,14 +1,40 @@ package game import t "../tween" -import sa "core:container/small_array" import "core:fmt" -import "core:math" -import "core:math/linalg" -import "core:slice" +import "core:log" +import "core:mem" import rl "vendor:raylib" main :: proc() { + context.logger = log.create_console_logger( + log.Level.Debug, + log.Options{.Level, .Procedure, .Line, .Terminal_Color}, + ) + defer log.destroy_console_logger(context.logger) + + when ODIN_DEBUG { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + + defer { + if len(track.allocation_map) > 0 { + log.errorf("=== %v allocations not freed: ===\n", len(track.allocation_map)) + for _, entry in track.allocation_map { + log.errorf("- %v bytes @ %v\n", entry.size, entry.location) + } + } + if len(track.bad_free_array) > 0 { + log.errorf("=== %v incorrect frees: ===\n", len(track.bad_free_array)) + for entry in track.bad_free_array { + log.errorf("- %p @ %v\n", entry.memory, entry.location) + } + } + mem.tracking_allocator_destroy(&track) + } + } + run() } @@ -25,8 +51,6 @@ run :: proc() { } load_resources() - rl.SetSoundVolume(pickup_sound, 0.1) - rl.PlaySound(pickup_sound) for !rl.WindowShouldClose() { free_all(context.temp_allocator) @@ -70,6 +94,8 @@ update :: proc() { GAME_SCREEN_HEIGHT * screen_scale, } + update_hotbar() + // :animation update update_entities() @@ -99,7 +125,7 @@ update :: proc() { } if rl.IsMouseButtonDown(.LEFT) { - spawn_tile_under_mouse() + interact_with_tile_under_mouse(.LEFT) } } @@ -112,6 +138,8 @@ draw :: proc() { draw_entities() + draw_hotbar() + rl.DrawTextureV(sell_button_texture, SELL_BUTTON_POS, rl.WHITE) rl.EndMode2D() rl.EndTextureMode() @@ -127,60 +155,3 @@ draw :: proc() { ) rl.EndDrawing() } - -load_resources :: proc() { - tween_ctx_f32 = t.context_init(f32) - tween_ctx_vec2 = t.context_init([2]f32) - - 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 - - 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) - pickup_sound = load_sound_from_memory(pickup_sound_data) - - - 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^) -} - -free_resources :: proc() { - t.context_destroy(tween_ctx_f32) - t.context_destroy(tween_ctx_vec2) - - rl.UnloadSound(pickup_sound) - - 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) - - rl.UnloadRenderTexture(game_render_buffer) // Unload render texture -} diff --git a/src/resources.odin b/src/resources.odin index 7b2d339..cedf723 100644 --- a/src/resources.odin +++ b/src/resources.odin @@ -1,5 +1,6 @@ package game +import t "../tween" import rl "vendor:raylib" pickup_sound_data: []u8 : #load("../assets/sounds/pickup_sound.mp3") @@ -11,6 +12,21 @@ 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") +hoe_sprite: []u8 : #load("../assets/hoe.png") +scythe_sprite: []u8 : #load("../assets/scythe.png") + +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 +hoe_texture: rl.Texture2D +scythe_texture: rl.Texture2D + +player_animation: Animation +tomato_animation: Animation load_texture_from_memory :: proc(data: []u8) -> (texture: rl.Texture2D) { img := rl.LoadImageFromMemory(".png", raw_data(data), auto_cast len(data)) @@ -31,13 +47,63 @@ load_sound_from_memory :: proc( 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 +load_resources :: proc() { + tween_ctx_f32 = t.context_init(f32) + tween_ctx_vec2 = t.context_init([2]f32) -player_animation: Animation -tomato_animation: Animation + 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 + + 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) + hoe_texture = load_texture_from_memory(hoe_sprite) + scythe_texture = load_texture_from_memory(scythe_sprite) + + pickup_sound = load_sound_from_memory(pickup_sound_data) + rl.SetSoundVolume(pickup_sound, 0.1) + + 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^) +} + +free_resources :: proc() { + t.context_destroy(tween_ctx_f32) + t.context_destroy(tween_ctx_vec2) + + rl.UnloadSound(pickup_sound) + + 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) + + rl.UnloadRenderTexture(game_render_buffer) // Unload render texture +} diff --git a/src/tile.odin b/src/tile.odin index 4927c3f..35cc388 100644 --- a/src/tile.odin +++ b/src/tile.odin @@ -1,5 +1,6 @@ package game +import "core:fmt" import "core:math" import rl "vendor:raylib" @@ -9,26 +10,98 @@ Tile :: struct { animation: Animation, } -spawn_tile_under_mouse :: proc() { +interact_with_tile_under_mouse :: proc(mouse_button: rl.MouseButton) { tile_position := get_tile_position(world_mouse) - old_tile: bool - for e in entities { + current_hand_item := get_current_hand_item() + + tile_entity: ^Entity + for &e in entities { if .Allocated not_in e.flags {continue} if e.kind == .Tile && e.pos == tile_position { - old_tile = true + tile_entity = &e } } - if !old_tile { - create_entity( - Entity { - pos = tile_position, - kind = .Tile, - data = TileData{has_plant = true, plant_id = 0, animation = tomato_animation}, - }, - ) + if tile_entity == nil { + tile_entity = create_entity(Entity{pos = tile_position, kind = .Tile, data = TileData{}}) + } + + tile_data, ok := &tile_entity.data.(TileData);if !ok { + fmt.panicf("Valid tile entity has invalid tile data!") + } + if mouse_button == .LEFT { + switch hotbar.kind { + case .tools: + #partial switch current_hand_item { + case .hoe: + if !tile_data.is_tiled { + tile_data.is_tiled = true + } + case .watering_can: + if tile_data.is_tiled && !tile_data.is_watered { + tile_data.is_watered = true + } + case .scythe: + if tile_data.has_plant && tile_data.plant_grown { + tile_data.has_plant = false + tile_data.plant_grown = false + + rl.PlaySound(pickup_sound) + create_entity( + Entity { + pos = tile_entity.pos, + kind = .Item, + data = ItemData { + id = get_seed_to_plant_id(tile_data.plant_id), + count = 3, + texture = get_plant_texture(tile_data.plant_id)^, + }, + }, + ) + } + } + case .seeds: + if !tile_data.has_plant { + tile_data.has_plant = true + tile_data.plant_id = current_hand_item + tile_data.animation = get_plant_animation(current_hand_item) + } + } } } 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} } + +get_seed_to_plant_id :: proc(seed: InventoryItem) -> InventoryItem { + return .tomato +} + +get_plant_animation :: proc(plant: InventoryItem) -> Animation { + return tomato_animation +} + +get_plant_texture :: proc(plant: InventoryItem) -> ^rl.Texture2D { + return ITEM_TEXTURES[plant] +} + +update_tile :: proc(e: Entity, data: ^TileData) { + if !data.is_tiled {return} + + update_animation(&data.animation) + if data.has_plant && data.animation.done && data.plant_grown == false { + data.plant_grown = true + } +} + +draw_tile :: proc(e: Entity) { + data, ok := e.data.(TileData);if ok { + if !data.is_tiled {return} + + tile_tint := data.is_watered ? rl.SKYBLUE : rl.WHITE + rl.DrawTextureV(tile_dirt_dry_texture, e.pos, tile_tint) + if data.has_plant { + draw_animation(data.animation, e.pos) + } + } +}