Add Initial Sound Effects
Adds support for sound effects in Doodle and configures some for various doodads to start out with: * Buttons and Switches: "Clicked down" and "clicked up" sounds. * Colored Doors: an "unlocked" sound and a "door opened" sound. * Electric Door: sci-fi sounds when opening and closing. * Keys: sound effect for collecting keys. JavaScript API for Doodads adds a global function `Sound.Play(filename)` to play sounds. All sounds in the `rtp/sfx/` folder are pre-loaded on startup for efficient use in the app. Otherwise sounds are lazy-loaded on first playback.
This commit is contained in:
parent
38614ee280
commit
27896a9253
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,8 +3,9 @@ fonts/
|
||||||
maps/
|
maps/
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
|
rtp/
|
||||||
dev-assets/guidebook/venv
|
dev-assets/guidebook/venv
|
||||||
dev-assets/guidebook/compiled/pages
|
dev-assets/guidebook/site/
|
||||||
wasm/assets/
|
wasm/assets/
|
||||||
*.wasm
|
*.wasm
|
||||||
*.doodad
|
*.doodad
|
||||||
|
@ -15,3 +16,4 @@ docker/fedora
|
||||||
screenshot-*.png
|
screenshot-*.png
|
||||||
map-*.json
|
map-*.json
|
||||||
pkg/wallpaper/*.png
|
pkg/wallpaper/*.png
|
||||||
|
|
||||||
|
|
|
@ -84,10 +84,10 @@ Dependencies are Go, SDL2 and SDL2_ttf:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Fedora
|
# Fedora
|
||||||
sudo dnf -y install golang SDL2-devel SDL2_ttf-devel
|
sudo dnf -y install golang SDL2-devel SDL2_ttf-devel SDL2_mixer-devel
|
||||||
|
|
||||||
# Ubuntu and Debian
|
# Ubuntu and Debian
|
||||||
sudo apt -y install golang libsdl2-dev libsdl2-ttf-dev
|
sudo apt -y install golang libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mac OS
|
## Mac OS
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
"git.kirsle.net/apps/doodle/pkg/bindata"
|
||||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/sound"
|
||||||
"git.kirsle.net/go/render/sdl"
|
"git.kirsle.net/go/render/sdl"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
|
@ -95,6 +96,9 @@ func main() {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Preload all sound effects.
|
||||||
|
sound.PreloadAll()
|
||||||
|
|
||||||
game := doodle.New(c.Bool("debug"), engine)
|
game := doodle.New(c.Bool("debug"), engine)
|
||||||
game.SetupEngine()
|
game.SetupEngine()
|
||||||
if c.Bool("guitest") {
|
if c.Bool("guitest") {
|
||||||
|
|
|
@ -2,6 +2,7 @@ function main() {
|
||||||
console.log("%s initialized!", Self.Title);
|
console.log("%s initialized!", Self.Title);
|
||||||
|
|
||||||
var timer = 0;
|
var timer = 0;
|
||||||
|
var pressed = false;
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
if (!e.Settled) {
|
if (!e.Settled) {
|
||||||
|
@ -13,7 +14,12 @@ function main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.Publish("power", true);
|
if (!pressed) {
|
||||||
|
Sound.Play("button-down.wav")
|
||||||
|
Message.Publish("power", true);
|
||||||
|
pressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (timer > 0) {
|
if (timer > 0) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
|
@ -21,9 +27,11 @@ function main() {
|
||||||
|
|
||||||
Self.ShowLayer(1);
|
Self.ShowLayer(1);
|
||||||
timer = setTimeout(function() {
|
timer = setTimeout(function() {
|
||||||
|
Sound.Play("button-up.wav")
|
||||||
Self.ShowLayer(0);
|
Self.ShowLayer(0);
|
||||||
Message.Publish("power", false);
|
Message.Publish("power", false);
|
||||||
timer = 0;
|
timer = 0;
|
||||||
|
pressed = false;
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ function main() {
|
||||||
if (powered && pressed) {
|
if (powered && pressed) {
|
||||||
Self.ShowLayer(0);
|
Self.ShowLayer(0);
|
||||||
pressed = false;
|
pressed = false;
|
||||||
|
Sound.Play("button-up.wav")
|
||||||
Message.Publish("power", false);
|
Message.Publish("power", false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -26,6 +27,7 @@ function main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Sound.Play("button-down.wav")
|
||||||
Self.ShowLayer(1);
|
Self.ShowLayer(1);
|
||||||
pressed = true;
|
pressed = true;
|
||||||
Message.Publish("power", true);
|
Message.Publish("power", true);
|
||||||
|
|
|
@ -43,6 +43,7 @@ function main() {
|
||||||
Self.PlayAnimation("shake", function() {
|
Self.PlayAnimation("shake", function() {
|
||||||
state = stateFalling;
|
state = stateFalling;
|
||||||
Self.PlayAnimation("fall", function() {
|
Self.PlayAnimation("fall", function() {
|
||||||
|
Sound.Play("crumbly-break.wav")
|
||||||
state = stateFallen;
|
state = stateFallen;
|
||||||
Self.ShowLayerNamed("fallen");
|
Self.ShowLayerNamed("fallen");
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ function main() {
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
Self.ShowLayer(enterSide < 0 ? layer.right : layer.left);
|
Self.ShowLayer(enterSide < 0 ? layer.right : layer.left);
|
||||||
opened = true;
|
opened = true;
|
||||||
|
Sound.Play("door-open.wav")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +46,13 @@ function main() {
|
||||||
if (e.Settled) {
|
if (e.Settled) {
|
||||||
unlocked = true;
|
unlocked = true;
|
||||||
Self.ShowLayer(enterSide < 0 ? layer.right : layer.left);
|
Self.ShowLayer(enterSide < 0 ? layer.right : layer.left);
|
||||||
|
Sound.Play("unlock.wav")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Events.OnLeave(function(e) {
|
Events.OnLeave(function(e) {
|
||||||
Self.ShowLayer(layer.closed);
|
Self.ShowLayer(layer.closed);
|
||||||
|
// Sound.Play("door-close.wav")
|
||||||
|
|
||||||
// Reset collision state.
|
// Reset collision state.
|
||||||
opened = false;
|
opened = false;
|
||||||
|
|
|
@ -17,12 +17,14 @@ function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
animating = true;
|
animating = true;
|
||||||
|
Sound.Play("electric-door.wav")
|
||||||
Self.PlayAnimation("open", function() {
|
Self.PlayAnimation("open", function() {
|
||||||
opened = true;
|
opened = true;
|
||||||
animating = false;
|
animating = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
animating = true;
|
animating = true;
|
||||||
|
Sound.Play("electric-door.wav")
|
||||||
Self.PlayAnimation("close", function() {
|
Self.PlayAnimation("close", function() {
|
||||||
opened = false;
|
opened = false;
|
||||||
animating = false;
|
animating = false;
|
||||||
|
|
|
@ -3,6 +3,7 @@ function main() {
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
if (e.Settled) {
|
if (e.Settled) {
|
||||||
|
Sound.Play("item-get.wav")
|
||||||
e.Actor.AddItem(Self.Filename, 0);
|
e.Actor.AddItem(Self.Filename, 0);
|
||||||
Self.Destroy();
|
Self.Destroy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collide === false) {
|
if (collide === false) {
|
||||||
|
Sound.Play("button-down.wav")
|
||||||
state = !state;
|
state = !state;
|
||||||
Message.Publish("power", state);
|
Message.Publish("power", state);
|
||||||
showState(state);
|
showState(state);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
"git.kirsle.net/apps/doodle/pkg/physics"
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/sound"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,6 +26,11 @@ func NewJSProxy(vm *VM) JSProxy {
|
||||||
"error": log.Error,
|
"error": log.Error,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Audio API.
|
||||||
|
"Sound": map[string]interface{}{
|
||||||
|
"Play": sound.PlaySound,
|
||||||
|
},
|
||||||
|
|
||||||
// Type constructors.
|
// Type constructors.
|
||||||
"RGBA": render.RGBA,
|
"RGBA": render.RGBA,
|
||||||
"Point": render.NewPoint,
|
"Point": render.NewPoint,
|
||||||
|
|
25
pkg/sound/preload.go
Normal file
25
pkg/sound/preload.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package sound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PreloadAll looks in the SoundRoot and MusicRoot folders and preloads all
|
||||||
|
// supported files into the caches.
|
||||||
|
func PreloadAll() {
|
||||||
|
if engine == nil || !Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload sound effects.
|
||||||
|
if files, err := ioutil.ReadDir(SoundRoot); err == nil {
|
||||||
|
for _, file := range files {
|
||||||
|
if filepath.Ext(file.Name()) != ".wav" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadSound(file.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
pkg/sound/sound.go
Normal file
114
pkg/sound/sound.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// Package sound provides audio functions for Doodle.
|
||||||
|
package sound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/go/audio/sdl"
|
||||||
|
"github.com/veandco/go-sdl2/mix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Globals.
|
||||||
|
var (
|
||||||
|
// If enabled is false, all sound functions are no-ops.
|
||||||
|
Enabled bool
|
||||||
|
|
||||||
|
// Root folder on disk where sound and music files should live.
|
||||||
|
SoundRoot = filepath.Join("rtp", "sfx")
|
||||||
|
MusicRoot = filepath.Join("rtp", "music")
|
||||||
|
|
||||||
|
// Cache of loaded music and sound effects.
|
||||||
|
music = map[string]*sdl.Track{}
|
||||||
|
sounds = map[string]*sdl.Track{}
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
engine *sdl.Engine
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize SDL2 Audio at startup.
|
||||||
|
func init() {
|
||||||
|
eng, err := sdl.New(mix.INIT_MP3 | mix.INIT_OGG)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("sound.init(): error initializing SDL2 audio: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eng.Setup()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("sound.init(): error setting up SDL2 audio: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
engine = eng
|
||||||
|
Enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadMusic loads filename from the MusicRoot into the global music cache.
|
||||||
|
// If the music is already loaded, does nothing.
|
||||||
|
func LoadMusic(filename string) *sdl.Track {
|
||||||
|
if engine == nil || !Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the music is already loaded.
|
||||||
|
mu.RLock()
|
||||||
|
mus, ok := music[filename]
|
||||||
|
mu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return mus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the music in.
|
||||||
|
track, err := engine.LoadMusic(filepath.Join(MusicRoot, filename))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("sound.LoadMusic: failed to load file %s: %s", filename, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
music[filename] = &track
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return &track
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSound loads filename from the SoundRoot into the global SFX cache.
|
||||||
|
// If the sound is already loaded, does nothing.
|
||||||
|
func LoadSound(filename string) *sdl.Track {
|
||||||
|
if engine == nil || !Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the music is already loaded.
|
||||||
|
mu.RLock()
|
||||||
|
sfx, ok := sounds[filename]
|
||||||
|
mu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return sfx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the sound in.
|
||||||
|
log.Info("Loading sound: %s", filename)
|
||||||
|
track, err := engine.LoadSound(filepath.Join(SoundRoot, filename))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("sound.LoadSound: failed to load file %s: %s", filename, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
sounds[filename] = &track
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return &track
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlaySound plays the named sound.
|
||||||
|
func PlaySound(filename string) {
|
||||||
|
log.Debug("Play sound: %s", filename)
|
||||||
|
sound := LoadSound(filename)
|
||||||
|
if sound != nil {
|
||||||
|
sound.Play(1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user