added transient entities: when crossed, turns into wall tile
This commit is contained in:
parent
f696660ccd
commit
308f335cd1
@ -4,8 +4,8 @@ Background: background-lila
|
||||
|
||||
|
||||
#######
|
||||
# v #
|
||||
# #
|
||||
# t #
|
||||
#> S <#
|
||||
# #
|
||||
# ^ #
|
||||
|
||||
@ -3,13 +3,13 @@ Background: background-lila
|
||||
|
||||
|
||||
|
||||
#######
|
||||
# ^ #
|
||||
#############
|
||||
# #
|
||||
#< S >#
|
||||
# #
|
||||
#^ v#
|
||||
#######
|
||||
#> S # #
|
||||
# ># >#
|
||||
#< # #
|
||||
# v# <#
|
||||
#############
|
||||
|
||||
|
||||
|
||||
|
||||
@ -30,8 +30,10 @@ type Tile struct {
|
||||
Renderable bool
|
||||
Velocity bool
|
||||
Collectible bool
|
||||
Transient bool
|
||||
Particle int // -1=unused, 0-3 = show image of slice
|
||||
Particles []*ebiten.Image
|
||||
Tiles []*ebiten.Image
|
||||
TileNames []string // same thing, only the names
|
||||
Obstacle bool
|
||||
Direction int // obstacles
|
||||
}
|
||||
@ -77,6 +79,7 @@ func NewTileObstacle(class string, direction int) *Tile {
|
||||
Renderable: true,
|
||||
Obstacle: true,
|
||||
Direction: direction,
|
||||
Velocity: true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +96,28 @@ func NewTileParticle(class []string) *Tile {
|
||||
Solid: false,
|
||||
Renderable: false,
|
||||
Particle: 0,
|
||||
Particles: sprites,
|
||||
Tiles: sprites,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTileTranswall(class []string) *Tile {
|
||||
sprites := []*ebiten.Image{}
|
||||
names := []string{}
|
||||
|
||||
for _, sprite := range class {
|
||||
sprites = append(sprites, Assets[sprite])
|
||||
names = append(names, sprite)
|
||||
}
|
||||
|
||||
return &Tile{
|
||||
Id: '*',
|
||||
Class: "transwall",
|
||||
Solid: false,
|
||||
Renderable: true,
|
||||
Transient: true,
|
||||
Tiles: sprites,
|
||||
Sprite: sprites[0], // initially use the first
|
||||
TileNames: names,
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,6 +147,7 @@ func InitTiles() TileRegistry {
|
||||
return TileRegistry{
|
||||
' ': {Id: ' ', Class: "floor", Renderable: false},
|
||||
'#': NewTileBlock("block-grey32"),
|
||||
'B': NewTileBlock("block-orange-32"),
|
||||
'S': NewTilePlayer(),
|
||||
'o': NewTileCollectible("collectible-orange"),
|
||||
'+': NewTileObstacle("obstacle-star", config.All),
|
||||
@ -138,6 +163,7 @@ func InitTiles() TileRegistry {
|
||||
"particle-ring-5",
|
||||
"particle-ring-6",
|
||||
}),
|
||||
't': NewTileTranswall([]string{"transwall", "block-orange-32"}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
assets/sprites/transwall.png
Normal file
BIN
assets/sprites/transwall.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@ -13,7 +13,7 @@ type Renderable struct {
|
||||
type Particle struct {
|
||||
Show bool
|
||||
Index int
|
||||
Particles []*ebiten.Image
|
||||
Tiles []*ebiten.Image
|
||||
}
|
||||
|
||||
type Speed struct {
|
||||
@ -26,4 +26,7 @@ type Solid struct{}
|
||||
type Floor struct{}
|
||||
type Player struct{}
|
||||
type Collectible struct{}
|
||||
type Obstacle struct{}
|
||||
|
||||
type Obstacle struct {
|
||||
Direction int
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package components
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log/slog"
|
||||
. "openquell/config"
|
||||
)
|
||||
|
||||
@ -90,13 +89,14 @@ func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *P
|
||||
|
||||
is := tile.Rect.Bounds().Intersect(object.Rect.Bounds())
|
||||
if is != image.ZR {
|
||||
/*
|
||||
slog.Debug("Intersect",
|
||||
"velocity", velocity.Data,
|
||||
"player", moving.Rect,
|
||||
"moved player", object.Rect,
|
||||
"collision at", is,
|
||||
)
|
||||
|
||||
*/
|
||||
// collision, snap into neighbouring tile depending on the direction
|
||||
switch velocity.Direction {
|
||||
case West:
|
||||
|
||||
24
components/transient.go
Normal file
24
components/transient.go
Normal file
@ -0,0 +1,24 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type Transient struct {
|
||||
Activated bool
|
||||
Sprites []string
|
||||
Current int // sprite index
|
||||
}
|
||||
|
||||
func (trans *Transient) GetNext() string {
|
||||
if len(trans.Sprites) > trans.Current {
|
||||
trans.Current++
|
||||
return trans.Sprites[trans.Current]
|
||||
}
|
||||
|
||||
log.Fatalf("not enough sprites in transient tile, have %d sprites, index requested: %d",
|
||||
len(trans.Sprites), trans.Current+1,
|
||||
)
|
||||
|
||||
return ""
|
||||
}
|
||||
@ -8,6 +8,18 @@ import (
|
||||
type Velocity struct {
|
||||
Data Position
|
||||
Direction int
|
||||
PointingAt int
|
||||
}
|
||||
|
||||
func (velocity *Velocity) Set(new *Velocity) {
|
||||
velocity.Direction = new.Direction
|
||||
velocity.Data.Set(&new.Data)
|
||||
}
|
||||
|
||||
func (velocity *Velocity) ResetDirectionAndStop() {
|
||||
velocity.Data.X = 0
|
||||
velocity.Data.Y = 0
|
||||
velocity.Direction = velocity.PointingAt
|
||||
}
|
||||
|
||||
func (velocity *Velocity) Change(direction int) {
|
||||
|
||||
@ -33,6 +33,7 @@ func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Gam
|
||||
|
||||
observers.NewPlayerObserver(&world)
|
||||
observers.NewParticleObserver(&world)
|
||||
observers.NewObstacleObserver(&world)
|
||||
game.Observer = observers.NewGameObserver(&world, startlevel, width, height, cellsize)
|
||||
|
||||
game.Scenes[Welcome] = NewWelcomeScene(game)
|
||||
|
||||
@ -70,7 +70,8 @@ func (scene *LevelScene) Update() error {
|
||||
|
||||
func (scene *LevelScene) Draw(screen *ebiten.Image) {
|
||||
if scene.CurrentLevel != scene.Game.Observer.CurrentLevel {
|
||||
slog.Debug("level", "current", scene.CurrentLevel, "next", scene.Game.Observer.CurrentLevel)
|
||||
slog.Debug("level", "current", scene.CurrentLevel,
|
||||
"next", scene.Game.Observer.CurrentLevel)
|
||||
scene.CurrentLevel = scene.Game.Observer.CurrentLevel
|
||||
scene.Levels[scene.CurrentLevel].SetupGrid(scene.Game)
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"openquell/grid"
|
||||
"openquell/observers"
|
||||
"openquell/systems"
|
||||
"openquell/util"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
@ -79,9 +80,11 @@ func (level *Level) SetupGrid(game *Game) {
|
||||
playerobserver.RemoveEntities()
|
||||
|
||||
// setup world
|
||||
level.GridSystem.SetGrid(grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice))
|
||||
level.GridSystem.SetGrid(
|
||||
grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice))
|
||||
}
|
||||
|
||||
// parses a RawLevel and generates a mapslice from it, which is being used as grid
|
||||
func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile {
|
||||
size := game.ScreenWidth * game.ScreenHeight
|
||||
mapslice := make(map[image.Point]*assets.Tile, size)
|
||||
@ -93,6 +96,10 @@ func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Po
|
||||
}
|
||||
|
||||
for x, char := range line {
|
||||
if !util.Exists(assets.Tiles, byte(char)) {
|
||||
log.Fatalf("unregistered tile type %c encountered", char)
|
||||
}
|
||||
|
||||
mapslice[image.Point{x, y}] = assets.Tiles[byte(char)]
|
||||
}
|
||||
}
|
||||
|
||||
40
grid/grid.go
40
grid/grid.go
@ -3,7 +3,6 @@ package grid
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
"openquell/config"
|
||||
@ -14,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type Grid struct {
|
||||
World *ecs.World
|
||||
Width int
|
||||
Height int
|
||||
Size int
|
||||
@ -48,18 +48,26 @@ func NewGrid(world *ecs.World,
|
||||
components.Renderable,
|
||||
components.Collectible](world)
|
||||
|
||||
obsmapper := generic.NewMap4[
|
||||
obsmapper := generic.NewMap5[
|
||||
components.Position,
|
||||
components.Velocity,
|
||||
components.Renderable,
|
||||
components.Speed,
|
||||
components.Obstacle](world)
|
||||
|
||||
transmapper := generic.NewMap3[
|
||||
components.Position,
|
||||
components.Renderable,
|
||||
components.Transient](world)
|
||||
|
||||
var pos *components.Position
|
||||
var vel *components.Velocity
|
||||
var render *components.Renderable
|
||||
var speed *components.Speed
|
||||
var transient *components.Transient
|
||||
|
||||
playerobserver := observers.GetPlayerObserver(world)
|
||||
obstacleobserver := observers.GetObstacleObserver(world)
|
||||
|
||||
for point, tile := range mapslice {
|
||||
switch tile.Renderable {
|
||||
@ -73,15 +81,20 @@ func NewGrid(world *ecs.World,
|
||||
pos, _, render, speed, _ = playermapper.Get(entity)
|
||||
playerobserver.AddEntity(entity)
|
||||
speed.Value = config.PLAYERSPEED
|
||||
slog.Debug("player start pos", "X", point.X*tilesize,
|
||||
"Y", point.Y*tilesize, "Z", 191)
|
||||
case tile.Collectible:
|
||||
entity := colmapper.New()
|
||||
pos, render, _ = colmapper.Get(entity)
|
||||
case tile.Obstacle:
|
||||
entity := obsmapper.New()
|
||||
pos, vel, render, _ = obsmapper.Get(entity)
|
||||
pos, vel, render, speed, _ = obsmapper.Get(entity)
|
||||
vel.Direction = tile.Direction
|
||||
vel.PointingAt = tile.Direction
|
||||
speed.Value = config.PLAYERSPEED
|
||||
obstacleobserver.AddEntity(entity)
|
||||
case tile.Transient:
|
||||
entity := transmapper.New()
|
||||
pos, render, transient = transmapper.Get(entity)
|
||||
transient.Sprites = tile.TileNames
|
||||
default:
|
||||
log.Fatalln("unsupported tile type encountered")
|
||||
}
|
||||
@ -106,6 +119,7 @@ func NewGrid(world *ecs.World,
|
||||
Width: width,
|
||||
Height: height,
|
||||
Map: mapslice,
|
||||
World: world,
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,3 +136,19 @@ func (grid *Grid) GetTile(
|
||||
tile := grid.Map[newpoint]
|
||||
return tile
|
||||
}
|
||||
|
||||
func (grid *Grid) SetTile(tile *assets.Tile, point image.Point) {
|
||||
solidmapper := generic.NewMap4[
|
||||
components.Position,
|
||||
components.Renderable,
|
||||
components.Tilish,
|
||||
components.Solid](grid.World)
|
||||
|
||||
grid.Map[point] = tile
|
||||
|
||||
entity := solidmapper.New()
|
||||
pos, render, _, _ := solidmapper.Get(entity)
|
||||
|
||||
render.Image = tile.Sprite
|
||||
pos.Update(point.X*grid.Tilesize, point.Y*grid.Tilesize, grid.Tilesize)
|
||||
}
|
||||
|
||||
54
observers/obstacle_observer.go
Normal file
54
observers/obstacle_observer.go
Normal file
@ -0,0 +1,54 @@
|
||||
package observers
|
||||
|
||||
import (
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
"github.com/mlange-42/arche/ecs/event"
|
||||
"github.com/mlange-42/arche/generic"
|
||||
"github.com/mlange-42/arche/listener"
|
||||
)
|
||||
|
||||
// will be added as an ecs.Resource to the world so we can use it in
|
||||
// CollisionSystem to dynamically make it appear or disappear
|
||||
type ObstacleObserver struct {
|
||||
// we only have one obstacle so far, if we use multiple ones, turn
|
||||
// this in to a map, see player_observer.go for an example
|
||||
Entities map[ecs.Entity]int
|
||||
}
|
||||
|
||||
// Create a new resource of type PlayerObserver which will hold all
|
||||
// obstacle entities. We also add a Listener which looks for
|
||||
// EntityRemoved events and if a player gets removed, we also remove
|
||||
// it from the Entity map in our observer.
|
||||
func NewObstacleObserver(world *ecs.World) {
|
||||
observer := &ObstacleObserver{}
|
||||
observer.Entities = make(map[ecs.Entity]int)
|
||||
|
||||
resmanger := generic.NewResource[ObstacleObserver](world)
|
||||
resmanger.Add(observer)
|
||||
|
||||
listen := listener.NewCallback(
|
||||
func(world *ecs.World, event ecs.EntityEvent) {
|
||||
observerID := ecs.ResourceID[ObstacleObserver](world)
|
||||
observer := world.Resources().Get(observerID).(*ObstacleObserver)
|
||||
observer.RemoveEntity(event.Entity)
|
||||
},
|
||||
|
||||
event.EntityRemoved,
|
||||
)
|
||||
|
||||
world.SetListener(&listen)
|
||||
}
|
||||
|
||||
func GetObstacleObserver(world *ecs.World) *ObstacleObserver {
|
||||
observerID := ecs.ResourceID[ObstacleObserver](world)
|
||||
observer := world.Resources().Get(observerID).(*ObstacleObserver)
|
||||
return observer
|
||||
}
|
||||
|
||||
func (observer *ObstacleObserver) AddEntity(entity ecs.Entity) {
|
||||
observer.Entities[entity] = 1
|
||||
}
|
||||
|
||||
func (observer *ObstacleObserver) RemoveEntity(entity ecs.Entity) {
|
||||
observer.Entities = make(map[ecs.Entity]int)
|
||||
}
|
||||
BIN
src/transwall.xcf
Normal file
BIN
src/transwall.xcf
Normal file
Binary file not shown.
@ -109,7 +109,7 @@ func (system *CollectibleSystem) AddParticle(position *components.Position) {
|
||||
particleobserver.AddEntity(entity)
|
||||
|
||||
particle.Index = assets.Tiles['*'].Particle
|
||||
particle.Particles = assets.Tiles['*'].Particles
|
||||
particle.Tiles = assets.Tiles['*'].Tiles
|
||||
|
||||
pos.Update(
|
||||
position.X-(16), // FIXME: use global tilesize!
|
||||
|
||||
@ -18,6 +18,7 @@ type GridSystem struct {
|
||||
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
|
||||
@ -52,12 +53,13 @@ func (system *GridSystem) Update() {}
|
||||
|
||||
func (system *GridSystem) Draw(screen *ebiten.Image) {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
query := system.Selector.Query(system.World)
|
||||
|
||||
if !system.UseCache {
|
||||
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)
|
||||
|
||||
query := system.Selector.Query(system.World)
|
||||
system.Count = query.Count()
|
||||
|
||||
for query.Next() {
|
||||
sprite, pos, _ := query.Get()
|
||||
@ -76,6 +78,7 @@ func (system *GridSystem) Draw(screen *ebiten.Image) {
|
||||
// use the cached map
|
||||
op.GeoM.Reset()
|
||||
screen.DrawImage(system.Cache, op)
|
||||
query.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,14 +16,15 @@ import (
|
||||
|
||||
type ObstacleSystem struct {
|
||||
World *ecs.World
|
||||
Selector *generic.Filter4[Position, Velocity, Obstacle, Renderable]
|
||||
PreviousFreePos *components.Position
|
||||
Selector *generic.Filter5[Position, Velocity, Obstacle, Renderable, Speed]
|
||||
Grid *GridSystem
|
||||
}
|
||||
|
||||
func NewObstacleSystem(world *ecs.World) *ObstacleSystem {
|
||||
func NewObstacleSystem(world *ecs.World, grid *GridSystem) *ObstacleSystem {
|
||||
system := &ObstacleSystem{
|
||||
Selector: generic.NewFilter4[Position, Velocity, Obstacle, Renderable](),
|
||||
Selector: generic.NewFilter5[Position, Velocity, Obstacle, Renderable, Speed](),
|
||||
World: world,
|
||||
Grid: grid,
|
||||
}
|
||||
|
||||
return system
|
||||
@ -32,6 +33,7 @@ func NewObstacleSystem(world *ecs.World) *ObstacleSystem {
|
||||
func (system *ObstacleSystem) Update() {
|
||||
playerobserver := observers.GetPlayerObserver(system.World)
|
||||
gameobserver := observers.GetGameObserver(system.World)
|
||||
obstacleobserver := observers.GetObstacleObserver(system.World)
|
||||
|
||||
if gameobserver.Lost {
|
||||
return
|
||||
@ -46,27 +48,67 @@ func (system *ObstacleSystem) Update() {
|
||||
gameover := false
|
||||
|
||||
for query.Next() {
|
||||
obsposition, obsvelocity, obstacle, _ := query.Get()
|
||||
obsposition, obsvelocity, _, _, speed := query.Get()
|
||||
|
||||
// check if one player has bumped into current obstacle
|
||||
for player := range playerobserver.Entities {
|
||||
playerposition := (*Position)(system.World.Get(player, posID))
|
||||
playervelocity := (*Velocity)(system.World.Get(player, veloID))
|
||||
|
||||
ok, newpos := obsposition.Intersects(playerposition, playervelocity)
|
||||
if ok {
|
||||
slog.Debug("bumped into obstacle", "obstacle", obstacle)
|
||||
// slog.Debug("bumped into obstacle", "obstacle", obstacle)
|
||||
|
||||
if CheckObstacleSide(playervelocity, obsvelocity.Direction) {
|
||||
// player died
|
||||
EntitiesToRemove = append(EntitiesToRemove, player)
|
||||
gameover = true
|
||||
} else {
|
||||
playervelocity.Change(Stop)
|
||||
// bumped into nonlethal obstacle side, stop the
|
||||
// player, set the obstacle in motion, if possible
|
||||
slog.Debug("bump not die", "originalpos", playerposition)
|
||||
obsvelocity.Set(playervelocity)
|
||||
playervelocity.Change(Stop)
|
||||
playerposition.Set(newpos)
|
||||
slog.Debug("bump not die", "newpos", newpos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if current obstacle bumped into another obstacle
|
||||
for foreign_obstacle := range obstacleobserver.Entities {
|
||||
if foreign_obstacle == query.Entity() {
|
||||
// don't check obstacle against itself
|
||||
continue
|
||||
}
|
||||
|
||||
foreign_obstacle_position := (*Position)(system.World.Get(foreign_obstacle, posID))
|
||||
|
||||
ok, newpos := foreign_obstacle_position.Intersects(obsposition, obsvelocity)
|
||||
if ok {
|
||||
//slog.Debug("bumped into foreign obstacle", "obstacle", foreign_obstacle)
|
||||
obsposition.Set(newpos)
|
||||
obsvelocity.ResetDirectionAndStop()
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if ok {
|
||||
intersects, newpos := tilepos.Intersects(obsposition, obsvelocity)
|
||||
if intersects {
|
||||
// slog.Debug("collision with foreign obstacle detected", "tile",
|
||||
// tilepos, "obs", obsposition, "new", newpos)
|
||||
|
||||
obsposition.Set(newpos)
|
||||
obsvelocity.ResetDirectionAndStop()
|
||||
}
|
||||
}
|
||||
|
||||
obsposition.Move(obsvelocity, speed)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, entity := range EntitiesToRemove {
|
||||
@ -91,7 +133,7 @@ func (system *ObstacleSystem) Draw(screen *ebiten.Image) {
|
||||
query := system.Selector.Query(system.World)
|
||||
|
||||
for query.Next() {
|
||||
pos, _, _, sprite := query.Get()
|
||||
pos, _, _, sprite, _ := query.Get()
|
||||
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
||||
@ -114,7 +156,7 @@ func (system *ObstacleSystem) AddParticle(position *components.Position) {
|
||||
particleobserver.AddEntity(entity)
|
||||
|
||||
particle.Index = assets.Tiles['*'].Particle
|
||||
particle.Particles = assets.Tiles['*'].Particles
|
||||
particle.Tiles = assets.Tiles['*'].Tiles
|
||||
|
||||
pos.Update(
|
||||
position.X-(16), // FIXME: use global tilesize!
|
||||
|
||||
@ -40,7 +40,7 @@ func (system *ParticleSystem) Update() {
|
||||
if timer.IsReady() {
|
||||
switch {
|
||||
// particle shows from earlier tick, animate
|
||||
case particle.Index > -1 && particle.Index < len(particle.Particles)-1:
|
||||
case particle.Index > -1 && particle.Index < len(particle.Tiles)-1:
|
||||
particle.Index++
|
||||
timer.Start(config.PARTICLE_LOOPWAIT)
|
||||
default:
|
||||
@ -69,7 +69,7 @@ func (system *ParticleSystem) Draw(screen *ebiten.Image) {
|
||||
if particle.Show {
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
||||
screen.DrawImage(particle.Particles[particle.Index], op)
|
||||
screen.DrawImage(particle.Tiles[particle.Index], op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package systems
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
. "openquell/components"
|
||||
. "openquell/config"
|
||||
|
||||
@ -16,6 +15,7 @@ type PlayerSystem struct {
|
||||
Particle *ParticleSystem
|
||||
Collectible *CollectibleSystem
|
||||
Obstacle *ObstacleSystem
|
||||
Transient *TransientSystem
|
||||
Grid *GridSystem
|
||||
}
|
||||
|
||||
@ -24,7 +24,8 @@ func NewPlayerSystem(world *ecs.World, grid *GridSystem) *PlayerSystem {
|
||||
Selector: generic.NewFilter5[Position, Velocity, Player, Renderable, Speed](),
|
||||
Particle: NewParticleSystem(world, grid.Tilesize),
|
||||
Collectible: NewCollectibleSystem(world),
|
||||
Obstacle: NewObstacleSystem(world),
|
||||
Obstacle: NewObstacleSystem(world, grid),
|
||||
Transient: NewTransientSystem(world, grid),
|
||||
Grid: grid,
|
||||
World: world,
|
||||
}
|
||||
@ -55,15 +56,15 @@ func (system PlayerSystem) Update() error {
|
||||
if velocity.Moving() {
|
||||
ok, newpos := system.Grid.BumpEdge(playerposition, velocity)
|
||||
if ok {
|
||||
slog.Debug("falling off the edge", "newpos", newpos)
|
||||
//slog.Debug("falling off the edge", "newpos", newpos)
|
||||
playerposition.Set(newpos)
|
||||
} else {
|
||||
ok, tilepos := system.Grid.GetSolidNeighborPosition(playerposition, velocity, true)
|
||||
if ok {
|
||||
intersects, newpos := tilepos.Intersects(playerposition, velocity)
|
||||
if intersects {
|
||||
slog.Debug("collision detected", "tile",
|
||||
tilepos, "player", playerposition, "new", newpos)
|
||||
// slog.Debug("collision detected", "tile",
|
||||
// tilepos, "player", playerposition, "new", newpos)
|
||||
|
||||
playerposition.Set(newpos)
|
||||
velocity.Change(Stop)
|
||||
@ -76,6 +77,7 @@ 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() {
|
||||
@ -104,4 +106,5 @@ func (system *PlayerSystem) Draw(screen *ebiten.Image) {
|
||||
system.Collectible.Draw(screen)
|
||||
system.Particle.Draw(screen)
|
||||
system.Obstacle.Draw(screen)
|
||||
system.Transient.Draw(screen)
|
||||
}
|
||||
|
||||
116
systems/transient_system.go
Normal file
116
systems/transient_system.go
Normal file
@ -0,0 +1,116 @@
|
||||
package systems
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
. "openquell/components"
|
||||
"openquell/observers"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
"github.com/mlange-42/arche/generic"
|
||||
)
|
||||
|
||||
type TransientSystem struct {
|
||||
World *ecs.World
|
||||
Selector *generic.Filter3[Position, Renderable, Transient]
|
||||
Grid *GridSystem
|
||||
SolidMapper generic.Map4[ // needed for replacement
|
||||
components.Position,
|
||||
components.Renderable,
|
||||
components.Tilish,
|
||||
components.Solid]
|
||||
}
|
||||
|
||||
type TransientToWall struct {
|
||||
Entity ecs.Entity
|
||||
NewSprite string
|
||||
Position components.Position
|
||||
}
|
||||
|
||||
func NewTransientSystem(world *ecs.World, grid *GridSystem) *TransientSystem {
|
||||
solidmapper := generic.NewMap4[
|
||||
components.Position,
|
||||
components.Renderable,
|
||||
components.Tilish,
|
||||
components.Solid](world)
|
||||
|
||||
system := &TransientSystem{
|
||||
Selector: generic.NewFilter3[Position, Renderable, Transient](),
|
||||
World: world,
|
||||
Grid: grid,
|
||||
SolidMapper: solidmapper,
|
||||
}
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
func (system *TransientSystem) Update() {
|
||||
playerobserver := observers.GetPlayerObserver(system.World)
|
||||
posID := ecs.ComponentID[components.Position](system.World)
|
||||
veloID := ecs.ComponentID[components.Velocity](system.World)
|
||||
|
||||
query := system.Selector.Query(system.World)
|
||||
|
||||
EntitiestoMakeSolid := []TransientToWall{}
|
||||
|
||||
for query.Next() {
|
||||
transientposition, _, transient := query.Get()
|
||||
|
||||
for player := range playerobserver.Entities {
|
||||
playerposition := (*Position)(system.World.Get(player, posID))
|
||||
playervelocity := (*Velocity)(system.World.Get(player, veloID))
|
||||
|
||||
ok, _ := transientposition.Intersects(playerposition, playervelocity)
|
||||
if ok {
|
||||
// display the transient sprite as long as the player crosses it
|
||||
transient.Activated = true
|
||||
} else {
|
||||
// the player crossed the transient wall completely
|
||||
if transient.Activated {
|
||||
EntitiestoMakeSolid = append(EntitiestoMakeSolid, TransientToWall{
|
||||
Entity: query.Entity(),
|
||||
Position: *transientposition,
|
||||
NewSprite: transient.GetNext(),
|
||||
})
|
||||
slog.Debug("done transient", "transient", transientposition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, convertible := range EntitiestoMakeSolid {
|
||||
// remove transient entity
|
||||
system.World.RemoveEntity(convertible.Entity)
|
||||
|
||||
// replace with solid entity
|
||||
entity := system.SolidMapper.New()
|
||||
pos, render, _, _ := system.SolidMapper.Get(entity)
|
||||
|
||||
// set it up apropriately
|
||||
pos.Set(&convertible.Position)
|
||||
render.Image = assets.Assets[convertible.NewSprite]
|
||||
|
||||
// also setup the grid tile with a new solid, so that
|
||||
// collision detection works
|
||||
system.Grid.Grid.SetTile(
|
||||
assets.NewTileBlock(convertible.NewSprite),
|
||||
convertible.Position.Point(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (system *TransientSystem) Draw(screen *ebiten.Image) {
|
||||
// write transients (these are no tiles!)
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
query := system.Selector.Query(system.World)
|
||||
|
||||
for query.Next() {
|
||||
pos, render, _ := query.Get()
|
||||
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
||||
screen.DrawImage(render.Image, op)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user