From ac643f49d3eb378ab0c02e739e885b1efdd1368d Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Sat, 10 Feb 2024 19:45:06 +0100 Subject: [PATCH] completed refactoring to use systems --- game/collectible_system.go | 54 ------ game/levels.go | 237 +++------------------------ {game => grid}/grid.go | 110 +++---------- systems/collectible_system.go | 75 +++++++++ systems/grid_system.go | 163 ++++++++++++++++++ {game => systems}/particle_system.go | 24 ++- {game => systems}/player_system.go | 37 ++++- systems/system.go | 2 +- 8 files changed, 330 insertions(+), 372 deletions(-) delete mode 100644 game/collectible_system.go rename {game => grid}/grid.go (52%) create mode 100644 systems/collectible_system.go create mode 100644 systems/grid_system.go rename {game => systems}/particle_system.go (64%) rename {game => systems}/player_system.go (66%) diff --git a/game/collectible_system.go b/game/collectible_system.go deleted file mode 100644 index 763443e..0000000 --- a/game/collectible_system.go +++ /dev/null @@ -1,54 +0,0 @@ -package game - -import ( - "fmt" - "image" - . "openquell/components" - - "github.com/mlange-42/arche/ecs" - "github.com/mlange-42/arche/generic" -) - -type CollectibleSystem struct { - World *ecs.World - Selector *generic.Filter2[Position, Collectible] -} - -func NewCollectibleSystem(world *ecs.World) *CollectibleSystem { - system := &CollectibleSystem{ - Selector: generic.NewFilter2[Position, Collectible](), - } - - return system -} - -func (system *CollectibleSystem) CheckPlayerCollision( - playerposition *Position, - playervelocity *Velocity) (bool, image.Point) { - - toRemove := []ecs.Entity{} - particle_pos := image.Point{} - var bumped bool - - query := system.Selector.Query(system.World) - - for query.Next() { - colposition, collectible := query.Get() - - ok, _ := playerposition.Intersects(colposition, playervelocity) - if ok { - fmt.Printf("bumped into collectible %v\n", collectible) - toRemove = append(toRemove, query.Entity()) - particle_pos.X = colposition.X - particle_pos.Y = colposition.Y - bumped = true - } - } - - // remove collectible if collected - for _, entity := range toRemove { - system.World.RemoveEntity(entity) - } - - return bumped, particle_pos -} diff --git a/game/levels.go b/game/levels.go index c2e9a14..b39d393 100644 --- a/game/levels.go +++ b/game/levels.go @@ -1,60 +1,35 @@ package game import ( - "fmt" "image" - "image/draw" "log" "openquell/assets" "openquell/components" - . "openquell/config" + "openquell/grid" + "openquell/systems" "strings" "github.com/hajimehoshi/ebiten/v2" "github.com/mlange-42/arche/ecs" - "github.com/mlange-42/arche/filter" ) type Level struct { - Grid *Grid Cellsize, Width, Height int World *ecs.World Name string Description string - Background *ebiten.Image Mapslice map[image.Point]*assets.Tile - UseCache bool - Cache *ebiten.Image - Selector map[string]ecs.Mask - Component map[string]ecs.ID + + Player *systems.PlayerSystem + GridSystem *systems.GridSystem + + Grid *grid.Grid } func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { - cache := ebiten.NewImage(game.ScreenWidth, game.ScreenHeight) - - positionid := ecs.ComponentID[components.Position](game.World) - velocityid := ecs.ComponentID[components.Velocity](game.World) - playerid := ecs.ComponentID[components.Player](game.World) - colid := ecs.ComponentID[components.Collectible](game.World) - ptid := ecs.ComponentID[components.Particle](game.World) - sid := ecs.ComponentID[components.Solid](game.World) - renderid := ecs.ComponentID[components.Renderable](game.World) - - selectors := map[string]ecs.Mask{} - selectors["player"] = filter.All(positionid, velocityid, playerid) - selectors["tile"] = filter.All(renderid, positionid, sid) - selectors["movable"] = filter.All(renderid, positionid) - selectors["collectible"] = filter.All(positionid, colid) - selectors["particle"] = filter.All(positionid, ptid) - - components := map[string]ecs.ID{} - components["position"] = positionid - components["velocity"] = velocityid - components["player"] = playerid - components["collectible"] = colid - components["particle"] = ptid - components["solid"] = sid - components["renderable"] = renderid + gridsystem := systems.NewGridSystem(game.World, game.ScreenWidth, + game.ScreenHeight, cellsize, plan.Background) + playersystem := systems.NewPlayerSystem(game.World, gridsystem) return &Level{ Mapslice: LevelToSlice(game, plan, cellsize), @@ -63,109 +38,19 @@ func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { Width: game.ScreenWidth, Height: game.ScreenHeight, Description: plan.Description, - Background: plan.Background, - UseCache: false, - Cache: cache, - Selector: selectors, - Component: components, + GridSystem: gridsystem, + Player: playersystem, } } func (level *Level) Update() { - query := level.World.Query(level.Selector["player"]) - - toRemove := []ecs.Entity{} - particle_pos := image.Point{} - - for query.Next() { - playerposition := (*components.Position)(query.Get(level.Component["position"])) - velocity := (*components.Velocity)(query.Get(level.Component["velocity"])) - - if !velocity.Moving() { - switch { - case ebiten.IsKeyPressed(ebiten.KeyRight): - velocity.Change(East) - case ebiten.IsKeyPressed(ebiten.KeyLeft): - velocity.Change(West) - case ebiten.IsKeyPressed(ebiten.KeyDown): - velocity.Change(South) - case ebiten.IsKeyPressed(ebiten.KeyUp): - velocity.Change(North) - // other keys: : switch player, etc - } - } - - if velocity.Moving() { - ok, newpos := level.Grid.BumpEdge(playerposition, velocity) - if ok { - fmt.Printf("falling off the edge, new pos: %v\n", newpos) - playerposition.Set(newpos) - } else { - ok, tilepos := level.Grid.GetSolidNeighborPosition(playerposition, velocity, true) - if ok { - intersects, newpos := tilepos.Intersects(playerposition, velocity) - if intersects { - fmt.Printf("collision detected. tile: %s\n", tilepos) - fmt.Printf(" player: %s\n", playerposition) - fmt.Printf(" new: %s\n", newpos) - - playerposition.Set(newpos) - fmt.Printf(" player new: %s\n", playerposition) - velocity.Change(Stop) - } - } - } - - colquery := level.World.Query(level.Selector["collectible"]) - for colquery.Next() { - collectible := (*components.Collectible)(colquery.Get(level.Component["collectible"])) - colposition := (*components.Position)(colquery.Get(level.Component["position"])) - ok, _ := playerposition.Intersects(colposition, velocity) - if ok { - fmt.Printf("bumped into collectible %v\n", collectible) - toRemove = append(toRemove, colquery.Entity()) - particle_pos.X = colposition.X - particle_pos.Y = colposition.Y - } - } - } - - playerposition.Move(velocity) - } - - // remove collectible if collected - for _, entity := range toRemove { - // FIXME: or keep them and prepare an animated death - level.World.RemoveEntity(entity) - } - - // display debris after collecting - ptquery := level.World.Query(level.Selector["particle"]) - for ptquery.Next() { - // we loop, but it's only one anyway - particle := (*components.Particle)(ptquery.Get(level.Component["particle"])) - colposition := (*components.Position)(ptquery.Get(level.Component["position"])) - - if len(toRemove) > 0 { - // particle appears - colposition.Update( - particle_pos.X-(level.Cellsize/2), - particle_pos.Y-(level.Cellsize/2), - 64, - ) - - particle.Index = 0 // start displaying the particle - } else { - switch { - case particle.Index > -1 && particle.Index < len(particle.Particles)-1: - particle.Index++ - default: - // last sprite reached, remove it - particle.Index = -1 - } - } - } + level.GridSystem.Update() + level.Player.Update() +} +func (level *Level) Draw(screen *ebiten.Image) { + level.GridSystem.Draw(screen) + level.Player.Draw(screen) } func (level *Level) Position2Point(position *components.Position) image.Point { @@ -175,90 +60,6 @@ func (level *Level) Position2Point(position *components.Position) image.Point { } } -// return the tile the moving object would end up on if indeed moving -func (level *Level) GetTile( - position *components.Position, - velocity *components.Velocity) *assets.Tile { - - newpoint := image.Point{ - int(position.X+velocity.Data.X) / level.Cellsize, - int(position.Y+velocity.Data.Y) / level.Cellsize, - } - - tile := level.Mapslice[newpoint] - return tile -} - -func (level *Level) Draw(screen *ebiten.Image) { - level.DrawTiles(screen) - level.DrawMovables(screen) - level.DrawParticles(screen) -} - -func (level *Level) DrawTiles(screen *ebiten.Image) { - op := &ebiten.DrawImageOptions{} - - if !level.UseCache { - // map not cached or cacheable, write it to the cache - draw.Draw(level.Cache, level.Background.Bounds(), level.Background, image.ZP, draw.Src) - - query := level.World.Query(level.Selector["tile"]) - for query.Next() { - pos := (*components.Position)(query.Get(level.Component["position"])) - sprite := (*components.Renderable)(query.Get(level.Component["renderable"])) - - draw.Draw( - level.Cache, - image.Rect(pos.X, pos.Y, pos.X+pos.Cellsize, pos.Y+pos.Cellsize), - sprite.Image, image.ZP, draw.Over) - } - - op.GeoM.Reset() - screen.DrawImage(level.Cache, op) - - level.UseCache = true - } else { - // use the cached map - op.GeoM.Reset() - screen.DrawImage(level.Cache, op) - } -} - -func (level *Level) DrawMovables(screen *ebiten.Image) { - // write the movable tiles - op := &ebiten.DrawImageOptions{} - selector := level.Selector["movable"].Without(level.Component["solid"]) - query := level.World.Query(&selector) - - for query.Next() { - pos := (*components.Position)(query.Get(level.Component["position"])) - sprite := (*components.Renderable)(query.Get(level.Component["renderable"])) - - op.GeoM.Reset() - op.GeoM.Translate(float64(pos.X), float64(pos.Y)) - - screen.DrawImage(sprite.Image, op) - } -} - -func (level *Level) DrawParticles(screen *ebiten.Image) { - // write particles (these are no tiles!) - op := &ebiten.DrawImageOptions{} - query := level.World.Query(level.Selector["particle"]) - - for query.Next() { - pos := (*components.Position)(query.Get(level.Component["position"])) - particle := (*components.Particle)(query.Get(level.Component["particle"])) - - if particle.Index > -1 { - op.GeoM.Reset() - op.GeoM.Translate(float64(pos.X), float64(pos.Y)) - screen.DrawImage(particle.Particles[particle.Index], op) - } - } - -} - func (level *Level) SetupGrid(game *Game) { // generic variant does not work here: // selector := generic.NewFilter1[components.Position]() @@ -271,7 +72,7 @@ func (level *Level) SetupGrid(game *Game) { level.World.Batch().RemoveEntities(selector) // setup world - level.Grid = NewGrid(game, level.Cellsize, level.Mapslice) + level.GridSystem.SetGrid(grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice)) } func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile { diff --git a/game/grid.go b/grid/grid.go similarity index 52% rename from game/grid.go rename to grid/grid.go index 4e02a82..a9ba74f 100644 --- a/game/grid.go +++ b/grid/grid.go @@ -1,4 +1,4 @@ -package game +package grid import ( "fmt" @@ -6,9 +6,8 @@ import ( "log" "openquell/assets" "openquell/components" - . "openquell/config" - "github.com/alecthomas/repr" + "github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/generic" ) @@ -17,39 +16,39 @@ type Grid struct { Height int Size int Tilesize int - TilesX int - TilesY int Map map[image.Point]*assets.Tile } // FIXME: put component addition into extra callable function, to be called // by game.go in a level-loop. Also remove components of previous level -func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *Grid { +func NewGrid(world *ecs.World, + tilesize, width, height int, mapslice map[image.Point]*assets.Tile) *Grid { + // we use this to turn our tiles into iterable entities, used for // collision detection, transformation and other things playermapper := generic.NewMap4[ components.Position, components.Velocity, components.Renderable, - components.Player](game.World) + components.Player](world) solidmapper := generic.NewMap4[ components.Position, components.Renderable, components.Tilish, - components.Solid](game.World) + components.Solid](world) - emptymapper := generic.NewMap2[components.Position, components.Tilish](game.World) + emptymapper := generic.NewMap2[components.Position, components.Tilish](world) colmapper := generic.NewMap3[ components.Position, components.Renderable, - components.Collectible](game.World) + components.Collectible](world) ptmapper := generic.NewMap2[ components.Position, components.Particle, - ](game.World) + ](world) var pos *components.Position var render *components.Renderable @@ -95,91 +94,22 @@ func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *G return &Grid{ Size: len(mapslice), Tilesize: tilesize, - Width: game.ScreenWidth, - Height: game.ScreenHeight, - TilesX: game.ScreenWidth / tilesize, - TilesY: game.ScreenHeight / tilesize, + Width: width, + Height: height, Map: mapslice, } } -func (grid *Grid) GetSolidNeighborPosition( +// return the tile the moving object would end up on if indeed moving +func (grid *Grid) GetTile( position *components.Position, - velocity *components.Velocity, - solid bool) (bool, *components.Position) { + velocity *components.Velocity) *assets.Tile { - if !solid { - return false, nil + newpoint := image.Point{ + int(position.X+velocity.Data.X) / grid.Tilesize, + int(position.Y+velocity.Data.Y) / grid.Tilesize, } - // set to true, ifwe 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) - repr.Println(newpos) - - 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 - fmt.Println("east X=0") - case West: - X = grid.Width - grid.Tilesize - fmt.Println("west X=max") - case South: - Y = 0 - fmt.Println("south y=0") - case North: - Y = grid.Height - grid.Tilesize - fmt.Println("north y=max") - } - - newpos.Update(X, Y, grid.Tilesize) - return true, newpos - } - - return false, nil + tile := grid.Map[newpoint] + return tile } diff --git a/systems/collectible_system.go b/systems/collectible_system.go new file mode 100644 index 0000000..0164897 --- /dev/null +++ b/systems/collectible_system.go @@ -0,0 +1,75 @@ +package systems + +import ( + "fmt" + "image" + . "openquell/components" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/mlange-42/arche/ecs" + "github.com/mlange-42/arche/generic" +) + +type CollectibleSystem struct { + World *ecs.World + Selector *generic.Filter3[Position, Collectible, Renderable] + EntitiesToRemove []ecs.Entity +} + +func NewCollectibleSystem(world *ecs.World) *CollectibleSystem { + system := &CollectibleSystem{ + Selector: generic.NewFilter3[Position, Collectible, Renderable](), + World: world, + } + + return system +} + +func (system *CollectibleSystem) CheckPlayerCollision( + playerposition *Position, + playervelocity *Velocity) (bool, image.Point) { + + particle_pos := image.Point{} + var bumped bool + + query := system.Selector.Query(system.World) + + for query.Next() { + colposition, collectible, _ := query.Get() + + ok, _ := playerposition.Intersects(colposition, playervelocity) + if ok { + fmt.Printf("bumped into collectible %v\n", collectible) + system.EntitiesToRemove = append(system.EntitiesToRemove, query.Entity()) + particle_pos.X = colposition.X + particle_pos.Y = colposition.Y + bumped = true + } + } + + return bumped, particle_pos +} + +func (system *CollectibleSystem) Update() { + // remove collectible if collected + for _, entity := range system.EntitiesToRemove { + system.World.RemoveEntity(entity) + } + + system.EntitiesToRemove = []ecs.Entity{} +} + +func (system *CollectibleSystem) Draw(screen *ebiten.Image) { + // write the movable tiles + op := &ebiten.DrawImageOptions{} + query := system.Selector.Query(system.World) + + for query.Next() { + pos, _, sprite := query.Get() + + op.GeoM.Reset() + op.GeoM.Translate(float64(pos.X), float64(pos.Y)) + + screen.DrawImage(sprite.Image, op) + } +} diff --git a/systems/grid_system.go b/systems/grid_system.go new file mode 100644 index 0000000..464ea5c --- /dev/null +++ b/systems/grid_system.go @@ -0,0 +1,163 @@ +package systems + +import ( + "fmt" + "image" + "image/draw" + "openquell/components" + . "openquell/components" + . "openquell/config" + "openquell/grid" + + "github.com/alecthomas/repr" + "github.com/hajimehoshi/ebiten/v2" + "github.com/mlange-42/arche/ecs" + "github.com/mlange-42/arche/generic" +) + +type GridSystem struct { + World *ecs.World + Selector *generic.Filter3[Renderable, Position, Solid] + UseCache bool + Cache *ebiten.Image + Background *ebiten.Image + Width, Height, TilesX, TilesY, Tilesize int + Grid *grid.Grid +} + +func NewGridSystem(world *ecs.World, width, height, + tilesize int, background *ebiten.Image) *GridSystem { + + cache := ebiten.NewImage(width, height) + + system := &GridSystem{ + Selector: generic.NewFilter3[Renderable, Position, Solid](), + UseCache: false, + Cache: cache, + Width: width, + Height: height, + TilesX: width / tilesize, + TilesY: height / tilesize, + Tilesize: tilesize, + Background: background, + World: world, + } + + return system +} + +func (system *GridSystem) SetGrid(grid *grid.Grid) { + system.Grid = grid +} + +func (system *GridSystem) Update() {} + +func (system *GridSystem) Draw(screen *ebiten.Image) { + op := &ebiten.DrawImageOptions{} + + if !system.UseCache { + // map not cached or cacheable, write it to the cache + draw.Draw(system.Cache, system.Background.Bounds(), system.Background, image.ZP, draw.Src) + + query := system.Selector.Query(system.World) + + for query.Next() { + sprite, pos, _ := query.Get() + + draw.Draw( + system.Cache, + image.Rect(pos.X, pos.Y, pos.X+pos.Cellsize, pos.Y+pos.Cellsize), + sprite.Image, image.ZP, draw.Over) + } + + op.GeoM.Reset() + screen.DrawImage(system.Cache, op) + + system.UseCache = true + } else { + // use the cached map + op.GeoM.Reset() + screen.DrawImage(system.Cache, op) + } +} + +func (system *GridSystem) GetSolidNeighborPosition( + position *components.Position, + velocity *components.Velocity, + solid bool) (bool, *components.Position) { + + if !solid { + return false, nil + } + + // set to true, ifwe 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) + repr.Println(newpos) + + 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 + fmt.Println("east X=0") + case West: + X = system.Width - system.Tilesize + fmt.Println("west X=max") + case South: + Y = 0 + fmt.Println("south y=0") + case North: + Y = system.Height - system.Tilesize + fmt.Println("north y=max") + } + + newpos.Update(X, Y, system.Tilesize) + return true, newpos + } + + return false, nil +} diff --git a/game/particle_system.go b/systems/particle_system.go similarity index 64% rename from game/particle_system.go rename to systems/particle_system.go index 4012854..f9ab9c4 100644 --- a/game/particle_system.go +++ b/systems/particle_system.go @@ -1,9 +1,10 @@ -package game +package systems import ( "image" . "openquell/components" + "github.com/hajimehoshi/ebiten/v2" "github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/generic" ) @@ -14,9 +15,11 @@ type ParticleSystem struct { Cellsize int } -func NewParticleSystem(world *ecs.World) *ParticleSystem { +func NewParticleSystem(world *ecs.World, cellsize int) *ParticleSystem { system := &ParticleSystem{ Selector: generic.NewFilter2[Position, Particle](), + World: world, + Cellsize: cellsize, } return system @@ -51,3 +54,20 @@ func (system *ParticleSystem) Update(detonate bool, position *image.Point) { } } } + +func (system *ParticleSystem) Draw(screen *ebiten.Image) { + // write particles (these are no tiles!) + op := &ebiten.DrawImageOptions{} + query := system.Selector.Query(system.World) + + for query.Next() { + pos, particle := query.Get() + + if particle.Index > -1 { + op.GeoM.Reset() + op.GeoM.Translate(float64(pos.X), float64(pos.Y)) + screen.DrawImage(particle.Particles[particle.Index], op) + } + } + +} diff --git a/game/player_system.go b/systems/player_system.go similarity index 66% rename from game/player_system.go rename to systems/player_system.go index 24ff20d..26f4f6b 100644 --- a/game/player_system.go +++ b/systems/player_system.go @@ -1,4 +1,4 @@ -package game +package systems import ( "fmt" @@ -13,16 +13,19 @@ import ( type PlayerSystem struct { World *ecs.World - Grid *Grid - Selector *generic.Filter3[Position, Velocity, Player] + Selector *generic.Filter4[Position, Velocity, Player, Renderable] Particle *ParticleSystem Collectible *CollectibleSystem + Grid *GridSystem } -func NewPlayerSystem(world *ecs.World) *PlayerSystem { - +func NewPlayerSystem(world *ecs.World, grid *GridSystem) *PlayerSystem { system := &PlayerSystem{ - Selector: generic.NewFilter3[Position, Velocity, Player](), + Selector: generic.NewFilter4[Position, Velocity, Player, Renderable](), + Particle: NewParticleSystem(world, grid.Tilesize), + Collectible: NewCollectibleSystem(world), + Grid: grid, + World: world, } return system @@ -35,7 +38,7 @@ func (system PlayerSystem) Update() error { var particle_pos image.Point for query.Next() { - playerposition, velocity, _ := query.Get() + playerposition, velocity, _, _ := query.Get() if !velocity.Moving() { switch { @@ -75,9 +78,29 @@ func (system PlayerSystem) Update() error { bumped, particle_pos = system.Collectible.CheckPlayerCollision(playerposition, velocity) } + playerposition.Move(velocity) } system.Particle.Update(bumped, &particle_pos) + system.Collectible.Update() return nil } + +func (system *PlayerSystem) Draw(screen *ebiten.Image) { + // write the movable tiles + op := &ebiten.DrawImageOptions{} + query := system.Selector.Query(system.World) + + for query.Next() { + pos, _, _, sprite := query.Get() + + op.GeoM.Reset() + op.GeoM.Translate(float64(pos.X), float64(pos.Y)) + + screen.DrawImage(sprite.Image, op) + } + + system.Collectible.Draw(screen) + system.Particle.Draw(screen) +} diff --git a/systems/system.go b/systems/system.go index 92d37df..9a534b2 100644 --- a/systems/system.go +++ b/systems/system.go @@ -1,4 +1,4 @@ -package system +package systems import "github.com/hajimehoshi/ebiten/v2"