entities, tiles and player walking, need y sorting

This commit is contained in:
Stefan Stefanov 2024-12-28 17:53:08 +02:00
parent b656f16c84
commit 5b4dab6292
9 changed files with 432 additions and 0 deletions

10
Makefile Normal file
View file

@ -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/

BIN
assets/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/player.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

BIN
assets/sell_button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

BIN
assets/tile_dirt_dry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

BIN
assets/tile_dirt_wet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

BIN
assets/tomato.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

12
ols.json Normal file
View file

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

410
src/main.odin Normal file
View file

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