Skip to content

Assets and import

The resource manager, typed asset handles and import settings.

Files: resource_manager.odin, assets.odin, asset_import.odin.

Typed asset handles: a project asset’s path, with its kind in the type, so code can’t play a texture or draw a sound. Scripts get them two ways:

  • The editor writes scripts/assets.odin with a constant for every project asset (TEXTURE_SHIP, SOUND_LASER, DATA_GOBLIN, …): checked by the compiler, completed by the code editor, and only naming assets exports ship.
  • As exported fields (ship: e.TextureAsset \export:“”``): the inspector shows a picker of the project’s assets of that kind.

Handles resolve against the project folder (Game.projectDir) and are loaded on first use, then cached by the resource manager.

Example

package mygame
import e "engine:."
// What the editor writes in scripts/assets.odin
SOUND_LASER :: e.SoundAsset{"assets/laser.wav"}
TEXTURE_SHIP :: e.TextureAsset{"assets/ship.png"}
DATA_FAST_SHIP :: e.DataAsset{"data/fast_ship.data.json"}
ShipStats :: struct {
speed: f32 `export:"default=300"`,
}
Fire :: proc(game: ^e.Game) {
e.Game_PlaySound(game, SOUND_LASER)
texture := e.Asset_Texture(game, TEXTURE_SHIP)
stats := e.Data(ShipStats, DATA_FAST_SHIP)
_, _ = texture, stats
}

Import settings of a project’s assets: how its images are filtered, and which fonts have MSDF atlases (fonts.odin).

An image drawn bigger, smaller or turned is sampled either smoothly (linear filtering: soft edges, right for painted art and photos) or by pixels (nearest: hard square pixels, right for pixel art). Each image asset can say which (<asset path=... filter="Pixel"/>, AssetRef.filter), else the project’s textureFilter decides, else Smooth for images and Pixel for Aseprite files. Game_LoadProject hands the assets’ settings to the resource manager (ResourceManager_SetImportSettings), which applies them to each texture it loads; editors call ResourceManager_SetTextureFilter when one changes, which also changes the textures already loaded.

ResourceManager :: struct {
// The renderer textures are made for
renderer: ^sdl.Renderer,
// The mixer sounds are loaded for
mixer: ^mix.Mixer,
// Loaded textures, by path (the keys are owned)
textures: map[string]^sdl.Texture,
// Each texture file's modification time when it was read (keys shared with `textures`), for
// `ResourceManager_ReloadChangedTextures`
textureTimes: map[string]i64,
// Loaded sounds, by path (the keys are owned)
audio: map[string]^mix.Audio,
// Loaded fonts, by "path@size" (the keys are owned)
fonts: map[string]^ttf.Font,
// Images' filtering by path, as loadTexture is given it (asset_import.odin: the project's
// assets; keys owned), and the filtering of the rest (Default: Smooth, Aseprite files Pixel)
textureFilters: map[string]TextureFilter,
defaultFilter: TextureFilter,
// Bitmap fonts and MSDF fonts' atlases read so far, by path (nil: couldn't be read; fonts.odin)
glyphFonts: map[string]^GlyphFont,
// TrueType fonts that have an MSDF atlas (asset_import.odin; keys owned)
msdfFonts: map[string]struct {},
// The shader MSDF fonts draw with (fonts.odin; made on first use)
msdfProgram: ^ShaderProgram,
// The embedded font at DEFAULT_FONT_SIZE, loaded by ResourceManager_Init
defaultFont: ^ttf.Font,
// Draws text through the renderer, caching glyphs in a GPU atlas. Used by the text primitives
textEngine: ^ttf.TextEngine,
// Loads an image (any format SDL_image reads: png, jpg, bmp, webp...) as a texture, or
// returns the one already loaded. nil (logged) if it can't be read
loadTexture: proc(rm: ^ResourceManager, filePath: string) -> ^sdl.Texture,
// Loads a sound or music file, or returns the one already loaded. `predecode` decodes the
// whole file up front (best for short effects); pass false to stream long music. nil (logged)
// if it can't be read
loadAudio: proc(rm: ^ResourceManager, filePath: string, predecode: bool = true) -> ^mix.Audio,
// Loads a font at a point size (`DEFAULT_FONT` for the embedded one), or returns the one
// already loaded at that size. nil (logged) if it can't be read
loadFont: proc(rm: ^ResourceManager, filePath: string, size: f32) -> ^ttf.Font,
// Frees a loaded texture. Anything still drawing it must stop first
unloadTexture: proc(rm: ^ResourceManager, filePath: string),
// Frees a loaded sound. Anything still playing it must stop first
unloadAudio: proc(rm: ^ResourceManager, filePath: string),
// Frees a font loaded at that size. Anything still using it must stop first
unloadFont: proc(rm: ^ResourceManager, filePath: string, size: f32),
// Frees the text engine and everything still loaded (`Game_Destroy` calls it)
destroy: proc(rm: ^ResourceManager),
}

The game’s cache of textures, sounds and fonts (Game.resourceManager).

Assets are keyed by file path (fonts by path and size), so loading the same file twice returns the handle already loaded. Paths are read as given (relative to the working directory, or looked up in the game’s data pack). The manager owns what it loads: unload* frees one asset, destroy (called by Game_Destroy) frees everything still loaded. Call the procedures with ->.

Example

rm := &game.resourceManager
ship := rm->loadTexture("assets/ship.png")
laser := rm->loadAudio("assets/laser.wav")
music := rm->loadAudio("assets/theme.ogg", predecode = false)
title := rm->loadFont(e.DEFAULT_FONT, 48)
_, _, _, _ = ship, laser, music, title

struct. Source: resource_manager.odin:36

TextureAsset :: struct {
// The image's path, relative to the project folder ("" for none)
path: string,
}

An image in the project (Sprite textures)

struct. Source: assets.odin:43

SoundAsset :: struct {
// The sound's path, relative to the project folder ("" for none)
path: string,
}

A sound in the project

struct. Source: assets.odin:49

FontAsset :: struct {
// The font's path, relative to the project folder ("" for none)
path: string,
}

A font in the project

struct. Source: assets.odin:55

DataAsset :: struct {
// The data file's path, relative to the project folder ("" for none)
path: string,
}

A data resource in the project (data.odin): a .data.json file holding a registered data type

struct. Source: assets.odin:61

MusicAsset :: struct {
// The music file's path, relative to the project folder ("" for none)
path: string,
}

Music in the project: streamed by a MusicPlayer as it plays (not loaded whole like a sound)

struct. Source: assets.odin:67

ComponentAsset :: struct {
// The component file's path, relative to the project folder ("" for none)
path: string,
}

A component in the project (a saved node tree, .component.xml): Scene_AddComponent adds one

struct. Source: assets.odin:73

AnimationAsset :: struct {
// The file's path, relative to the project folder ("" for none)
path: string,
}

An animation asset (an .anim.xml file, or an Aseprite file: its tags): animations AnimatedSprite nodes share (AnimatedSprite_UseAnimations, animation_sets.odin)

struct. Source: assets.odin:80

LDtkAsset :: struct {
// The .ldtk file's path, relative to the project folder ("" for none)
path: string,
}

An LDtk project in the project (.ldtk: ldtk.odin): its levels go in scenes as LDtkLevel nodes

struct. Source: assets.odin:86

TextureFilter :: enum {
// The project's setting (an asset's), or Smooth (Aseprite files: Pixel) for the project
Default,
// Linear filtering: soft, for painted art
Smooth,
// Nearest filtering: hard pixels, for pixel art
Pixel,
}

How an image is sampled when drawn scaled or turned

enum. Source: asset_import.odin:21

ResourceManager_Init :: proc(rm: ^ResourceManager, renderer: ^sdl.Renderer, mixer: ^mix.Mixer)

Sets up a resource manager: its maps and procedures, the text engine, and the default font (DEFAULT_FONT at DEFAULT_FONT_SIZE). Game_Init calls it for Game.resourceManager.

Inputs

  • rm: The resource manager
  • renderer: The renderer textures and text are made for
  • mixer: The mixer sounds are loaded for

procedure. Source: resource_manager.odin:93

ResourceManager_ReloadChangedTextures :: proc(rm: ^ResourceManager) -> (reloaded: int)

Reads again the textures whose files changed since they were loaded (saved in Aseprite or an image editor), updating them in place: nodes keep drawing the same texture, now with the new pixels. A file that changed size isn’t updated (logged): it shows when its scene is built again. Development runs call it twice a second (Game_Update); editors call it for their previews.

Inputs

  • rm: The resource manager

Returns

  • How many textures were updated

procedure. Source: resource_manager.odin:169

Asset_LDtk :: proc(game: ^Game, asset: LDtkAsset) -> ^LDtkProject

An LDtk asset’s project, read the first time and kept for the game’s life (nil, logged, if it can’t be read): Game_LoadLDtk.

Inputs

  • game: The game that keeps it
  • asset: The LDtk asset

Returns

  • The project (the game’s: don’t free it), or nil for an empty asset or a file that can’t be read

Example

e.LDtkAsset
world := e.Asset_LDtk(game, LDTK_WORLD)
for &level in world.worlds[0].levels do log.infof("%s: %v", level.identifier, level.size)

procedure. Source: assets.odin:107

Asset_Texture :: proc(game: ^Game, asset: TextureAsset) -> ^sdl.Texture

A texture asset’s texture, loaded the first time and cached by the resource manager.

Inputs

  • game: The game
  • asset: The texture asset

Returns

  • The texture (the resource manager’s: don’t destroy it), or nil for an empty asset or (logged) a file that can’t be loaded

Example

e.TextureAsset
ship := e.Asset_Texture(game, TEXTURE_SHIP)
e.DrawTexture(renderer, ship, {-32, -32, 64, 64})

procedure. Source: assets.odin:128

Asset_Sound :: proc(game: ^Game, asset: SoundAsset) -> ^mix.Audio

A sound asset’s audio, loaded (decoded whole) the first time and cached by the resource manager.

Inputs

  • game: The game
  • asset: The sound asset

Returns

  • The audio (the resource manager’s: don’t destroy it), or nil for an empty asset or (logged) a file that can’t be loaded

procedure. Source: assets.odin:144

Asset_Font :: proc(game: ^Game, asset: FontAsset, size: f32) -> ^ttf.Font

A font asset at a point size, loaded the first time and cached by the resource manager.

Inputs

  • game: The game
  • asset: The font asset
  • size: The point size

Returns

  • The font (the resource manager’s: don’t close it), or nil for an empty asset or (logged) a file that can’t be loaded

procedure. Source: assets.odin:161

Game_PlaySoundAsset :: proc(game: ^Game, asset: SoundAsset) -> bool

Game_PlaySound for a SoundAsset (see Game_PlaySoundFile); false (logged) for an empty one

procedure. Source: assets.odin:167

Game_PlaySound :: proc{Game_PlaySoundAsset, Game_PlaySoundFile}

Plays a sound once, loading it the first time (cached by the resource manager).

Inputs

  • game: The game
  • asset or path: A SoundAsset, or a path relative to the project folder

Returns

  • false if there’s no audio device, or (logged) the sound couldn’t be loaded or played

Example

e.SoundAsset
e.Game_PlaySound(game, SOUND_LASER)
e.Game_PlaySound(game, "assets/explosion.wav")

procedure group. Source: assets.odin:190

ResourceManager_SetImportSettings :: proc(rm: ^ResourceManager, assets: []AssetRef, projectDir: string, default: TextureFilter)

Sets the resource manager’s import settings from a project: image filtering (the default and each image asset’s; textures already loaded change too) and which fonts have MSDF atlases.

Inputs

  • rm: The resource manager
  • assets: The project’s assets (ProjectData.assets)
  • projectDir: Their folder (what their paths are joined to when loaded)
  • default: The project’s filtering (ProjectData.textureFilter)

procedure. Source: asset_import.odin:40

ResourceManager_SetTextureFilter :: proc(rm: ^ResourceManager, path: string, filter: TextureFilter)

Sets one image’s filtering (Default: the project’s), the texture too if it’s loaded (its Aseprite sheets and layers with it).

Inputs

  • rm: The resource manager
  • path: The image’s path as textures are loaded with it (the project folder joined)
  • filter: Its filtering

procedure. Source: asset_import.odin:69

ResourceManager_TextureFilter :: proc(rm: ^ResourceManager, path: string) -> TextureFilter

The filtering an image is drawn with: its own, the project’s, else Smooth (Aseprite files Pixel).

Inputs

  • rm: The resource manager
  • path: The image’s file

procedure. Source: asset_import.odin:90

DEFAULT_FONT :: "engine:default_font"

Open Sans Regular, compiled into the binary. Pass DEFAULT_FONT as the path to loadFont to get it at other sizes

constant. Source: resource_manager.odin:13

DEFAULT_FONT_SIZE :: 16

The point size of ResourceManager.defaultFont

constant. Source: resource_manager.odin:15