Skip to content

Commit

Permalink
Merge pull request #40 from pankona/play-sound
Browse files Browse the repository at this point in the history
play sound
  • Loading branch information
pankona authored Jun 27, 2024
2 parents b90206a + ca5f0b8 commit 2ba4d20
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 17 deletions.
2 changes: 2 additions & 0 deletions atkpane.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ func (h *smallHand) Update() {

// クリックから 5 フレーム後に攻撃を実行する
if h.erapsedTime == 5 {
getAudioPlayer().play(soundBinta)

// 攻撃範囲内にいる敵に対してダメージを与える
// ループの中で複数の h.game.enemies が減る可能性があるので、逆順でループする
for i := len(h.game.enemies) - 1; i >= 0; i-- {
Expand Down
154 changes: 154 additions & 0 deletions audio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package main

import (
"bytes"
"io"
"log"

"github.com/hajimehoshi/ebiten/v2/audio"
"github.com/hajimehoshi/ebiten/v2/audio/mp3"

_ "embed"
)

type audioPlayer struct {
audioContext *audio.Context

players map[string]*audio.Player
}

var aplayer *audioPlayer

var (
//go:embed assets/bakuhatsu.mp3
soundBakuhatsuMP3 []byte
//go:embed assets/beam.mp3
soundBeamMP3 []byte
//go:embed assets/binta.mp3
soundBintaMP3 []byte
//go:embed assets/choice.mp3
soundChoiceMP3 []byte
//go:embed assets/clear.mp3
soundClearMP3 []byte
//go:embed assets/don.mp3
soundDonMP3 []byte
//go:embed assets/gameover.mp3
soundGameoverMP3 []byte
//go:embed assets/gyaa.mp3
soundGyaaMP3 []byte
//go:embed assets/hikkaki.mp3
soundHikkakiMP3 []byte
//go:embed assets/kettei.mp3
soundKetteiMP3 []byte
//go:embed assets/kuzureru.mp3
soundKuzureruMP3 []byte
//go:embed assets/shot.mp3
soundShotMP3 []byte

//go:embed assets/bgm.mp3
soundBgmMP3 []byte
)

const (
soundGyaa = "gyaa" // 虫が死んだときの音
soundBakuhatsu = "bakuhatsu" // 電波塔が範囲攻撃するときの音
soundBeam = "beam" // 塔がビームを撃つときの音
soundBinta = "binta" // 手で叩くときの音
soundChoice = "choice" // ボタンを押下したときの音
soundClear = "clear" // ゲームクリア時の音
soundDon = "don" // 建物を置いたときの音
soundGameover = "gameover" // ゲームオーバー時の音
soundHikkaki = "hikkaki" // 虫が建物を攻撃するときの音
soundKettei = "kettei" // Ready ボタンを押したときの音
soundKuzureru = "kuzureru" // 建物が壊れるときの音
soundShot = "shot" // 緑虫が弾を撃つときの音

soundBgm = "bgm"
)

func getAudioPlayer() *audioPlayer {
if aplayer != nil {
return aplayer
}

aplayer = &audioPlayer{
audioContext: audio.NewContext(44100),
players: map[string]*audio.Player{},
}

sounds := map[string][]byte{
soundGyaa: soundGyaaMP3,
soundBakuhatsu: soundBakuhatsuMP3,
soundBeam: soundBeamMP3,
soundBinta: soundBintaMP3,
soundChoice: soundChoiceMP3,
soundClear: soundClearMP3,
soundDon: soundDonMP3,
soundGameover: soundGameoverMP3,
soundHikkaki: soundHikkakiMP3,
soundKettei: soundKetteiMP3,
soundKuzureru: soundKuzureruMP3,
soundShot: soundShotMP3,
}

for name, buf := range sounds {
stream, err := mp3.DecodeWithSampleRate(aplayer.audioContext.SampleRate(), bytes.NewReader(buf))
if err != nil {
log.Fatalf("failed to decode mp3: %v", err)
}
aplayer.players[name] = mustNewPlayer(aplayer.audioContext, stream)
}

stream, err := mp3.DecodeWithSampleRate(aplayer.audioContext.SampleRate(), bytes.NewReader(soundBgmMP3))
if err != nil {
log.Fatalf("failed to decode mp3: %v", err)
}
bgmStream := audio.NewInfiniteLoop(stream, 160_000*40.6)
bgmPlayer := mustNewPlayer(aplayer.audioContext, bgmStream)
aplayer.players[soundBgm] = bgmPlayer

return aplayer
}

func mustNewPlayer(context *audio.Context, stream io.Reader) *audio.Player {
player, err := context.NewPlayer(stream)
if err != nil {
log.Fatalf("failed to create player: %v", err)
}
player.SetVolume(0.2) // なんか音がうるさいのでちっさくしておく

return player
}

func (a *audioPlayer) play(soundname string) {
// 同時に鳴らす音を制限する
var active int
for _, player := range a.players {
if player.IsPlaying() {
active++
}
}
if active >= 5 {
return
}

if player, ok := a.players[soundname]; ok {
if player.IsPlaying() {
return
}
player.Rewind()
player.Play()
return
}
}

func (a *audioPlayer) playBGM() {
player := a.players[soundBgm]
player.Rewind()
player.Play()
}

func (a *audioPlayer) stopBGM() {
player := a.players[soundBgm]
player.Pause()
}
2 changes: 2 additions & 0 deletions barricade.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,15 @@ func (b *barricade) Damage(d int) {

b.health -= d
if b.health <= 0 {
getAudioPlayer().play(soundKuzureru)
b.health = 0
}
}

// barricade implements Clickable interface
func (b *barricade) OnClick(x, y int) bool {
b.game.clickedObject = "barricade"
getAudioPlayer().play(soundChoice)

// infoPanel に情報を表示する

Expand Down
9 changes: 6 additions & 3 deletions bugs.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,15 @@ func (b *bug) Update() {
func (b *bug) attack(a Damager) {
a.Damage(b.attackPower)

// エフェクトを表示する
// エフェクトや音を制御する
switch b.selfColor {
case bugsRed:
// TODO: implement
getAudioPlayer().play(soundHikkaki)
case bugsBlue:
// TODO: implement
getAudioPlayer().play(soundHikkaki)
case bugsGreen:
getAudioPlayer().play(soundShot)

tx, ty := a.(Building).Position()
e := newGreenBugAttackEffect(b.game, b.x, b.y, tx, ty)
b.game.updateHandler.Add(e)
Expand Down Expand Up @@ -249,6 +251,7 @@ func (b *bug) Damage(d int) {

if b.health <= 0 {
b.health = 0
getAudioPlayer().play(soundGyaa)
}
}

Expand Down
6 changes: 6 additions & 0 deletions buildpane.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func newBuildPane(game *Game) *buildPane {
// buildCandidate は次の建築のために初期化する
game.buildCandidate = nil

getAudioPlayer().play(soundDon)

return false
},
func(screen *ebiten.Image, x, y, width, height int) {
Expand Down Expand Up @@ -89,6 +91,8 @@ func newBuildPane(game *Game) *buildPane {
game.drawHandler.Remove(game.buildCandidate)
game.buildCandidate = nil

getAudioPlayer().play(soundChoice)

return false
},
func(screen *ebiten.Image, x, y, width, height int) {
Expand Down Expand Up @@ -174,6 +178,8 @@ func newReadyButton(g *Game) *Button {
// - atkpane を取り去る
// - buildpane を追加する

getAudioPlayer().play(soundKettei)

switch g.phase {
case PhaseBuilding:
g.SetWavePhase()
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ module github.com/pankona/gj

go 1.22.4

require github.com/hajimehoshi/ebiten/v2 v2.7.4
require github.com/hajimehoshi/ebiten/v2 v2.7.5

require (
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
github.com/ebitengine/hideconsole v1.0.0 // indirect
github.com/ebitengine/oto/v3 v3.2.0 // indirect
github.com/ebitengine/purego v0.7.0 // indirect
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
github.com/jezek/xgb v1.1.1 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@ github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
github.com/ebitengine/oto/v3 v3.2.0 h1:FuggTJTSI3/3hEYwZEIN0CZVXYT29ZOdCu+z/f4QjTw=
github.com/ebitengine/oto/v3 v3.2.0/go.mod h1:dOKXShvy1EQbIXhXPFcKLargdnFqH0RjptecvyAxhyw=
github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8=
github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY=
github.com/hajimehoshi/ebiten/v2 v2.7.5 h1:jN6FnhCd9NGYCsm5GtrweuikrlyVGCSUpH5YgL+7UKA=
github.com/hajimehoshi/ebiten/v2 v2.7.5/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY=
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
8 changes: 8 additions & 0 deletions house.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ func (h *house) Damage(d int) {

// house implements Clickable interface
func (h *house) OnClick(x, y int) bool {
getAudioPlayer().play(soundChoice)

h.game.clickedObject = "House"
// infoPanel に情報を表示する
h.game.infoPanel.ClearButtons()
Expand All @@ -159,6 +161,8 @@ func (h *house) OnClick(x, y int) bool {
return false
}

getAudioPlayer().play(soundChoice)

// buildCandidate を持っているときにバリケードボタンを押したときの振る舞い
// 選択肢なおしということ、いったん手放す
if h.game.buildCandidate != nil {
Expand Down Expand Up @@ -207,6 +211,8 @@ func (h *house) OnClick(x, y int) bool {
return false
}

getAudioPlayer().play(soundChoice)

// buildCandidate を持っているときにバリケードボタンを押したときの振る舞い
// 選択肢なおしということ、いったん手放す
if h.game.buildCandidate != nil {
Expand Down Expand Up @@ -255,6 +261,8 @@ func (h *house) OnClick(x, y int) bool {
return false
}

getAudioPlayer().play(soundChoice)

// buildCandidate を持っているときにバリケードボタンを押したときの振る舞い
// 選択肢なおしということ、いったん手放す
if h.game.buildCandidate != nil {
Expand Down
16 changes: 16 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const (
)

func (g *Game) Update() error {

// getClickPosition の戻り値を clickHandler.HandleClick に渡す
// これをやると登録された Clickable の OnClick が呼ばれる
if x, y, clicked := getClickedPosition(); clicked {
Expand Down Expand Up @@ -134,6 +135,18 @@ func (g *Game) Draw(screen *ebiten.Image) {
vector.DrawFilledRect(screen, screenWidth/2, eScreenHeight/2, 1, 1, color.RGBA{255, 255, 255, 255}, true)
// 残りの敵の数を表示
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Enemies: %d", len(g.enemies)), 0, 120)

// FPS を表示
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("FPS: %0.2f", ebiten.ActualFPS()), 0, 140)

// active な audio の数を表示
var activeAudioNum int
for _, aplayer := range getAudioPlayer().players {
if aplayer.IsPlaying() {
activeAudioNum++
}
}
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Active Audio: %d", activeAudioNum), 0, 160)
}
}

Expand Down Expand Up @@ -184,6 +197,9 @@ func (g *Game) SetWavePhase() {
func (g *Game) initialize() {
// とりあえずいきなりゲームが始まるとする。
// TODO: まずタイトルバックを表示して、その後にゲーム画面に遷移するようにする
aplayer := getAudioPlayer()
aplayer.playBGM()

g.house = newHouse(g)
g.updateHandler.Add(g.house)
g.drawHandler.Add(g.house)
Expand Down
18 changes: 18 additions & 0 deletions public/game.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Your Game Title</title>
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
const wasmURL = "main.wasm?v=" + new Date().getTime();
WebAssembly.instantiateStreaming(fetch(wasmURL), go.importObject).then(result => {
go.run(result.instance);
});
</script>
</body>
</html>

24 changes: 15 additions & 9 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
<head>
<meta charset="UTF-8">
<title>Your Game Title</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
iframe {
border: none;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
const wasmURL = "main.wasm?v=" + new Date().getTime();
WebAssembly.instantiateStreaming(fetch(wasmURL), go.importObject).then(result => {
go.run(result.instance);
});
</script>
<iframe src="game.html" allow="autoplay"></iframe>
</body>
</html>

Loading

0 comments on commit 2ba4d20

Please sign in to comment.