Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ Modify and tear it apart as needed to fit the specific needs of whatever you're
This is practically my entire toolset - being iterated on since 2018.

Games shipped using the concepts in this template:
- https://store.steampowered.com/app/2571560/ARCANA/
- https://store.steampowered.com/app/3309460/Demon_Knives/
- https://store.steampowered.com/app/3433610/Terrafactor/

- <https://store.steampowered.com/app/2571560/ARCANA/>
- <https://store.steampowered.com/app/3309460/Demon_Knives/>
- <https://store.steampowered.com/app/3433610/Terrafactor/>

Things are in various stages of completion, lots of TODOs thrown around, dumb performance bottlenecks, etc. But as it stands, it's about as production ready as I've been able to pull off so far.

I'll be updating this as I continue making games and learning new things.

# Features

- Very fast pixel art asset creation & iteration pipeline with Aseprite via `asset_workbench/aseprite_asset_export.lua`
- Shaders with a rendering system that can be completely overhauled to suit whatever VFX you need
- A single-function entity gameplay programming workflow that scales well
Expand All @@ -25,21 +27,25 @@ I'll be updating this as I continue making games and learning new things.
# The Structure

## Core

Any file with `core_` is the meat of the tooling and is more or less seperate from the game logic.

I used to have these in standalone packages, but it's too annoying to enforce a clear boundary. Often you want to be able to access the entity structure or other game-specific stuff in the core.

## `main.odin`
## `main.odin`

This is the entrypoint and the structure of the main loop.

By default, I use a variable timestep. It works nicely in most situations for as little complexity as possible. But this can be altered to fit whatever the constraints of the game are. Maybe it's multiplayer. Maybe it needs a fixed timestep. Etc.

## `game.odin`

This is where most of the magic happens. The intersection of all tech. A place to "just make game".

This is the file I spend 90% of my time in, adding in new content to whatever the game is. It gets pretty big pretty quick. It's a very cozy place for writing gameplay slop.

## `entity.odin`

The backbone of the entity megastruct. As talked about [in here](https://randyprime.beehiiv.com/p/entity-structure-made-simple).

# Building
Expand All @@ -49,40 +55,52 @@ In general, development is way easier on windows since there's more tooling and
I get that some people prefer to be linux or mac chads though. It's relatively simple to get working natively since Sokol is great, so I'm beginning to add in support for both.

## Windows

1. [install Odin](https://odin-lang.org/docs/install/)
2. call `build.bat`
3. check `build/windows_debug`
4. see instructions below for running

## Mac

1. [install Odin](https://odin-lang.org/docs/install/)
2. call `build_mac.sh`
3. check `build/mac_debug`
4. see instructions below for running

## Linux
todo

1. [install Odin](https://odin-lang.org/docs/install/)
2. call `build_linux.sh`
3. check `build/linux_debug`
4. see instructions below for running

## Web

coming soon™️

# Running

Needs to run from the root directory since it's accessing /res.

I'd recommend setting up the [RAD Debugger](https://github.com/EpicGamesExt/raddebugger) (windows-only) for a great running & debugging experience. I always launch my game directly from there so it's easier to catch bugs on the fly.

# FAQ

## Why is this a "blueprint" (not a library)?

Game development is complicated.

I think trying to abstract everything away behind a library is a mistake. It makes things look and feel "clean" but sacrifices the capability, limiting what you're able to do and forcing you to use hacky workarounds instead of just doing the simplest and most direct thing possible to solve the problem.

old way:

1. use library
2. hit a wall in using it
3. work around it in a hacky way or cut the idea

new way:

1. use this blueprint
2. hit a wall in using it
3. learn the fundamental thing it's doing, then build on top of it, adjusting the source
Expand All @@ -95,13 +113,16 @@ I tried my best to seperate the core stuff from the game specific stuff so it's
I'll continue trying to simplify this and make it as readable and usable as possible, without sacrificing the full production-ready power of it.

## Why Odin?

Compared to C, it's a lot more fun to work in. Less overall typing, more safety by default, and great quality of life. Happy programming = more gameplay.

Compared to Jai, it has more users and is public (Jai is still in a closed beta). So that means more stability and a better ecosystem around packages, tooling etc, (because more people use it).

## Why Sokol?

It feels like a nice sweet spot high and low level.

It's not as high level as something like Raylib, so there's more flexibility with what you can do.

But usually to use Sokol you need to learn graphics programming, and usually there's a lot of boilerplate involved. That's why I've built this blueprint.
But usually to use Sokol you need to learn graphics programming, and usually there's a lot of boilerplate involved. That's why I've built this blueprint.

11 changes: 9 additions & 2 deletions sauce/build/build.odin
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,20 @@ main :: proc() {
append(&files_to_copy, "sauce/fmod/core/lib/darwin/libfmod.dylib")
append(&files_to_copy, "sauce/fmod/core/lib/darwin/libfmodL.dylib")
case .linux:
//TODO: linux fmod support
append(&files_to_copy, "sauce/fmod/studio/lib/linux/libfmodstudio.so")
append(&files_to_copy, "sauce/fmod/studio/lib/linux/libfmodstudio.so.13")
append(&files_to_copy, "sauce/fmod/studio/lib/linux/libfmodstudioL.so")
append(&files_to_copy, "sauce/fmod/studio/lib/linux/libfmodstudioL.so.13")
append(&files_to_copy, "sauce/fmod/core/lib/linux/libfmod.so")
append(&files_to_copy, "sauce/fmod/core/lib/linux/libfmod.so.13")
append(&files_to_copy, "sauce/fmod/core/lib/linux/libfmodL.so")
append(&files_to_copy, "sauce/fmod/core/lib/linux/libfmodL.so.13")
}

for src in files_to_copy {
dir, file_name := path.split(src)
assert(os.exists(dir), fmt.tprint("directory doesn't exist:", dir))
dest := fmt.tprintf("%v/%v", out_dir, file_name)
dest := path.join({out_dir, file_name})
if !os.exists(dest) {
os2.copy_file(dest, src)
}
Expand Down
2 changes: 1 addition & 1 deletion sauce/core_main.odin
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import sg "sokol/gfx"
import sglue "sokol/glue"
import slog "sokol/log"

import win32 "core:sys/windows" // wait, how is this building on mac?
import win32 "core:sys/windows" // wait, how is this building on mac? even more so, how is this building on linux?D:

Core_Context :: struct {
gs: ^Game_State,
Expand Down
15 changes: 14 additions & 1 deletion sauce/fmod/core/fmod.odin
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#+build windows
package fmod_core

// ========================================================================================
Expand All @@ -21,6 +20,20 @@ when ODIN_OS == .Windows {
foreign import lib "lib/windows/x64/fmod_vc.lib"
}
}
when ODIN_OS == .Darwin {
when LOGGING_ENABLED {
foreign import lib "lib/darwin/libfmodL.dylib"
} else {
foreign import lib "lib/darwin/libfmod.dylib"
}
}
when ODIN_OS == .Linux {
when LOGGING_ENABLED {
foreign import lib "lib/linux/libfmodL.so"
} else {
foreign import lib "lib/linux/libfmod.so"
}
}

@(default_calling_convention = "c", link_prefix = "FMOD_")
foreign lib {
Expand Down
33 changes: 16 additions & 17 deletions sauce/fmod/core/fmod_codec.odin
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#+build windows
package fmod_core

/* ======================================================================================== */
Expand Down Expand Up @@ -34,47 +33,47 @@ CODEC_SEEK_METHOD :: enum i32 {
// Codec callbacks
//

CODEC_OPEN_CALLBACK :: #type proc "stdcall" (
CODEC_OPEN_CALLBACK :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
usermode: MODE,
userexinfo: ^CREATESOUNDEXINFO,
) -> RESULT

CODEC_CLOSE_CALLBACK :: #type proc "stdcall" (codec_state: ^CODEC_STATE) -> RESULT
CODEC_CLOSE_CALLBACK :: #type proc "cdecl" (codec_state: ^CODEC_STATE) -> RESULT

CODEC_READ_CALLBACK :: #type proc "stdcall" (
CODEC_READ_CALLBACK :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
buffer: rawptr,
samples_in: u32,
samples_out: ^u32,
) -> RESULT

CODEC_GETLENGTH_CALLBACK :: #type proc "stdcall" (
CODEC_GETLENGTH_CALLBACK :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
length: ^u32,
lengthtype: TIMEUNIT,
) -> RESULT

CODEC_SETPOSITION_CALLBACK :: #type proc "stdcall" (
CODEC_SETPOSITION_CALLBACK :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
subsound: i32,
position: u32,
postype: TIMEUNIT,
) -> RESULT

CODEC_GETPOSITION_CALLBACK :: #type proc "stdcall" (
CODEC_GETPOSITION_CALLBACK :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
position: ^u32,
postype: TIMEUNIT,
) -> RESULT

CODEC_SOUNDCREATE_CALLBACK :: #type proc "stdcall" (
CODEC_SOUNDCREATE_CALLBACK :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
subsound: i32,
sound: ^SOUND,
) -> RESULT

CODEC_GETWAVEFORMAT_CALLBACK :: #type proc "stdcall" (
CODEC_GETWAVEFORMAT_CALLBACK :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
index: i32,
waveformat: ^CODEC_WAVEFORMAT,
Expand All @@ -86,7 +85,7 @@ CODEC_GETWAVEFORMAT_CALLBACK :: #type proc "stdcall" (
// Codec functions
//

CODEC_METADATA_FUNC :: #type proc "stdcall" (
CODEC_METADATA_FUNC :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
tagtype: TAGTYPE,
name: [^]u8,
Expand All @@ -96,10 +95,10 @@ CODEC_METADATA_FUNC :: #type proc "stdcall" (
unique: i32,
) -> RESULT

CODEC_ALLOC_FUNC :: #type proc "stdcall" (size: u32, align: u32, file: cstring, line: i32) -> rawptr
CODEC_FREE_FUNC :: #type proc "stdcall" (ptr: rawptr, file: cstring, line: i32)
CODEC_ALLOC_FUNC :: #type proc "cdecl" (size: u32, align: u32, file: cstring, line: i32) -> rawptr
CODEC_FREE_FUNC :: #type proc "cdecl" (ptr: rawptr, file: cstring, line: i32)

CODEC_LOG_FUNC :: #type proc "stdcall" (
CODEC_LOG_FUNC :: #type proc "cdecl" (
level: DEBUG_FLAGS,
file: cstring,
line: i32,
Expand All @@ -108,21 +107,21 @@ CODEC_LOG_FUNC :: #type proc "stdcall" (
#c_vararg args: ..any,
)

CODEC_FILE_READ_FUNC :: #type proc "stdcall" (
CODEC_FILE_READ_FUNC :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
buffer: rawptr,
sizebytes: u32,
bytesread: ^u32,
) -> RESULT

CODEC_FILE_SEEK_FUNC :: #type proc "stdcall" (
CODEC_FILE_SEEK_FUNC :: #type proc "cdecl" (
codec_state: ^CODEC_STATE,
pos: u32,
method: CODEC_SEEK_METHOD,
) -> RESULT

CODEC_FILE_TELL_FUNC :: #type proc "stdcall" (codec_state: ^CODEC_STATE, pos: ^u32) -> RESULT
CODEC_FILE_SIZE_FUNC :: #type proc "stdcall" (codec_state: ^CODEC_STATE, size: ^u32) -> RESULT
CODEC_FILE_TELL_FUNC :: #type proc "cdecl" (codec_state: ^CODEC_STATE, pos: ^u32) -> RESULT
CODEC_FILE_SIZE_FUNC :: #type proc "cdecl" (codec_state: ^CODEC_STATE, size: ^u32) -> RESULT



Expand Down
Loading