added player loop detection, loose level if last player loops

This commit is contained in:
Thomas von Dein 2024-02-27 18:20:00 +01:00
parent 5d2475d525
commit 707281212a
6 changed files with 76 additions and 25 deletions

14
TODO.md
View File

@ -2,9 +2,6 @@
- ignore comments in lvl files - ignore comments in lvl files
- check when sphere bounces from one end to the other endlessly w/o
any solids in between. this is a game lock and equals game over
- Start the game end timer and add a score FIXME: put the actual max - Start the game end timer and add a score FIXME: put the actual max
reachable score into the level definition along with the minimum reachable score into the level definition along with the minimum
steps required to reach it, also add a step counter steps required to reach it, also add a step counter
@ -15,19 +12,8 @@
- Add some final message when the player reaches the last level, start - Add some final message when the player reaches the last level, start
from scratch or a message to buy me some beer, whatever from scratch or a message to buy me some beer, whatever
- Check player-player collisions!
- Add player mergers, possibly as an option, so maybe we could have
different primary and secondary players: one pair can merge, the
other not. Like S + s and M + m or something.
- Add shaders for animation (player destruction etc) - Add shaders for animation (player destruction etc)
- Add player HUD + Stats (as hud_system!)
- Entity Observers don't work, removed entities remain there, errors
about dead entities, had to add Alive checks, and in obstacle_system
even remove the player manually from the world.
## Collider Rework ## Collider Rework

View File

@ -4,14 +4,14 @@ Background: background-lila
# W
S S # o
#
o
#

View File

@ -5,6 +5,9 @@ import "github.com/hajimehoshi/ebiten/v2"
type Player struct { type Player struct {
IsPrimary bool IsPrimary bool
Sprites []*ebiten.Image Sprites []*ebiten.Image
LoopPos *Position
LoopStart bool
LoopCount int
} }
func (player *Player) SwitchSprite() *ebiten.Image { func (player *Player) SwitchSprite() *ebiten.Image {

View File

@ -44,7 +44,7 @@ func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer)) systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer))
systemlist = append(systemlist, systemlist = append(systemlist,
systems.NewPlayerSystem(game.World, gridcontainer)) systems.NewPlayerSystem(game.World, gridcontainer, game.ScreenWidth, game.ScreenHeight))
systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize)) systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize))

View File

@ -90,6 +90,7 @@ func NewGrid(world *ecs.World,
vel.Speed = config.PLAYERSPEED vel.Speed = config.PLAYERSPEED
player.IsPrimary = tile.IsPrimary player.IsPrimary = tile.IsPrimary
player.Sprites = tile.Tiles player.Sprites = tile.Tiles
player.LoopPos = &components.Position{Cellsize: tilesize}
case tile.Collectible: case tile.Collectible:
entity := colmapper.New() entity := colmapper.New()
pos, render, _ = colmapper.Get(entity) pos, render, _ = colmapper.Get(entity)

View File

@ -18,13 +18,16 @@ type PlayerSystem struct {
World *ecs.World World *ecs.World
Selector *generic.Filter4[Position, Velocity, Player, Renderable] Selector *generic.Filter4[Position, Velocity, Player, Renderable]
GridContainer *grid.GridContainer GridContainer *grid.GridContainer
Width, Height int
} }
func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer) System { func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer, width, height int) System {
system := &PlayerSystem{ system := &PlayerSystem{
Selector: generic.NewFilter4[Position, Velocity, Player, Renderable](), Selector: generic.NewFilter4[Position, Velocity, Player, Renderable](),
GridContainer: gridcontainer, GridContainer: gridcontainer,
World: world, World: world,
Width: width,
Height: height,
} }
return system return system
@ -48,7 +51,7 @@ func (system PlayerSystem) Update() error {
} }
// check if the user alters or initiates movement // check if the user alters or initiates movement
system.CheckMovement(velocity) system.CheckMovement(playerposition, velocity, player)
// check if player collides with walls or edges // check if player collides with walls or edges
system.CheckGridCollision(playerposition, velocity) system.CheckGridCollision(playerposition, velocity)
@ -56,6 +59,15 @@ func (system PlayerSystem) Update() error {
if count > 1 { if count > 1 {
// check if player collides with another player, fuse them if any // check if player collides with another player, fuse them if any
EntitiesToRemove = system.CheckPlayerCollision(playerposition, velocity, query.Entity()) EntitiesToRemove = system.CheckPlayerCollision(playerposition, velocity, query.Entity())
} else {
// only 1 player left or one is max
EntitiesToRemove = system.CheckPlayerLooping(
playerposition, velocity, player, query.Entity())
}
if !velocity.Moving() {
// disable loop detection
player.LoopCount = 0
} }
} }
@ -130,17 +142,33 @@ func (system *PlayerSystem) SwitchPlayers() {
} }
} }
func (system *PlayerSystem) CheckMovement(velocity *components.Velocity) { func (system *PlayerSystem) CheckMovement(
position *components.Position,
velocity *components.Velocity,
player *components.Player) {
moved := false
if !velocity.Moving() { if !velocity.Moving() {
switch { switch {
case ebiten.IsKeyPressed(ebiten.KeyRight): case ebiten.IsKeyPressed(ebiten.KeyRight):
velocity.Change(East) velocity.Change(East)
moved = true
case ebiten.IsKeyPressed(ebiten.KeyLeft): case ebiten.IsKeyPressed(ebiten.KeyLeft):
velocity.Change(West) velocity.Change(West)
moved = true
case ebiten.IsKeyPressed(ebiten.KeyDown): case ebiten.IsKeyPressed(ebiten.KeyDown):
velocity.Change(South) velocity.Change(South)
moved = true
case ebiten.IsKeyPressed(ebiten.KeyUp): case ebiten.IsKeyPressed(ebiten.KeyUp):
velocity.Change(North) velocity.Change(North)
moved = true
}
if moved {
// will be reset every time the user initiates player movement
player.LoopPos.Set(position)
player.LoopCount = 0
} }
} }
} }
@ -161,9 +189,6 @@ func (system *PlayerSystem) CheckGridCollision(
tilepos.Point(), "playerpos", playerposition) tilepos.Point(), "playerpos", playerposition)
intersects, newpos := tilepos.Intersects(playerposition, velocity) intersects, newpos := tilepos.Intersects(playerposition, velocity)
if intersects { if intersects {
// slog.Debug("collision detected", "tile",
// tilepos, "player", playerposition, "new", newpos)
playerposition.Set(newpos) playerposition.Set(newpos)
velocity.Change(Stop) velocity.Change(Stop)
} }
@ -202,3 +227,39 @@ func (system *PlayerSystem) CheckPlayerCollision(
// FIXME: add an animation highlighting the fusion // FIXME: add an animation highlighting the fusion
return EntitiesToRemove return EntitiesToRemove
} }
func (system *PlayerSystem) CheckPlayerLooping(
position *components.Position,
velocity *components.Velocity,
player *components.Player,
entity ecs.Entity) []ecs.Entity {
if player.LoopPos.Rect == nil {
// hasn't moved
return nil
}
EntitiesToRemove := []ecs.Entity{}
ok, _ := player.LoopPos.Intersects(position, velocity)
if ok {
// Fatal: loop detected with last player
player.LoopStart = true
} else {
// no intersection with old pos anymore
if player.LoopStart {
// loop detection active
player.LoopCount++
max := system.Width
if velocity.Direction == North || velocity.Direction == South {
max = system.Height
}
// we haved moved one time around the whole screen, loose
if (player.LoopCount * velocity.Speed) > max {
EntitiesToRemove = append(EntitiesToRemove, entity)
}
}
}
return EntitiesToRemove
}