Organize project

This commit is contained in:
Stefan Stefanov 2024-12-31 13:27:36 +02:00
parent d6143951d8
commit 6993054d63
7 changed files with 368 additions and 327 deletions

58
src/animation.odin Normal file
View file

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

5
src/constants.odin Normal file
View file

@ -0,0 +1,5 @@
package game
GAME_SCREEN_WIDTH: f32 = 480
GAME_SCREEN_HEIGHT: f32 = 270
TILE_SIZE :: 16

144
src/entity.odin Normal file
View file

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

29
src/globals.odin Normal file
View file

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

View file

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

43
src/resources.odin Normal file
View file

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

34
src/tile.odin Normal file
View file

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