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 <#
|
#> S <#
|
||||||
# #
|
# #
|
||||||
# ^ #
|
# ^ #
|
||||||
|
|||||||
@ -3,13 +3,13 @@ Background: background-lila
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#######
|
#############
|
||||||
# ^ #
|
# #
|
||||||
# #
|
#> S # #
|
||||||
#< S >#
|
# ># >#
|
||||||
# #
|
#< # #
|
||||||
#^ v#
|
# v# <#
|
||||||
#######
|
#############
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -30,8 +30,10 @@ type Tile struct {
|
|||||||
Renderable bool
|
Renderable bool
|
||||||
Velocity bool
|
Velocity bool
|
||||||
Collectible bool
|
Collectible bool
|
||||||
|
Transient bool
|
||||||
Particle int // -1=unused, 0-3 = show image of slice
|
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
|
Obstacle bool
|
||||||
Direction int // obstacles
|
Direction int // obstacles
|
||||||
}
|
}
|
||||||
@ -77,6 +79,7 @@ func NewTileObstacle(class string, direction int) *Tile {
|
|||||||
Renderable: true,
|
Renderable: true,
|
||||||
Obstacle: true,
|
Obstacle: true,
|
||||||
Direction: direction,
|
Direction: direction,
|
||||||
|
Velocity: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +96,28 @@ func NewTileParticle(class []string) *Tile {
|
|||||||
Solid: false,
|
Solid: false,
|
||||||
Renderable: false,
|
Renderable: false,
|
||||||
Particle: 0,
|
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{
|
return TileRegistry{
|
||||||
' ': {Id: ' ', Class: "floor", Renderable: false},
|
' ': {Id: ' ', Class: "floor", Renderable: false},
|
||||||
'#': NewTileBlock("block-grey32"),
|
'#': NewTileBlock("block-grey32"),
|
||||||
|
'B': NewTileBlock("block-orange-32"),
|
||||||
'S': NewTilePlayer(),
|
'S': NewTilePlayer(),
|
||||||
'o': NewTileCollectible("collectible-orange"),
|
'o': NewTileCollectible("collectible-orange"),
|
||||||
'+': NewTileObstacle("obstacle-star", config.All),
|
'+': NewTileObstacle("obstacle-star", config.All),
|
||||||
@ -138,6 +163,7 @@ func InitTiles() TileRegistry {
|
|||||||
"particle-ring-5",
|
"particle-ring-5",
|
||||||
"particle-ring-6",
|
"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 |
@ -11,9 +11,9 @@ type Renderable struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Particle struct {
|
type Particle struct {
|
||||||
Show bool
|
Show bool
|
||||||
Index int
|
Index int
|
||||||
Particles []*ebiten.Image
|
Tiles []*ebiten.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
type Speed struct {
|
type Speed struct {
|
||||||
@ -26,4 +26,7 @@ type Solid struct{}
|
|||||||
type Floor struct{}
|
type Floor struct{}
|
||||||
type Player struct{}
|
type Player struct{}
|
||||||
type Collectible struct{}
|
type Collectible struct{}
|
||||||
type Obstacle struct{}
|
|
||||||
|
type Obstacle struct {
|
||||||
|
Direction int
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package components
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"log/slog"
|
|
||||||
. "openquell/config"
|
. "openquell/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -90,13 +89,14 @@ func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *P
|
|||||||
|
|
||||||
is := tile.Rect.Bounds().Intersect(object.Rect.Bounds())
|
is := tile.Rect.Bounds().Intersect(object.Rect.Bounds())
|
||||||
if is != image.ZR {
|
if is != image.ZR {
|
||||||
slog.Debug("Intersect",
|
/*
|
||||||
"velocity", velocity.Data,
|
slog.Debug("Intersect",
|
||||||
"player", moving.Rect,
|
"velocity", velocity.Data,
|
||||||
"moved player", object.Rect,
|
"player", moving.Rect,
|
||||||
"collision at", is,
|
"moved player", object.Rect,
|
||||||
)
|
"collision at", is,
|
||||||
|
)
|
||||||
|
*/
|
||||||
// collision, snap into neighbouring tile depending on the direction
|
// collision, snap into neighbouring tile depending on the direction
|
||||||
switch velocity.Direction {
|
switch velocity.Direction {
|
||||||
case West:
|
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 ""
|
||||||
|
}
|
||||||
@ -6,8 +6,20 @@ import (
|
|||||||
|
|
||||||
// movement in relation to position
|
// movement in relation to position
|
||||||
type Velocity struct {
|
type Velocity struct {
|
||||||
Data Position
|
Data Position
|
||||||
Direction int
|
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) {
|
func (velocity *Velocity) Change(direction int) {
|
||||||
|
|||||||
@ -33,6 +33,7 @@ func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Gam
|
|||||||
|
|
||||||
observers.NewPlayerObserver(&world)
|
observers.NewPlayerObserver(&world)
|
||||||
observers.NewParticleObserver(&world)
|
observers.NewParticleObserver(&world)
|
||||||
|
observers.NewObstacleObserver(&world)
|
||||||
game.Observer = observers.NewGameObserver(&world, startlevel, width, height, cellsize)
|
game.Observer = observers.NewGameObserver(&world, startlevel, width, height, cellsize)
|
||||||
|
|
||||||
game.Scenes[Welcome] = NewWelcomeScene(game)
|
game.Scenes[Welcome] = NewWelcomeScene(game)
|
||||||
|
|||||||
@ -70,7 +70,8 @@ func (scene *LevelScene) Update() error {
|
|||||||
|
|
||||||
func (scene *LevelScene) Draw(screen *ebiten.Image) {
|
func (scene *LevelScene) Draw(screen *ebiten.Image) {
|
||||||
if scene.CurrentLevel != scene.Game.Observer.CurrentLevel {
|
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.CurrentLevel = scene.Game.Observer.CurrentLevel
|
||||||
scene.Levels[scene.CurrentLevel].SetupGrid(scene.Game)
|
scene.Levels[scene.CurrentLevel].SetupGrid(scene.Game)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"openquell/grid"
|
"openquell/grid"
|
||||||
"openquell/observers"
|
"openquell/observers"
|
||||||
"openquell/systems"
|
"openquell/systems"
|
||||||
|
"openquell/util"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
@ -79,9 +80,11 @@ func (level *Level) SetupGrid(game *Game) {
|
|||||||
playerobserver.RemoveEntities()
|
playerobserver.RemoveEntities()
|
||||||
|
|
||||||
// setup world
|
// 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 {
|
func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile {
|
||||||
size := game.ScreenWidth * game.ScreenHeight
|
size := game.ScreenWidth * game.ScreenHeight
|
||||||
mapslice := make(map[image.Point]*assets.Tile, size)
|
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 {
|
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)]
|
mapslice[image.Point{x, y}] = assets.Tiles[byte(char)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
grid/grid.go
40
grid/grid.go
@ -3,7 +3,6 @@ package grid
|
|||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"openquell/assets"
|
"openquell/assets"
|
||||||
"openquell/components"
|
"openquell/components"
|
||||||
"openquell/config"
|
"openquell/config"
|
||||||
@ -14,6 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Grid struct {
|
type Grid struct {
|
||||||
|
World *ecs.World
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
Size int
|
Size int
|
||||||
@ -48,18 +48,26 @@ func NewGrid(world *ecs.World,
|
|||||||
components.Renderable,
|
components.Renderable,
|
||||||
components.Collectible](world)
|
components.Collectible](world)
|
||||||
|
|
||||||
obsmapper := generic.NewMap4[
|
obsmapper := generic.NewMap5[
|
||||||
components.Position,
|
components.Position,
|
||||||
components.Velocity,
|
components.Velocity,
|
||||||
components.Renderable,
|
components.Renderable,
|
||||||
|
components.Speed,
|
||||||
components.Obstacle](world)
|
components.Obstacle](world)
|
||||||
|
|
||||||
|
transmapper := generic.NewMap3[
|
||||||
|
components.Position,
|
||||||
|
components.Renderable,
|
||||||
|
components.Transient](world)
|
||||||
|
|
||||||
var pos *components.Position
|
var pos *components.Position
|
||||||
var vel *components.Velocity
|
var vel *components.Velocity
|
||||||
var render *components.Renderable
|
var render *components.Renderable
|
||||||
var speed *components.Speed
|
var speed *components.Speed
|
||||||
|
var transient *components.Transient
|
||||||
|
|
||||||
playerobserver := observers.GetPlayerObserver(world)
|
playerobserver := observers.GetPlayerObserver(world)
|
||||||
|
obstacleobserver := observers.GetObstacleObserver(world)
|
||||||
|
|
||||||
for point, tile := range mapslice {
|
for point, tile := range mapslice {
|
||||||
switch tile.Renderable {
|
switch tile.Renderable {
|
||||||
@ -73,15 +81,20 @@ func NewGrid(world *ecs.World,
|
|||||||
pos, _, render, speed, _ = playermapper.Get(entity)
|
pos, _, render, speed, _ = playermapper.Get(entity)
|
||||||
playerobserver.AddEntity(entity)
|
playerobserver.AddEntity(entity)
|
||||||
speed.Value = config.PLAYERSPEED
|
speed.Value = config.PLAYERSPEED
|
||||||
slog.Debug("player start pos", "X", point.X*tilesize,
|
|
||||||
"Y", point.Y*tilesize, "Z", 191)
|
|
||||||
case tile.Collectible:
|
case tile.Collectible:
|
||||||
entity := colmapper.New()
|
entity := colmapper.New()
|
||||||
pos, render, _ = colmapper.Get(entity)
|
pos, render, _ = colmapper.Get(entity)
|
||||||
case tile.Obstacle:
|
case tile.Obstacle:
|
||||||
entity := obsmapper.New()
|
entity := obsmapper.New()
|
||||||
pos, vel, render, _ = obsmapper.Get(entity)
|
pos, vel, render, speed, _ = obsmapper.Get(entity)
|
||||||
vel.Direction = tile.Direction
|
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:
|
default:
|
||||||
log.Fatalln("unsupported tile type encountered")
|
log.Fatalln("unsupported tile type encountered")
|
||||||
}
|
}
|
||||||
@ -106,6 +119,7 @@ func NewGrid(world *ecs.World,
|
|||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
Map: mapslice,
|
Map: mapslice,
|
||||||
|
World: world,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,3 +136,19 @@ func (grid *Grid) GetTile(
|
|||||||
tile := grid.Map[newpoint]
|
tile := grid.Map[newpoint]
|
||||||
return tile
|
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)
|
particleobserver.AddEntity(entity)
|
||||||
|
|
||||||
particle.Index = assets.Tiles['*'].Particle
|
particle.Index = assets.Tiles['*'].Particle
|
||||||
particle.Particles = assets.Tiles['*'].Particles
|
particle.Tiles = assets.Tiles['*'].Tiles
|
||||||
|
|
||||||
pos.Update(
|
pos.Update(
|
||||||
position.X-(16), // FIXME: use global tilesize!
|
position.X-(16), // FIXME: use global tilesize!
|
||||||
|
|||||||
@ -18,6 +18,7 @@ type GridSystem struct {
|
|||||||
Selector *generic.Filter3[Renderable, Position, Solid]
|
Selector *generic.Filter3[Renderable, Position, Solid]
|
||||||
UseCache bool
|
UseCache bool
|
||||||
Cache *ebiten.Image
|
Cache *ebiten.Image
|
||||||
|
Count int // register tile count, invalidates cache
|
||||||
Background *ebiten.Image
|
Background *ebiten.Image
|
||||||
Width, Height, TilesX, TilesY, Tilesize int
|
Width, Height, TilesX, TilesY, Tilesize int
|
||||||
Grid *grid.Grid
|
Grid *grid.Grid
|
||||||
@ -52,12 +53,13 @@ func (system *GridSystem) Update() {}
|
|||||||
|
|
||||||
func (system *GridSystem) Draw(screen *ebiten.Image) {
|
func (system *GridSystem) Draw(screen *ebiten.Image) {
|
||||||
op := &ebiten.DrawImageOptions{}
|
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
|
// map not cached or cacheable, write it to the cache
|
||||||
draw.Draw(system.Cache, system.Background.Bounds(), system.Background, image.ZP, draw.Src)
|
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() {
|
for query.Next() {
|
||||||
sprite, pos, _ := query.Get()
|
sprite, pos, _ := query.Get()
|
||||||
@ -76,6 +78,7 @@ func (system *GridSystem) Draw(screen *ebiten.Image) {
|
|||||||
// use the cached map
|
// use the cached map
|
||||||
op.GeoM.Reset()
|
op.GeoM.Reset()
|
||||||
screen.DrawImage(system.Cache, op)
|
screen.DrawImage(system.Cache, op)
|
||||||
|
query.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ func (system *GridSystem) GetSolidNeighborPosition(
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// set to true, ifwe are already on the last tile in the current
|
// set to true, if we are already on the last tile in the current
|
||||||
// direction, i.e. on the edge of the grid
|
// direction, i.e. on the edge of the grid
|
||||||
edge := true
|
edge := true
|
||||||
neighborpos := position.Point()
|
neighborpos := position.Point()
|
||||||
|
|||||||
@ -15,15 +15,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ObstacleSystem struct {
|
type ObstacleSystem struct {
|
||||||
World *ecs.World
|
World *ecs.World
|
||||||
Selector *generic.Filter4[Position, Velocity, Obstacle, Renderable]
|
Selector *generic.Filter5[Position, Velocity, Obstacle, Renderable, Speed]
|
||||||
PreviousFreePos *components.Position
|
Grid *GridSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewObstacleSystem(world *ecs.World) *ObstacleSystem {
|
func NewObstacleSystem(world *ecs.World, grid *GridSystem) *ObstacleSystem {
|
||||||
system := &ObstacleSystem{
|
system := &ObstacleSystem{
|
||||||
Selector: generic.NewFilter4[Position, Velocity, Obstacle, Renderable](),
|
Selector: generic.NewFilter5[Position, Velocity, Obstacle, Renderable, Speed](),
|
||||||
World: world,
|
World: world,
|
||||||
|
Grid: grid,
|
||||||
}
|
}
|
||||||
|
|
||||||
return system
|
return system
|
||||||
@ -32,6 +33,7 @@ func NewObstacleSystem(world *ecs.World) *ObstacleSystem {
|
|||||||
func (system *ObstacleSystem) Update() {
|
func (system *ObstacleSystem) Update() {
|
||||||
playerobserver := observers.GetPlayerObserver(system.World)
|
playerobserver := observers.GetPlayerObserver(system.World)
|
||||||
gameobserver := observers.GetGameObserver(system.World)
|
gameobserver := observers.GetGameObserver(system.World)
|
||||||
|
obstacleobserver := observers.GetObstacleObserver(system.World)
|
||||||
|
|
||||||
if gameobserver.Lost {
|
if gameobserver.Lost {
|
||||||
return
|
return
|
||||||
@ -46,27 +48,67 @@ func (system *ObstacleSystem) Update() {
|
|||||||
gameover := false
|
gameover := false
|
||||||
|
|
||||||
for query.Next() {
|
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 {
|
for player := range playerobserver.Entities {
|
||||||
playerposition := (*Position)(system.World.Get(player, posID))
|
playerposition := (*Position)(system.World.Get(player, posID))
|
||||||
playervelocity := (*Velocity)(system.World.Get(player, veloID))
|
playervelocity := (*Velocity)(system.World.Get(player, veloID))
|
||||||
|
|
||||||
ok, newpos := obsposition.Intersects(playerposition, playervelocity)
|
ok, newpos := obsposition.Intersects(playerposition, playervelocity)
|
||||||
if ok {
|
if ok {
|
||||||
slog.Debug("bumped into obstacle", "obstacle", obstacle)
|
// slog.Debug("bumped into obstacle", "obstacle", obstacle)
|
||||||
|
|
||||||
if CheckObstacleSide(playervelocity, obsvelocity.Direction) {
|
if CheckObstacleSide(playervelocity, obsvelocity.Direction) {
|
||||||
|
// player died
|
||||||
EntitiesToRemove = append(EntitiesToRemove, player)
|
EntitiesToRemove = append(EntitiesToRemove, player)
|
||||||
gameover = true
|
gameover = true
|
||||||
} else {
|
} 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)
|
slog.Debug("bump not die", "originalpos", playerposition)
|
||||||
|
obsvelocity.Set(playervelocity)
|
||||||
|
playervelocity.Change(Stop)
|
||||||
playerposition.Set(newpos)
|
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 {
|
for _, entity := range EntitiesToRemove {
|
||||||
@ -91,7 +133,7 @@ func (system *ObstacleSystem) Draw(screen *ebiten.Image) {
|
|||||||
query := system.Selector.Query(system.World)
|
query := system.Selector.Query(system.World)
|
||||||
|
|
||||||
for query.Next() {
|
for query.Next() {
|
||||||
pos, _, _, sprite := query.Get()
|
pos, _, _, sprite, _ := query.Get()
|
||||||
|
|
||||||
op.GeoM.Reset()
|
op.GeoM.Reset()
|
||||||
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
||||||
@ -114,7 +156,7 @@ func (system *ObstacleSystem) AddParticle(position *components.Position) {
|
|||||||
particleobserver.AddEntity(entity)
|
particleobserver.AddEntity(entity)
|
||||||
|
|
||||||
particle.Index = assets.Tiles['*'].Particle
|
particle.Index = assets.Tiles['*'].Particle
|
||||||
particle.Particles = assets.Tiles['*'].Particles
|
particle.Tiles = assets.Tiles['*'].Tiles
|
||||||
|
|
||||||
pos.Update(
|
pos.Update(
|
||||||
position.X-(16), // FIXME: use global tilesize!
|
position.X-(16), // FIXME: use global tilesize!
|
||||||
|
|||||||
@ -40,7 +40,7 @@ func (system *ParticleSystem) Update() {
|
|||||||
if timer.IsReady() {
|
if timer.IsReady() {
|
||||||
switch {
|
switch {
|
||||||
// particle shows from earlier tick, animate
|
// 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++
|
particle.Index++
|
||||||
timer.Start(config.PARTICLE_LOOPWAIT)
|
timer.Start(config.PARTICLE_LOOPWAIT)
|
||||||
default:
|
default:
|
||||||
@ -69,7 +69,7 @@ func (system *ParticleSystem) Draw(screen *ebiten.Image) {
|
|||||||
if particle.Show {
|
if particle.Show {
|
||||||
op.GeoM.Reset()
|
op.GeoM.Reset()
|
||||||
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
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
|
package systems
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
. "openquell/components"
|
. "openquell/components"
|
||||||
. "openquell/config"
|
. "openquell/config"
|
||||||
|
|
||||||
@ -16,6 +15,7 @@ type PlayerSystem struct {
|
|||||||
Particle *ParticleSystem
|
Particle *ParticleSystem
|
||||||
Collectible *CollectibleSystem
|
Collectible *CollectibleSystem
|
||||||
Obstacle *ObstacleSystem
|
Obstacle *ObstacleSystem
|
||||||
|
Transient *TransientSystem
|
||||||
Grid *GridSystem
|
Grid *GridSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,8 @@ func NewPlayerSystem(world *ecs.World, grid *GridSystem) *PlayerSystem {
|
|||||||
Selector: generic.NewFilter5[Position, Velocity, Player, Renderable, Speed](),
|
Selector: generic.NewFilter5[Position, Velocity, Player, Renderable, Speed](),
|
||||||
Particle: NewParticleSystem(world, grid.Tilesize),
|
Particle: NewParticleSystem(world, grid.Tilesize),
|
||||||
Collectible: NewCollectibleSystem(world),
|
Collectible: NewCollectibleSystem(world),
|
||||||
Obstacle: NewObstacleSystem(world),
|
Obstacle: NewObstacleSystem(world, grid),
|
||||||
|
Transient: NewTransientSystem(world, grid),
|
||||||
Grid: grid,
|
Grid: grid,
|
||||||
World: world,
|
World: world,
|
||||||
}
|
}
|
||||||
@ -55,15 +56,15 @@ func (system PlayerSystem) Update() error {
|
|||||||
if velocity.Moving() {
|
if velocity.Moving() {
|
||||||
ok, newpos := system.Grid.BumpEdge(playerposition, velocity)
|
ok, newpos := system.Grid.BumpEdge(playerposition, velocity)
|
||||||
if ok {
|
if ok {
|
||||||
slog.Debug("falling off the edge", "newpos", newpos)
|
//slog.Debug("falling off the edge", "newpos", newpos)
|
||||||
playerposition.Set(newpos)
|
playerposition.Set(newpos)
|
||||||
} else {
|
} else {
|
||||||
ok, tilepos := system.Grid.GetSolidNeighborPosition(playerposition, velocity, true)
|
ok, tilepos := system.Grid.GetSolidNeighborPosition(playerposition, velocity, true)
|
||||||
if ok {
|
if ok {
|
||||||
intersects, newpos := tilepos.Intersects(playerposition, velocity)
|
intersects, newpos := tilepos.Intersects(playerposition, velocity)
|
||||||
if intersects {
|
if intersects {
|
||||||
slog.Debug("collision detected", "tile",
|
// slog.Debug("collision detected", "tile",
|
||||||
tilepos, "player", playerposition, "new", newpos)
|
// tilepos, "player", playerposition, "new", newpos)
|
||||||
|
|
||||||
playerposition.Set(newpos)
|
playerposition.Set(newpos)
|
||||||
velocity.Change(Stop)
|
velocity.Change(Stop)
|
||||||
@ -76,6 +77,7 @@ func (system PlayerSystem) Update() error {
|
|||||||
system.Particle.Update() // may set player position
|
system.Particle.Update() // may set player position
|
||||||
system.Obstacle.Update()
|
system.Obstacle.Update()
|
||||||
system.Collectible.Update()
|
system.Collectible.Update()
|
||||||
|
system.Transient.Update()
|
||||||
|
|
||||||
query = system.Selector.Query(system.World)
|
query = system.Selector.Query(system.World)
|
||||||
for query.Next() {
|
for query.Next() {
|
||||||
@ -104,4 +106,5 @@ func (system *PlayerSystem) Draw(screen *ebiten.Image) {
|
|||||||
system.Collectible.Draw(screen)
|
system.Collectible.Draw(screen)
|
||||||
system.Particle.Draw(screen)
|
system.Particle.Draw(screen)
|
||||||
system.Obstacle.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