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" "github.com/mlange-42/arche/generic" ) 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 } 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{} query := system.Selector.Query(system.World) if !system.UseCache || query.Count() != system.Count { // map not cached or cacheable, write it to the cache draw.Draw(system.Cache, system.Background.Bounds(), system.Background, image.ZP, draw.Src) system.Count = query.Count() 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) 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 }