Add shadow effect

This commit is contained in:
Trevor Slocum 2021-10-21 18:07:59 -07:00
parent e4cab469d8
commit ea7b8994df
4 changed files with 85 additions and 75 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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
}

136
game.go
View File

@ -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)
loadStream := func(p string) (*audio.Player, error) {
stream, err := loadWav(g.audioContext, p)
if err != nil {
return nil, err
}
// Workaround to prevent delays when playing for the first time.
stream.SetVolume(0)
stream.Play()
stream.Pause()
stream.Rewind()
return stream, nil
}
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++ {
stream, err := loadWav(g.audioContext, "assets/audio/gunshot.wav")
if err != nil {
return err
for soundID, soundPath := range soundMap {
g.soundBuffer[soundID][i], err = loadStream(soundPath)
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")
if err != nil {
return 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
stream, err = loadWav(g.audioContext, "assets/audio/playerdie.wav")
if err != nil {
return err
}
g.soundBuffer[SoundPlayerDie][i] = stream
stream, err = loadWav(g.audioContext, "assets/audio/munch.wav")
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]++