diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/OdinSdkConfig.xml b/.idea/OdinSdkConfig.xml new file mode 100644 index 0000000..cfb49e8 --- /dev/null +++ b/.idea/OdinSdkConfig.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..1f0ef49 --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,580 @@ + + + + + \ No newline at end of file diff --git a/.idea/libraries/Odin_SDK.xml b/.idea/libraries/Odin_SDK.xml new file mode 100644 index 0000000..fc0afde --- /dev/null +++ b/.idea/libraries/Odin_SDK.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..dd7d771 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0034a38 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "cppvsdbg", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/game.exe", + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7d71ffc --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,19 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "make", + // "problemMatcher": [ + // "$msvc" + // ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 61153b4..982bac1 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ run: - odin run src -out:game.exe -debug -use-separate-modules -sanitize:address + odin run src -out:game.exe -debug build: odin build src -out:game.exe -debug -use-separate-modules @@ -8,3 +8,4 @@ clean: rm *.exe rm -rf *.dSYM/ +all: build diff --git a/assets/sounds/pickup_sound.ogg b/assets/sounds/pickup_sound.ogg deleted file mode 100644 index 47648df..0000000 Binary files a/assets/sounds/pickup_sound.ogg and /dev/null differ diff --git a/src/main.odin b/src/main.odin index 545c8b0..95a8d04 100644 --- a/src/main.odin +++ b/src/main.odin @@ -27,7 +27,8 @@ 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.LoadSound("assets/sounds/pickup_sound.ogg") +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") @@ -44,6 +45,18 @@ load_texture_from_memory :: proc(data: []u8) -> (texture: rl.Texture2D) { 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 @@ -64,11 +77,19 @@ tween_ctx_f32: t.TweenContext(f32) tween_ctx_vec2: t.TweenContext([2]f32) main :: proc() { - rl.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT}) + rl.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .MSAA_4X_HINT}) rl.InitWindow(1280, 720, "Raylib Minimal") rl.SetTargetFPS(rl.GetMonitorRefreshRate(rl.GetCurrentMonitor())) rl.InitAudioDevice() + if rl.IsAudioDeviceReady() { + fmt.println("Audio working!") + } else { + 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, @@ -110,13 +131,13 @@ main :: proc() { rl.PlaySound(pickup_sound) - for !rl.WindowShouldClose() { - free_all(context.temp_allocator) - update() - draw() + if true {for !rl.WindowShouldClose() { + free_all(context.temp_allocator) + update() + draw() + } + } - - rl.UnloadRenderTexture(game_render_buffer) // Unload render texture rl.CloseAudioDevice() rl.CloseWindow() @@ -128,15 +149,16 @@ update :: proc() { t.tween_update(&tween_ctx_vec2, auto_cast rl.GetFrameTime()) // :scaling update + screen_width := rl.GetScreenWidth() + screen_height := rl.GetScreenHeight() screen_scale = min( - f32(rl.GetScreenWidth()) / GAME_SCREEN_WIDTH, - f32(rl.GetScreenHeight()) / GAME_SCREEN_HEIGHT, + f32(screen_width) / GAME_SCREEN_WIDTH, + f32(screen_height) / 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) / + (mouse.x - (f32(screen_width) - (GAME_SCREEN_WIDTH * screen_scale)) * 0.5) / screen_scale, + (mouse.y - (f32(screen_height) - (GAME_SCREEN_HEIGHT * screen_scale)) * 0.5) / screen_scale, } virtual_mouse = rl.Vector2Clamp( @@ -385,35 +407,15 @@ 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 - player := handle_to_entity(player_handle) - if player != nil { - item := create_entity( - Entity { - pos = e.pos, - kind = .Item, - data = ItemData{id = data.plant_id, count = 3, texture = tomato_texture}, - }, - ) - tw := t.tween_to(&tween_ctx_vec2, &item.pos, player.pos) - tw.id = item.handle - tw.data = tw - tw.on_update = proc(ctx: ^t.TweenContext([2]f32), data: rawptr) { - player := handle_to_entity(player_handle) - if player != nil { - tween: ^t.Tween([2]f32) = transmute(^t.Tween([2]f32))data - tween.goal = player.pos - {TILE_SIZE, TILE_SIZE} - } - } - tw.on_complete = proc(ctx: ^t.TweenContext([2]f32), data: rawptr) { - tween: ^t.Tween([2]f32) = transmute(^t.Tween([2]f32))data - fmt.println("Done") - rl.PlaySound(pickup_sound) - e := handle_to_entity(tween.id) - if e != nil && e.kind == .Item { - destroy_entity(e) - } - } - } + 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}, + }, + ) } } @@ -444,7 +446,9 @@ 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) + if data.has_plant { + draw_animation(data.animation, e.pos) + } } draw_item :: proc(e: Entity) { data, ok := e.data.(ItemData) diff --git a/tween/tween.odin b/tween/tween.odin new file mode 100644 index 0000000..08e77b9 --- /dev/null +++ b/tween/tween.odin @@ -0,0 +1,239 @@ +package tween + +import "base:intrinsics" +import "core:math/ease" +import "core:time" + +TweenContext :: struct($T: typeid) { + tweens: [dynamic]Tween(T), +} + +Tween :: struct($T: typeid) { + value: ^T, + start: T, + diff: T, + goal: T, + delay: f64, // in seconds + duration: time.Duration, + progress: f64, + rate: f64, + type: ease.Ease, + inited: bool, + id: int, + + // callbacks, data can be set, will be pushed to callback + data: rawptr, // by default gets set to value input + on_start: proc(ctx: ^TweenContext(T), data: rawptr), + on_update: proc(ctx: ^TweenContext(T), data: rawptr), + on_complete: proc(ctx: ^TweenContext(T), data: rawptr), +} + +@(require_results) +context_init_f :: proc( + $T: typeid, +) -> TweenContext(T) where intrinsics.type_is_float(T) { + return {tweens = make([dynamic]Tween(T))} +} + +@(require_results) +context_init_v :: proc( + $T: typeid/[$N]$E, +) -> TweenContext(T) where intrinsics.type_is_float(E) { + return {tweens = make([dynamic]Tween(T))} +} + +context_init :: proc { + context_init_f, + context_init_v, +} + +// delete map content +context_destroy :: proc(ctx: TweenContext($T)) { + delete(ctx.tweens) +} + +// clear map content, stops all animations +context_clear :: proc(ctx: ^TweenContext($T)) { + clear(&ctx.tweens) +} + +// append / overwrite existing tween value to parameters +// rest is initialized in tween_init, inside update +// return value can be used to set callbacks +@(require_results) +tween_to :: proc( + ctx: ^TweenContext($T), + value: ^T, + goal: T, + type: ease.Ease = .Quadratic_Out, + duration: time.Duration = time.Second, + delay: f64 = 0, + id: int = 0, +) -> ( + tween: ^Tween(T), +) { + append(&ctx.tweens, Tween(T){}) + n := len(ctx.tweens) - 1 + tween = &ctx.tweens[n] + + tween^ = { + value = value, + goal = goal, + duration = duration, + delay = delay, + type = type, + data = value, + id = id, + } + + return +} + +// init internal properties +_tween_init :: proc(tween: ^Tween($T), duration: time.Duration) { + tween.inited = true + tween.start = tween.value^ + tween.diff = tween.goal - tween.value^ + s := time.duration_seconds(duration) + tween.rate = duration > 0 ? 1.0 / s : 0 + tween.progress = duration > 0 ? 0 : 1 +} + +_tween_init_f :: proc( + tween: ^Tween($T), + duration: time.Duration, +) where intrinsics.type_is_float(T) { + _tween_init(tween, duration) +} + +_tween_init_v :: proc( + tween: ^Tween($T/[$N]$E), + duration: time.Duration, +) where intrinsics.type_is_float(E) { + _tween_init(tween, duration) +} + +tween_init :: proc { + _tween_init_f, + _tween_init_v, +} + +_tween_interpolate :: proc(tween: ^Tween($T), dt, delay_remainder: f64) { + tween.progress += tween.rate * (dt + delay_remainder) + x := tween.progress >= 1 ? 1 : ease.ease(tween.type, tween.progress) + tween.value^ = tween.start + tween.diff * T(x) +} + +tween_interpolate_f :: proc( + tween: ^Tween($T), + dt, delay_remainder: f64, +) where intrinsics.type_is_float(T) { + _tween_interpolate(tween, dt, delay_remainder) +} + +tween_interpolate_v :: proc( + tween: ^Tween($T/[$N]$E), + dt, delay_remainder: f64, +) where intrinsics.type_is_float(E) { + _tween_interpolate(tween, dt, delay_remainder) +} + +tween_interpolate :: proc { + tween_interpolate_f, + tween_interpolate_v, +} + +// update all tweens, wait for their delay if one exists +// calls callbacks in all stages, when they're filled +// deletes tween from the map after completion +_tween_update :: proc(ctx: ^TweenContext($T), dt: f64) { + for &tween in ctx.tweens { + delay_remainder := f64(0) + + // Update delay if necessary. + if tween.delay > 0 { + tween.delay -= dt + + if tween.delay < 0 { + // We finished the delay, but in doing so consumed part of this frame's `dt` budget. + // Keep track of it so we can apply it to this tween without affecting others. + delay_remainder = tween.delay + // We're done with this delay. + tween.delay = 0 + } + } + + // We either had no delay, or the delay has been consumed. + if tween.delay <= 0 { + if !tween.inited { + tween_init(&tween, tween.duration) + + if tween.on_start != nil { + tween.on_start(ctx, tween.data) + } + } + + // If part of the `dt` budget was consumed this frame, then `delay_remainder` will be + // that remainder, a negative value. Adding it to `dt` applies what's left of the `dt` + // to the tween so it advances properly, instead of too much or little. + tween_interpolate(&tween, dt, delay_remainder) + // tween.progress += tween.rate * (dt + delay_remainder) + // x := tween.progress >= 1 ? 1 : ease(tween.type, tween.progress) + // tween.value^ = tween.start + tween.diff * T(x) + + if tween.on_update != nil { + tween.on_update(ctx, tween.data) + } + + if tween.progress >= 1 { + // append keys to array that will be deleted after the loop + + if tween.on_complete != nil { + tween.on_complete(ctx, tween.data) + } + } + } + } +} + +tween_update_f :: proc(ctx: ^TweenContext($T), dt: f64) where intrinsics.type_is_float(T) { + _tween_update(ctx, dt) +} + +tween_update_v :: proc(ctx: ^TweenContext($T/[$N]$E), dt: f64) where intrinsics.type_is_float(E) { + _tween_update(ctx, dt) +} + +tween_update :: proc { + tween_update_v, + tween_update_f, +} + +// stop a specific key inside the map +// returns true when it successfully removed the key +// @(require_results) +tween_stop :: proc(ctx: ^TweenContext($T), id: int) -> bool { + removed_tweens := false + for i := 0; i < len(ctx.tweens); { + if tween.id == id { + unordered_remove(&ctx.tweens, i) + removed_tweens = true + } else { + i += 1 + } + } + + return false +} + +// Returns the first tween animation with that id +// If no tween exists with the given id, will return 0 +@(require_results) +tween_time_left :: proc(flux: TweenContext($T), id: int) -> f64 { + for tween in ctx.tweens { + if tween.id == id { + return ((1 - tween.progress) * tween.rate) + tween.delay + } + } + return 0 +}