openquell/game/levels.go

166 lines
4.9 KiB
Go
Raw Normal View History

2024-02-06 15:26:20 +01:00
package game
import (
"image"
"log/slog"
2024-02-06 15:26:20 +01:00
"openquell/assets"
"openquell/components"
2024-02-10 19:45:06 +01:00
"openquell/grid"
2024-02-11 14:24:30 +01:00
"openquell/observers"
2024-02-10 19:45:06 +01:00
"openquell/systems"
2024-02-06 15:26:20 +01:00
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/mlange-42/arche/ecs"
"github.com/solarlune/ldtkgo"
2024-02-06 15:26:20 +01:00
)
type Map map[image.Point]*assets.Tile
type BackupMap map[image.Point]assets.Tile
2024-02-06 15:26:20 +01:00
type Level struct {
Cellsize, Width, Height int
World *ecs.World
Name string
Description string
Mapslice Map
BackupMapslice Map
GridContainer *grid.GridContainer
Systems []systems.System
Grid *grid.Grid
2024-02-10 19:45:06 +01:00
}
2024-02-07 18:01:58 +01:00
func NewLevel(game *Game, cellsize int, plan *ldtkgo.Level) *Level {
systemlist := []systems.System{}
gridcontainer := &grid.GridContainer{}
systemlist = append(systemlist,
systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize,
assets.Assets[plan.PropertyByIdentifier("background").AsString()]))
systemlist = append(systemlist, systems.NewCollectibleSystem(game.World))
systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer))
systemlist = append(systemlist,
systems.NewPlayerSystem(game.World, gridcontainer, game.ScreenWidth, game.ScreenHeight))
systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize))
systemlist = append(systemlist, systems.NewTransientSystem(game.World, gridcontainer))
2024-02-07 18:01:58 +01:00
systemlist = append(systemlist, systems.NewDestroyableSystem(game.World, gridcontainer))
systemlist = append(systemlist, systems.NewHudSystem(game.World, plan))
mapslice, backupmap := LevelToSlice(game, plan, cellsize)
2024-02-06 15:26:20 +01:00
return &Level{
Mapslice: mapslice,
BackupMapslice: backupmap,
Cellsize: cellsize,
World: game.World,
Width: game.ScreenWidth,
Height: game.ScreenHeight,
Description: plan.PropertyByIdentifier("description").AsString(),
Name: plan.Identifier,
GridContainer: gridcontainer,
Systems: systemlist,
2024-02-06 15:26:20 +01:00
}
}
func (level *Level) Update() {
for _, sys := range level.Systems {
sys.Update()
}
2024-02-10 19:45:06 +01:00
}
2024-02-10 19:45:06 +01:00
func (level *Level) Draw(screen *ebiten.Image) {
for _, sys := range level.Systems {
sys.Draw(screen)
}
2024-02-06 15:26:20 +01:00
}
func (level *Level) RestoreMap() {
for point, tile := range level.BackupMapslice {
level.Mapslice[point] = tile.Clone()
}
}
2024-02-06 15:26:20 +01:00
func (level *Level) SetupGrid(game *Game) {
2024-02-09 20:20:13 +01:00
// generic variant does not work here:
// selector := generic.NewFilter1[components.Position]()
// level.World.Batch().RemoveEntities(selector)
// missing argument in conversion to generic.Filter1[components.Position]
// erase all entities of previous level, if any
posID := ecs.ComponentID[components.Position](level.World)
selector := ecs.All(posID)
level.World.Batch().RemoveEntities(selector)
2024-02-11 14:24:30 +01:00
// get rid of any players on PlayerObserver. FIXME: remove them in grid.NewGrid()?
observer := observers.GetGameObserver(level.World)
observer.RemoveEntities()
2024-02-11 14:24:30 +01:00
// get rid of possibly manipulated map
level.RestoreMap()
2024-02-09 20:20:13 +01:00
// setup world
slog.Debug("new grid?")
level.GridContainer.SetGrid(
grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice))
2024-02-06 15:26:20 +01:00
}
// parses a RawLevel and generates a mapslice from it, which is being used as grid
func LevelToSlice(game *Game, level *ldtkgo.Level, tilesize int) (Map, Map) {
2024-02-06 15:26:20 +01:00
size := game.ScreenWidth * game.ScreenHeight
mapslice := make(Map, size)
backupmap := make(Map, size)
2024-02-06 15:26:20 +01:00
for _, layer := range level.Layers {
switch layer.Type {
case ldtkgo.LayerTypeTile:
// load tile from LDTK tile layer, use sprites from associated map.
if tiles := layer.AllTiles(); len(tiles) > 0 {
for _, tileData := range tiles {
// Subimage the Tile from the already loaded map,
// but referenced from LDTK file, that way we
// could use multiple tileset images
tile := assets.Tiles["default"]
tile.Sprite = assets.Assets[strings.TrimSuffix(layer.Tileset.Path, ".png")].SubImage(
image.Rect(tileData.Src[0],
tileData.Src[1],
tileData.Src[0]+layer.GridSize,
tileData.Src[1]+layer.GridSize)).(*ebiten.Image)
mapslice[image.Point{tileData.Position[0], tileData.Position[1]}] = tile
backupmap[image.Point{tileData.Position[0], tileData.Position[1]}] = tile.Clone()
}
}
case ldtkgo.LayerTypeEntity:
// load mobile tiles (they call them entities) using static map map.png.
tileset := assets.Assets["map"]
for _, entity := range layer.Entities {
if entity.TileRect != nil {
tile := assets.Tiles[entity.Identifier]
tileRect := entity.TileRect
tile.Sprite = tileset.SubImage(
image.Rect(tileRect.X, tileRect.Y,
tileRect.X+tileRect.W,
tileRect.Y+tileRect.H)).(*ebiten.Image)
mapslice[image.Point{entity.Position[0], entity.Position[1]}] = tile
backupmap[image.Point{entity.Position[0], entity.Position[1]}] = tile.Clone()
}
}
2024-02-06 15:26:20 +01:00
}
}
return mapslice, backupmap
2024-02-06 15:26:20 +01:00
}