From 5b4dab62924112b0ae4d1e0f60ae283a89a4ee30 Mon Sep 17 00:00:00 2001 From: Stefan Stefanov Date: Sat, 28 Dec 2024 17:53:08 +0200 Subject: [PATCH] entities, tiles and player walking, need y sorting --- Makefile | 10 + assets/background.png | Bin 0 -> 1913 bytes assets/player.png | Bin 0 -> 219 bytes assets/sell_button.png | Bin 0 -> 279 bytes assets/tile_dirt_dry.png | Bin 0 -> 185 bytes assets/tile_dirt_wet.png | Bin 0 -> 188 bytes assets/tomato.png | Bin 0 -> 248 bytes ols.json | 12 ++ src/main.odin | 410 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 432 insertions(+) create mode 100644 Makefile create mode 100644 assets/background.png create mode 100644 assets/player.png create mode 100644 assets/sell_button.png create mode 100644 assets/tile_dirt_dry.png create mode 100644 assets/tile_dirt_wet.png create mode 100644 assets/tomato.png create mode 100644 ols.json create mode 100644 src/main.odin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61153b4 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +run: + odin run src -out:game.exe -debug -use-separate-modules -sanitize:address + +build: + odin build src -out:game.exe -debug -use-separate-modules + +clean: + rm *.exe + rm -rf *.dSYM/ + diff --git a/assets/background.png b/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..216beed1d624e800f6249988a381dfcfba943437 GIT binary patch literal 1913 zcmbVLe=wA39Dm;3Z7pu?;$jJrAB|>@Ep?+U@?-s$x=JmQx5REa>=kh$&mAq5(bb95 zNxyD1QS423R003%GAZHx_hysTZg^d6C>Uc8`Kakz)0(?OAEvr5N^xT3t-W$@(2IQ;PbqCY; z-)UBQyXLT>(1{g&S^R7B=ti+O+j5Fu#Gk)a>RT1!GvE2V@>R$9ll2Jj9S=+f;6flo zY`y*`6ub-~=xmer3=PyslDZ0`uJT9>beEW;mJHfoyKaiYpnyYG^8+B_v}=3H zqrD!+o&q&L20nVB++!V2BlV!A3TUo}un6TEe(eSws1QAZVY^4w}@{ed>; zc`wFsXf7YNn8WZE0<#em$j*Bl0cnu{1Tt*!a7dv`&D`1A1mL+sd$sp8!B1qTM+^f=_ zqYrbNC@etI;-!ruDLcUwu$Lf2F9?WUm>sgm!V(s;J@gX^WC*2%uMK9Haf3@V7_LMd zAEf`R71)R-a%R9J_GY2Y666pIjI}!Q0fk|Pu4jQ0zqtC+PC!2fD8Edi0z^v0Q=K~W zG>(t6ByqHf(bP}LsVlGUJ%3JTHiv-5>BJTv%d~MeQ{>%n9BBu19^O4b$Y@aCVX46s zTb%KYpqPEtCMd|CN?P2m9NK*hC!UK#D*z5rfr~o3YI-!2O5+OGPeeK5v$UHvPaQL9 z&BatIcLgP1jrn*$v#t1kU~wsLnWCAJA4(Uw(6;Hnm#4c&Oap<7r{*%%+Vs1BotO$} z+=_rhwD=2&es~;Co`OsYU=i)IY7^Dyy}xcwIvg`$M}JTutfp&NORQ}3_0f%Z>4mao zjp4ZajVCt%+mZT?xK?)?{>$B$m?^J4;;IJ{r}qO*htWbZz-_>pu|ZhX-rx-}D9d%M2-AvI_(Jxr)kf%}wCz_$2=Zu&GsY3Rn%+C*BpGiGx)f z2FyNTccp1OG#Lx1?%GHz3CVB+6n2OpCc#><$e7G+jmXhNYQU!Pzbx#1uXG@a5< z(L`QSK~W{Urro}{b>8onnugfhs42DE91brHhcWCY->GDKP2y~o1*@y1(I=B6ekR&~YNy NJYD@<);T3K0RUVgQ;z@u literal 0 HcmV?d00001 diff --git a/assets/sell_button.png b/assets/sell_button.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd0d5662bcea902c01b5b331a371976b9e6320e GIT binary patch literal 279 zcmV+y0qFjTP)Px#(Md!>R9J=Wm%$ByFbGAf(SbZVftz^gJPzY*9`w|NG^K@pv}q#8YT)sqpu@f{ zw`*u)%iupip8&@>L(gFVYikZLXe8SXgj1a7~sW%kRT${%2=qC1|urw&Y2)Fr8Q=NTI;=L$HKDRwdL_|wTh;VIJ^wg+4`i+B}|{(RS4{4i@~fu{iXhO7hP fQ=ddQMKdr+vuFwiCQbhYw4TA!)z4*}Q$iB}>Lo&x literal 0 HcmV?d00001 diff --git a/assets/tile_dirt_wet.png b/assets/tile_dirt_wet.png new file mode 100644 index 0000000000000000000000000000000000000000..05af2abd5f728edcd4d4329c0a88a03cde67574a GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|$~;{hLo9le z6C~~_oW1nHUeUDYhe_6{BEy*aKfR4Wka+Fm=ZLFG9Lpl5{#~E)^iTkYF~?m4v69vs zCX2h58%q3Zmps&R_GJshrbG?42cOjK9Hwz^kZREW&|%gf@?3nK6<7G_7VoBrhEV7F i8K&npC>$5M#l#R~r&jI$- zh4l@TPV+~DMbAu*{mJ%!VzRur#Ft2hXPh}Fr%P3_r;2fW3Tr%mr_(xK;#%(B!yY-G zmb&Q29X@eY{>j=;^F`PXd^zw&O-(X*(e!7U33snq_q;xF#l3UxkyWxKkqvQj?Tl(p rTq`E>YFxDEayzIY!MeDije#LYCoI=L=g?H3>lr*<{an^LB{Ts5s)$}k literal 0 HcmV?d00001 diff --git a/ols.json b/ols.json new file mode 100644 index 0000000..25afb1d --- /dev/null +++ b/ols.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json", + "collections": [ + ], + "enable_references": true, + "enable_inlay_hints": true, + "enable_rename:": true, + "enable_semantic_tokens": true, + "enable_document_symbols": true, + "enable_hover": true, + "enable_snippets": true +} diff --git a/src/main.odin b/src/main.odin new file mode 100644 index 0000000..fec4ff6 --- /dev/null +++ b/src/main.odin @@ -0,0 +1,410 @@ +package game + +import sa "core:container/small_array" +import "core:fmt" +import "core:math" +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} + +player_sprite: []u8 : #load("../assets/player.png") +tomato_sprite: []u8 : #load("../assets/tomato.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") + +player_spritesheet_image := rl.LoadImageFromMemory( + ".png", + raw_data(player_sprite), + auto_cast len(player_sprite), +) +tomato_spritesheet_image := rl.LoadImageFromMemory( + ".png", + raw_data(tomato_sprite), + auto_cast len(tomato_sprite), +) +sell_button_image := rl.LoadImageFromMemory( + ".png", + raw_data(sell_button_sprite), + auto_cast len(sell_button_sprite), +) +background_image := rl.LoadImageFromMemory( + ".png", + raw_data(background_sprite), + auto_cast len(background_sprite), +) +sell_button_texture: rl.Texture2D +background_texture: rl.Texture2D + +tile_dirt_dry_image := rl.LoadImageFromMemory( + ".png", + raw_data(tile_dirt_dry_sprite), + auto_cast len(tile_dirt_dry_sprite), +) +tile_dirt_wet_image := rl.LoadImageFromMemory( + ".png", + raw_data(tile_dirt_wet_sprite), + auto_cast len(tile_dirt_wet_sprite), +) +tile_dirt_dry_texture: rl.Texture2D +tile_dirt_wet_texture: rl.Texture2D + +player_animation: Animation +tomato_animation: Animation + +player_handle: EntityHandle + +tiles: [dynamic]Tile +animations: [dynamic]^Animation + +entities: [256]Entity + +main :: proc() { + rl.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT}) + rl.InitWindow(1280, 720, "Raylib Minimal") + rl.SetTargetFPS(rl.GetMonitorRefreshRate(rl.GetCurrentMonitor())) + + 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 = rl.LoadTextureFromImage(player_spritesheet_image), + num_frames = 2, + frame_length = 0.5, + loop = true, + } + tomato_animation = Animation { + texture = rl.LoadTextureFromImage(tomato_spritesheet_image), + num_frames = 4, + frame_length = 2, + loop = false, + offset = {TILE_SIZE / 2, TILE_SIZE}, + } + sell_button_texture = rl.LoadTextureFromImage(sell_button_image) + background_texture = rl.LoadTextureFromImage(background_image) + + tile_dirt_dry_texture = rl.LoadTextureFromImage(tile_dirt_dry_image) + tile_dirt_wet_texture = rl.LoadTextureFromImage(tile_dirt_wet_image) + + 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^) + + for !rl.WindowShouldClose() { + free_all(context.temp_allocator) + update() + draw() + } + + rl.UnloadRenderTexture(game_render_buffer) // Unload render texture + rl.CloseWindow() +} + +update :: proc() { + // :scaling update + screen_scale = min( + f32(rl.GetScreenWidth()) / GAME_SCREEN_WIDTH, + f32(rl.GetScreenHeight()) / GAME_SCREEN_HEIGHT, + ) + mouse := rl.GetMousePosition() + virtual_mouse := rl.Vector2 { + (mouse.x - (f32(rl.GetScreenWidth()) - (GAME_SCREEN_WIDTH * screen_scale)) * 0.5) / + screen_scale, + (mouse.y - (f32(rl.GetScreenHeight()) - (GAME_SCREEN_HEIGHT * screen_scale)) * 0.5) / + screen_scale, + } + virtual_mouse = rl.Vector2Clamp( + virtual_mouse, + {}, + rl.Vector2{GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT}, + ) + world_mouse = rl.GetScreenToWorld2D(virtual_mouse, game_camera) + screen_game_area = rl.Rectangle { + (f32(rl.GetScreenWidth()) - (GAME_SCREEN_WIDTH * screen_scale)) * 0.5, + (f32(rl.GetScreenHeight()) - (GAME_SCREEN_HEIGHT * screen_scale)) * 0.5, + GAME_SCREEN_WIDTH * screen_scale, + GAME_SCREEN_HEIGHT * screen_scale, + } + + // :animation update + for &e in entities { + if .Allocated not_in e.flags {continue} + + #partial switch &data in e.data { + case TileData: + update_animation(&data.animation) + case PlayerData: + update_animation(&data.animation) + } + } + + // :sell + hover_sell_button := rl.CheckCollisionPointRec( + world_mouse, + {SELL_BUTTON_POS.x, SELL_BUTTON_POS.y, TILE_SIZE * 2, TILE_SIZE * 2}, + ) + if rl.IsMouseButtonPressed(.LEFT) && hover_sell_button { + // todo: sell + fmt.println("Sell button pressed!") + } + + // :player update + player := handle_to_entity(player_handle) + if player != nil { + pd, ok := player.data.(PlayerData);if ok { + vel: rl.Vector2 + if rl.IsKeyDown(.D) do vel.x += 1 + if rl.IsKeyDown(.A) do vel.x -= 1 + if rl.IsKeyDown(.W) do vel.y -= 1 + if rl.IsKeyDown(.S) do vel.y += 1 + if vel.x == -1 do pd.animation.flip = true + if vel.x == 1 do pd.animation.flip = false + player.pos += vel * 100 * rl.GetFrameTime() + } + + } + if rl.IsMouseButtonDown(.LEFT) { + spawn_tile_under_mouse() + } +} + +draw :: proc() { + rl.BeginTextureMode(game_render_buffer) + rl.BeginMode2D(game_camera) + rl.BeginDrawing() + + rl.DrawTextureV(background_texture, {}, rl.WHITE) + // :animation draw + //for t in tiles { + // rl.DrawRectangle(auto_cast t.pos.x, auto_cast t.pos.y, TILE_SIZE, TILE_SIZE, rl.BLACK) + // draw_animation(t.animation, t.pos) + //} + for e in entities { + if .Allocated not_in e.flags {continue} + + #partial switch &data in e.data { + case TileData: + draw_tile(e) + case PlayerData: + draw_player(e) + } + } + + rl.DrawTextureV(sell_button_texture, SELL_BUTTON_POS, rl.WHITE) + rl.EndMode2D() + rl.EndTextureMode() + + rl.ClearBackground(rl.BLACK) + rl.DrawTexturePro( + game_render_buffer.texture, + game_render_buffer_area, + screen_game_area, + {}, + 0, + rl.WHITE, + ) + 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, +} + +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) +} + +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} +} + +EntityKind :: enum { + Player, + Tile, + Item, +} + +PlayerData :: struct { + animation: Animation, +} + +ItemData :: struct { + id: int, + count: int, + animation: Animation, +} + +TileData :: struct { + has_plant: bool, + is_watered: bool, + plant_id: int, + animation: Animation, +} + +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 +} + +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) + draw_animation(data.animation, e.pos) +} +draw_item :: proc(e: Entity) { + data, ok := e.data.(ItemData) + draw_animation(data.animation, e.pos) +}