diff --git a/assets/audio/vampiredie1.mp3 b/assets/audio/vampiredie1.mp3 new file mode 100644 index 0000000..ca24125 Binary files /dev/null and b/assets/audio/vampiredie1.mp3 differ diff --git a/assets/audio/vampiredie2.mp3 b/assets/audio/vampiredie2.mp3 new file mode 100644 index 0000000..24848e5 Binary files /dev/null and b/assets/audio/vampiredie2.mp3 differ diff --git a/creep.go b/creep.go index f92c4b3..48bc3fb 100644 --- a/creep.go +++ b/creep.go @@ -20,16 +20,19 @@ type gameCreep struct { health int + killScore int + sync.Mutex } func NewCreep(sprite *ebiten.Image, level *Level) *gameCreep { return &gameCreep{ - x: float64(1 + rand.Intn(108)), - y: float64(1 + rand.Intn(108)), - sprite: sprite, - level: level, - health: 1, + x: float64(1 + rand.Intn(108)), + y: float64(1 + rand.Intn(108)), + sprite: sprite, + level: level, + health: 1, + killScore: 50, } } diff --git a/game.go b/game.go index b2f8466..c9f9edf 100644 --- a/game.go +++ b/game.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "image" "image/color" @@ -9,12 +10,15 @@ import ( "os" "time" + "github.com/hajimehoshi/ebiten/v2/audio/mp3" + "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/audio" - "github.com/hajimehoshi/ebiten/v2/audio/mp3" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/inpututil" "golang.org/x/image/colornames" + "golang.org/x/text/language" + "golang.org/x/text/message" ) var spinner = []byte(`-\|/`) @@ -22,6 +26,8 @@ var spinner = []byte(`-\|/`) var bulletImage *ebiten.Image var flashImage *ebiten.Image +var numberPrinter = message.NewPrinter(language.English) + type projectile struct { x, y float64 angle float64 @@ -34,9 +40,11 @@ type game struct { w, h int currentLevel *Level - audioPlayerGunshot *audio.Player - audioPlayerGib *audio.Player - audioPlayerDie *audio.Player + soundGunshot []byte + soundVampireDie1 []byte + soundVampireDie2 []byte + soundGib []byte + soundDie []byte player *gamePlayer @@ -58,7 +66,10 @@ type game struct { overlayImg *ebiten.Image op *ebiten.DrawImageOptions - godMode bool + audioContext *audio.Context + + godMode bool + debugMode bool } const sampleRate = 48000 @@ -75,8 +86,6 @@ func NewGame() (*game, error) { return nil, err } - audioContext := audio.NewContext(sampleRate) - g := &game{ currentLevel: l, camScale: 2, @@ -87,6 +96,8 @@ func NewGame() (*game, error) { op: &ebiten.DrawImageOptions{}, } + g.audioContext = audio.NewContext(sampleRate) + g.player.x = float64(rand.Intn(108)) g.player.y = float64(rand.Intn(108)) @@ -118,41 +129,40 @@ func NewGame() (*game, error) { flashImage = ebiten.NewImageFromImage(img) - loadSound := func(p string) (*audio.Player, error) { - f, err := assetsFS.Open(p) + loadSound := func(p string) ([]byte, error) { + b, err := assetsFS.ReadFile(p) if err != nil { return nil, err } defer f.Close() - gunshotSound, err := mp3.DecodeWithSampleRate(sampleRate, f) - if err != nil { - return nil, err - } - - return audioContext.NewPlayer(gunshotSound) + return b, nil } - g.audioPlayerGunshot, err = loadSound("assets/audio/gunshot.mp3") - if err != nil { - return nil, err - } - g.audioPlayerGunshot.SetVolume(0.6) - - g.audioPlayerGib, err = loadSound("assets/audio/gib.mp3") - if err != nil { - return nil, err - } - g.audioPlayerGib.SetVolume(1.0) - - g.audioPlayerDie, err = loadSound("assets/audio/die.mp3") + g.soundGunshot, err = loadSound("assets/audio/gunshot.mp3") if err != nil { return nil, err } - // The death sound will have a delay without this. - g.audioPlayerDie.SetVolume(0) - g.audioPlayerDie.Play() + g.soundGib, err = loadSound("assets/audio/gib.mp3") + if err != nil { + return nil, err + } + + 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 + } f, err = assetsFS.Open("assets/creeps/vampire.png") if err != nil { @@ -188,14 +198,24 @@ func NewGame() (*game, error) { ebiten.SetCursorShape(ebiten.CursorShapeCrosshair) - // The death sound will have a delay without this. - g.audioPlayerDie.Pause() - g.audioPlayerDie.Rewind() - g.audioPlayerDie.SetVolume(1.6) - 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 + } + player.SetVolume(volume) + player.Play() + return nil +} + // Update reads current user input and updates the game state. func (g *game) Update() error { if ebiten.IsKeyPressed(ebiten.KeyEscape) || ebiten.IsWindowBeingClosed() { @@ -289,12 +309,19 @@ func (g *game) Update() error { // Killed creep. if c.health == 0 { + g.player.score += c.killScore + g.addBloodSplatter(cx, cy) - // Play gib sound. - g.audioPlayerGib.Pause() - g.audioPlayerGib.Rewind() - g.audioPlayerGib.Play() + // Play vampire die sound. + dieSound := g.soundVampireDie1 + if rand.Intn(2) == 1 { + dieSound = g.soundVampireDie2 + } + err := g.playSound(dieSound, 0.25) + if err != nil { + return err + } } // Remove projectile @@ -320,12 +347,16 @@ func (g *game) Update() error { g.player.weapon.lastFire = time.Now() // Play gunshot sound. - g.audioPlayerGunshot.Pause() - g.audioPlayerGunshot.Rewind() - g.audioPlayerGunshot.Play() + err := g.playSound(g.soundGunshot, 0.4) + if err != nil { + return err + } } // TODO debug only + if inpututil.IsKeyJustPressed(ebiten.KeyV) { + g.debugMode = !g.debugMode + } if inpututil.IsKeyJustPressed(ebiten.KeyG) { g.godMode = !g.godMode } @@ -353,12 +384,21 @@ func (g *game) addBloodSplatter(x, y float64) { if rand.Intn(2) != 0 { continue } - for x := 12; x < 20; x++ { if rand.Intn(5) != 0 { continue } - + splatterSprite.Set(x, y, colornames.Red) + } + } + for y := 2; y < 26; y++ { + if rand.Intn(5) != 0 { + continue + } + for x := 2; x < 26; x++ { + if rand.Intn(12) != 0 { + continue + } splatterSprite.Set(x, y, colornames.Red) } } @@ -371,8 +411,13 @@ func (g *game) addBloodSplatter(x, y float64) { // Draw draws the game on the screen. func (g *game) Draw(screen *ebiten.Image) { - // Game over. - if g.player.health <= 0 && !g.godMode { + gameOver := g.player.health <= 0 && !g.godMode + + var drawn int + if !gameOver { + drawn = g.renderLevel(screen) + } else { + // Game over. screen.Fill(color.RGBA{102, 0, 0, 255}) if time.Since(g.gameOverTime).Milliseconds()%2000 < 1500 { @@ -381,19 +426,27 @@ func (g *game) Draw(screen *ebiten.Image) { g.op.GeoM.Reset() g.op.GeoM.Translate(3, 0) g.op.GeoM.Scale(16, 16) - g.op.GeoM.Translate(float64(g.w/2)-485, float64(g.h/2)-200) + g.op.GeoM.Translate(float64(g.w/2)-495, float64(g.h/2)-200) screen.DrawImage(g.overlayImg, g.op) } + } + scoreLabel := numberPrinter.Sprintf("%d", g.player.score) + + g.overlayImg.Clear() + ebitenutil.DebugPrint(g.overlayImg, scoreLabel) + g.op.GeoM.Reset() + g.op.GeoM.Scale(8, 8) + g.op.GeoM.Translate(float64(g.w/2)-float64(24*len(scoreLabel)), float64(g.h-150)) + screen.DrawImage(g.overlayImg, g.op) + + if !g.debugMode { return } - // Render level. - drawn := g.renderLevel(screen) - // Print game info. g.overlayImg.Clear() - ebitenutil.DebugPrint(g.overlayImg, fmt.Sprintf("FPS %0.0f\nTPS %0.0f\nSPR %d\nSCA %0.2f\nPOS %0.0f,%0.0f", ebiten.CurrentFPS(), ebiten.CurrentTPS(), drawn, g.camScale, g.player.x, g.player.y)) + ebitenutil.DebugPrint(g.overlayImg, fmt.Sprintf("SPR %d\nTPS %0.0f\nFPS %0.0f", drawn, ebiten.CurrentTPS(), ebiten.CurrentFPS())) g.op.GeoM.Reset() g.op.GeoM.Translate(3, 0) g.op.GeoM.Scale(2, 2) @@ -474,7 +527,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { } } - biteThreshold := 0.5 + biteThreshold := 0.75 for _, c := range g.creeps { if c.health == 0 { continue @@ -491,7 +544,11 @@ func (g *game) renderLevel(screen *ebiten.Image) int { g.gameOverTime = time.Now() // Play die sound. - g.audioPlayerDie.Play() + err := g.playSound(g.soundDie, 1.6) + if err != nil { + // TODO return err + panic(err) + } } } diff --git a/go.mod b/go.mod index 100ae5f..9b026f5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/hajimehoshi/ebiten/v2 v2.3.0-alpha.0.20211005153847-3f5d1762bb36 golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d + golang.org/x/text v0.3.7 ) require ( @@ -15,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-20211004093028-2c5d950f24ef // indirect + golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed // indirect ) diff --git a/go.sum b/go.sum index 4575e95..e4319c4 100644 --- a/go.sum +++ b/go.sum @@ -369,13 +369,15 @@ 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-20211004093028-2c5d950f24ef h1:fPxZ3Umkct3LZ8gK9nbk+DWDJ9fstZa2grBn+lWVKPs= -golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/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/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= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/player.go b/player.go index 1edb258..ee80aa1 100644 --- a/player.go +++ b/player.go @@ -14,6 +14,8 @@ type gamePlayer struct { weapon *playerWeapon + score int + health int } @@ -34,7 +36,7 @@ func NewPlayer() (*gamePlayer, error) { sprite: uziSprite, cooldown: 100 * time.Millisecond, }, - health: 7, + health: 1, } return p, nil }