diff --git a/assets/loader-levels.go b/assets/loader-levels.go index 048fad3..bc6f601 100644 --- a/assets/loader-levels.go +++ b/assets/loader-levels.go @@ -28,6 +28,8 @@ type Tile struct { Renderable bool Velocity bool Collectible bool + Particle int // -1=unused, 0-3 = show image of slice + Particles []*ebiten.Image } func NewTilePlayer() *Tile { @@ -62,6 +64,23 @@ func NewTileCollectible(class string) *Tile { } } +func NewTileParticle(class []string) *Tile { + sprites := []*ebiten.Image{} + + for _, sprite := range class { + sprites = append(sprites, Assets[sprite]) + } + + return &Tile{ + Id: '*', + Class: "particle", + Solid: false, + Renderable: false, + Particle: -1, + Particles: sprites, + } +} + // used to map level data bytes to actual tiles type TileRegistry map[byte]*Tile @@ -90,6 +109,10 @@ func InitTiles() TileRegistry { '#': NewTileBlock("block-grey32"), 'S': NewTilePlayer(), 'o': NewTileCollectible("collectible-orange"), + '*': NewTileParticle([]string{ + "particle-dust-0", "particle-dust-0", "particle-dust-1", "particle-dust-1", + "particle-dust-0", "particle-dust-2", "particle-dust-3", "particle-dust-3", + }), } } diff --git a/assets/sprites/particle-dust-0.png b/assets/sprites/particle-dust-0.png new file mode 100644 index 0000000..029ee22 Binary files /dev/null and b/assets/sprites/particle-dust-0.png differ diff --git a/assets/sprites/particle-dust-1.png b/assets/sprites/particle-dust-1.png new file mode 100644 index 0000000..b169e84 Binary files /dev/null and b/assets/sprites/particle-dust-1.png differ diff --git a/assets/sprites/particle-dust-2.png b/assets/sprites/particle-dust-2.png new file mode 100644 index 0000000..378b2be Binary files /dev/null and b/assets/sprites/particle-dust-2.png differ diff --git a/assets/sprites/particle-dust-3.png b/assets/sprites/particle-dust-3.png new file mode 100644 index 0000000..af76c46 Binary files /dev/null and b/assets/sprites/particle-dust-3.png differ diff --git a/components/components.go b/components/components.go index f178a6c..3fa72f8 100644 --- a/components/components.go +++ b/components/components.go @@ -16,3 +16,8 @@ type Solid struct{} type Floor struct{} type Player struct{} type Collectible struct{} + +type Particle struct { + Index int + Particles []*ebiten.Image +} diff --git a/game/grid.go b/game/grid.go index c43c5b4..4e02a82 100644 --- a/game/grid.go +++ b/game/grid.go @@ -46,6 +46,11 @@ func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *G components.Renderable, components.Collectible](game.World) + ptmapper := generic.NewMap2[ + components.Position, + components.Particle, + ](game.World) + var pos *components.Position var render *components.Renderable @@ -81,6 +86,12 @@ func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *G pos.Update(point.X*tilesize, point.Y*tilesize, tilesize) } + // not part of the grid, but add them as well + entity := ptmapper.New() + _, particle := ptmapper.Get(entity) + particle.Index = assets.Tiles['*'].Particle + particle.Particles = assets.Tiles['*'].Particles + return &Grid{ Size: len(mapslice), Tilesize: tilesize, diff --git a/game/levels.go b/game/levels.go index b205dc5..171bd15 100644 --- a/game/levels.go +++ b/game/levels.go @@ -36,16 +36,25 @@ func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { 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{ Mapslice: LevelToSlice(game, plan, cellsize), @@ -66,6 +75,7 @@ func (level *Level) Update() { query := level.World.Query(level.Selector["player"]) toRemove := []ecs.Entity{} + particle_pos := image.Point{} for query.Next() { playerposition := (*components.Position)(query.Get(level.Component["position"])) @@ -114,6 +124,8 @@ func (level *Level) Update() { 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 } } } @@ -121,11 +133,39 @@ func (level *Level) Update() { 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) Position2Point(position *components.Position) image.Point { @@ -136,7 +176,10 @@ 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 { +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, @@ -147,20 +190,22 @@ func (level *Level) GetTile(position *components.Position, velocity *components. } func (level *Level) Draw(screen *ebiten.Image) { - rid := ecs.ComponentID[components.Renderable](level.World) - pid := ecs.ComponentID[components.Position](level.World) - sid := ecs.ComponentID[components.Solid](level.World) + 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) - selector := filter.All(rid, pid, sid) - - query := level.World.Query(selector) + query := level.World.Query(level.Selector["tile"]) for query.Next() { - pos := (*components.Position)(query.Get(pid)) - sprite := (*components.Renderable)(query.Get(rid)) + pos := (*components.Position)(query.Get(level.Component["position"])) + sprite := (*components.Renderable)(query.Get(level.Component["renderable"])) draw.Draw( level.Cache, @@ -168,31 +213,52 @@ func (level *Level) Draw(screen *ebiten.Image) { sprite.Image, image.ZP, draw.Over) } - op := &ebiten.DrawImageOptions{} + op.GeoM.Reset() screen.DrawImage(level.Cache, op) level.UseCache = true } else { // use the cached map - op := &ebiten.DrawImageOptions{} + op.GeoM.Reset() screen.DrawImage(level.Cache, op) } +} +func (level *Level) DrawMovables(screen *ebiten.Image) { // write the movable tiles - selector := filter.All(rid, pid).Without(sid) - + op := &ebiten.DrawImageOptions{} + selector := level.Selector["movable"].Without(level.Component["solid"]) query := level.World.Query(&selector) - for query.Next() { - pos := (*components.Position)(query.Get(pid)) - sprite := (*components.Renderable)(query.Get(rid)) - op := &ebiten.DrawImageOptions{} + 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) { level.Grid = NewGrid(game, level.Cellsize, level.Mapslice) } diff --git a/src/particle-dust.xcf b/src/particle-dust.xcf new file mode 100644 index 0000000..a058256 Binary files /dev/null and b/src/particle-dust.xcf differ