completed refactoring to use systems

This commit is contained in:
Thomas von Dein 2024-02-10 19:45:06 +01:00
parent bbbe873ff7
commit ac643f49d3
8 changed files with 330 additions and 372 deletions

View File

@ -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
}

View File

@ -1,60 +1,35 @@
package game package game
import ( import (
"fmt"
"image" "image"
"image/draw"
"log" "log"
"openquell/assets" "openquell/assets"
"openquell/components" "openquell/components"
. "openquell/config" "openquell/grid"
"openquell/systems"
"strings" "strings"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/filter"
) )
type Level struct { type Level struct {
Grid *Grid
Cellsize, Width, Height int Cellsize, Width, Height int
World *ecs.World World *ecs.World
Name string Name string
Description string Description string
Background *ebiten.Image
Mapslice map[image.Point]*assets.Tile Mapslice map[image.Point]*assets.Tile
UseCache bool
Cache *ebiten.Image Player *systems.PlayerSystem
Selector map[string]ecs.Mask GridSystem *systems.GridSystem
Component map[string]ecs.ID
Grid *grid.Grid
} }
func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
cache := ebiten.NewImage(game.ScreenWidth, game.ScreenHeight) gridsystem := systems.NewGridSystem(game.World, game.ScreenWidth,
game.ScreenHeight, cellsize, plan.Background)
positionid := ecs.ComponentID[components.Position](game.World) playersystem := systems.NewPlayerSystem(game.World, gridsystem)
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
return &Level{ return &Level{
Mapslice: LevelToSlice(game, plan, cellsize), Mapslice: LevelToSlice(game, plan, cellsize),
@ -63,109 +38,19 @@ func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
Width: game.ScreenWidth, Width: game.ScreenWidth,
Height: game.ScreenHeight, Height: game.ScreenHeight,
Description: plan.Description, Description: plan.Description,
Background: plan.Background, GridSystem: gridsystem,
UseCache: false, Player: playersystem,
Cache: cache,
Selector: selectors,
Component: components,
} }
} }
func (level *Level) Update() { func (level *Level) Update() {
query := level.World.Query(level.Selector["player"]) level.GridSystem.Update()
level.Player.Update()
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: <tab>: 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
}
}
}
func (level *Level) Draw(screen *ebiten.Image) {
level.GridSystem.Draw(screen)
level.Player.Draw(screen)
} }
func (level *Level) Position2Point(position *components.Position) image.Point { 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) { func (level *Level) SetupGrid(game *Game) {
// generic variant does not work here: // generic variant does not work here:
// selector := generic.NewFilter1[components.Position]() // selector := generic.NewFilter1[components.Position]()
@ -271,7 +72,7 @@ func (level *Level) SetupGrid(game *Game) {
level.World.Batch().RemoveEntities(selector) level.World.Batch().RemoveEntities(selector)
// setup world // 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 { func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile {

View File

@ -1,4 +1,4 @@
package game package grid
import ( import (
"fmt" "fmt"
@ -6,9 +6,8 @@ import (
"log" "log"
"openquell/assets" "openquell/assets"
"openquell/components" "openquell/components"
. "openquell/config"
"github.com/alecthomas/repr" "github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/generic" "github.com/mlange-42/arche/generic"
) )
@ -17,39 +16,39 @@ type Grid struct {
Height int Height int
Size int Size int
Tilesize int Tilesize int
TilesX int
TilesY int
Map map[image.Point]*assets.Tile 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
// by game.go in a level-loop. Also remove components of previous level // 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 // we use this to turn our tiles into iterable entities, used for
// collision detection, transformation and other things // collision detection, transformation and other things
playermapper := generic.NewMap4[ playermapper := generic.NewMap4[
components.Position, components.Position,
components.Velocity, components.Velocity,
components.Renderable, components.Renderable,
components.Player](game.World) components.Player](world)
solidmapper := generic.NewMap4[ solidmapper := generic.NewMap4[
components.Position, components.Position,
components.Renderable, components.Renderable,
components.Tilish, 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[ colmapper := generic.NewMap3[
components.Position, components.Position,
components.Renderable, components.Renderable,
components.Collectible](game.World) components.Collectible](world)
ptmapper := generic.NewMap2[ ptmapper := generic.NewMap2[
components.Position, components.Position,
components.Particle, components.Particle,
](game.World) ](world)
var pos *components.Position var pos *components.Position
var render *components.Renderable var render *components.Renderable
@ -95,91 +94,22 @@ func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *G
return &Grid{ return &Grid{
Size: len(mapslice), Size: len(mapslice),
Tilesize: tilesize, Tilesize: tilesize,
Width: game.ScreenWidth, Width: width,
Height: game.ScreenHeight, Height: height,
TilesX: game.ScreenWidth / tilesize,
TilesY: game.ScreenHeight / tilesize,
Map: mapslice, 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, position *components.Position,
velocity *components.Velocity, velocity *components.Velocity) *assets.Tile {
solid bool) (bool, *components.Position) {
if !solid { newpoint := image.Point{
return false, nil 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 tile := grid.Map[newpoint]
// direction, i.e. on the edge of the grid return tile
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
} }

View File

@ -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)
}
}

163
systems/grid_system.go Normal file
View File

@ -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
}

View File

@ -1,9 +1,10 @@
package game package systems
import ( import (
"image" "image"
. "openquell/components" . "openquell/components"
"github.com/hajimehoshi/ebiten/v2"
"github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/generic" "github.com/mlange-42/arche/generic"
) )
@ -14,9 +15,11 @@ type ParticleSystem struct {
Cellsize int Cellsize int
} }
func NewParticleSystem(world *ecs.World) *ParticleSystem { func NewParticleSystem(world *ecs.World, cellsize int) *ParticleSystem {
system := &ParticleSystem{ system := &ParticleSystem{
Selector: generic.NewFilter2[Position, Particle](), Selector: generic.NewFilter2[Position, Particle](),
World: world,
Cellsize: cellsize,
} }
return system 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)
}
}
}

View File

@ -1,4 +1,4 @@
package game package systems
import ( import (
"fmt" "fmt"
@ -13,16 +13,19 @@ import (
type PlayerSystem struct { type PlayerSystem struct {
World *ecs.World World *ecs.World
Grid *Grid Selector *generic.Filter4[Position, Velocity, Player, Renderable]
Selector *generic.Filter3[Position, Velocity, Player]
Particle *ParticleSystem Particle *ParticleSystem
Collectible *CollectibleSystem Collectible *CollectibleSystem
Grid *GridSystem
} }
func NewPlayerSystem(world *ecs.World) *PlayerSystem { func NewPlayerSystem(world *ecs.World, grid *GridSystem) *PlayerSystem {
system := &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 return system
@ -35,7 +38,7 @@ func (system PlayerSystem) Update() error {
var particle_pos image.Point var particle_pos image.Point
for query.Next() { for query.Next() {
playerposition, velocity, _ := query.Get() playerposition, velocity, _, _ := query.Get()
if !velocity.Moving() { if !velocity.Moving() {
switch { switch {
@ -75,9 +78,29 @@ func (system PlayerSystem) Update() error {
bumped, particle_pos = system.Collectible.CheckPlayerCollision(playerposition, velocity) bumped, particle_pos = system.Collectible.CheckPlayerCollision(playerposition, velocity)
} }
playerposition.Move(velocity)
} }
system.Particle.Update(bumped, &particle_pos) system.Particle.Update(bumped, &particle_pos)
system.Collectible.Update()
return nil 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)
}

View File

@ -1,4 +1,4 @@
package system package systems
import "github.com/hajimehoshi/ebiten/v2" import "github.com/hajimehoshi/ebiten/v2"