diff --git a/README.md b/README.md index 41b2676..04a9217 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,11 @@ Run `~/go/bin/brownboxbatman` to play. Please share issues and suggestions [here](https://code.rocketnine.space/tslocum/brownboxbatman/issues). +## Credits + +- [Trevor Slocum](https://rocketnine.space) - Game design and programming +- [node punk](https://open.spotify.com/artist/15eFpWQPNRxB89PnFNWvjU?si=z-jfVwYHTxugaC-BGZiyNg) - Music + ## Dependencies - [ebiten](https://github.com/hajimehoshi/ebiten) - Game engine diff --git a/asset/asset.go b/asset/asset.go index e79c262..f222785 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -1,16 +1,16 @@ package asset import ( + "bytes" "embed" "image" "image/color" _ "image/png" - "github.com/hajimehoshi/ebiten/v2/audio/wav" - - "github.com/hajimehoshi/ebiten/v2/audio" - "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/audio" + "github.com/hajimehoshi/ebiten/v2/audio/vorbis" + "github.com/hajimehoshi/ebiten/v2/audio/wav" ) const sampleRate = 44100 @@ -38,6 +38,8 @@ var ( SoundBatHit2 *audio.Player SoundBatHit3 *audio.Player SoundBatHit4 *audio.Player + + SoundLevelMusic *audio.Player ) func init() { @@ -50,6 +52,10 @@ func LoadSounds(ctx *audio.Context) { SoundBatHit2 = LoadWAV(ctx, "sound/bat_hit/hit2.wav") SoundBatHit3 = LoadWAV(ctx, "sound/bat_hit/hit3.wav") SoundBatHit4 = LoadWAV(ctx, "sound/bonk.wav") + + SoundLevelMusic = LoadOGG(ctx, "sound/level_music.ogg") + + SoundLevelMusic.SetVolume(0.5) } func LoadImage(p string) *ebiten.Image { @@ -101,3 +107,26 @@ func LoadWAV(context *audio.Context, p string) *audio.Player { return player } + +func LoadOGG(context *audio.Context, p string) *audio.Player { + b := LoadBytes(p) + + stream, err := vorbis.DecodeWithSampleRate(sampleRate, bytes.NewReader(b)) + if err != nil { + panic(err) + } + + player, err := context.NewPlayer(audio.NewInfiniteLoop(stream, stream.Length())) + 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 +} diff --git a/asset/sound/level_music.ogg b/asset/sound/level_music.ogg new file mode 100644 index 0000000..a12a4fe Binary files /dev/null and b/asset/sound/level_music.ogg differ diff --git a/component/creepbullet.go b/component/creepbullet.go index 4b39cf8..8a7bfef 100644 --- a/component/creepbullet.go +++ b/component/creepbullet.go @@ -6,6 +6,7 @@ import ( ) type CreepBulletComponent struct { + Invulnerable bool // Invulnerable to hazards } var CreepBulletComponentID = ECS.NewComponentID() diff --git a/flags.go b/flags.go index 6393433..ad01c28 100644 --- a/flags.go +++ b/flags.go @@ -25,8 +25,8 @@ func parseFlags() { ebiten.SetFullscreen(true) } - if noSplash { - world.World.GameStarted = true + if noSplash || world.World.Debug > 0 { + world.StartGame() //world.World.MessageVisible = false } } diff --git a/game/game.go b/game/game.go index 02b3d00..38c530f 100644 --- a/game/game.go +++ b/game/game.go @@ -9,14 +9,12 @@ import ( "sync" "time" - "code.rocketnine.space/tslocum/brownboxbatman/entity" - "code.rocketnine.space/tslocum/brownboxbatman/asset" "code.rocketnine.space/tslocum/brownboxbatman/component" . "code.rocketnine.space/tslocum/brownboxbatman/ecs" + "code.rocketnine.space/tslocum/brownboxbatman/entity" "code.rocketnine.space/tslocum/brownboxbatman/system" "code.rocketnine.space/tslocum/brownboxbatman/world" - "code.rocketnine.space/tslocum/gohan" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/audio" "golang.org/x/text/language" @@ -51,8 +49,6 @@ const sampleRate = 44100 type game struct { w, h int - player gohan.Entity - audioContext *audio.Context op *ebiten.DrawImageOptions @@ -65,6 +61,8 @@ type game struct { movementSystem *system.MovementSystem renderSystem *system.RenderSystem + addedSystems bool + sync.Mutex } @@ -78,21 +76,6 @@ func NewGame() (*game, error) { const numEntities = 30000 ECS.Preallocate(numEntities) - g.changeMap("map/m1.tmx") - - g.addSystems() - - err := g.loadAssets() - if err != nil { - return nil, err - } - - asset.ImgWhiteSquare.Fill(color.White) - - asset.LoadSounds(g.audioContext) - - rand.Seed(time.Now().UnixNano()) - return g, nil } @@ -105,7 +88,6 @@ func (g *game) changeMap(filePath string) { if world.World.Player == 0 { world.World.Player = entity.NewPlayer() - g.player = world.World.Player } const playerStartOffset = 128 @@ -114,7 +96,7 @@ func (g *game) changeMap(filePath string) { w := float64(world.World.Map.Width * world.World.Map.TileWidth) h := float64(world.World.Map.Height * world.World.Map.TileHeight) - position := ECS.Component(g.player, component.PositionComponentID).(*component.PositionComponent) + position := ECS.Component(world.World.Player, component.PositionComponentID).(*component.PositionComponent) position.X, position.Y = w/2, h-playerStartOffset world.World.CamX, world.World.CamY = 0, h-camStartOffset @@ -141,6 +123,32 @@ func (g *game) Update() error { return nil } + if world.World.ResetGame { + world.Reset() + + g.changeMap("map/m1.tmx") + + if !g.addedSystems { + g.addSystems() + + err := g.loadAssets() + if err != nil { + return err + } + + asset.ImgWhiteSquare.Fill(color.White) + + asset.LoadSounds(g.audioContext) + + g.addedSystems = true // TODO + } + + rand.Seed(time.Now().UnixNano()) + + world.World.ResetGame = false + world.World.GameOver = false + } + err := ECS.Update() if err != nil { return err @@ -160,7 +168,7 @@ func (g *game) addSystems() { g.movementSystem = system.NewMovementSystem() - ecs.AddSystem(system.NewPlayerMoveSystem(g.player, g.movementSystem)) + ecs.AddSystem(system.NewPlayerMoveSystem(world.World.Player, g.movementSystem)) ecs.AddSystem(system.NewplayerFireSystem()) ecs.AddSystem(g.movementSystem) @@ -169,19 +177,21 @@ func (g *game) addSystems() { ecs.AddSystem(system.NewCameraSystem()) ecs.AddSystem(system.NewRailSystem()) - /*ecs.AddSystem(system.NewFireWeaponSystem(g.player)) + /*ecs.AddSystem(system.NewFireWeaponSystem(world.World.Player)) ecs.AddSystem(system.NewRenderBackgroundSystem())*/ g.renderSystem = system.NewRenderSystem() ecs.AddSystem(g.renderSystem) - /*g.messageSystem = system.NewRenderMessageSystem(g.player) + /*g.messageSystem = system.NewRenderMessageSystem(world.World.Player) ecs.AddSystem(g.messageSystem)*/ - ecs.AddSystem(system.NewRenderDebugTextSystem(g.player)) + ecs.AddSystem(system.NewRenderMessageSystem()) - ecs.AddSystem(system.NewProfileSystem(g.player)) + ecs.AddSystem(system.NewRenderDebugTextSystem(world.World.Player)) + + ecs.AddSystem(system.NewProfileSystem(world.World.Player)) // TODO /* @@ -195,7 +205,7 @@ func (g *game) loadAssets() error { } func (g *game) WarpTo(x, y float64) { - position := ECS.Component(g.player, component.PositionComponentID).(*component.PositionComponent) + position := ECS.Component(world.World.Player, component.PositionComponentID).(*component.PositionComponent) position.X, position.Y = x, y log.Printf("Warped to %.2f,%.2f", x, y) } diff --git a/go.mod b/go.mod index cacb02d..1115c0f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module code.rocketnine.space/tslocum/brownboxbatman go 1.17 require ( - code.rocketnine.space/tslocum/gohan v0.0.0-20211212050415-e08cfe7970d8 + code.rocketnine.space/tslocum/gohan v0.0.0-20211229205912-263cd48bca66 github.com/hajimehoshi/ebiten/v2 v2.2.3 github.com/lafriks/go-tiled v0.6.0 golang.org/x/text v0.3.7 @@ -11,8 +11,10 @@ require ( require ( github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec // indirect - github.com/hajimehoshi/oto/v2 v2.1.0-alpha.4 // indirect + github.com/hajimehoshi/oto/v2 v2.1.0-alpha.5 // indirect github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect + github.com/jfreymuth/oggvorbis v1.0.3 // indirect + github.com/jfreymuth/vorbis v1.0.2 // indirect golang.org/x/exp v0.0.0-20211221223016-e29036178569 // indirect golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect diff --git a/go.sum b/go.sum index 8bfd430..81e47b2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -code.rocketnine.space/tslocum/gohan v0.0.0-20211212050415-e08cfe7970d8 h1:tsIId//EUkKtk0v2wNEv6qUmS2yDnOTd7cmXhQemDyE= -code.rocketnine.space/tslocum/gohan v0.0.0-20211212050415-e08cfe7970d8/go.mod h1:nOvFBFvFPl5sDtkMy2Fn/7QZcWq5RE98/mK+INLqIWg= +code.rocketnine.space/tslocum/gohan v0.0.0-20211229205912-263cd48bca66 h1:H8rapZ2HCQZH+DpcXRZh52kUcXDzgHlJwsZgEGqezpo= +code.rocketnine.space/tslocum/gohan v0.0.0-20211229205912-263cd48bca66/go.mod h1:nOvFBFvFPl5sDtkMy2Fn/7QZcWq5RE98/mK+INLqIWg= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -14,12 +14,14 @@ github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hajimehoshi/oto/v2 v2.1.0-alpha.2/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ= -github.com/hajimehoshi/oto/v2 v2.1.0-alpha.4 h1:6NIzk6tIJIOUB7mB00FtE5pz0Yt9LDBPcGirBIteJsI= -github.com/hajimehoshi/oto/v2 v2.1.0-alpha.4/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ= +github.com/hajimehoshi/oto/v2 v2.1.0-alpha.5 h1:AwLKf51fpOTVIBxgQUvNokmj/IaYMYsqJQBh6wif1c8= +github.com/hajimehoshi/oto/v2 v2.1.0-alpha.5/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ= github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4= +github.com/jfreymuth/oggvorbis v1.0.3 h1:MLNGGyhOMiVcvea9Dp5+gbs2SAwqwQbtrWnonYa0M0Y= github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= +github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE= github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ= github.com/lafriks/go-tiled v0.6.0 h1:kDJSvRPep6/5dtfdu0hqiAhXL40HmciMEXRb1TN/AU0= github.com/lafriks/go-tiled v0.6.0/go.mod h1:xy+4iO8AKWpFNBWeqBqnq+Cb3Oirm5oin/irP/jPx6A= diff --git a/main.go b/main.go index 80aefd1..d1c62cc 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,8 @@ import ( "os/signal" "syscall" + "code.rocketnine.space/tslocum/brownboxbatman/world" + "code.rocketnine.space/tslocum/brownboxbatman/game" "github.com/hajimehoshi/ebiten/v2" ) @@ -27,6 +29,10 @@ func main() { parseFlags() + if world.World.Debug == 0 { + world.SetMessage("MOVE: ARROW KEYS\nFIRE: Z KEY\nMUTE: M KEY", 144*4) + } + sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, diff --git a/system/camera.go b/system/camera.go index ed197e9..949d841 100644 --- a/system/camera.go +++ b/system/camera.go @@ -29,7 +29,7 @@ func (_ *CameraSystem) Uses() []gohan.ComponentID { } func (s *CameraSystem) Update(ctx *gohan.Context) error { - if world.World.MessageVisible || !world.World.GameStarted || world.World.GameOver { + if !world.World.GameStarted || world.World.GameOver { return nil } diff --git a/system/creep.go b/system/creep.go index c72a5aa..00117d4 100644 --- a/system/creep.go +++ b/system/creep.go @@ -31,7 +31,7 @@ func (_ *CreepSystem) Uses() []gohan.ComponentID { } func (s *CreepSystem) Update(ctx *gohan.Context) error { - if world.World.MessageVisible || !world.World.GameStarted { + if !world.World.GameStarted { return nil } diff --git a/system/input_move.go b/system/input_move.go index 73cb054..90a31a2 100644 --- a/system/input_move.go +++ b/system/input_move.go @@ -3,6 +3,8 @@ package system import ( "os" + "code.rocketnine.space/tslocum/brownboxbatman/asset" + "code.rocketnine.space/tslocum/brownboxbatman/component" "code.rocketnine.space/tslocum/brownboxbatman/world" "code.rocketnine.space/tslocum/gohan" @@ -70,14 +72,22 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error { if !world.World.GameStarted { if ebiten.IsKeyPressed(ebiten.KeyEnter) || ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { - world.World.GameStarted = true + world.StartGame() } return nil } - if world.World.MessageVisible { + if inpututil.IsKeyJustPressed(ebiten.KeyM) { + if asset.SoundLevelMusic.IsPlaying() { + asset.SoundLevelMusic.Pause() + } else { + asset.SoundLevelMusic.Play() + } + } + + if world.World.GameOver { if inpututil.IsKeyJustPressed(ebiten.KeyEnter) { - world.World.MessageVisible = false + world.World.ResetGame = true } return nil } diff --git a/system/movement.go b/system/movement.go index 6cd814e..749e7e3 100644 --- a/system/movement.go +++ b/system/movement.go @@ -59,7 +59,7 @@ func (_ *MovementSystem) Uses() []gohan.ComponentID { } func (s *MovementSystem) Update(ctx *gohan.Context) error { - if !world.World.GameStarted || world.World.MessageVisible { + if !world.World.GameStarted { return nil } @@ -78,6 +78,7 @@ func (s *MovementSystem) Update(ctx *gohan.Context) error { position.X, position.Y = position.X+vx, position.Y+vy // Force player to remain within the screen bounds. + // TODO same for bullets if ctx.Entity == world.World.Player { screenX, screenY := s.levelCoordinatesToScreen(position.X, position.Y) if screenX < 0 { @@ -125,10 +126,17 @@ func (s *MovementSystem) Update(ctx *gohan.Context) error { // Check hazard collisions. if creepBullet != nil || playerBullet != nil { - for _, hazardRect := range world.World.HazardRects { - if bulletRect.Overlaps(hazardRect) { - ctx.RemoveEntity() - return nil + var invulnerable bool + if creepBullet != nil { + b := creepBullet.(*component.CreepBulletComponent) + invulnerable = b.Invulnerable + } + if !invulnerable { + for _, hazardRect := range world.World.HazardRects { + if bulletRect.Overlaps(hazardRect) { + ctx.RemoveEntity() + return nil + } } } } diff --git a/system/rail.go b/system/rail.go index bc6849d..fad8fe8 100644 --- a/system/rail.go +++ b/system/rail.go @@ -27,7 +27,7 @@ func (_ *RailSystem) Uses() []gohan.ComponentID { } func (s *RailSystem) Update(ctx *gohan.Context) error { - if !world.World.GameStarted || world.World.MessageVisible || world.World.GameOver || !world.World.CamMoving { + if !world.World.GameStarted || world.World.GameOver || !world.World.CamMoving { return nil } diff --git a/system/rendermessage.go b/system/rendermessage.go new file mode 100644 index 0000000..677080a --- /dev/null +++ b/system/rendermessage.go @@ -0,0 +1,100 @@ +package system + +import ( + "image/color" + "strings" + + "code.rocketnine.space/tslocum/brownboxbatman/component" + "code.rocketnine.space/tslocum/brownboxbatman/world" + "code.rocketnine.space/tslocum/gohan" + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" +) + +type RenderMessageSystem struct { + op *ebiten.DrawImageOptions + logoImg *ebiten.Image + msgImg *ebiten.Image + tmpImg *ebiten.Image +} + +func NewRenderMessageSystem() *RenderMessageSystem { + s := &RenderMessageSystem{ + op: &ebiten.DrawImageOptions{}, + logoImg: ebiten.NewImage(1, 1), + msgImg: ebiten.NewImage(1, 1), + tmpImg: ebiten.NewImage(200, 200), + } + + return s +} + +func (s *RenderMessageSystem) Needs() []gohan.ComponentID { + return []gohan.ComponentID{ + component.PositionComponentID, + component.VelocityComponentID, + component.WeaponComponentID, + } +} + +func (s *RenderMessageSystem) Uses() []gohan.ComponentID { + return nil +} + +func (s *RenderMessageSystem) Update(_ *gohan.Context) error { + if !world.World.GameStarted || world.World.GameOver || !world.World.MessageVisible { + return nil + } + + world.World.MessageTicks++ + if world.World.MessageTicks == world.World.MessageDuration { + world.World.MessageVisible = false + return nil + } + return nil +} + +func (s *RenderMessageSystem) Draw(_ *gohan.Context, screen *ebiten.Image) error { + if !world.World.GameStarted || !world.World.MessageVisible { + return nil + } + + // Draw message. + if world.World.MessageUpdated { + s.drawMessage() + } + bounds := s.msgImg.Bounds() + x := (float64(world.World.ScreenW) / 2) - (float64(bounds.Dx()) / 2) + y := (float64(world.World.ScreenH) / 2) - (float64(bounds.Dy()) / 2) + s.op.GeoM.Reset() + s.op.GeoM.Translate(x, y) + screen.DrawImage(s.msgImg, s.op) + return nil +} + +func (s *RenderMessageSystem) drawMessage() { + split := strings.Split(world.World.MessageText, "\n") + width := 0 + for _, line := range split { + lineSize := len(line) * 12 + if lineSize > width { + width = lineSize + } + } + height := len(split) * 32 + + const padding = 8 + width, height = width+padding*2, height+padding*2 + + s.msgImg = ebiten.NewImage(width, height) + s.msgImg.Fill(color.RGBA{17, 17, 17, 255}) + + s.tmpImg.Clear() + s.tmpImg = ebiten.NewImage(width*2, height*2) + s.op.GeoM.Reset() + s.op.GeoM.Scale(2, 2) + s.op.GeoM.Translate(float64(padding), float64(padding)) + ebitenutil.DebugPrint(s.tmpImg, world.World.MessageText) + s.msgImg.DrawImage(s.tmpImg, s.op) + s.op.ColorM.Reset() +} diff --git a/world/world.go b/world/world.go index eb9c54d..bd6e368 100644 --- a/world/world.go +++ b/world/world.go @@ -3,6 +3,7 @@ package world import ( "image" "log" + "math" "math/rand" "path/filepath" @@ -32,6 +33,7 @@ var World = &GameWorld{ PlayerWidth: 8, PlayerHeight: 32, TileImages: make(map[uint32]*ebiten.Image), + ResetGame: true, } type GameWorld struct { @@ -50,7 +52,11 @@ type GameWorld struct { GameStartedTicks int GameOver bool - MessageVisible bool + MessageVisible bool + MessageTicks int + MessageDuration int + MessageUpdated bool + MessageText string PlayerX, PlayerY float64 @@ -75,10 +81,10 @@ type GameWorld struct { BrokenPieceA, BrokenPieceB gohan.Entity TileImages map[uint32]*ebiten.Image -} -func SetMessage(message string) { - // TODO + ResetGame bool + + resetTipShown bool } func TileToGameCoords(x, y int) (float64, float64) { @@ -86,6 +92,23 @@ func TileToGameCoords(x, y int) (float64, float64) { return float64(x) * 32, float64(y) * 32 } +func Reset() { + for _, e := range ECS.Entities() { + ECS.RemoveEntity(e) + } + World.Player = 0 + + World.ObjectGroups = nil + World.HazardRects = nil + World.CreepRects = nil + World.CreepEntities = nil + World.TriggerEntities = nil + World.TriggerRects = nil + World.TriggerNames = nil + + World.MessageVisible = false +} + func LoadMap(filePath string) { loader := tiled.Loader{ FileSystem: asset.FS, @@ -228,15 +251,19 @@ func (w *GameWorld) SetGameOver(vx, vy float64) { w.GameOver = true if rand.Intn(100) == 7 { + asset.SoundBatHit4.Rewind() asset.SoundBatHit4.Play() } else { deathSound := rand.Intn(3) switch deathSound { case 0: + asset.SoundBatHit1.Rewind() asset.SoundBatHit1.Play() case 1: + asset.SoundBatHit2.Rewind() asset.SoundBatHit2.Play() case 2: + asset.SoundBatHit3.Rewind() asset.SoundBatHit3.Play() } } @@ -275,12 +302,25 @@ func (w *GameWorld) SetGameOver(vx, vy float64) { Image: asset.ImgBatBroken1, } ECS.AddComponent(w.BrokenPieceA, pieceASprite) + ECS.AddComponent(w.BrokenPieceA, &component.CreepBulletComponent{ + Invulnerable: true, + }) w.BrokenPieceB = entity.NewCreepBullet(position.X, position.Y, xSpeedB, ySpeedB) pieceBSprite := &component.SpriteComponent{ Image: asset.ImgBatBroken2, } ECS.AddComponent(w.BrokenPieceB, pieceBSprite) + ECS.AddComponent(w.BrokenPieceB, &component.CreepBulletComponent{ + Invulnerable: true, + }) + + if !World.resetTipShown { + SetMessage(" GAME OVER\n\nRESET: ", math.MaxInt) + World.resetTipShown = true + } else { + SetMessage("GAME OVER", math.MaxInt) + } } // TODO move @@ -322,3 +362,20 @@ func NewCreep(creepType int, creepID int64, x float64, y float64) gohan.Entity { return creep } + +func StartGame() { + if World.GameStarted { + return + } + World.GameStarted = true + + asset.SoundLevelMusic.Play() +} + +func SetMessage(message string, duration int) { + World.MessageText = message + World.MessageVisible = true + World.MessageUpdated = true + World.MessageDuration = duration + World.MessageTicks = 0 +}