refactored recursive systems out of player_system => game/levels.go

This commit is contained in:
Thomas von Dein 2024-02-23 18:47:15 +01:00
parent c93070883a
commit ab07bc23e3
11 changed files with 198 additions and 183 deletions

View File

@ -11,13 +11,13 @@ import (
) )
type Game struct { type Game struct {
World *ecs.World World *ecs.World
Bounds image.Rectangle Bounds image.Rectangle
ScreenWidth, ScreenHeight int ScreenWidth, ScreenHeight, Cellsize int
Scenes map[SceneName]Scene Scenes map[SceneName]Scene
CurrentScene SceneName CurrentScene SceneName
Observer *observers.GameObserver Observer *observers.GameObserver
Levels []*Level // needed to feed select_scene Levels []*Level // needed to feed select_scene
} }
func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Game { func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Game {
@ -29,6 +29,7 @@ func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Gam
ScreenWidth: width, ScreenWidth: width,
ScreenHeight: height, ScreenHeight: height,
Scenes: map[SceneName]Scene{}, Scenes: map[SceneName]Scene{},
Cellsize: cellsize,
} }
observers.NewPlayerObserver(&world) observers.NewPlayerObserver(&world)

View File

@ -21,40 +21,53 @@ type Level struct {
Name string Name string
Description string Description string
Mapslice map[image.Point]*assets.Tile Mapslice map[image.Point]*assets.Tile
GridContainer *grid.GridContainer
Player *systems.PlayerSystem Systems []systems.System
GridSystem *systems.GridSystem Grid *grid.Grid
Grid *grid.Grid
} }
func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
gridsystem := systems.NewGridSystem(game.World, game.ScreenWidth, systemlist := []systems.System{}
game.ScreenHeight, cellsize, plan.Background)
playersystem := systems.NewPlayerSystem(game.World, gridsystem) gridcontainer := &grid.GridContainer{}
systemlist = append(systemlist,
systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize, plan.Background))
systemlist = append(systemlist, systems.NewCollectibleSystem(game.World))
systemlist = append(systemlist,
systems.NewPlayerSystem(game.World, gridcontainer))
systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize))
systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer))
systemlist = append(systemlist, systems.NewTransientSystem(game.World, gridcontainer))
return &Level{ return &Level{
Mapslice: LevelToSlice(game, plan, cellsize), Mapslice: LevelToSlice(game, plan, cellsize),
Cellsize: cellsize, Cellsize: cellsize,
World: game.World, World: game.World,
Width: game.ScreenWidth, Width: game.ScreenWidth,
Height: game.ScreenHeight, Height: game.ScreenHeight,
Description: plan.Description, Description: plan.Description,
Name: plan.Name, Name: plan.Name,
GridSystem: gridsystem, GridContainer: gridcontainer,
Player: playersystem, Systems: systemlist,
} }
} }
func (level *Level) Update() { func (level *Level) Update() {
level.GridSystem.Update() for _, sys := range level.Systems {
level.Player.Update() sys.Update()
}
} }
func (level *Level) Draw(screen *ebiten.Image) { func (level *Level) Draw(screen *ebiten.Image) {
level.GridSystem.Draw(screen) for _, sys := range level.Systems {
level.Player.Draw(screen) sys.Draw(screen)
}
} }
func (level *Level) Position2Point(position *components.Position) image.Point { func (level *Level) Position2Point(position *components.Position) image.Point {
@ -80,7 +93,7 @@ func (level *Level) SetupGrid(game *Game) {
playerobserver.RemoveEntities() playerobserver.RemoveEntities()
// setup world // setup world
level.GridSystem.SetGrid( level.GridContainer.SetGrid(
grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice)) grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice))
} }

82
grid/collider.go Normal file
View File

@ -0,0 +1,82 @@
package grid
import (
"openquell/components"
. "openquell/config"
)
func (grid *Grid) GetSolidNeighborPosition(
position *components.Position,
velocity *components.Velocity,
solid bool) (bool, *components.Position) {
if !solid {
return false, nil
}
// set to true, if we are already on the last tile in the current
// direction, i.e. on the edge of the grid
edge := true
neighborpos := position.Point()
switch velocity.Direction {
case East:
if neighborpos.X < grid.TilesX {
neighborpos.X++
edge = false
}
case West:
if neighborpos.X > 0 {
neighborpos.X--
edge = false
}
case South:
if neighborpos.Y < grid.TilesY {
neighborpos.Y++
edge = false
}
case North:
if neighborpos.Y > 0 {
neighborpos.Y--
edge = false
}
}
newpos := components.NewPosition(neighborpos, grid.Tilesize)
if !edge && grid.Map[neighborpos].Solid {
return true, newpos
}
return false, nil
}
func (grid *Grid) BumpEdge(
pos *components.Position,
velocity *components.Velocity) (bool, *components.Position) {
x := pos.X + velocity.Data.X
y := pos.Y + velocity.Data.Y
if x < 0 || x > grid.Width-grid.Tilesize || y < 0 || y > grid.Height-grid.Tilesize {
newpos := &components.Position{}
X := pos.X
Y := pos.Y
switch velocity.Direction {
case East:
X = 0
case West:
X = grid.Width - grid.Tilesize
case South:
Y = 0
case North:
Y = grid.Height - grid.Tilesize
}
newpos.Update(X, Y, grid.Tilesize)
return true, newpos
}
return false, nil
}

9
grid/container.go Normal file
View File

@ -0,0 +1,9 @@
package grid
type GridContainer struct {
Grid *Grid
}
func (container *GridContainer) SetGrid(grid *Grid) {
container.Grid = grid
}

View File

@ -13,12 +13,13 @@ import (
) )
type Grid struct { type Grid struct {
World *ecs.World World *ecs.World
Width int Width int
Height int Height int
Size int Size int
Tilesize int Tilesize int
Map map[image.Point]*assets.Tile TilesX, TilesY int
Map map[image.Point]*assets.Tile
} }
// FIXME: put component addition into extra callable function, to be called // FIXME: put component addition into extra callable function, to be called
@ -123,6 +124,8 @@ func NewGrid(world *ecs.World,
Height: height, Height: height,
Map: mapslice, Map: mapslice,
World: world, World: world,
TilesX: width / tilesize,
TilesY: height / tilesize,
} }
} }

View File

@ -19,7 +19,7 @@ type CollectibleSystem struct {
Selector *generic.Filter3[Position, Collectible, Renderable] Selector *generic.Filter3[Position, Collectible, Renderable]
} }
func NewCollectibleSystem(world *ecs.World) *CollectibleSystem { func NewCollectibleSystem(world *ecs.World) System {
system := &CollectibleSystem{ system := &CollectibleSystem{
Selector: generic.NewFilter3[Position, Collectible, Renderable](), Selector: generic.NewFilter3[Position, Collectible, Renderable](),
World: world, World: world,
@ -28,7 +28,7 @@ func NewCollectibleSystem(world *ecs.World) *CollectibleSystem {
return system return system
} }
func (system *CollectibleSystem) Update() { func (system *CollectibleSystem) Update() error {
playerobserver := observers.GetPlayerObserver(system.World) playerobserver := observers.GetPlayerObserver(system.World)
gameobserver := observers.GetGameObserver(system.World) gameobserver := observers.GetGameObserver(system.World)
@ -43,7 +43,7 @@ func (system *CollectibleSystem) Update() {
if numcollectibles == 0 || gameobserver.Lost { if numcollectibles == 0 || gameobserver.Lost {
query.Close() query.Close()
return return nil
} }
for query.Next() { for query.Next() {
@ -78,6 +78,8 @@ func (system *CollectibleSystem) Update() {
timer.Start(LEVEL_END_WAIT) timer.Start(LEVEL_END_WAIT)
} }
} }
return nil
} }
func (system *CollectibleSystem) Draw(screen *ebiten.Image) { func (system *CollectibleSystem) Draw(screen *ebiten.Image) {

View File

@ -3,10 +3,7 @@ package systems
import ( import (
"image" "image"
"image/draw" "image/draw"
"openquell/components"
. "openquell/components" . "openquell/components"
. "openquell/config"
"openquell/grid"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/ecs"
@ -14,18 +11,17 @@ import (
) )
type GridSystem struct { type GridSystem struct {
World *ecs.World World *ecs.World
Selector *generic.Filter3[Renderable, Position, Solid] Selector *generic.Filter3[Renderable, Position, Solid]
UseCache bool UseCache bool
Cache *ebiten.Image Cache *ebiten.Image
Count int // register tile count, invalidates cache Count int // register tile count, invalidates cache
Background *ebiten.Image Background *ebiten.Image
Width, Height, TilesX, TilesY, Tilesize int Width, Height, Tilesize int
Grid *grid.Grid
} }
func NewGridSystem(world *ecs.World, width, height, func NewGridSystem(world *ecs.World, width, height,
tilesize int, background *ebiten.Image) *GridSystem { tilesize int, background *ebiten.Image) System {
cache := ebiten.NewImage(width, height) cache := ebiten.NewImage(width, height)
@ -35,8 +31,6 @@ func NewGridSystem(world *ecs.World, width, height,
Cache: cache, Cache: cache,
Width: width, Width: width,
Height: height, Height: height,
TilesX: width / tilesize,
TilesY: height / tilesize,
Tilesize: tilesize, Tilesize: tilesize,
Background: background, Background: background,
World: world, World: world,
@ -45,11 +39,7 @@ func NewGridSystem(world *ecs.World, width, height,
return system return system
} }
func (system *GridSystem) SetGrid(grid *grid.Grid) { func (system *GridSystem) Update() error { return nil }
system.Grid = grid
}
func (system *GridSystem) Update() {}
func (system *GridSystem) Draw(screen *ebiten.Image) { func (system *GridSystem) Draw(screen *ebiten.Image) {
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
@ -81,79 +71,3 @@ func (system *GridSystem) Draw(screen *ebiten.Image) {
query.Close() query.Close()
} }
} }
func (system *GridSystem) GetSolidNeighborPosition(
position *components.Position,
velocity *components.Velocity,
solid bool) (bool, *components.Position) {
if !solid {
return false, nil
}
// set to true, if we are already on the last tile in the current
// direction, i.e. on the edge of the grid
edge := true
neighborpos := position.Point()
switch velocity.Direction {
case East:
if neighborpos.X < system.TilesX {
neighborpos.X++
edge = false
}
case West:
if neighborpos.X > 0 {
neighborpos.X--
edge = false
}
case South:
if neighborpos.Y < system.TilesY {
neighborpos.Y++
edge = false
}
case North:
if neighborpos.Y > 0 {
neighborpos.Y--
edge = false
}
}
newpos := components.NewPosition(neighborpos, system.Tilesize)
if !edge && system.Grid.Map[neighborpos].Solid {
return true, newpos
}
return false, nil
}
func (system *GridSystem) BumpEdge(
pos *components.Position,
velocity *components.Velocity) (bool, *components.Position) {
x := pos.X + velocity.Data.X
y := pos.Y + velocity.Data.Y
if x < 0 || x > system.Width-system.Tilesize || y < 0 || y > system.Height-system.Tilesize {
newpos := &components.Position{}
X := pos.X
Y := pos.Y
switch velocity.Direction {
case East:
X = 0
case West:
X = system.Width - system.Tilesize
case South:
Y = 0
case North:
Y = system.Height - system.Tilesize
}
newpos.Update(X, Y, system.Tilesize)
return true, newpos
}
return false, nil
}

View File

@ -6,6 +6,7 @@ import (
"openquell/components" "openquell/components"
. "openquell/components" . "openquell/components"
. "openquell/config" . "openquell/config"
"openquell/grid"
"openquell/observers" "openquell/observers"
"openquell/util" "openquell/util"
@ -15,28 +16,28 @@ import (
) )
type ObstacleSystem struct { type ObstacleSystem struct {
World *ecs.World World *ecs.World
Selector *generic.Filter5[Position, Velocity, Obstacle, Renderable, Speed] Selector *generic.Filter5[Position, Velocity, Obstacle, Renderable, Speed]
Grid *GridSystem GridContainer *grid.GridContainer
} }
func NewObstacleSystem(world *ecs.World, grid *GridSystem) *ObstacleSystem { func NewObstacleSystem(world *ecs.World, gridcontainer *grid.GridContainer) System {
system := &ObstacleSystem{ system := &ObstacleSystem{
Selector: generic.NewFilter5[Position, Velocity, Obstacle, Renderable, Speed](), Selector: generic.NewFilter5[Position, Velocity, Obstacle, Renderable, Speed](),
World: world, World: world,
Grid: grid, GridContainer: gridcontainer,
} }
return system return system
} }
func (system *ObstacleSystem) Update() { func (system *ObstacleSystem) Update() error {
playerobserver := observers.GetPlayerObserver(system.World) playerobserver := observers.GetPlayerObserver(system.World)
gameobserver := observers.GetGameObserver(system.World) gameobserver := observers.GetGameObserver(system.World)
obstacleobserver := observers.GetObstacleObserver(system.World) obstacleobserver := observers.GetObstacleObserver(system.World)
if gameobserver.Lost { if gameobserver.Lost {
return return nil
} }
posID := ecs.ComponentID[components.Position](system.World) posID := ecs.ComponentID[components.Position](system.World)
@ -94,7 +95,7 @@ func (system *ObstacleSystem) Update() {
// FIXME: this is the same loop as in player_system, unite the // FIXME: this is the same loop as in player_system, unite the
// two, just iterate over all entities with pos,vel,render, dammit // two, just iterate over all entities with pos,vel,render, dammit
if obsvelocity.Moving() { if obsvelocity.Moving() {
ok, tilepos := system.Grid.GetSolidNeighborPosition(obsposition, obsvelocity, true) ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(obsposition, obsvelocity, true)
if ok { if ok {
intersects, newpos := tilepos.Intersects(obsposition, obsvelocity) intersects, newpos := tilepos.Intersects(obsposition, obsvelocity)
if intersects { if intersects {
@ -125,6 +126,8 @@ func (system *ObstacleSystem) Update() {
gameobserver.Gameover() gameobserver.Gameover()
} }
return nil
} }
func (system *ObstacleSystem) Draw(screen *ebiten.Image) { func (system *ObstacleSystem) Draw(screen *ebiten.Image) {

View File

@ -15,7 +15,7 @@ type ParticleSystem struct {
Cellsize int Cellsize int
} }
func NewParticleSystem(world *ecs.World, cellsize int) *ParticleSystem { func NewParticleSystem(world *ecs.World, cellsize int) System {
system := &ParticleSystem{ system := &ParticleSystem{
Selector: generic.NewFilter3[Position, Particle, Timer](), Selector: generic.NewFilter3[Position, Particle, Timer](),
World: world, World: world,
@ -25,7 +25,7 @@ func NewParticleSystem(world *ecs.World, cellsize int) *ParticleSystem {
return system return system
} }
func (system *ParticleSystem) Update() { func (system *ParticleSystem) Update() error {
// display debris after collecting // display debris after collecting
EntitiesToRemove := []ecs.Entity{} EntitiesToRemove := []ecs.Entity{}
@ -56,6 +56,8 @@ func (system *ParticleSystem) Update() {
for _, entity := range EntitiesToRemove { for _, entity := range EntitiesToRemove {
system.World.RemoveEntity(entity) system.World.RemoveEntity(entity)
} }
return nil
} }
func (system *ParticleSystem) Draw(screen *ebiten.Image) { func (system *ParticleSystem) Draw(screen *ebiten.Image) {

View File

@ -4,6 +4,7 @@ import (
"log/slog" "log/slog"
. "openquell/components" . "openquell/components"
. "openquell/config" . "openquell/config"
"openquell/grid"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
@ -12,24 +13,16 @@ import (
) )
type PlayerSystem struct { type PlayerSystem struct {
World *ecs.World World *ecs.World
Selector *generic.Filter5[Position, Velocity, Player, Renderable, Speed] Selector *generic.Filter5[Position, Velocity, Player, Renderable, Speed]
Particle *ParticleSystem GridContainer *grid.GridContainer
Collectible *CollectibleSystem
Obstacle *ObstacleSystem
Transient *TransientSystem
Grid *GridSystem
} }
func NewPlayerSystem(world *ecs.World, grid *GridSystem) *PlayerSystem { func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer) System {
system := &PlayerSystem{ system := &PlayerSystem{
Selector: generic.NewFilter5[Position, Velocity, Player, Renderable, Speed](), Selector: generic.NewFilter5[Position, Velocity, Player, Renderable, Speed](),
Particle: NewParticleSystem(world, grid.Tilesize), GridContainer: gridcontainer,
Collectible: NewCollectibleSystem(world), World: world,
Obstacle: NewObstacleSystem(world, grid),
Transient: NewTransientSystem(world, grid),
Grid: grid,
World: world,
} }
return system return system
@ -87,12 +80,12 @@ func (system PlayerSystem) Update() error {
} }
if velocity.Moving() { if velocity.Moving() {
ok, newpos := system.Grid.BumpEdge(playerposition, velocity) ok, newpos := system.GridContainer.Grid.BumpEdge(playerposition, velocity)
if ok { if ok {
//slog.Debug("falling off the edge", "newpos", newpos) //slog.Debug("falling off the edge", "newpos", newpos)
playerposition.Set(newpos) playerposition.Set(newpos)
} else { } else {
ok, tilepos := system.Grid.GetSolidNeighborPosition(playerposition, velocity, true) ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(playerposition, velocity, true)
if ok { if ok {
intersects, newpos := tilepos.Intersects(playerposition, velocity) intersects, newpos := tilepos.Intersects(playerposition, velocity)
if intersects { if intersects {
@ -107,11 +100,6 @@ func (system PlayerSystem) Update() error {
} }
} }
system.Particle.Update() // may set player position
system.Obstacle.Update()
system.Collectible.Update()
system.Transient.Update()
query = system.Selector.Query(system.World) query = system.Selector.Query(system.World)
for query.Next() { for query.Next() {
// move player after obstacle or collectible updates // move player after obstacle or collectible updates
@ -135,9 +123,4 @@ func (system *PlayerSystem) Draw(screen *ebiten.Image) {
screen.DrawImage(sprite.Image, op) screen.DrawImage(sprite.Image, op)
} }
system.Collectible.Draw(screen)
system.Particle.Draw(screen)
system.Obstacle.Draw(screen)
system.Transient.Draw(screen)
} }

View File

@ -5,6 +5,7 @@ import (
"openquell/assets" "openquell/assets"
"openquell/components" "openquell/components"
. "openquell/components" . "openquell/components"
"openquell/grid"
"openquell/observers" "openquell/observers"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
@ -13,10 +14,10 @@ import (
) )
type TransientSystem struct { type TransientSystem struct {
World *ecs.World World *ecs.World
Selector *generic.Filter3[Position, Renderable, Transient] Selector *generic.Filter3[Position, Renderable, Transient]
Grid *GridSystem GridContainer *grid.GridContainer
SolidMapper generic.Map4[ // needed for replacement SolidMapper generic.Map4[ // needed for replacement
components.Position, components.Position,
components.Renderable, components.Renderable,
components.Tilish, components.Tilish,
@ -29,7 +30,7 @@ type TransientToWall struct {
Position components.Position Position components.Position
} }
func NewTransientSystem(world *ecs.World, grid *GridSystem) *TransientSystem { func NewTransientSystem(world *ecs.World, gridcontainer *grid.GridContainer) System {
solidmapper := generic.NewMap4[ solidmapper := generic.NewMap4[
components.Position, components.Position,
components.Renderable, components.Renderable,
@ -37,16 +38,16 @@ func NewTransientSystem(world *ecs.World, grid *GridSystem) *TransientSystem {
components.Solid](world) components.Solid](world)
system := &TransientSystem{ system := &TransientSystem{
Selector: generic.NewFilter3[Position, Renderable, Transient](), Selector: generic.NewFilter3[Position, Renderable, Transient](),
World: world, World: world,
Grid: grid, GridContainer: gridcontainer,
SolidMapper: solidmapper, SolidMapper: solidmapper,
} }
return system return system
} }
func (system *TransientSystem) Update() { func (system *TransientSystem) Update() error {
playerobserver := observers.GetPlayerObserver(system.World) playerobserver := observers.GetPlayerObserver(system.World)
posID := ecs.ComponentID[components.Position](system.World) posID := ecs.ComponentID[components.Position](system.World)
veloID := ecs.ComponentID[components.Velocity](system.World) veloID := ecs.ComponentID[components.Velocity](system.World)
@ -94,11 +95,13 @@ func (system *TransientSystem) Update() {
// also setup the grid tile with a new solid, so that // also setup the grid tile with a new solid, so that
// collision detection works // collision detection works
system.Grid.Grid.SetTile( system.GridContainer.Grid.SetTile(
assets.NewTileBlock(convertible.NewSprite), assets.NewTileBlock(convertible.NewSprite),
convertible.Position.Point(), convertible.Position.Point(),
) )
} }
return nil
} }
func (system *TransientSystem) Draw(screen *ebiten.Image) { func (system *TransientSystem) Draw(screen *ebiten.Image) {