init
This commit is contained in:
parent
10f2cebd46
commit
15e5ad3454
22 changed files with 965 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -12,4 +12,5 @@ log.txt
|
||||||
*.dSYM
|
*.dSYM
|
||||||
linux
|
linux
|
||||||
|
|
||||||
build/
|
build/
|
||||||
|
ols.json
|
||||||
31
.vscode/launch.json
vendored
Normal file
31
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "cppvsdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "Build Debug",
|
||||||
|
"name": "Debug",
|
||||||
|
"program": "${workspaceFolder}/game_debug.exe",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cppvsdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "Build Release",
|
||||||
|
"name": "Release",
|
||||||
|
"program": "${workspaceFolder}/game_release.exe",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cppvsdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Run File",
|
||||||
|
"program": "odin",
|
||||||
|
"args": ["run", "${fileBasename}", "-file"],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
"activityBar.background": "#322C2D",
|
||||||
|
"titleBar.activeBackground": "#463E3F",
|
||||||
|
"titleBar.activeForeground": "#FAFAFA"
|
||||||
|
}
|
||||||
|
}
|
||||||
60
.vscode/tasks.json
vendored
Normal file
60
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"command": "",
|
||||||
|
"args": [],
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Build Debug",
|
||||||
|
"type": "shell",
|
||||||
|
"windows": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_debug.bat",
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_debug.sh",
|
||||||
|
},
|
||||||
|
"osx": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_debug.sh",
|
||||||
|
},
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build Release",
|
||||||
|
"type": "shell",
|
||||||
|
"windows": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_release.bat",
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_release.sh",
|
||||||
|
},
|
||||||
|
"osx": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_release.sh",
|
||||||
|
},
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build Hot Reload",
|
||||||
|
"type": "shell",
|
||||||
|
"windows": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_hot_reload.bat; start game.exe",
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_hot_reload.sh",
|
||||||
|
},
|
||||||
|
"osx": {
|
||||||
|
"command": "${workspaceFolder}/scripts/build_hot_reload.sh",
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": false,
|
||||||
|
"clear": true
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
LICENSE
Normal file
7
LICENSE
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Copyright (c) 2024 Stefan Stefanov
|
||||||
|
|
||||||
|
This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely.
|
||||||
|
|
||||||
|
You do not need to credit the authors and this notice may be removed from redistributed versions.
|
||||||
9
README.md
Normal file
9
README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
# YAAP
|
||||||
|
Yet-Another-Atlas-Packer by Stefan Stefanov
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Simple atlas packer using `stb_rect_pack` from the `stb` family of header libraries & `raylib` for rendering/ui.
|
||||||
|
|
||||||
|
Project template provided by Karl Zylinski on github [here](https://github.com/karl-zylinski/odin-raylib-hot-reload-game-template).
|
||||||
2
scripts/build_debug.bat
Normal file
2
scripts/build_debug.bat
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
@echo off
|
||||||
|
odin build src/main_release -define:RAYLIB_SHARED=false -out:build/game_debug.exe -no-bounds-check -subsystem:windows -debug
|
||||||
3
scripts/build_debug.sh
Normal file
3
scripts/build_debug.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
odin build src/main_release -out:build/game_debug.bin -no-bounds-check -debug
|
||||||
21
scripts/build_hot_reload.bat
Normal file
21
scripts/build_hot_reload.bat
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
rem Build game.dll
|
||||||
|
odin build src -show-timings -use-separate-modules -define:RAYLIB_SHARED=true -build-mode:dll -out:build/game.dll -strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon -debug
|
||||||
|
IF %ERRORLEVEL% NEQ 0 exit /b 1
|
||||||
|
|
||||||
|
rem If game.exe already running: Then only compile game.dll and exit cleanly
|
||||||
|
QPROCESS "game.exe">NUL
|
||||||
|
IF %ERRORLEVEL% EQU 0 exit /b 1
|
||||||
|
|
||||||
|
rem build game.exe
|
||||||
|
odin build src/main_hot_reload -use-separate-modules -out:build/game.exe -strict-style -vet-using-stmt -vet-using-param -vet-style -vet-semicolon -debug
|
||||||
|
IF %ERRORLEVEL% NEQ 0 exit /b 1
|
||||||
|
|
||||||
|
rem copy raylib.dll from odin folder to here
|
||||||
|
if not exist "raylib.dll" (
|
||||||
|
echo "Please copy raylib.dll from <your_odin_compiler>/vendor/raylib/windows/raylib.dll to the same directory as game.exe"
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
exit /b 0
|
||||||
47
scripts/build_hot_reload.sh
Normal file
47
scripts/build_hot_reload.sh
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VET="-strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon"
|
||||||
|
|
||||||
|
# NOTE: this is a recent addition to the Odin compiler, if you don't have this command
|
||||||
|
# you can change this to the path to the Odin folder that contains vendor, eg: "~/Odin".
|
||||||
|
ROOT=$(odin root)
|
||||||
|
if [ ! $? -eq 0 ]; then
|
||||||
|
echo "Your Odin compiler does not have the 'odin root' command, please update or hardcode it in the script."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Figure out the mess that is dynamic libraries.
|
||||||
|
case $(uname) in
|
||||||
|
"Darwin")
|
||||||
|
case $(uname -m) in
|
||||||
|
"arm64") LIB_PATH="macos-arm64" ;;
|
||||||
|
*) LIB_PATH="macos" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
DLL_EXT=".dylib"
|
||||||
|
EXTRA_LINKER_FLAGS="-Wl,-rpath $ROOT/vendor/raylib/$LIB_PATH"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
DLL_EXT=".so"
|
||||||
|
EXTRA_LINKER_FLAGS="'-Wl,-rpath=\$ORIGIN/linux'"
|
||||||
|
|
||||||
|
# Copy the linux libraries into the project automatically.
|
||||||
|
if [ ! -d "linux" ]; then
|
||||||
|
mkdir linux
|
||||||
|
cp -r $ROOT/vendor/raylib/linux/libraylib*.so* linux
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Build the game.
|
||||||
|
odin build . -use-separate-modules -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -show-timings -define:RAYLIB_SHARED=true -build-mode:dll -out:build/game_tmp$DLL_EXT -debug $VET
|
||||||
|
|
||||||
|
# Need to use a temp file on Linux because it first writes an empty `game.so`, which the game will load before it is actually fully written.
|
||||||
|
mv game_tmp$DLL_EXT game$DLL_EXT
|
||||||
|
|
||||||
|
# Do not build the game.bin if it is already running.
|
||||||
|
if ! pgrep game.bin > /dev/null; then
|
||||||
|
odin build src/main_hot_reload -use-separate-modules -out:build/game.bin $VET -debug
|
||||||
|
fi
|
||||||
2
scripts/build_release.bat
Normal file
2
scripts/build_release.bat
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
@echo off
|
||||||
|
odin build src/main_release -define:RAYLIB_SHARED=false -out:build/game_release.exe -no-bounds-check -o:speed -strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon -subsystem:windows
|
||||||
3
scripts/build_release.sh
Normal file
3
scripts/build_release.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
odin build src/main_release -out:build/game_release.bin -no-bounds-check -o:speed -strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon
|
||||||
53
src/animation.odin
Normal file
53
src/animation.odin
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// This implements simple animations using sprite sheets. The texture in the
|
||||||
|
// `Animation` struct is assumed to contain a horizontal strip of the frames
|
||||||
|
// in the animation. Call `animation_update` to update and then call
|
||||||
|
// `animation_rect` when you wish to know the source rect to use in the texture
|
||||||
|
// With the source rect you can run rl.DrawTextureRec to draw the current frame.
|
||||||
|
|
||||||
|
package game
|
||||||
|
|
||||||
|
import "core:log"
|
||||||
|
|
||||||
|
Animation :: struct {
|
||||||
|
texture: Texture,
|
||||||
|
num_frames: int,
|
||||||
|
current_frame: int,
|
||||||
|
frame_timer: f32,
|
||||||
|
frame_length: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_create :: proc(tex: Texture, num_frames: int, frame_length: f32) -> Animation {
|
||||||
|
return(
|
||||||
|
Animation {
|
||||||
|
texture = tex,
|
||||||
|
num_frames = num_frames,
|
||||||
|
frame_length = frame_length,
|
||||||
|
frame_timer = frame_length,
|
||||||
|
} \
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_update :: proc(a: ^Animation, dt: f32) {
|
||||||
|
a.frame_timer -= dt
|
||||||
|
|
||||||
|
if a.frame_timer <= 0 {
|
||||||
|
a.frame_timer = a.frame_length + a.frame_timer
|
||||||
|
a.current_frame += 1
|
||||||
|
|
||||||
|
if a.current_frame >= a.num_frames {
|
||||||
|
a.current_frame = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_rect :: proc(a: Animation) -> Rect {
|
||||||
|
if a.num_frames == 0 {
|
||||||
|
log.error("Animation has zero frames")
|
||||||
|
return RectEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
w := f32(a.texture.width) / f32(a.num_frames)
|
||||||
|
h := f32(a.texture.height)
|
||||||
|
|
||||||
|
return {x = f32(a.current_frame) * w, y = 0, width = w, height = h}
|
||||||
|
}
|
||||||
58
src/game.odin
Normal file
58
src/game.odin
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// This file is compiled as part of the `odin.dll` file. It contains the
|
||||||
|
// procs that `game.exe` will call, such as:
|
||||||
|
//
|
||||||
|
// game_init: Sets up the game state
|
||||||
|
// game_update: Run once per frame
|
||||||
|
// game_shutdown: Shuts down game and frees memory
|
||||||
|
// game_memory: Run just before a hot reload, so game.exe has a pointer to the
|
||||||
|
// game's memory.
|
||||||
|
// game_hot_reloaded: Run after a hot reload so that the `g_mem` global variable
|
||||||
|
// can be set to whatever pointer it was in the old DLL.
|
||||||
|
|
||||||
|
package game
|
||||||
|
|
||||||
|
// import "core:fmt"
|
||||||
|
// import "core:math/linalg"
|
||||||
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
|
PixelWindowHeight :: 180
|
||||||
|
|
||||||
|
GameMemory :: struct {}
|
||||||
|
|
||||||
|
g_mem: ^GameMemory
|
||||||
|
|
||||||
|
game_camera :: proc() -> rl.Camera2D {
|
||||||
|
w := f32(rl.GetScreenWidth())
|
||||||
|
h := f32(rl.GetScreenHeight())
|
||||||
|
|
||||||
|
return {zoom = h / PixelWindowHeight, target = {}, offset = {w / 2, h / 2}}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_camera :: proc() -> rl.Camera2D {
|
||||||
|
return {zoom = f32(rl.GetScreenHeight()) / PixelWindowHeight}
|
||||||
|
}
|
||||||
|
|
||||||
|
update :: proc() {
|
||||||
|
}
|
||||||
|
|
||||||
|
draw :: proc() {
|
||||||
|
rl.BeginDrawing()
|
||||||
|
rl.ClearBackground(rl.BLACK)
|
||||||
|
|
||||||
|
rl.BeginMode2D(game_camera())
|
||||||
|
// rl.DrawRectangleV(g_mem.player_pos, {4, 8}, rl.WHITE)
|
||||||
|
rl.DrawRectangleV({20, 20}, {10, 20}, rl.RED)
|
||||||
|
rl.EndMode2D()
|
||||||
|
|
||||||
|
rl.BeginMode2D(ui_camera())
|
||||||
|
// rl.DrawText(
|
||||||
|
// fmt.ctprintf("some_number: %v\nplayer_pos: %v", g_mem.some_number, g_mem.player_pos),
|
||||||
|
// 5,
|
||||||
|
// 5,
|
||||||
|
// 8,
|
||||||
|
// rl.WHITE,
|
||||||
|
// )
|
||||||
|
rl.EndMode2D()
|
||||||
|
|
||||||
|
rl.EndDrawing()
|
||||||
|
}
|
||||||
141
src/handle_array.odin
Normal file
141
src/handle_array.odin
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
// This handle-based array gives you a statically allocated array where you can
|
||||||
|
// use index based handles instead of pointers. The handles have a generation
|
||||||
|
// that makes sure you don't get bugs when slots are re-used.
|
||||||
|
// Read more about it here: https://floooh.github.io/2018/06/17/handles-vs-pointers.html */
|
||||||
|
|
||||||
|
package game
|
||||||
|
|
||||||
|
Handle :: struct($T: typeid) {
|
||||||
|
// idx 0 means unused. Note that slot 0 is a dummy slot, it can never be used.
|
||||||
|
idx: u32,
|
||||||
|
gen: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleArrayItem :: struct($T: typeid) {
|
||||||
|
item: T,
|
||||||
|
handle: Handle(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add a freelist that uses some kind of bit array... We should be able to
|
||||||
|
// check 64 item slots at a time that way, but without any dynamic array.
|
||||||
|
HandleArray :: struct($T: typeid, $N: int) {
|
||||||
|
items: #soa[N]HandleArrayItem(T),
|
||||||
|
num_items: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_add :: proc(a: ^HandleArray($T, $N), v: T) -> (Handle(T), bool) #optional_ok {
|
||||||
|
for idx in 1 ..< a.num_items {
|
||||||
|
i := &a.items[idx]
|
||||||
|
|
||||||
|
if idx != 0 && i.handle.idx == 0 {
|
||||||
|
i.handle.idx = u32(idx)
|
||||||
|
i.item = v
|
||||||
|
return i.handle, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index 0 is dummy
|
||||||
|
if a.num_items == 0 {
|
||||||
|
a.num_items += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.num_items == len(a.items) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := a.num_items
|
||||||
|
i := &a.items[a.num_items]
|
||||||
|
a.num_items += 1
|
||||||
|
i.handle.idx = idx
|
||||||
|
i.handle.gen = 1
|
||||||
|
i.item = v
|
||||||
|
return i.handle, true
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_get :: proc(a: HandleArray($T, $N), h: Handle(T)) -> (T, bool) {
|
||||||
|
if h.idx == 0 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h {
|
||||||
|
return a.items[h.idx].item, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_get_ptr :: proc(a: HandleArray($T, $N), h: Handle(T)) -> ^T {
|
||||||
|
if h.idx == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h {
|
||||||
|
return &ha.items[h.idx].item
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_remove :: proc(a: ^HandleArray($T, $N), h: Handle(T)) {
|
||||||
|
if h.idx == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h {
|
||||||
|
a.items[h.idx].handle.idx = 0
|
||||||
|
a.items[h.idx].handle.gen += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_valid :: proc(a: HandleArray($T, $N), h: Handle(T)) -> bool {
|
||||||
|
if h.idx == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleArrayIter :: struct($T: typeid, $N: int) {
|
||||||
|
a: ^HandleArray(T, N),
|
||||||
|
index: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_make_iter :: proc(a: ^HandleArray($T, $N)) -> HandleArrayIter(T, N) {
|
||||||
|
return HandleArrayIter(T, N){a = a}
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_iter :: proc(it: ^HandleArrayIter($T, $N)) -> (val: T, h: Handle(T), cond: bool) {
|
||||||
|
cond = it.index < int(it.a.num_items)
|
||||||
|
|
||||||
|
for ; cond; cond = it.index < int(it.a.num_items) {
|
||||||
|
if it.a.items[it.index].handle.idx == 0 {
|
||||||
|
it.index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val = it.a.items[it.index].item
|
||||||
|
h = it.a.items[it.index].handle
|
||||||
|
it.index += 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ha_iter_ptr :: proc(it: ^HandleArrayIter($T, $N)) -> (val: ^T, h: Handle(T), cond: bool) {
|
||||||
|
cond = it.index < int(it.a.num_items)
|
||||||
|
|
||||||
|
for ; cond; cond = it.index < int(it.a.num_items) {
|
||||||
|
if it.a.items[it.index].handle.idx == 0 {
|
||||||
|
it.index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val = &it.a.items[it.index].item
|
||||||
|
h = it.a.items[it.index].handle
|
||||||
|
it.index += 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
41
src/helpers.odin
Normal file
41
src/helpers.odin
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
// generic odin helpers
|
||||||
|
|
||||||
|
package game
|
||||||
|
|
||||||
|
import "core:intrinsics"
|
||||||
|
import "core:reflect"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
increase_or_wrap_enum :: proc(e: $T) -> T {
|
||||||
|
ei := int(e) + 1
|
||||||
|
|
||||||
|
if ei >= len(T) {
|
||||||
|
ei = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return T(ei)
|
||||||
|
}
|
||||||
|
|
||||||
|
union_type :: proc(a: any) -> typeid {
|
||||||
|
return reflect.union_variant_typeid(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_cstring :: proc(s: string) -> cstring {
|
||||||
|
return strings.clone_to_cstring(s, context.temp_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a remap in core:math but it doesn't clamp in the new range, which I
|
||||||
|
// always want.
|
||||||
|
remap :: proc "contextless" (
|
||||||
|
old_value, old_min, old_max, new_min, new_max: $T,
|
||||||
|
) -> (
|
||||||
|
x: T,
|
||||||
|
) where intrinsics.type_is_numeric(T),
|
||||||
|
!intrinsics.type_is_array(T) {
|
||||||
|
old_range := old_max - old_min
|
||||||
|
new_range := new_max - new_min
|
||||||
|
if old_range == 0 {
|
||||||
|
return new_range / 2
|
||||||
|
}
|
||||||
|
return clamp(((old_value - old_min) / old_range) * new_range + new_min, new_min, new_max)
|
||||||
|
}
|
||||||
196
src/main_hot_reload/main_hot_reload.odin
Normal file
196
src/main_hot_reload/main_hot_reload.odin
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
// Development game exe. Loads game.dll and reloads it whenever it changes.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "core:c/libc"
|
||||||
|
import "core:dynlib"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:log"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
when ODIN_OS == .Windows {
|
||||||
|
DLL_EXT :: ".dll"
|
||||||
|
} else when ODIN_OS == .Darwin {
|
||||||
|
DLL_EXT :: ".dylib"
|
||||||
|
} else {
|
||||||
|
DLL_EXT :: ".so"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_dll :: proc(to: string) -> bool {
|
||||||
|
exit: i32
|
||||||
|
when ODIN_OS == .Windows {
|
||||||
|
exit = libc.system(fmt.ctprintf("copy game.dll {0}", to))
|
||||||
|
} else {
|
||||||
|
exit = libc.system(fmt.ctprintf("cp game" + DLL_EXT + " {0}", to))
|
||||||
|
}
|
||||||
|
|
||||||
|
if exit != 0 {
|
||||||
|
fmt.printfln("Failed to copy game" + DLL_EXT + " to {0}", to)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
GameAPI :: struct {
|
||||||
|
lib: dynlib.Library,
|
||||||
|
init_window: proc(),
|
||||||
|
init: proc(),
|
||||||
|
update: proc() -> bool,
|
||||||
|
shutdown: proc(),
|
||||||
|
shutdown_window: proc(),
|
||||||
|
memory: proc() -> rawptr,
|
||||||
|
memory_size: proc() -> int,
|
||||||
|
hot_reloaded: proc(mem: rawptr),
|
||||||
|
force_reload: proc() -> bool,
|
||||||
|
force_restart: proc() -> bool,
|
||||||
|
modification_time: os.File_Time,
|
||||||
|
api_version: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
load_game_api :: proc(api_version: int) -> (api: GameAPI, ok: bool) {
|
||||||
|
mod_time, mod_time_error := os.last_write_time_by_name("game" + DLL_EXT)
|
||||||
|
if mod_time_error != os.ERROR_NONE {
|
||||||
|
fmt.printfln(
|
||||||
|
"Failed getting last write time of game" + DLL_EXT + ", error code: {1}",
|
||||||
|
mod_time_error,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this needs to be a relative path for Linux to work.
|
||||||
|
game_dll_name := fmt.tprintf(
|
||||||
|
"{0}game_{1}" + DLL_EXT,
|
||||||
|
"./" when ODIN_OS != .Windows else "",
|
||||||
|
api_version,
|
||||||
|
)
|
||||||
|
copy_dll(game_dll_name) or_return
|
||||||
|
|
||||||
|
_, ok = dynlib.initialize_symbols(&api, game_dll_name, "game_", "lib")
|
||||||
|
if !ok {
|
||||||
|
fmt.printfln("Failed initializing symbols: {0}", dynlib.last_error())
|
||||||
|
}
|
||||||
|
|
||||||
|
api.api_version = api_version
|
||||||
|
api.modification_time = mod_time
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
unload_game_api :: proc(api: ^GameAPI) {
|
||||||
|
if api.lib != nil {
|
||||||
|
if !dynlib.unload_library(api.lib) {
|
||||||
|
fmt.printfln("Failed unloading lib: {0}", dynlib.last_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.remove(fmt.tprintf("game_{0}" + DLL_EXT, api.api_version)) != 0 {
|
||||||
|
fmt.printfln("Failed to remove game_{0}" + DLL_EXT + " copy", api.api_version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
context.logger = log.create_console_logger()
|
||||||
|
|
||||||
|
default_allocator := context.allocator
|
||||||
|
tracking_allocator: mem.Tracking_Allocator
|
||||||
|
mem.tracking_allocator_init(&tracking_allocator, default_allocator)
|
||||||
|
context.allocator = mem.tracking_allocator(&tracking_allocator)
|
||||||
|
|
||||||
|
reset_tracking_allocator :: proc(a: ^mem.Tracking_Allocator) -> bool {
|
||||||
|
err := false
|
||||||
|
|
||||||
|
for _, value in a.allocation_map {
|
||||||
|
fmt.printf("%v: Leaked %v bytes\n", value.location, value.size)
|
||||||
|
err = true
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.tracking_allocator_clear(a)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
game_api_version := 0
|
||||||
|
game_api, game_api_ok := load_game_api(game_api_version)
|
||||||
|
|
||||||
|
if !game_api_ok {
|
||||||
|
fmt.println("Failed to load Game API")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
game_api_version += 1
|
||||||
|
game_api.init_window()
|
||||||
|
game_api.init()
|
||||||
|
|
||||||
|
old_game_apis := make([dynamic]GameAPI, default_allocator)
|
||||||
|
|
||||||
|
window_open := true
|
||||||
|
for window_open {
|
||||||
|
window_open = game_api.update()
|
||||||
|
force_reload := game_api.force_reload()
|
||||||
|
force_restart := game_api.force_restart()
|
||||||
|
reload := force_reload || force_restart
|
||||||
|
game_dll_mod, game_dll_mod_err := os.last_write_time_by_name("game" + DLL_EXT)
|
||||||
|
|
||||||
|
if game_dll_mod_err == os.ERROR_NONE && game_api.modification_time != game_dll_mod {
|
||||||
|
reload = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if reload {
|
||||||
|
new_game_api, new_game_api_ok := load_game_api(game_api_version)
|
||||||
|
|
||||||
|
if new_game_api_ok {
|
||||||
|
if game_api.memory_size() != new_game_api.memory_size() || force_restart {
|
||||||
|
game_api.shutdown()
|
||||||
|
reset_tracking_allocator(&tracking_allocator)
|
||||||
|
|
||||||
|
for &g in old_game_apis {
|
||||||
|
unload_game_api(&g)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(&old_game_apis)
|
||||||
|
unload_game_api(&game_api)
|
||||||
|
game_api = new_game_api
|
||||||
|
game_api.init()
|
||||||
|
} else {
|
||||||
|
append(&old_game_apis, game_api)
|
||||||
|
game_memory := game_api.memory()
|
||||||
|
game_api = new_game_api
|
||||||
|
game_api.hot_reloaded(game_memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
game_api_version += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for b in tracking_allocator.bad_free_array {
|
||||||
|
log.error("Bad free at: %v", b.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(&tracking_allocator.bad_free_array)
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
game_api.shutdown()
|
||||||
|
reset_tracking_allocator(&tracking_allocator)
|
||||||
|
|
||||||
|
for &g in old_game_apis {
|
||||||
|
unload_game_api(&g)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(old_game_apis)
|
||||||
|
|
||||||
|
game_api.shutdown_window()
|
||||||
|
unload_game_api(&game_api)
|
||||||
|
mem.tracking_allocator_destroy(&tracking_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make game use good GPU on laptops etc
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
NvOptimusEnablement: u32 = 1
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
AmdPowerXpressRequestHighPerformance: i32 = 1
|
||||||
77
src/main_release/main_release.odin
Normal file
77
src/main_release/main_release.odin
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// For making a release exe that does not use hot reload.
|
||||||
|
|
||||||
|
package main_release
|
||||||
|
|
||||||
|
import "core:log"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
import game ".."
|
||||||
|
|
||||||
|
UseTrackingAllocator :: #config(UseTrackingAllocator, false)
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
when UseTrackingAllocator {
|
||||||
|
default_allocator := context.allocator
|
||||||
|
tracking_allocator: Tracking_Allocator
|
||||||
|
tracking_allocator_init(&tracking_allocator, default_allocator)
|
||||||
|
context.allocator = allocator_from_tracking_allocator(&tracking_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
mode: int = 0
|
||||||
|
when ODIN_OS == .Linux || ODIN_OS == .Darwin {
|
||||||
|
mode = os.S_IRUSR | os.S_IWUSR | os.S_IRGRP | os.S_IROTH
|
||||||
|
}
|
||||||
|
|
||||||
|
logh, logh_err := os.open("log.txt", (os.O_CREATE | os.O_TRUNC | os.O_RDWR), mode)
|
||||||
|
|
||||||
|
if logh_err == os.ERROR_NONE {
|
||||||
|
os.stdout = logh
|
||||||
|
os.stderr = logh
|
||||||
|
}
|
||||||
|
|
||||||
|
logger :=
|
||||||
|
logh_err == os.ERROR_NONE ? log.create_file_logger(logh) : log.create_console_logger()
|
||||||
|
context.logger = logger
|
||||||
|
|
||||||
|
game.game_init_window()
|
||||||
|
game.game_init()
|
||||||
|
|
||||||
|
window_open := true
|
||||||
|
for window_open {
|
||||||
|
window_open = game.game_update()
|
||||||
|
|
||||||
|
when UseTrackingAllocator {
|
||||||
|
for b in tracking_allocator.bad_free_array {
|
||||||
|
log.error("Bad free at: %v", b.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(&tracking_allocator.bad_free_array)
|
||||||
|
}
|
||||||
|
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
game.game_shutdown()
|
||||||
|
game.game_shutdown_window()
|
||||||
|
|
||||||
|
if logh_err == os.ERROR_NONE {
|
||||||
|
log.destroy_file_logger(&logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
when UseTrackingAllocator {
|
||||||
|
for key, value in tracking_allocator.allocation_map {
|
||||||
|
log.error("%v: Leaked %v bytes\n", value.location, value.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
tracking_allocator_destroy(&tracking_allocator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make game use good GPU on laptops etc
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
NvOptimusEnablement: u32 = 1
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
AmdPowerXpressRequestHighPerformance: i32 = 1
|
||||||
8
src/math.odin
Normal file
8
src/math.odin
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package game
|
||||||
|
|
||||||
|
Vec2i :: [2]int
|
||||||
|
Vec2 :: [2]f32
|
||||||
|
|
||||||
|
vec2_from_vec2i :: proc(p: Vec2i) -> Vec2 {
|
||||||
|
return {f32(p.x), f32(p.y)}
|
||||||
|
}
|
||||||
70
src/raylib_helpers.odin
Normal file
70
src/raylib_helpers.odin
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
package game
|
||||||
|
|
||||||
|
import "core:slice"
|
||||||
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
|
Texture :: rl.Texture
|
||||||
|
Color :: rl.Color
|
||||||
|
|
||||||
|
texture_rect :: proc(tex: Texture, flip_x: bool) -> Rect {
|
||||||
|
return(
|
||||||
|
{
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = flip_x ? -f32(tex.width) : f32(tex.width),
|
||||||
|
height = f32(tex.height),
|
||||||
|
} \
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
load_premultiplied_alpha_ttf_from_memory :: proc(file_data: []byte, font_size: int) -> rl.Font {
|
||||||
|
font := rl.Font {
|
||||||
|
baseSize = i32(font_size),
|
||||||
|
glyphCount = 95,
|
||||||
|
}
|
||||||
|
|
||||||
|
font.glyphs = rl.LoadFontData(
|
||||||
|
&file_data[0],
|
||||||
|
i32(len(file_data)),
|
||||||
|
font.baseSize,
|
||||||
|
{},
|
||||||
|
font.glyphCount,
|
||||||
|
.DEFAULT,
|
||||||
|
)
|
||||||
|
|
||||||
|
if font.glyphs != nil {
|
||||||
|
font.glyphPadding = 4
|
||||||
|
|
||||||
|
atlas := rl.GenImageFontAtlas(
|
||||||
|
font.glyphs,
|
||||||
|
&font.recs,
|
||||||
|
font.glyphCount,
|
||||||
|
font.baseSize,
|
||||||
|
font.glyphPadding,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
atlas_u8 := slice.from_ptr((^u8)(atlas.data), int(atlas.width * atlas.height * 2))
|
||||||
|
|
||||||
|
for i in 0 ..< atlas.width * atlas.height {
|
||||||
|
a := atlas_u8[i * 2 + 1]
|
||||||
|
v := atlas_u8[i * 2]
|
||||||
|
atlas_u8[i * 2] = u8(f32(v) * (f32(a) / 255))
|
||||||
|
}
|
||||||
|
|
||||||
|
font.texture = rl.LoadTextureFromImage(atlas)
|
||||||
|
rl.SetTextureFilter(font.texture, .BILINEAR)
|
||||||
|
|
||||||
|
// Update glyphs[i].image to use alpha, required to be used on ImageDrawText()
|
||||||
|
for i in 0 ..< font.glyphCount {
|
||||||
|
rl.UnloadImage(font.glyphs[i].image)
|
||||||
|
font.glyphs[i].image = rl.ImageFromImage(atlas, font.recs[i])
|
||||||
|
}
|
||||||
|
//TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount);
|
||||||
|
|
||||||
|
rl.UnloadImage(atlas)
|
||||||
|
} else {
|
||||||
|
font = rl.GetFontDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
return font
|
||||||
|
}
|
||||||
66
src/rect.odin
Normal file
66
src/rect.odin
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// procs for modifying and managing rects
|
||||||
|
|
||||||
|
package game
|
||||||
|
|
||||||
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
|
Rect :: rl.Rectangle
|
||||||
|
|
||||||
|
RectEmpty :: Rect{}
|
||||||
|
|
||||||
|
split_rect_top :: proc(r: Rect, y: f32, m: f32) -> (top, bottom: Rect) {
|
||||||
|
top = r
|
||||||
|
bottom = r
|
||||||
|
top.y += m
|
||||||
|
top.height = y
|
||||||
|
bottom.y += y + m
|
||||||
|
bottom.height -= y + m
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
split_rect_left :: proc(r: Rect, x: f32, m: f32) -> (left, right: Rect) {
|
||||||
|
left = r
|
||||||
|
right = r
|
||||||
|
left.width = x
|
||||||
|
right.x += x + m
|
||||||
|
right.width -= x + m
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
split_rect_bottom :: proc(r: rl.Rectangle, y: f32, m: f32) -> (top, bottom: rl.Rectangle) {
|
||||||
|
top = r
|
||||||
|
top.height -= y + m
|
||||||
|
bottom = r
|
||||||
|
bottom.y = top.y + top.height + m
|
||||||
|
bottom.height = y
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
split_rect_right :: proc(r: Rect, x: f32, m: f32) -> (left, right: Rect) {
|
||||||
|
left = r
|
||||||
|
right = r
|
||||||
|
right.width = x
|
||||||
|
left.width -= x + m
|
||||||
|
right.x = left.x + left.width
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rect_middle :: proc(r: Rect) -> Vec2 {
|
||||||
|
return {r.x + f32(r.width) * 0.5, r.y + f32(r.height) * 0.5}
|
||||||
|
}
|
||||||
|
|
||||||
|
inset_rect :: proc(r: Rect, x: f32, y: f32) -> Rect {
|
||||||
|
return {r.x + x, r.y + y, r.width - x * 2, r.height - y * 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
rect_add_pos :: proc(r: Rect, p: Vec2) -> Rect {
|
||||||
|
return {r.x + p.x, r.y + p.y, r.width, r.height}
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse_in_rect :: proc(r: Rect) -> bool {
|
||||||
|
return rl.CheckCollisionPointRec(rl.GetMousePosition(), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse_in_world_rect :: proc(r: Rect, camera: rl.Camera2D) -> bool {
|
||||||
|
return rl.CheckCollisionPointRec(rl.GetScreenToWorld2D(rl.GetMousePosition(), camera), r)
|
||||||
|
}
|
||||||
61
src/symbol_exports.odin
Normal file
61
src/symbol_exports.odin
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package game
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_update :: proc() -> bool {
|
||||||
|
update()
|
||||||
|
draw()
|
||||||
|
return !rl.WindowShouldClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_init_window :: proc() {
|
||||||
|
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
||||||
|
rl.InitWindow(1280, 720, "Odin + Raylib + Hot Reload template!")
|
||||||
|
rl.SetWindowPosition(200, 200)
|
||||||
|
rl.SetTargetFPS(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_init :: proc() {
|
||||||
|
g_mem = new(GameMemory)
|
||||||
|
|
||||||
|
g_mem^ = GameMemory {
|
||||||
|
}
|
||||||
|
|
||||||
|
game_hot_reloaded(g_mem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_shutdown :: proc() {
|
||||||
|
free(g_mem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_shutdown_window :: proc() {
|
||||||
|
rl.CloseWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_memory :: proc() -> rawptr {
|
||||||
|
return g_mem
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_memory_size :: proc() -> int {
|
||||||
|
return size_of(GameMemory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_hot_reloaded :: proc(mem: rawptr) {
|
||||||
|
g_mem = (^GameMemory)(mem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_force_reload :: proc() -> bool {
|
||||||
|
return rl.IsKeyPressed(.F5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
game_force_restart :: proc() -> bool {
|
||||||
|
return rl.IsKeyPressed(.F6)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue