From ea7b8994dffaaad3d48cd422e20e2102496526ba Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Thu, 21 Oct 2021 18:07:59 -0700 Subject: [PATCH] Add shadow effect --- README.md | 3 +- audio.go | 16 ------- creep.go | 5 +-- game.go | 128 +++++++++++++++++++++++++++++++++--------------------- 4 files changed, 81 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index b8933be..28869f7 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,5 @@ Please share issues and suggestions [here](https://code.rocketnine.space/tslocum ## Dependencies -- [ebiten](https://github.com/hajimehoshi/ebiten) - 2D game engine +- [ebiten](https://github.com/hajimehoshi/ebiten) - Game engine +- [go-dungeon](https://github.com/meshiest/go-dungeon) - Dungeon layout generator diff --git a/audio.go b/audio.go index c9f71c6..5c2a456 100644 --- a/audio.go +++ b/audio.go @@ -2,7 +2,6 @@ package main import ( "github.com/hajimehoshi/ebiten/v2/audio" - "github.com/hajimehoshi/ebiten/v2/audio/mp3" "github.com/hajimehoshi/ebiten/v2/audio/wav" ) @@ -19,21 +18,6 @@ const ( const numSounds = 7 // Must match above size. -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 { diff --git a/creep.go b/creep.go index 59a1b68..fbeac1f 100644 --- a/creep.go +++ b/creep.go @@ -121,10 +121,7 @@ func (c *gameCreep) doNextAction() { } func (c *gameCreep) repelled() bool { - repelled := !c.player.garlicUntil.IsZero() && c.player.garlicUntil.Sub(time.Now()) > 0 - if !c.player.holyWaterUntil.IsZero() && c.player.holyWaterUntil.Sub(time.Now()) > 0 { - repelled = true - } + repelled := !c.player.garlicUntil.IsZero() || !c.player.holyWaterUntil.IsZero() return repelled } diff --git a/game.go b/game.go index cab7689..7febe5c 100644 --- a/game.go +++ b/game.go @@ -42,7 +42,9 @@ const ( garlicActiveTime = 7 * time.Second holyWaterActiveTime = time.Second - maxCreeps = 3000 // TODO optimize and raise + maxCreeps = 3333 // TODO optimize and raise + + batSoundDelay = 250 * time.Millisecond ) var startButtons = []ebiten.StandardGamepadButton{ @@ -211,48 +213,37 @@ func (g *game) loadAssets() error { g.soundBuffer[SoundPlayerDie] = make([]*audio.Player, 4) g.soundBuffer[SoundMunch] = make([]*audio.Player, 4) - for i := 0; i < 4; i++ { - stream, err := loadWav(g.audioContext, "assets/audio/gunshot.wav") - if err != nil { - return err - } - g.soundBuffer[SoundGunshot][i] = stream - - stream, err = loadWav(g.audioContext, "assets/audio/vampiredie1.wav") - if err != nil { - return err - } - g.soundBuffer[SoundVampireDie1][i] = stream - - stream, err = loadWav(g.audioContext, "assets/audio/vampiredie2.wav") - if err != nil { - return err - } - g.soundBuffer[SoundVampireDie2][i] = stream - - stream, err = loadWav(g.audioContext, "assets/audio/bat.wav") + loadStream := func(p string) (*audio.Player, error) { + stream, err := loadWav(g.audioContext, p) if err != nil { - return err + return nil, err } - g.soundBuffer[SoundBat][i] = stream - stream, err = loadWav(g.audioContext, "assets/audio/playerhurt.wav") - if err != nil { - return err - } - g.soundBuffer[SoundPlayerHurt][i] = stream + // Workaround to prevent delays when playing for the first time. + stream.SetVolume(0) + stream.Play() + stream.Pause() + stream.Rewind() - stream, err = loadWav(g.audioContext, "assets/audio/playerdie.wav") - if err != nil { - return err - } - g.soundBuffer[SoundPlayerDie][i] = stream + return stream, nil + } - stream, err = loadWav(g.audioContext, "assets/audio/munch.wav") - if err != nil { - return err + soundMap := map[int]string{ + SoundGunshot: "assets/audio/gunshot.wav", + SoundVampireDie1: "assets/audio/vampiredie1.wav", + SoundVampireDie2: "assets/audio/vampiredie2.wav", + SoundBat: "assets/audio/bat.wav", + SoundPlayerHurt: "assets/audio/playerhurt.wav", + SoundPlayerDie: "assets/audio/playerdie.wav", + SoundMunch: "assets/audio/munch.wav", + } + for i := 0; i < 4; i++ { + for soundID, soundPath := range soundMap { + g.soundBuffer[soundID][i], err = loadStream(soundPath) + if err != nil { + return err + } } - g.soundBuffer[SoundMunch][i] = stream } f, err = assetsFS.Open("assets/creeps/vampire1.png") @@ -532,6 +523,8 @@ func (g *game) Update() error { return nil } + g.resetExpiredTimers() + biteThreshold := 0.75 liveCreeps := 0 for _, c := range g.level.creeps { @@ -544,7 +537,7 @@ func (g *game) Update() error { cx, cy := c.Position() dx, dy := deltaXY(g.player.x, g.player.y, cx, cy) if dx <= biteThreshold && dy <= biteThreshold { - if !g.godMode && g.player.garlicUntil.Sub(time.Now()) <= 0 && g.player.holyWaterUntil.Sub(time.Now()) <= 0 { + if !g.godMode && !c.repelled() { if g.player.holyWaters > 0 { // TODO g.playSound(SoundItemUseHolyWater, useholyWaterVolume) g.player.holyWaterUntil = time.Now().Add(holyWaterActiveTime) @@ -582,7 +575,7 @@ func (g *game) Update() error { panic(err) } } - } else if c.creepType == TypeBat && (dx <= 12 && dy <= 7) && rand.Intn(166) == 6 && time.Since(g.lastBatSound) >= 100*time.Millisecond { + } else if c.creepType == TypeBat && (dx <= 12 && dy <= 7) && rand.Intn(166) == 6 && time.Since(g.lastBatSound) >= batSoundDelay { g.playSound(SoundBat, batDieVolume) g.lastBatSound = time.Now() } @@ -971,7 +964,8 @@ func (g *game) tilePosition(x, y float64) (float64, float64) { return x * tileSize, y * tileSize } -func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float64, angle float64, scale float64, alpha float64, sprite *ebiten.Image, target *ebiten.Image) int { +// renderSprite renders a sprite on the screen. +func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float64, angle float64, scale float64, colorScale float64, alpha float64, sprite *ebiten.Image, target *ebiten.Image) int { x, y = g.tilePosition(x, y) // Skip drawing off-screen tiles. @@ -997,7 +991,7 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float // Center. g.op.GeoM.Translate(float64(g.w/2.0), float64(g.h/2.0)) - g.op.ColorM.Scale(1.0, 1.0, 1.0, alpha) + g.op.ColorM.Scale(colorScale, colorScale, colorScale, alpha) target.DrawImage(sprite, g.op) @@ -1006,6 +1000,31 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float return 1 } +// Calculate color scale to apply shadows. +func (g *game) colorScale(x, y float64) float64 { + dx, dy := deltaXY(x, y, g.player.x, g.player.y) + + sD := 7 / (dx + dy) + if sD > 1 { + sD = 1 + } + + sDB := sD + if dx > 4 { + sDB *= 0.6 / (dx / 4) + } + if dy > 4 { + sDB *= 0.6 / (dy / 4) + } + + sD = sD * 2 * sDB + if sD > 1 { + sD = 1 + } + + return sD +} + // renderLevel draws the current Level on the screen. func (g *game) renderLevel(screen *ebiten.Image) int { var drawn int @@ -1019,7 +1038,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { } for i := range t.sprites { - drawn += g.renderSprite(float64(x), float64(y), 0, 0, 0, 1.0, 1.0, t.sprites[i], screen) + drawn += g.renderSprite(float64(x), float64(y), 0, 0, 0, 1.0, g.colorScale(float64(x), float64(y)), 1.0, t.sprites[i], screen) } } } @@ -1029,7 +1048,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { continue } - drawn += g.renderSprite(item.x, item.y, 0, 0, 0, 1.0, 1.0, item.sprite, screen) + drawn += g.renderSprite(item.x, item.y, 0, 0, 0, 1.0, g.colorScale(item.x, item.y), 1.0, item.sprite, screen) } for _, c := range g.level.creeps { @@ -1037,7 +1056,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { continue } - drawn += g.renderSprite(c.x, c.y, 0, 0, 0, 1.0, 1.0, c.sprites[c.frame], screen) + drawn += g.renderSprite(c.x, c.y, 0, 0, 0, 1.0, g.colorScale(c.x, c.y), 1.0, c.sprites[c.frame], screen) if c.frames > 1 && time.Since(c.lastFrame) >= 75*time.Millisecond { c.frame++ if c.frame == c.frames { @@ -1048,7 +1067,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { } for _, p := range g.projectiles { - drawn += g.renderSprite(p.x, p.y, 0, 0, p.angle, 1.0, 1.0, bulletImage, screen) + drawn += g.renderSprite(p.x, p.y, 0, 0, p.angle, 1.0, g.colorScale(p.x, p.y), 1.0, bulletImage, screen) } repelTime := g.player.garlicUntil.Sub(time.Now()) @@ -1059,7 +1078,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { if repelTime.Seconds() < 3 { alpha = repelTime.Seconds() / 12 } - drawn += g.renderSprite(g.player.x+0.25, g.player.y+0.25, -offset, -offset, 0, scale, alpha, g.garlicImage, screen) + drawn += g.renderSprite(g.player.x+0.25, g.player.y+0.25, -offset, -offset, 0, scale, 1.0, alpha, g.garlicImage, screen) } holyWaterTime := g.player.holyWaterUntil.Sub(time.Now()) @@ -1070,7 +1089,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int { if holyWaterTime.Seconds() < 3 { alpha = holyWaterTime.Seconds() / 2 } - drawn += g.renderSprite(g.player.x+0.25, g.player.y+0.25, -offset, -offset, 0, scale, alpha, g.holyWaterImage, screen) + drawn += g.renderSprite(g.player.x+0.25, g.player.y+0.25, -offset, -offset, 0, scale, 1.0, alpha, g.holyWaterImage, screen) } playerSprite := g.ojasSS.Frame1 @@ -1083,19 +1102,28 @@ func (g *game) renderLevel(screen *ebiten.Image) int { weaponSprite = g.player.weapon.sprite mul = -1 } - drawn += g.renderSprite(g.player.x, g.player.y, 0, 0, playerAngle, 1.0, 1.0, playerSprite, screen) + drawn += g.renderSprite(g.player.x, g.player.y, 0, 0, playerAngle, 1.0, 1.0, 1.0, playerSprite, screen) if g.player.weapon != nil { - drawn += g.renderSprite(g.player.x, g.player.y, 11*mul, 9, playerAngle, 1.0, 1.0, weaponSprite, screen) + drawn += g.renderSprite(g.player.x, g.player.y, 11*mul, 9, playerAngle, 1.0, 1.0, 1.0, weaponSprite, screen) } flashDuration := 40 * time.Millisecond if time.Since(g.player.weapon.lastFire) < flashDuration { - drawn += g.renderSprite(g.player.x, g.player.y, 39, -1, g.player.angle, 1.0, 1.0, flashImage, screen) + drawn += g.renderSprite(g.player.x, g.player.y, 39, -1, g.player.angle, 1.0, 1.0, 1.0, flashImage, screen) } return drawn } +func (g *game) resetExpiredTimers() { + if !g.player.garlicUntil.IsZero() && g.player.garlicUntil.Sub(time.Now()) <= 0 { + g.player.garlicUntil = time.Time{} + } + if !g.player.holyWaterUntil.IsZero() && g.player.holyWaterUntil.Sub(time.Now()) <= 0 { + g.player.holyWaterUntil = time.Time{} + } +} + func (g *game) playSound(sound int, volume float64) error { player := g.soundBuffer[sound][g.nextSound[sound]] g.nextSound[sound]++