diff --git a/.gitignore b/.gitignore index ded7d35..1569417 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea carotidartillery +*.wasm diff --git a/assets/audio/hurt.wav b/assets/audio/hurt.wav new file mode 100644 index 0000000..ccfd438 Binary files /dev/null and b/assets/audio/hurt.wav differ diff --git a/audio.go b/audio.go new file mode 100644 index 0000000..3ad9ef6 --- /dev/null +++ b/audio.go @@ -0,0 +1,46 @@ +package main + +import ( + "github.com/hajimehoshi/ebiten/v2/audio" + "github.com/hajimehoshi/ebiten/v2/audio/mp3" + "github.com/hajimehoshi/ebiten/v2/audio/wav" +) + +const ( + SoundGunshot = iota + SoundVampireDie1 + SoundVampireDie2 + SoundPlayerHurt + SoundPlayerDie + SoundGib +) + +func loadMP3(context *audio.Context, p string) (*audio.Player, error) { + f, err := assetsFS.Open(p) + if err != nil { + return nil, err + } + defer f.Close() + + stream, err := mp3.DecodeWithSampleRate(sampleRate, f) + if err != nil { + return nil, err + } + + return context.NewPlayer(stream) +} + +func loadWav(context *audio.Context, p string) (*audio.Player, error) { + f, err := assetsFS.Open(p) + if err != nil { + return nil, err + } + defer f.Close() + + stream, err := wav.DecodeWithSampleRate(sampleRate, f) + if err != nil { + return nil, err + } + + return context.NewPlayer(stream) +} diff --git a/game.go b/game.go index 601ed29..539de56 100644 --- a/game.go +++ b/game.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "fmt" "image" "image/color" @@ -13,8 +12,6 @@ import ( "runtime/pprof" "time" - "github.com/hajimehoshi/ebiten/v2/audio/mp3" - "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/audio" "github.com/hajimehoshi/ebiten/v2/ebitenutil" @@ -43,12 +40,6 @@ type game struct { w, h int currentLevel *Level - soundGunshot []byte - soundVampireDie1 []byte - soundVampireDie2 []byte - soundGib []byte - soundDie []byte - player *gamePlayer gameOverTime time.Time @@ -70,6 +61,8 @@ type game struct { op *ebiten.DrawImageOptions audioContext *audio.Context + nextSound map[int]int + soundBuffer map[int][]*audio.Player godMode bool debugMode bool @@ -98,6 +91,9 @@ func NewGame() (*game, error) { mousePanY: math.MinInt32, player: p, op: &ebiten.DrawImageOptions{}, + + soundBuffer: make(map[int][]*audio.Player), + nextSound: make(map[int]int), } g.audioContext = audio.NewContext(sampleRate) @@ -133,39 +129,36 @@ func NewGame() (*game, error) { flashImage = ebiten.NewImageFromImage(img) - loadSound := func(p string) ([]byte, error) { - b, err := assetsFS.ReadFile(p) + for i := 0; i < 4; i++ { + stream, err := loadMP3(g.audioContext, "assets/audio/gunshot.mp3") if err != nil { return nil, err } - defer f.Close() + g.soundBuffer[SoundGunshot] = append(g.soundBuffer[SoundGunshot], stream) - return b, nil - } + stream, err = loadMP3(g.audioContext, "assets/audio/vampiredie1.mp3") + if err != nil { + return nil, err + } + g.soundBuffer[SoundVampireDie1] = append(g.soundBuffer[SoundVampireDie1], stream) - g.soundGunshot, err = loadSound("assets/audio/gunshot.mp3") - if err != nil { - return nil, err - } + stream, err = loadMP3(g.audioContext, "assets/audio/vampiredie2.mp3") + if err != nil { + return nil, err + } + g.soundBuffer[SoundVampireDie2] = append(g.soundBuffer[SoundVampireDie2], stream) - g.soundGib, err = loadSound("assets/audio/gib.mp3") - if err != nil { - return nil, err - } + stream, err = loadWav(g.audioContext, "assets/audio/hurt.wav") + if err != nil { + return nil, err + } + g.soundBuffer[SoundPlayerHurt] = append(g.soundBuffer[SoundPlayerHurt], stream) - g.soundVampireDie1, err = loadSound("assets/audio/vampiredie1.mp3") - if err != nil { - return nil, err - } - - g.soundVampireDie2, err = loadSound("assets/audio/vampiredie2.mp3") - if err != nil { - return nil, err - } - - g.soundDie, err = loadSound("assets/audio/die.mp3") - if err != nil { - return nil, err + stream, err = loadMP3(g.audioContext, "assets/audio/die.mp3") + if err != nil { + return nil, err + } + g.soundBuffer[SoundPlayerDie] = append(g.soundBuffer[SoundPlayerDie], stream) } f, err = assetsFS.Open("assets/creeps/vampire.png") @@ -205,16 +198,14 @@ func NewGame() (*game, error) { return g, nil } -func (g *game) playSound(sound []byte, volume float64) error { - stream, err := mp3.DecodeWithSampleRate(sampleRate, bytes.NewReader(sound)) - if err != nil { - return err - } - - player, err := g.audioContext.NewPlayer(stream) - if err != nil { - return err +func (g *game) playSound(sound int, volume float64) error { + player := g.soundBuffer[sound][g.nextSound[sound]] + g.nextSound[sound]++ + if g.nextSound[sound] > 3 { + g.nextSound[sound] = 0 } + player.Pause() + player.Rewind() player.SetVolume(volume) player.Play() return nil @@ -235,9 +226,9 @@ func (g *game) hurtCreep(c *gameCreep, damage int) error { g.player.score += c.killScore // Play vampire die sound. - dieSound := g.soundVampireDie1 + dieSound := SoundVampireDie1 if rand.Intn(2) == 1 { - dieSound = g.soundVampireDie2 + dieSound = SoundVampireDie2 } err := g.playSound(dieSound, 0.25) if err != nil { @@ -365,7 +356,7 @@ func (g *game) Update() error { g.player.weapon.lastFire = time.Now() // Play gunshot sound. - err := g.playSound(g.soundGunshot, 0.4) + err := g.playSound(SoundGunshot, 0.4) if err != nil { return err } @@ -587,7 +578,11 @@ func (g *game) renderLevel(screen *ebiten.Image) int { panic(err) } - // TODO play ouch sound + if g.player.health == 2 { + g.playSound(SoundPlayerHurt, 0.4) + } else if g.player.health == 1 { + g.playSound(SoundPlayerHurt, 0.8) + } g.addBloodSplatter(g.player.x, g.player.y) @@ -597,7 +592,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { g.gameOverTime = time.Now() // Play die sound. - err := g.playSound(g.soundDie, 1.6) + err := g.playSound(SoundPlayerDie, 1.6) if err != nil { // TODO return err panic(err) diff --git a/go.mod b/go.mod index 9b026f5..1bf6565 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module code.rocketnine.space/tslocum/carotidartillery go 1.17 require ( - github.com/hajimehoshi/ebiten/v2 v2.3.0-alpha.0.20211005153847-3f5d1762bb36 + github.com/hajimehoshi/ebiten/v2 v2.3.0-alpha.0.20211007133459-69087cdc4006 golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d golang.org/x/text v0.3.7 ) @@ -16,5 +16,5 @@ require ( golang.org/x/exp v0.0.0-20210916165020-5cb4fee858ee // indirect golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed // indirect + golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect ) diff --git a/go.sum b/go.sum index e4319c4..cccbb83 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= -github.com/hajimehoshi/ebiten/v2 v2.3.0-alpha.0.20211005153847-3f5d1762bb36 h1:zp4kNpTpsWJ9OtUrbPkA9PCLUKk1Y1WHjpMLadVpwOI= -github.com/hajimehoshi/ebiten/v2 v2.3.0-alpha.0.20211005153847-3f5d1762bb36/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0= +github.com/hajimehoshi/ebiten/v2 v2.3.0-alpha.0.20211007133459-69087cdc4006 h1:81tAk6qxukWmcaP7zURm7jiIQ8efdDms6oGYRTeWIA8= +github.com/hajimehoshi/ebiten/v2 v2.3.0-alpha.0.20211007133459-69087cdc4006/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0= github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s= github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= @@ -369,8 +369,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed h1:E159xujlywdAeN3FqsTBPzRKGUq/pDHolXbuttkC36E= -golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=