From ab07bc23e3ffc02d1e4f9e509971084787a44a62 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Fri, 23 Feb 2024 18:47:15 +0100 Subject: [PATCH] refactored recursive systems out of player_system => game/levels.go --- game/game.go | 15 ++--- game/levels.go | 57 ++++++++++++------- grid/collider.go | 82 +++++++++++++++++++++++++++ grid/container.go | 9 +++ grid/grid.go | 15 +++-- systems/collectible_system.go | 8 ++- systems/grid_system.go | 104 +++------------------------------- systems/obstacle_system.go | 23 ++++---- systems/particle_system.go | 6 +- systems/player_system.go | 37 ++++-------- systems/transient_system.go | 25 ++++---- 11 files changed, 198 insertions(+), 183 deletions(-) create mode 100644 grid/collider.go create mode 100644 grid/container.go diff --git a/game/game.go b/game/game.go index 9188ece..b75ca31 100644 --- a/game/game.go +++ b/game/game.go @@ -11,13 +11,13 @@ import ( ) type Game struct { - World *ecs.World - Bounds image.Rectangle - ScreenWidth, ScreenHeight int - Scenes map[SceneName]Scene - CurrentScene SceneName - Observer *observers.GameObserver - Levels []*Level // needed to feed select_scene + World *ecs.World + Bounds image.Rectangle + ScreenWidth, ScreenHeight, Cellsize int + Scenes map[SceneName]Scene + CurrentScene SceneName + Observer *observers.GameObserver + Levels []*Level // needed to feed select_scene } 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, ScreenHeight: height, Scenes: map[SceneName]Scene{}, + Cellsize: cellsize, } observers.NewPlayerObserver(&world) diff --git a/game/levels.go b/game/levels.go index fbe4a25..69e7047 100644 --- a/game/levels.go +++ b/game/levels.go @@ -21,40 +21,53 @@ type Level struct { Name string Description string Mapslice map[image.Point]*assets.Tile - - Player *systems.PlayerSystem - GridSystem *systems.GridSystem - - Grid *grid.Grid + GridContainer *grid.GridContainer + Systems []systems.System + Grid *grid.Grid } func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { - gridsystem := systems.NewGridSystem(game.World, game.ScreenWidth, - game.ScreenHeight, cellsize, plan.Background) + systemlist := []systems.System{} - 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{ - Mapslice: LevelToSlice(game, plan, cellsize), - Cellsize: cellsize, - World: game.World, - Width: game.ScreenWidth, - Height: game.ScreenHeight, - Description: plan.Description, - Name: plan.Name, - GridSystem: gridsystem, - Player: playersystem, + Mapslice: LevelToSlice(game, plan, cellsize), + Cellsize: cellsize, + World: game.World, + Width: game.ScreenWidth, + Height: game.ScreenHeight, + Description: plan.Description, + Name: plan.Name, + GridContainer: gridcontainer, + Systems: systemlist, } } func (level *Level) Update() { - level.GridSystem.Update() - level.Player.Update() + for _, sys := range level.Systems { + sys.Update() + } } func (level *Level) Draw(screen *ebiten.Image) { - level.GridSystem.Draw(screen) - level.Player.Draw(screen) + for _, sys := range level.Systems { + sys.Draw(screen) + } } func (level *Level) Position2Point(position *components.Position) image.Point { @@ -80,7 +93,7 @@ func (level *Level) SetupGrid(game *Game) { playerobserver.RemoveEntities() // setup world - level.GridSystem.SetGrid( + level.GridContainer.SetGrid( grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice)) } diff --git a/grid/collider.go b/grid/collider.go new file mode 100644 index 0000000..68e8610 --- /dev/null +++ b/grid/collider.go @@ -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 +} diff --git a/grid/container.go b/grid/container.go new file mode 100644 index 0000000..a38b1b9 --- /dev/null +++ b/grid/container.go @@ -0,0 +1,9 @@ +package grid + +type GridContainer struct { + Grid *Grid +} + +func (container *GridContainer) SetGrid(grid *Grid) { + container.Grid = grid +} diff --git a/grid/grid.go b/grid/grid.go index 0e4ab9e..7870f96 100644 --- a/grid/grid.go +++ b/grid/grid.go @@ -13,12 +13,13 @@ import ( ) type Grid struct { - World *ecs.World - Width int - Height int - Size int - Tilesize int - Map map[image.Point]*assets.Tile + World *ecs.World + Width int + Height int + Size int + Tilesize int + TilesX, TilesY int + Map map[image.Point]*assets.Tile } // FIXME: put component addition into extra callable function, to be called @@ -123,6 +124,8 @@ func NewGrid(world *ecs.World, Height: height, Map: mapslice, World: world, + TilesX: width / tilesize, + TilesY: height / tilesize, } } diff --git a/systems/collectible_system.go b/systems/collectible_system.go index 958b964..d37065c 100644 --- a/systems/collectible_system.go +++ b/systems/collectible_system.go @@ -19,7 +19,7 @@ type CollectibleSystem struct { Selector *generic.Filter3[Position, Collectible, Renderable] } -func NewCollectibleSystem(world *ecs.World) *CollectibleSystem { +func NewCollectibleSystem(world *ecs.World) System { system := &CollectibleSystem{ Selector: generic.NewFilter3[Position, Collectible, Renderable](), World: world, @@ -28,7 +28,7 @@ func NewCollectibleSystem(world *ecs.World) *CollectibleSystem { return system } -func (system *CollectibleSystem) Update() { +func (system *CollectibleSystem) Update() error { playerobserver := observers.GetPlayerObserver(system.World) gameobserver := observers.GetGameObserver(system.World) @@ -43,7 +43,7 @@ func (system *CollectibleSystem) Update() { if numcollectibles == 0 || gameobserver.Lost { query.Close() - return + return nil } for query.Next() { @@ -78,6 +78,8 @@ func (system *CollectibleSystem) Update() { timer.Start(LEVEL_END_WAIT) } } + + return nil } func (system *CollectibleSystem) Draw(screen *ebiten.Image) { diff --git a/systems/grid_system.go b/systems/grid_system.go index fefaa09..ffcda72 100644 --- a/systems/grid_system.go +++ b/systems/grid_system.go @@ -3,10 +3,7 @@ package systems import ( "image" "image/draw" - "openquell/components" . "openquell/components" - . "openquell/config" - "openquell/grid" "github.com/hajimehoshi/ebiten/v2" "github.com/mlange-42/arche/ecs" @@ -14,18 +11,17 @@ import ( ) type GridSystem struct { - World *ecs.World - Selector *generic.Filter3[Renderable, Position, Solid] - UseCache bool - Cache *ebiten.Image - Count int // register tile count, invalidates cache - Background *ebiten.Image - Width, Height, TilesX, TilesY, Tilesize int - Grid *grid.Grid + World *ecs.World + Selector *generic.Filter3[Renderable, Position, Solid] + UseCache bool + Cache *ebiten.Image + Count int // register tile count, invalidates cache + Background *ebiten.Image + Width, Height, Tilesize int } func NewGridSystem(world *ecs.World, width, height, - tilesize int, background *ebiten.Image) *GridSystem { + tilesize int, background *ebiten.Image) System { cache := ebiten.NewImage(width, height) @@ -35,8 +31,6 @@ func NewGridSystem(world *ecs.World, width, height, Cache: cache, Width: width, Height: height, - TilesX: width / tilesize, - TilesY: height / tilesize, Tilesize: tilesize, Background: background, World: world, @@ -45,11 +39,7 @@ func NewGridSystem(world *ecs.World, width, height, return system } -func (system *GridSystem) SetGrid(grid *grid.Grid) { - system.Grid = grid -} - -func (system *GridSystem) Update() {} +func (system *GridSystem) Update() error { return nil } func (system *GridSystem) Draw(screen *ebiten.Image) { op := &ebiten.DrawImageOptions{} @@ -81,79 +71,3 @@ func (system *GridSystem) Draw(screen *ebiten.Image) { 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 -} diff --git a/systems/obstacle_system.go b/systems/obstacle_system.go index 749abb6..edf99f5 100644 --- a/systems/obstacle_system.go +++ b/systems/obstacle_system.go @@ -6,6 +6,7 @@ import ( "openquell/components" . "openquell/components" . "openquell/config" + "openquell/grid" "openquell/observers" "openquell/util" @@ -15,28 +16,28 @@ import ( ) type ObstacleSystem struct { - World *ecs.World - Selector *generic.Filter5[Position, Velocity, Obstacle, Renderable, Speed] - Grid *GridSystem + World *ecs.World + Selector *generic.Filter5[Position, Velocity, Obstacle, Renderable, Speed] + GridContainer *grid.GridContainer } -func NewObstacleSystem(world *ecs.World, grid *GridSystem) *ObstacleSystem { +func NewObstacleSystem(world *ecs.World, gridcontainer *grid.GridContainer) System { system := &ObstacleSystem{ - Selector: generic.NewFilter5[Position, Velocity, Obstacle, Renderable, Speed](), - World: world, - Grid: grid, + Selector: generic.NewFilter5[Position, Velocity, Obstacle, Renderable, Speed](), + World: world, + GridContainer: gridcontainer, } return system } -func (system *ObstacleSystem) Update() { +func (system *ObstacleSystem) Update() error { playerobserver := observers.GetPlayerObserver(system.World) gameobserver := observers.GetGameObserver(system.World) obstacleobserver := observers.GetObstacleObserver(system.World) if gameobserver.Lost { - return + return nil } 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 // two, just iterate over all entities with pos,vel,render, dammit if obsvelocity.Moving() { - ok, tilepos := system.Grid.GetSolidNeighborPosition(obsposition, obsvelocity, true) + ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(obsposition, obsvelocity, true) if ok { intersects, newpos := tilepos.Intersects(obsposition, obsvelocity) if intersects { @@ -125,6 +126,8 @@ func (system *ObstacleSystem) Update() { gameobserver.Gameover() } + + return nil } func (system *ObstacleSystem) Draw(screen *ebiten.Image) { diff --git a/systems/particle_system.go b/systems/particle_system.go index 5eb4c2a..2c7dcc7 100644 --- a/systems/particle_system.go +++ b/systems/particle_system.go @@ -15,7 +15,7 @@ type ParticleSystem struct { Cellsize int } -func NewParticleSystem(world *ecs.World, cellsize int) *ParticleSystem { +func NewParticleSystem(world *ecs.World, cellsize int) System { system := &ParticleSystem{ Selector: generic.NewFilter3[Position, Particle, Timer](), World: world, @@ -25,7 +25,7 @@ func NewParticleSystem(world *ecs.World, cellsize int) *ParticleSystem { return system } -func (system *ParticleSystem) Update() { +func (system *ParticleSystem) Update() error { // display debris after collecting EntitiesToRemove := []ecs.Entity{} @@ -56,6 +56,8 @@ func (system *ParticleSystem) Update() { for _, entity := range EntitiesToRemove { system.World.RemoveEntity(entity) } + + return nil } func (system *ParticleSystem) Draw(screen *ebiten.Image) { diff --git a/systems/player_system.go b/systems/player_system.go index 807cd8b..765190e 100644 --- a/systems/player_system.go +++ b/systems/player_system.go @@ -4,6 +4,7 @@ import ( "log/slog" . "openquell/components" . "openquell/config" + "openquell/grid" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" @@ -12,24 +13,16 @@ import ( ) type PlayerSystem struct { - World *ecs.World - Selector *generic.Filter5[Position, Velocity, Player, Renderable, Speed] - Particle *ParticleSystem - Collectible *CollectibleSystem - Obstacle *ObstacleSystem - Transient *TransientSystem - Grid *GridSystem + World *ecs.World + Selector *generic.Filter5[Position, Velocity, Player, Renderable, Speed] + GridContainer *grid.GridContainer } -func NewPlayerSystem(world *ecs.World, grid *GridSystem) *PlayerSystem { +func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer) System { system := &PlayerSystem{ - Selector: generic.NewFilter5[Position, Velocity, Player, Renderable, Speed](), - Particle: NewParticleSystem(world, grid.Tilesize), - Collectible: NewCollectibleSystem(world), - Obstacle: NewObstacleSystem(world, grid), - Transient: NewTransientSystem(world, grid), - Grid: grid, - World: world, + Selector: generic.NewFilter5[Position, Velocity, Player, Renderable, Speed](), + GridContainer: gridcontainer, + World: world, } return system @@ -87,12 +80,12 @@ func (system PlayerSystem) Update() error { } if velocity.Moving() { - ok, newpos := system.Grid.BumpEdge(playerposition, velocity) + ok, newpos := system.GridContainer.Grid.BumpEdge(playerposition, velocity) if ok { //slog.Debug("falling off the edge", "newpos", newpos) playerposition.Set(newpos) } else { - ok, tilepos := system.Grid.GetSolidNeighborPosition(playerposition, velocity, true) + ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(playerposition, velocity, true) if ok { intersects, newpos := tilepos.Intersects(playerposition, velocity) 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) for query.Next() { // move player after obstacle or collectible updates @@ -135,9 +123,4 @@ func (system *PlayerSystem) Draw(screen *ebiten.Image) { screen.DrawImage(sprite.Image, op) } - - system.Collectible.Draw(screen) - system.Particle.Draw(screen) - system.Obstacle.Draw(screen) - system.Transient.Draw(screen) } diff --git a/systems/transient_system.go b/systems/transient_system.go index ea4e7a5..1a03437 100644 --- a/systems/transient_system.go +++ b/systems/transient_system.go @@ -5,6 +5,7 @@ import ( "openquell/assets" "openquell/components" . "openquell/components" + "openquell/grid" "openquell/observers" "github.com/hajimehoshi/ebiten/v2" @@ -13,10 +14,10 @@ import ( ) type TransientSystem struct { - World *ecs.World - Selector *generic.Filter3[Position, Renderable, Transient] - Grid *GridSystem - SolidMapper generic.Map4[ // needed for replacement + World *ecs.World + Selector *generic.Filter3[Position, Renderable, Transient] + GridContainer *grid.GridContainer + SolidMapper generic.Map4[ // needed for replacement components.Position, components.Renderable, components.Tilish, @@ -29,7 +30,7 @@ type TransientToWall struct { Position components.Position } -func NewTransientSystem(world *ecs.World, grid *GridSystem) *TransientSystem { +func NewTransientSystem(world *ecs.World, gridcontainer *grid.GridContainer) System { solidmapper := generic.NewMap4[ components.Position, components.Renderable, @@ -37,16 +38,16 @@ func NewTransientSystem(world *ecs.World, grid *GridSystem) *TransientSystem { components.Solid](world) system := &TransientSystem{ - Selector: generic.NewFilter3[Position, Renderable, Transient](), - World: world, - Grid: grid, - SolidMapper: solidmapper, + Selector: generic.NewFilter3[Position, Renderable, Transient](), + World: world, + GridContainer: gridcontainer, + SolidMapper: solidmapper, } return system } -func (system *TransientSystem) Update() { +func (system *TransientSystem) Update() error { playerobserver := observers.GetPlayerObserver(system.World) posID := ecs.ComponentID[components.Position](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 // collision detection works - system.Grid.Grid.SetTile( + system.GridContainer.Grid.SetTile( assets.NewTileBlock(convertible.NewSprite), convertible.Position.Point(), ) } + + return nil } func (system *TransientSystem) Draw(screen *ebiten.Image) {