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/
|
||||
bin/
|
||||
dist/
|
||||
rtp/
|
||||
dev-assets/guidebook/venv
|
||||
dev-assets/guidebook/compiled/pages
|
||||
dev-assets/guidebook/site/
|
||||
wasm/assets/
|
||||
*.wasm
|
||||
*.doodad
|
||||
|
@ -15,3 +16,4 @@ docker/fedora
|
|||
screenshot-*.png
|
||||
map-*.json
|
||||
pkg/wallpaper/*.png
|
||||
|
||||
|
|
|
@ -84,10 +84,10 @@ Dependencies are Go, SDL2 and SDL2_ttf:
|
|||
|
||||
```bash
|
||||
# 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
|
||||
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
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/bindata"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/sound"
|
||||
"git.kirsle.net/go/render/sdl"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
|
@ -95,6 +96,9 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
// Preload all sound effects.
|
||||
sound.PreloadAll()
|
||||
|
||||
game := doodle.New(c.Bool("debug"), engine)
|
||||
game.SetupEngine()
|
||||
if c.Bool("guitest") {
|
||||
|
|
|
@ -2,6 +2,7 @@ function main() {
|
|||
console.log("%s initialized!", Self.Title);
|
||||
|
||||
var timer = 0;
|
||||
var pressed = false;
|
||||
|
||||
Events.OnCollide(function(e) {
|
||||
if (!e.Settled) {
|
||||
|
@ -13,7 +14,12 @@ function main() {
|
|||
return;
|
||||
}
|
||||
|
||||
Message.Publish("power", true);
|
||||
if (!pressed) {
|
||||
Sound.Play("button-down.wav")
|
||||
Message.Publish("power", true);
|
||||
pressed = true;
|
||||
}
|
||||
|
||||
|
||||
if (timer > 0) {
|
||||
clearTimeout(timer);
|
||||
|
@ -21,9 +27,11 @@ function main() {
|
|||
|
||||
Self.ShowLayer(1);
|
||||
timer = setTimeout(function() {
|
||||
Sound.Play("button-up.wav")
|
||||
Self.ShowLayer(0);
|
||||
Message.Publish("power", false);
|
||||
timer = 0;
|
||||
pressed = false;
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ function main() {
|
|||
if (powered && pressed) {
|
||||
Self.ShowLayer(0);
|
||||
pressed = false;
|
||||
Sound.Play("button-up.wav")
|
||||
Message.Publish("power", false);
|
||||
}
|
||||
})
|
||||
|
@ -26,6 +27,7 @@ function main() {
|
|||
return;
|
||||
}
|
||||
|
||||
Sound.Play("button-down.wav")
|
||||
Self.ShowLayer(1);
|
||||
pressed = true;
|
||||
Message.Publish("power", true);
|
||||
|
|
|
@ -43,6 +43,7 @@ function main() {
|
|||
Self.PlayAnimation("shake", function() {
|
||||
state = stateFalling;
|
||||
Self.PlayAnimation("fall", function() {
|
||||
Sound.Play("crumbly-break.wav")
|
||||
state = stateFallen;
|
||||
Self.ShowLayerNamed("fallen");
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ function main() {
|
|||
if (unlocked) {
|
||||
Self.ShowLayer(enterSide < 0 ? layer.right : layer.left);
|
||||
opened = true;
|
||||
Sound.Play("door-open.wav")
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -45,11 +46,13 @@ function main() {
|
|||
if (e.Settled) {
|
||||
unlocked = true;
|
||||
Self.ShowLayer(enterSide < 0 ? layer.right : layer.left);
|
||||
Sound.Play("unlock.wav")
|
||||
}
|
||||
}
|
||||
});
|
||||
Events.OnLeave(function(e) {
|
||||
Self.ShowLayer(layer.closed);
|
||||
// Sound.Play("door-close.wav")
|
||||
|
||||
// Reset collision state.
|
||||
opened = false;
|
||||
|
|
|
@ -17,12 +17,14 @@ function main() {
|
|||
}
|
||||
|
||||
animating = true;
|
||||
Sound.Play("electric-door.wav")
|
||||
Self.PlayAnimation("open", function() {
|
||||
opened = true;
|
||||
animating = false;
|
||||
});
|
||||
} else {
|
||||
animating = true;
|
||||
Sound.Play("electric-door.wav")
|
||||
Self.PlayAnimation("close", function() {
|
||||
opened = false;
|
||||
animating = false;
|
||||
|
|
|
@ -3,6 +3,7 @@ function main() {
|
|||
|
||||
Events.OnCollide(function(e) {
|
||||
if (e.Settled) {
|
||||
Sound.Play("item-get.wav")
|
||||
e.Actor.AddItem(Self.Filename, 0);
|
||||
Self.Destroy();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ function main() {
|
|||
}
|
||||
|
||||
if (collide === false) {
|
||||
Sound.Play("button-down.wav")
|
||||
state = !state;
|
||||
Message.Publish("power", state);
|
||||
showState(state);
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/apps/doodle/pkg/sound"
|
||||
"git.kirsle.net/go/render"
|
||||
)
|
||||
|
||||
|
@ -25,6 +26,11 @@ func NewJSProxy(vm *VM) JSProxy {
|
|||
"error": log.Error,
|
||||
},
|
||||
|
||||
// Audio API.
|
||||
"Sound": map[string]interface{}{
|
||||
"Play": sound.PlaySound,
|
||||
},
|
||||
|
||||
// Type constructors.
|
||||
"RGBA": render.RGBA,
|
||||
"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