Add sound effects

This commit is contained in:
Trevor Slocum 2023-10-28 18:45:55 -07:00
parent cb8feff508
commit ba4aa7af86
20 changed files with 241 additions and 15 deletions

View file

@ -1,3 +1,6 @@
1.0.5:
- Add sound effects
1.0.4:
- Optimize user interface for mobile devices

BIN
game/asset/audio/dice1.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/dice2.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/dice3.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/dice4.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/die1.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/die2.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/die3.ogg Normal file

Binary file not shown.

Binary file not shown.

BIN
game/asset/audio/move1.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/move2.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/move3.ogg Normal file

Binary file not shown.

BIN
game/asset/audio/say.ogg Normal file

Binary file not shown.

View file

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -826,7 +826,7 @@ func (b *board) setRect(x, y, w, h int) {
b.innerW = int(float64(b.w) - (b.horizontalBorderSize * 2))
b.innerH = int(float64(b.h) - (b.verticalBorderSize * 2))
loadAssets(int(b.spaceWidth))
loadImageAssets(int(b.spaceWidth))
for i := 0; i < b.Sprites.num; i++ {
s := b.Sprites.sprites[i]

View file

@ -7,7 +7,9 @@ import (
"image"
"image/color"
_ "image/png"
"io"
"log"
"math/rand"
"os"
"path"
"regexp"
@ -20,6 +22,9 @@ import (
"code.rocketnine.space/tslocum/kibodo"
"code.rocketnine.space/tslocum/messeji"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/audio"
"github.com/hajimehoshi/ebiten/v2/audio/vorbis"
"github.com/hajimehoshi/ebiten/v2/audio/wav"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"github.com/hajimehoshi/ebiten/v2/inpututil"
@ -35,8 +40,8 @@ const MaxDebug = 1
var onlyNumbers = regexp.MustCompile(`[0-9]+`)
//go:embed assets
var assetsFS embed.FS
//go:embed asset
var assetFS embed.FS
var debugExtra []byte
@ -119,6 +124,18 @@ var (
listGamesFrame *etk.Frame
)
const sampleRate = 44100
var (
audioContext *audio.Context
SoundDie1, SoundDie2, SoundDie3 []byte
SoundDice1, SoundDice2, SoundDice3, SoundDice4 []byte
SoundMove1, SoundMove2, SoundMove3 []byte
SoundJoinLeave []byte
SoundSay []byte
)
func l(s string) {
m := time.Now().Format("15:04") + " " + s
if statusLogged {
@ -182,10 +199,9 @@ func init() {
inputBuffer.Field.SetBackgroundColor(bufferBackgroundColor)
inputBuffer.Field.SetSuffix("")
}
func loadAssets(width int) {
imgCheckerLight = loadAsset("assets/checker_white.png", width)
imgCheckerDark = loadAsset("assets/checker_white.png", width)
func loadImageAssets(width int) {
imgCheckerLight = loadAsset("asset/image/checker_white.png", width)
imgCheckerDark = loadAsset("asset/image/checker_white.png", width)
//imgCheckerDark = loadAsset("assets/checker_black.png", width)
resizeDice := func(img image.Image) *ebiten.Image {
@ -198,7 +214,7 @@ func loadAssets(width int) {
}
const size = 184
imgDice = ebiten.NewImageFromImage(loadImage("assets/dice.png"))
imgDice = ebiten.NewImageFromImage(loadImage("asset/image/dice.png"))
imgDice1 = resizeDice(imgDice.SubImage(image.Rect(0, 0, size*1, size*1)))
imgDice2 = resizeDice(imgDice.SubImage(image.Rect(size*1, 0, size*2, size*1)))
imgDice3 = resizeDice(imgDice.SubImage(image.Rect(size*2, 0, size*3, size*1)))
@ -206,9 +222,56 @@ func loadAssets(width int) {
imgDice5 = resizeDice(imgDice.SubImage(image.Rect(size*1, size*1, size*2, size*2)))
imgDice6 = resizeDice(imgDice.SubImage(image.Rect(size*2, size*1, size*3, size*2)))
}
func loadAudioAssets() {
audioContext = audio.NewContext(sampleRate)
p := "asset/audio/"
SoundDie1 = LoadBytes(p + "die1.ogg")
SoundDie2 = LoadBytes(p + "die2.ogg")
SoundDie3 = LoadBytes(p + "die3.ogg")
SoundDice1 = LoadBytes(p + "dice1.ogg")
SoundDice2 = LoadBytes(p + "dice2.ogg")
SoundDice3 = LoadBytes(p + "dice3.ogg")
SoundDice4 = LoadBytes(p + "dice4.ogg")
SoundMove1 = LoadBytes(p + "move1.ogg")
SoundMove2 = LoadBytes(p + "move2.ogg")
SoundMove3 = LoadBytes(p + "move3.ogg")
SoundJoinLeave = LoadBytes(p + "joinleave.ogg")
SoundSay = LoadBytes(p + "say.ogg")
dieSounds = [][]byte{
SoundDie1,
SoundDie2,
SoundDie3,
}
randomizeByteSlice(dieSounds)
diceSounds = [][]byte{
SoundDice1,
SoundDice2,
SoundDice3,
SoundDice4,
}
randomizeByteSlice(diceSounds)
moveSounds = [][]byte{
SoundMove1,
SoundMove2,
SoundMove3,
}
randomizeByteSlice(moveSounds)
}
func loadAssets(width int) {
loadImageAssets(width)
loadAudioAssets()
}
func loadImage(assetPath string) image.Image {
f, err := assetsFS.Open(assetPath)
f, err := assetFS.Open(assetPath)
if err != nil {
panic(err)
}
@ -231,6 +294,65 @@ func loadAsset(assetPath string, width int) *ebiten.Image {
return ebiten.NewImageFromImage(img)
}
func LoadBytes(p string) []byte {
b, err := assetFS.ReadFile(p)
if err != nil {
panic(err)
}
return b
}
func LoadWAV(context *audio.Context, p string) *audio.Player {
f, err := assetFS.Open(p)
if err != nil {
panic(err)
}
defer f.Close()
stream, err := wav.DecodeWithSampleRate(sampleRate, f)
if err != nil {
panic(err)
}
player, err := audio.NewPlayer(audioContext, io.NopCloser(stream))
if err != nil {
panic(err)
}
// Workaround to prevent delays when playing for the first time.
player.SetVolume(0)
player.Play()
player.Pause()
player.Rewind()
player.SetVolume(1)
return player
}
type oggStream struct {
*vorbis.Stream
}
func (s *oggStream) Close() error {
return nil
}
func LoadOGG(context *audio.Context, p string) *audio.Player {
b := LoadBytes(p)
stream, err := vorbis.DecodeWithSampleRate(sampleRate, bytes.NewReader(b))
if err != nil {
panic(err)
}
player, err := audio.NewPlayer(audioContext, &oggStream{Stream: stream})
if err != nil {
panic(err)
}
return player
}
func initializeFonts() {
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
if err != nil {
@ -393,6 +515,8 @@ type Game struct {
lobby *lobby
volume float64 // Volume range is 0-1.
runeBuffer []rune
userInput string
@ -434,6 +558,7 @@ func NewGame() *Game {
TouchInput: AutoEnableTouchInput,
debugImg: ebiten.NewImage(200, 200),
volume: 1,
}
game = g
@ -671,6 +796,7 @@ func (g *Game) handleEvents() {
l(fmt.Sprintf("*** %s", ev.Message))
case *bgammon.EventSay:
l(fmt.Sprintf("<%s> %s", ev.Player, ev.Message))
playSoundEffect(effectSay)
case *bgammon.EventList:
g.lobby.setGameList(ev.Games)
if !viewBoard {
@ -692,6 +818,7 @@ func (g *Game) handleEvents() {
gameLogged = false
} else {
lg(fmt.Sprintf("%s joined the match.", ev.Player))
playSoundEffect(effectJoinLeave)
}
case *bgammon.EventFailedJoin:
l(fmt.Sprintf("*** Failed to join match: %s", ev.Reason))
@ -709,10 +836,9 @@ func (g *Game) handleEvents() {
g.Board.Unlock()
if ev.Player == g.Client.Username {
setViewBoard(false)
}
if ev.Player != g.Client.Username {
} else {
lg(fmt.Sprintf("%s left the match.", ev.Player))
playSoundEffect(effectJoinLeave)
}
case *bgammon.EventBoard:
g.Board.Lock()
@ -732,8 +858,10 @@ func (g *Game) handleEvents() {
} else {
diceFormatted = fmt.Sprintf("%d", g.Board.gameState.Roll2)
}
playSoundEffect(effectDie)
} else {
diceFormatted = fmt.Sprintf("%d-%d", g.Board.gameState.Roll1, g.Board.gameState.Roll2)
playSoundEffect(effectDice)
}
g.Board.processState()
g.Board.Unlock()
@ -743,6 +871,7 @@ func (g *Game) handleEvents() {
l(fmt.Sprintf("*** Failed to roll: %s", ev.Reason))
case *bgammon.EventMoved:
lg(fmt.Sprintf("%s moved %s.", ev.Player, bgammon.FormatMoves(ev.Moves)))
playSoundEffect(effectMove)
if ev.Player == g.Client.Username {
continue
}
@ -1281,3 +1410,88 @@ func (g *Game) toggleProfiling() error {
func (g *Game) Exit() {
os.Exit(0)
}
type SoundEffect int
const (
effectJoinLeave SoundEffect = iota
effectSay
effectDie
effectDice
effectMove
)
var (
dieSounds [][]byte
dieSoundPlays int
diceSounds [][]byte
diceSoundPlays int
moveSounds [][]byte
moveSoundPlays int
)
func playSoundEffect(effect SoundEffect) {
if game.volume == 0 {
return
}
var b []byte
switch effect {
case effectSay:
b = SoundSay
case effectJoinLeave:
b = SoundJoinLeave
case effectDie:
b = dieSounds[dieSoundPlays]
dieSoundPlays++
if dieSoundPlays == len(dieSounds)-1 {
randomizeByteSlice(dieSounds)
dieSoundPlays = 0
}
case effectDice:
b = diceSounds[diceSoundPlays]
diceSoundPlays++
if diceSoundPlays == len(diceSounds)-1 {
randomizeByteSlice(diceSounds)
diceSoundPlays = 0
}
case effectMove:
b = moveSounds[moveSoundPlays]
moveSoundPlays++
if moveSoundPlays == len(moveSounds)-1 {
randomizeByteSlice(moveSounds)
moveSoundPlays = 0
}
default:
log.Panicf("unknown sound effect: %d", effect)
return
}
stream, err := vorbis.DecodeWithoutResampling(bytes.NewReader(b))
if err != nil {
panic(err)
}
player, err := audioContext.NewPlayer(&oggStream{stream})
if err != nil {
panic(err)
}
if effect == effectSay {
player.SetVolume(game.volume / 2)
} else {
player.SetVolume(game.volume)
}
player.Play()
}
func randomizeByteSlice(b [][]byte) {
for i := range b {
j := rand.Intn(i + 1)
b[i], b[j] = b[j], b[i]
}
}

5
go.mod
View file

@ -4,7 +4,7 @@ go 1.17
require (
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231027191341-991fd6d481ca
code.rocket9labs.com/tslocum/etk v0.0.0-20231028192430-e54b05cdd05a
code.rocket9labs.com/tslocum/etk v0.0.0-20231028200807-4833fa2b2761
code.rocketnine.space/tslocum/kibodo v1.0.2-0.20231024233002-77bb43ba6fe8
code.rocketnine.space/tslocum/messeji v1.0.5-0.20231028192343-ebfed687fb71
github.com/hajimehoshi/ebiten/v2 v2.6.2
@ -15,9 +15,12 @@ require (
)
require (
github.com/ebitengine/oto/v3 v3.1.0 // indirect
github.com/ebitengine/purego v0.5.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/jezek/xgb v1.1.0 // indirect
github.com/jfreymuth/oggvorbis v1.0.5 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
golang.org/x/exp/shiny v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe // indirect
golang.org/x/sync v0.4.0 // indirect

10
go.sum
View file

@ -1,11 +1,13 @@
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231027191341-991fd6d481ca h1:JClRU4ONLpMiyW/BoI3O8Sb9yiYcySTZjcUfQ48GWvw=
code.rocket9labs.com/tslocum/bgammon v0.0.0-20231027191341-991fd6d481ca/go.mod h1:U8qo60VHGzKFUHLZZJcvT0yDzwWybJBabsCw3Lyqx4s=
code.rocket9labs.com/tslocum/etk v0.0.0-20231028192430-e54b05cdd05a h1:Gs/Xku+fPLNNlaYEAubFNI0j5QtlYEcQjxo47phkK0E=
code.rocket9labs.com/tslocum/etk v0.0.0-20231028192430-e54b05cdd05a/go.mod h1:C+pdWMPmOOPGk6adunX6PxE40F8CSg0h8LLwWIO2qeM=
code.rocket9labs.com/tslocum/etk v0.0.0-20231028200807-4833fa2b2761 h1:vSfheD/jWR0QkdlSME6mz2cJEET0d0DHiRMMASxL2N0=
code.rocket9labs.com/tslocum/etk v0.0.0-20231028200807-4833fa2b2761/go.mod h1:C+pdWMPmOOPGk6adunX6PxE40F8CSg0h8LLwWIO2qeM=
code.rocketnine.space/tslocum/kibodo v1.0.2-0.20231024233002-77bb43ba6fe8 h1:i1NzTMQA1DAAUIpFh2bnHVnH5j9hUkG6F3tqzsdD16Y=
code.rocketnine.space/tslocum/kibodo v1.0.2-0.20231024233002-77bb43ba6fe8/go.mod h1:C7M1NUuVi0Mv+/xraUurjl4XSLRIILmWDWCBBOY4UeM=
code.rocketnine.space/tslocum/messeji v1.0.5-0.20231028192343-ebfed687fb71 h1:mcvrQKHfec2Fb4dBXb879v0lBeHFzEBqs15ETnPv4pw=
code.rocketnine.space/tslocum/messeji v1.0.5-0.20231028192343-ebfed687fb71/go.mod h1:xszLyTZtpyjCVaGmznizLSAlnvraPOSoanlzUBeqGco=
github.com/ebitengine/oto/v3 v3.1.0 h1:9tChG6rizyeR2w3vsygTTTVVJ9QMMyu00m2yBOCch6U=
github.com/ebitengine/oto/v3 v3.1.0/go.mod h1:IK1QTnlfZK2GIB6ziyECm433hAdTaPpOsGMLhEyEGTg=
github.com/ebitengine/purego v0.5.0 h1:JrMGKfRIAM4/QVKaesIIT7m/UVjTj5GYhRSQYwfVdpo=
github.com/ebitengine/purego v0.5.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@ -15,6 +17,10 @@ github.com/hajimehoshi/ebiten/v2 v2.6.2 h1:tVa3ZJbp4Uz/VSjmpgtQIOvwd7aQH290XehHB
github.com/hajimehoshi/ebiten/v2 v2.6.2/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/jfreymuth/oggvorbis v1.0.5 h1:u+Ck+R0eLSRhgq8WTmffYnrVtSztJcYrl588DM4e3kQ=
github.com/jfreymuth/oggvorbis v1.0.5/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
github.com/llgcode/draw2d v0.0.0-20231022063514-1acb54133d2a h1:aP1ySrs3EYBaKOF+1hEUbIMNjT8FZlGGbB73cRAukZw=
github.com/llgcode/draw2d v0.0.0-20231022063514-1acb54133d2a/go.mod h1:zNlGqkQNLxAN7D2uihSJsrEzrkWrSIK5kmSZU/dN5NY=
github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb h1:61ndUreYSlWFeCY44JxDDkngVoI7/1MVhEl98Nm0KOk=