added scythe

This commit is contained in:
Stefan Stefanov 2024-12-31 17:06:06 +02:00
parent 6993054d63
commit 9504812ed5
10 changed files with 305 additions and 116 deletions

View file

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

BIN
assets/assets.aseprite Normal file

Binary file not shown.

BIN
assets/hoe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

BIN
assets/scythe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

View file

@ -0,0 +1,14 @@
package atlas_packer
import "core:fmt"
import "core:os"
import "vendor:stb/rect_pack"
main :: proc() {
if len(os.args) != 3 {
fmt.panicf(
"Invalid amount of args provided, valid call must look like: atlas_packer.exe <folder of images> <image extension>\nProvided args: %v",
os.args[1:],
)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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