completed refactoring to use systems
This commit is contained in:
parent
bbbe873ff7
commit
ac643f49d3
@ -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
|
|
||||||
}
|
|
||||||
237
game/levels.go
237
game/levels.go
@ -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 {
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
75
systems/collectible_system.go
Normal file
75
systems/collectible_system.go
Normal 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
163
systems/grid_system.go
Normal 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
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package system
|
package systems
|
||||||
|
|
||||||
import "github.com/hajimehoshi/ebiten/v2"
|
import "github.com/hajimehoshi/ebiten/v2"
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user