Add death sound

This commit is contained in:
Trevor Slocum 2021-12-23 18:04:30 -08:00
parent 8ca69dec29
commit c9ceabcbcf
14 changed files with 172 additions and 150 deletions

View File

@ -6,13 +6,19 @@ import (
"image/color"
_ "image/png"
"github.com/hajimehoshi/ebiten/v2/audio/wav"
"github.com/hajimehoshi/ebiten/v2/audio"
"github.com/hajimehoshi/ebiten/v2"
)
//go:embed image map
const sampleRate = 44100
//go:embed image map sound
var FS embed.FS
var ImgWhiteSquare = ebiten.NewImage(32, 32)
var ImgWhiteSquare = ebiten.NewImage(8, 8)
var (
ImgBat = LoadImage("image/bat.png")
@ -20,10 +26,24 @@ var (
ImgBatBroken2 = LoadImage("image/bat_broken2.png")
)
var (
SoundBatHit1 *audio.Player
SoundBatHit2 *audio.Player
SoundBatHit3 *audio.Player
SoundBatHit4 *audio.Player
)
func init() {
ImgWhiteSquare.Fill(color.White)
}
func LoadSounds(ctx *audio.Context) {
SoundBatHit1 = LoadWAV(ctx, "sound/bat_hit/hit1.wav")
SoundBatHit2 = LoadWAV(ctx, "sound/bat_hit/hit2.wav")
SoundBatHit3 = LoadWAV(ctx, "sound/bat_hit/hit3.wav")
SoundBatHit4 = LoadWAV(ctx, "sound/bonk.wav")
}
func LoadImage(p string) *ebiten.Image {
f, err := FS.Open(p)
if err != nil {
@ -38,3 +58,38 @@ func LoadImage(p string) *ebiten.Image {
return ebiten.NewImageFromImage(baseImg)
}
func LoadBytes(p string) []byte {
b, err := FS.ReadFile(p)
if err != nil {
panic(err)
}
return b
}
func LoadWAV(context *audio.Context, p string) *audio.Player {
f, err := FS.Open(p)
if err != nil {
panic(err)
}
defer f.Close()
stream, err := wav.DecodeWithSampleRate(sampleRate, f)
if err != nil {
panic(err)
}
player, err := context.NewPlayer(stream)
if err != nil {
panic(err)
}
// Workaround to prevent delays when playing for the first time.
player.SetVolume(0)
player.Play()
player.Pause()
player.Rewind()
player.SetVolume(1)
return player
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.5" tiledversion="1.7.2" orientation="orthogonal" renderorder="right-down" width="20" height="120" tilewidth="32" tileheight="32" infinite="0" nextlayerid="6" nextobjectid="42">
<map version="1.5" tiledversion="1.7.2" orientation="orthogonal" renderorder="right-down" width="20" height="120" tilewidth="32" tileheight="32" infinite="0" nextlayerid="6" nextobjectid="45">
<tileset firstgid="1" name="tileset" tilewidth="32" tileheight="32" tilecount="72" columns="9">
<image source="../image/tileset/tileset.png" width="288" height="256"/>
</tileset>
@ -338,6 +338,11 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,72,0,0,0,0,0,0,0,72,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,72,0,0,0,0,0,0,0,72,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -352,12 +357,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2147483720,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -411,11 +411,13 @@
<object id="33" x="513" y="3263" width="7" height="29"/>
<object id="34" x="469" y="3236" width="38" height="14"/>
<object id="35" x="491" y="3220" width="11" height="16"/>
<object id="36" x="338" y="3183" width="23" height="18"/>
<object id="36" x="321" y="3183" width="40" height="20"/>
<object id="37" x="548" y="3203" width="16" height="15"/>
<object id="38" x="589" y="3269" width="7" height="18"/>
<object id="39" x="621" y="3251" width="7" height="18"/>
<object id="40" x="450" y="2701" width="21" height="34"/>
<object id="41" x="54" y="1007" width="25" height="18"/>
<object id="42" x="340" y="3197" width="35" height="12"/>
<object id="43" x="95" y="1028" width="40" height="20" rotation="180"/>
<object id="44" x="76" y="1034" width="35" height="12" rotation="180"/>
</objectgroup>
</map>

View File

@ -0,0 +1,6 @@
These sound clips were made available for use by CGEffex under the
Creative Commons Attribution License.
Source: https://freesound.org/people/CGEffex/sounds/93136/
License: https://creativecommons.org/licenses/by/3.0/

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
asset/sound/bonk.wav Normal file

Binary file not shown.

View File

@ -1,14 +1,18 @@
package component
import (
"math/rand"
. "code.rocketnine.space/tslocum/brownboxbatman/ecs"
"code.rocketnine.space/tslocum/gohan"
)
type CreepComponent struct {
Health int
FireRate int // In ticks
Ticks int // Ticks until next action
Health int
FireAmount int
FireRate int // In ticks
Ticks int // Ticks until next action
Rand *rand.Rand
}
var CreepComponentID = ECS.NewComponentID()

View File

@ -26,7 +26,5 @@ func NewBullet(x, y, xSpeed, ySpeed float64) gohan.Entity {
ECS.AddComponent(bullet, &component.BulletComponent{})
ECS.AddComponent(bullet, &component.RailComponent{})
return bullet
}

View File

@ -4,8 +4,10 @@ import (
"image/color"
"log"
"math"
"math/rand"
"os"
"sync"
"time"
"code.rocketnine.space/tslocum/brownboxbatman/entity"
@ -64,13 +66,11 @@ type game struct {
renderSystem *system.RenderSystem
sync.Mutex
camScale float64
}
// NewGame returns a new isometric demo game.
func NewGame() (*game, error) {
g := &game{
camScale: 4,
audioContext: audio.NewContext(sampleRate),
op: &ebiten.DrawImageOptions{},
}
@ -89,6 +89,10 @@ func NewGame() (*game, error) {
asset.ImgWhiteSquare.Fill(color.White)
asset.LoadSounds(g.audioContext)
rand.Seed(time.Now().UnixNano())
return g, nil
}

View File

@ -1,9 +1,8 @@
package system
import (
"code.rocketnine.space/tslocum/brownboxbatman/entity"
"code.rocketnine.space/tslocum/brownboxbatman/component"
"code.rocketnine.space/tslocum/brownboxbatman/entity"
"code.rocketnine.space/tslocum/brownboxbatman/world"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
@ -31,21 +30,39 @@ func (_ *CreepSystem) Uses() []gohan.ComponentID {
}
func (s *CreepSystem) Update(ctx *gohan.Context) error {
if world.World.MessageVisible || world.World.GameOver {
if world.World.MessageVisible {
return nil
}
creep := component.Creep(ctx)
position := component.Position(ctx)
// Skip inactive creeps.
sx, sy := world.LevelCoordinatesToScreen(position.X, position.Y)
// TODO activate on visible
_, _ = sx, sy
if sx < 0 || sy < 0 || sx > 640 || sy > 480 {
return nil
}
randSpeed := func() float64 {
return 0.5 + creep.Rand.Float64()*0.5 + (0.5 - creep.Rand.Float64())
}
if creep.Ticks == 0 {
entity.NewBullet(position.X, position.Y, -0.5, 0)
for i := 0; i < 8; i++ {
speedA := randSpeed()
speedB := randSpeed()
if creep.Rand.Intn(2) == 0 {
speedA *= -1
}
if creep.Rand.Intn(2) == 0 {
speedB *= -1
}
entity.NewBullet(position.X, position.Y, speedA, speedB)
}
creep.Ticks = creep.FireRate
}
creep.Ticks--
return nil
}

View File

@ -61,7 +61,6 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
} else {
world.World.Debug = v
}
s.movement.UpdateDebugCollisionRects()
return nil
}
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.KeyN) {

View File

@ -3,8 +3,6 @@ package system
import (
"image"
"image/color"
"math"
"time"
"code.rocketnine.space/tslocum/brownboxbatman/component"
. "code.rocketnine.space/tslocum/brownboxbatman/ecs"
@ -17,38 +15,12 @@ const rewindThreshold = 1
type MovementSystem struct {
ScreenW, ScreenH float64
OnGround int
OnLadder int
Jumping bool
LastJump time.Time
Dashing bool
LastDash time.Time
collisionRects []image.Rectangle
ladderRects []image.Rectangle
fireRects []image.Rectangle
debugCollisionRects []gohan.Entity
debugLadderRects []gohan.Entity
debugFireRects []gohan.Entity
playerPositions [][2]float64
playerPosition [2]float64
playerPositionTicks int
recordedPosition bool
}
func NewMovementSystem() *MovementSystem {
s := &MovementSystem{
OnGround: -1,
OnLadder: -1,
ScreenW: 640,
ScreenH: 480,
ScreenW: 640,
ScreenH: 480,
}
return s
@ -73,72 +45,6 @@ func drawDebugRect(r image.Rectangle, c color.Color, overrideColorScale bool) go
return rectEntity
}
func (s *MovementSystem) removeDebugRects() {
for _, e := range s.debugCollisionRects {
ECS.RemoveEntity(e)
}
s.debugCollisionRects = nil
for _, e := range s.debugLadderRects {
ECS.RemoveEntity(e)
}
s.debugLadderRects = nil
for _, e := range s.debugFireRects {
ECS.RemoveEntity(e)
}
s.debugFireRects = nil
}
func (s *MovementSystem) addDebugCollisionRects() {
s.removeDebugRects()
for _, rect := range s.collisionRects {
c := color.RGBA{200, 200, 200, 150}
debugRect := drawDebugRect(rect, c, true)
s.debugCollisionRects = append(s.debugCollisionRects, debugRect)
}
for _, rect := range s.ladderRects {
c := color.RGBA{0, 0, 200, 150}
debugRect := drawDebugRect(rect, c, true)
s.debugLadderRects = append(s.debugLadderRects, debugRect)
}
for _, rect := range s.fireRects {
c := color.RGBA{200, 0, 0, 150}
debugRect := drawDebugRect(rect, c, false)
s.debugFireRects = append(s.debugFireRects, debugRect)
}
}
func (s *MovementSystem) UpdateDebugCollisionRects() {
if world.World.Debug < 2 {
s.removeDebugRects()
return
} else if len(s.debugCollisionRects) == 0 {
s.addDebugCollisionRects()
}
for i, debugRect := range s.debugCollisionRects {
sprite := ECS.Component(debugRect, component.SpriteComponentID).(*component.SpriteComponent)
if s.OnGround == i {
sprite.ColorScale = 1
} else {
sprite.ColorScale = 0.4
}
}
for i, debugRect := range s.debugLadderRects {
sprite := ECS.Component(debugRect, component.SpriteComponentID).(*component.SpriteComponent)
if s.OnLadder == i {
sprite.ColorScale = 1
} else {
sprite.ColorScale = 0.4
}
}
}
func (_ *MovementSystem) Needs() []gohan.ComponentID {
return []gohan.ComponentID{
component.PositionComponentID,
@ -189,10 +95,12 @@ func (s *MovementSystem) Update(ctx *gohan.Context) error {
position.Y += diff
}
world.World.PlayerX, world.World.PlayerY = position.X, position.Y
playerRect := image.Rect(int(position.X), int(position.Y), int(position.X+world.World.PlayerWidth), int(position.Y+world.World.PlayerHeight))
for _, r := range world.World.HazardRects {
if playerRect.Overlaps(r) {
world.World.SetGameOver()
world.World.SetGameOver(0, 0)
return nil
}
}
@ -205,7 +113,22 @@ func (s *MovementSystem) Update(ctx *gohan.Context) error {
}
}
// TODO check bullet kill player
// Check bullet collision.
if world.World.NoClip {
return nil
}
bullet := ECS.Component(ctx.Entity, component.BulletComponentID)
bulletSize := 8.0
if bullet != nil {
r := image.Rect(int(position.X), int(position.Y), int(position.X+bulletSize), int(position.Y+bulletSize))
playerRect := image.Rect(int(world.World.PlayerX), int(world.World.PlayerY), int(world.World.PlayerX+world.World.PlayerWidth), int(world.World.PlayerY+world.World.PlayerHeight))
if playerRect.Overlaps(r) {
world.World.SetGameOver(velocity.X, velocity.Y)
return nil
}
}
return nil
}
@ -217,23 +140,3 @@ func (s *MovementSystem) levelCoordinatesToScreen(x, y float64) (float64, float6
func (_ *MovementSystem) Draw(_ *gohan.Context, screen *ebiten.Image) error {
return gohan.ErrSystemWithoutDraw
}
func (s *MovementSystem) RecordPosition(position *component.PositionComponent) {
if math.Abs(position.X-s.playerPosition[0]) >= rewindThreshold || math.Abs(position.Y-s.playerPosition[1]) >= rewindThreshold {
s.playerPosition[0], s.playerPosition[1] = position.X, position.Y
s.playerPositions = append(s.playerPositions, s.playerPosition)
}
}
func (s *MovementSystem) RemoveLastPosition() {
if len(s.playerPositions) == 0 {
return
}
s.playerPositions = s.playerPositions[:len(s.playerPositions)-1]
if len(s.playerPositions) > 1 {
s.playerPosition = s.playerPositions[len(s.playerPositions)-1]
} else {
s.playerPosition[0], s.playerPosition[1] = 0, 0
}
}

View File

@ -3,6 +3,7 @@ package world
import (
"image"
"log"
"math/rand"
"path/filepath"
"code.rocketnine.space/tslocum/brownboxbatman/entity"
@ -15,6 +16,17 @@ import (
"github.com/lafriks/go-tiled"
)
const (
SoundGunshot = iota
SoundVampireDie1
SoundVampireDie2
SoundBat
SoundPlayerHurt
SoundPlayerDie
SoundPickup
SoundMunch
)
var World = &GameWorld{
GameStarted: true, // TODO
CamScale: 1,
@ -40,6 +52,8 @@ type GameWorld struct {
MessageVisible bool
PlayerX, PlayerY float64
CamX, CamY float64
CamScale float64
CamMoving bool
@ -141,8 +155,10 @@ func LoadMap(filePath string) {
e := createTileEntity(t, x, y)
if layer.Name == "CREEPS" {
creep := &component.CreepComponent{
Health: 1,
FireRate: 144 / 2,
Health: 1,
FireAmount: 8,
FireRate: 144 / 4,
Rand: rand.New(rand.NewSource(int64(t.ID))),
}
ECS.AddComponent(e, creep)
}
@ -205,32 +221,50 @@ func LevelCoordinatesToScreen(x, y float64) (float64, float64) {
return (x - World.CamX) * World.CamScale, (y - World.CamY) * World.CamScale
}
func (w *GameWorld) SetGameOver() {
func (w *GameWorld) SetGameOver(vx, vy float64) {
if w.GameOver {
return
}
w.GameOver = true
if rand.Intn(100) == 7 {
asset.SoundBatHit4.Play()
} else {
deathSound := rand.Intn(3)
switch deathSound {
case 0:
asset.SoundBatHit1.Play()
case 1:
asset.SoundBatHit2.Play()
case 2:
asset.SoundBatHit3.Play()
}
}
sprite := ECS.Component(w.Player, component.SpriteComponentID).(*component.SpriteComponent)
sprite.Image = ebiten.NewImage(1, 1)
position := ECS.Component(w.Player, component.PositionComponentID).(*component.PositionComponent)
velocity := ECS.Component(w.Player, component.VelocityComponentID).(*component.VelocityComponent)
if vx == 0 && vy == 0 {
velocity := ECS.Component(w.Player, component.VelocityComponentID).(*component.VelocityComponent)
vx, vy = velocity.X, velocity.Y
}
xSpeedA := 1.5
xSpeedB := -1.5
ySpeedA := -1.5
ySpeedB := -1.5
if velocity.Y > 0 {
if vy > 0 {
ySpeedA = 1.5
ySpeedB = 1.5
} else if velocity.X < 0 {
} else if vx < 0 {
xSpeedA = -1.5
xSpeedB = -1.5
ySpeedA = -1.5
ySpeedB = 1.5
} else if velocity.X > 0 {
} else if vx > 0 {
xSpeedA = 1.5
xSpeedB = 1.5
ySpeedA = -1.5