resource

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 31, 2023 License: MIT Imports: 12 Imported by: 37

README

Ebitengine Resource Manager Library

Build Status PkgGoDev

Overview

A resource manager (loader) for Ebitengine.

Key features:

  • Resource caching (only the first load decodes the resource)
  • Easy to use and opinionated
  • iota-friendly typed constants API
  • Int-based keys are also efficient, so the lookups are very fast

Some games that were built with this library:

Installation

go get github.com/quasilyte/ebitengine-resource

Quick Start

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strings"

	"github.com/hajimehoshi/ebiten/v2/audio"
	resource "github.com/quasilyte/ebitengine-resource"
)

// The essential game data resources could be enumerated using iota constants.
// Dynamic game content requires dynamically generated IDs during the run time.
const (
	rawNone resource.RawID = iota
	rawLevel1Data
	rawLevel2Data
	rawDefaultConfig
)

const (
	audioNone resource.AudioID = iota
	audioExample
)

func main() {
	audioContext := audio.NewContext(44100)

	l := resource.NewLoader(audioContext)

	l.OpenAssetFunc = func(path string) io.ReadCloser {
		return io.NopCloser(bytes.NewReader(resdata[path]))
	}

	// Before any resource is loadable, they should be bound.
	// You bind (register) resources by using typed registries.
	// For instance, RawRegistry is used to register Raw resources.
	rawResources := map[resource.RawID]resource.RawInfo{
		rawLevel1Data:    {Path: "maps/level1.json"},
		rawLevel2Data:    {Path: "maps/level2.json"},
		rawDefaultConfig: {Path: "config.txt"},
	}
	l.RawRegistry.Assign(rawResources)
	l.AudioRegistry.Assign(map[resource.AudioID]resource.AudioInfo{
		audioExample: {Path: "audio/example.wav", Volume: -0.2},
	})

	// It's possible to preload the resources.
	// Just load them once during the load screen or game initialization.
	// The second Load for the same resource would return a cached result.
	for id := range rawResources {
		l.LoadRaw(id)
	}

	// Raw resources are stored as bytes.
	var level1 map[string]any
	if err := json.Unmarshal(l.LoadRaw(rawLevel1Data).Data, &level1); err != nil {
		panic(err)
	}
	fmt.Println(level1["name"]) // Prints "level1"

	// Now let's try using audio resources.
	// Audio resources wrap the sound into an *audio.Player
	// that is ready to be used. Every AudioID has its own audio player.
	// Most of the time, if you want to play a sound, you need
	// to rewind the player before doing that.
	a := l.LoadWAV(audioExample)
	if err := a.Player.Rewind(); err != nil {
		panic(err)
	}
	a.Player.Play()
}

// This is our stub for the real data.
// In reality, you would probably use a combination of
// go:embed store and real filesystem.
var resdata = map[string][]byte{
	"maps/level1.json": []byte(`{"name": "level1"}`),
	"maps/level2.json": []byte(`{"name": "level2"}`),
	"config.txt":       []byte("some example config\n"),

	// Some minimal-size valid wav resource.
	"audio/example.wav": []byte(strings.Join([]string{
		"\x52\x49\x46\x46\x24\x00\x00\x00\x57\x41\x56\x45\x66\x6d\x74",
		"\x20\x10\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88\x58",
		"\x01\x00\x02\x00\x10\x00\x64\x61\x74\x61\x00\x00\x00\x00",
	}, "")),
}

Introduction

How to use this library properly?

You start by creating a loader with resource.NewLoader(). It should happen after you acquired an *audio.Context from Ebitengine. It's not recommended to make loader global, pass it as an explicit dependency everywhere you need to access the game resources.

Then you bind the resources using typed registries. For instance, binding image resources is done via loader.ImageRegistry field (use Set or Assign methods).

The loader acts as a cached resource access point. Resources are keyed by their ID. The ID is a simple integer. All metadata is associated with that ID too. It's recommended to make the core resources iota-style constants.

If you want to preload a resource, do a respective Load (e.g. LoadImage, LoadAudio) call either during a game launch or during the loading screen.

Most types in a package can be described by these categories:

  1. ID types that belong to specific kind of resource (e.g. ImageID, AudioID)
  2. Info objects that describe the resource (e.g. ImageInfo, AudioInfo)
  3. The actual resource objects (e.g. Image, Audio)

The info objects should be bound before the resource is accessed via Load method. It's possible to bind extra resources during the run-time.

Supported resource kinds:

  • Audio (*Audio.Player with decoded stream)
  • Font (font.Face with relevant properties like font size and line spacing)
  • Image (*ebiten.Image created from a texture)
  • Shader (a compiled *ebiten.Shader)
  • Raw (stored as []byte)

Documentation

Overview

Example
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strings"

	"github.com/hajimehoshi/ebiten/v2/audio"
	resource "github.com/quasilyte/ebitengine-resource"
)

// The essential game data resources could be enumerated using iota constants.
// Dynamic game content requires dynamically generated IDs during the run time.
const (
	rawNone resource.RawID = iota
	rawLevel1Data
	rawLevel2Data
	rawDefaultConfig
)

const (
	audioNone resource.AudioID = iota
	audioExample
)

func main() {
	audioContext := audio.NewContext(44100)

	l := resource.NewLoader(audioContext)

	l.OpenAssetFunc = func(path string) io.ReadCloser {
		return io.NopCloser(bytes.NewReader(resdata[path]))
	}

	// Before any resource is loadable, they should be bound.
	// You bind (register) resources by using typed registries.
	// For instance, RawRegistry is used to register Raw resources.
	rawResources := map[resource.RawID]resource.RawInfo{
		rawLevel1Data:    {Path: "maps/level1.json"},
		rawLevel2Data:    {Path: "maps/level2.json"},
		rawDefaultConfig: {Path: "config.txt"},
	}
	l.RawRegistry.Assign(rawResources)
	l.AudioRegistry.Assign(map[resource.AudioID]resource.AudioInfo{
		audioExample: {Path: "audio/example.wav", Volume: -0.2},
	})

	// It's possible to preload the resources.
	// Just load them once during the load screen or game initialization.
	// The second Load for the same resource would return a cached result.
	for id := range rawResources {
		l.LoadRaw(id)
	}

	// Raw resources are stored as bytes.
	var level1 map[string]any
	if err := json.Unmarshal(l.LoadRaw(rawLevel1Data).Data, &level1); err != nil {
		panic(err)
	}
	fmt.Println(level1["name"]) // Prints "level1"

	// Now let's try using audio resources.
	// Audio resources wrap the sound into an *audio.Player
	// that is ready to be used. Every AudioID has its own audio player.
	// Most of the time, if you want to play a sound, you need
	// to rewind the player before doing that.
	a := l.LoadWAV(audioExample)
	if err := a.Player.Rewind(); err != nil {
		panic(err)
	}
	a.Player.Play()

}

// This is our stub for the real data.
// In reality, you would probably use a combination of
// go:embed store and real filesystem.
var resdata = map[string][]byte{
	"maps/level1.json": []byte(`{"name": "level1"}`),
	"maps/level2.json": []byte(`{"name": "level2"}`),
	"config.txt":       []byte("some example config\n"),

	// Some minimal-size valid wav resource.
	"audio/example.wav": []byte(strings.Join([]string{
		"\x52\x49\x46\x46\x24\x00\x00\x00\x57\x41\x56\x45\x66\x6d\x74",
		"\x20\x10\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88\x58",
		"\x01\x00\x02\x00\x10\x00\x64\x61\x74\x61\x00\x00\x00\x00",
	}, "")),
}
Output:

level1

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Audio

type Audio struct {
	// An ID that was associated with this resource.
	ID AudioID

	// An initialized audio player that can be used to play the audio.
	// Note that you may need to rewind it before playing the sound.
	// The player wraps an original stream, so you can't access it directly.
	Player *audio.Player

	Group  uint
	Volume float64
}

type AudioID

type AudioID int

AudioID is a typed key for Audio resources. See also: AudioInfo.

type AudioInfo

type AudioInfo struct {
	// A path that will be used to read the resource data.
	Path string

	// Group is a sound group ID.
	// Groups are used to apply group-wide operations like
	// volume adjustments.
	// Conventionally, group 0 is "sound effect", 1 is "music", 2 is "voice".
	Group uint

	// Volume adjust how loud this sound will be.
	// The default value of 0 means "unadjusted".
	// Value greated than 0 increases the volume, negative values decrease it.
	// This setting accepts values in [-1, 1] range, where -1 mutes the sound
	// while 1 makes it as loud as possible.
	Volume float64
}

type Font

type Font struct {
	// An ID that was associated with this resource.
	ID FontID

	Face font.Face
}

type FontID

type FontID int

FontID is a typed key for Font resources. See also: FontInfo.

type FontInfo

type FontInfo struct {
	// A path that will be used to read the resource data.
	Path string

	Size int

	LineSpacing float64
}

type Image

type Image struct {
	// An ID that was associated with this resource.
	ID ImageID

	// An ebiten Image object initialized from the resource bytes.
	Data *ebiten.Image

	DefaultFrameWidth  float64
	DefaultFrameHeight float64
}

type ImageID

type ImageID int

ImageID is a typed key for Image resources. See also: ImageInfo.

type ImageInfo

type ImageInfo struct {
	// A path that will be used to read the resource data.
	Path string

	FrameWidth  float64
	FrameHeight float64
}

type Loader

type Loader struct {
	// OpenAssetFunc is used to open an asset resource identified by its path.
	// The returned resource will be closed after it will be loaded.
	OpenAssetFunc func(path string) io.ReadCloser

	ImageRegistry  registry[ImageID, ImageInfo]
	AudioRegistry  registry[AudioID, AudioInfo]
	FontRegistry   registry[FontID, FontInfo]
	ShaderRegistry registry[ShaderID, ShaderInfo]
	RawRegistry    registry[RawID, RawInfo]
	// contains filtered or unexported fields
}

Loader is used to load and cache game resources like images and audio files.

func NewLoader

func NewLoader(audioContext *audio.Context) *Loader

NewLoader creates a new resources loader that serves as both resource accessor and decoded resources cache.

An audio context is required to enable audio-related code to work. Audio resources are cached as *audio.Players and they can't be created without an initialized Ebitengine audio context.

func (*Loader) LoadAudio

func (l *Loader) LoadAudio(id AudioID) Audio

LoadAudio is a helper method that will use an appripriate Load method depending on the filename extension. For example, it will use LoadOGG for ".ogg" files.

func (*Loader) LoadFont

func (l *Loader) LoadFont(id FontID) Font

LoadFont returns a Font resource associated with a given key. Only a first call for this id will lead to resource decoding, all next calls return the cached result.

func (*Loader) LoadImage

func (l *Loader) LoadImage(id ImageID) Image

LoadImage returns an Image resource associated with a given key. Only a first call for this id will lead to resource decoding, all next calls return the cached result.

func (*Loader) LoadOGG

func (l *Loader) LoadOGG(id AudioID) Audio

LoadOGG returns a Audio resource associated with a given key. Only a first call for this id will lead to resource decoding, all next calls return the cached result.

func (*Loader) LoadRaw

func (l *Loader) LoadRaw(id RawID) Raw

LoadRaw returns a Raw resource associated with a given key. Only a first call for this id will lead to resource decoding, all next calls return the cached result.

func (*Loader) LoadShader

func (l *Loader) LoadShader(id ShaderID) Shader

LoadShader returns a Shader resource associated with a given key. Only a first call for this id will lead to resource decoding, all next calls return the cached result.

func (*Loader) LoadWAV

func (l *Loader) LoadWAV(id AudioID) Audio

LoadWAV returns a Audio resource associated with a given key. Only a first call for this id will lead to resource decoding, all next calls return the cached result.

type Raw

type Raw struct {
	// An ID that was associated with this resource.
	ID RawID

	// Data is an uninterpreted resource contents.
	Data []byte
}

type RawID

type RawID int

RawID is a typed key for Raw resources. See also: RawInfo.

type RawInfo

type RawInfo struct {
	// A path that will be used to read the resource data.
	Path string
}

type Shader

type Shader struct {
	// An ID that was associated with this resource.
	ID ShaderID

	// A compiled shader.
	Data *ebiten.Shader
}

type ShaderID

type ShaderID int

ShaderID is a typed key for Shader resources. See also: ShaderInfo.

type ShaderInfo

type ShaderInfo struct {
	// A path that will be used to read the resource data.
	Path string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL