monovania/world/world.go

215 lines
4.6 KiB
Go

package world
import (
"bytes"
"image"
"log"
"math"
"net/http"
"path/filepath"
"time"
"code.rocketnine.space/tslocum/monovania/asset"
"code.rocketnine.space/tslocum/gohan"
"code.rocketnine.space/tslocum/monovania/component"
"github.com/hajimehoshi/ebiten/v2"
"github.com/lafriks/go-tiled"
)
var World = &GameWorld{
StartedAt: time.Now(),
DuckStart: -1,
DuckEnd: -1,
}
type GameWorld struct {
Map *tiled.Map
SpawnX, SpawnY float64
ObjectGroups []*tiled.ObjectGroup
StartedAt time.Time
GameOver bool
Player gohan.Entity
ScreenW, ScreenH int
NoClip bool
Debug int
OffsetX, OffsetY float64
DuckStart float64
DuckEnd float64
// Abilities
CanDoubleJump bool
Jumps int
TriggerRects []image.Rectangle
TriggerEntities []gohan.Entity
TriggerNames []string
DisableEsc bool // TODO
}
func TileToGameCoords(x, y int) (float64, float64) {
//return float64(x) * 16, float64(g.currentMap.Height*16) - float64(y)*16 - 16
return float64(x) * 16, float64(y) * 16
}
func LoadMap(filePath string) {
loader := tiled.Loader{
FileSystem: http.FS(asset.FS),
}
b, err := asset.FS.ReadFile(filePath)
if err != nil {
panic(err)
}
// Parse .tmx file.
m, err := loader.LoadFromReader("/", bytes.NewReader(b))
if err != nil {
log.Fatalf("error parsing world: %+v", err)
}
// Load tileset.
tileset := m.Tilesets[0]
imgPath := filepath.Join("./map/", tileset.Image.Source)
f, err := asset.FS.Open(imgPath)
if err != nil {
panic(err)
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
panic(err)
}
tilesetImg := ebiten.NewImageFromImage(img)
// Load tiles.
tileCache := make(map[uint32]*ebiten.Image)
for i := uint32(0); i < uint32(tileset.TileCount); i++ {
rect := tileset.GetTileRect(i)
tileCache[i+tileset.FirstGID] = tilesetImg.SubImage(rect).(*ebiten.Image)
}
createTileEntity := func(t *tiled.LayerTile, x int, y int) gohan.Entity {
tileX, tileY := TileToGameCoords(x, y)
mapTile := gohan.NewEntity()
mapTile.AddComponent(&component.PositionComponent{
X: tileX,
Y: tileY,
})
mapTile.AddComponent(&component.SpriteComponent{
Image: tileCache[t.Tileset.FirstGID+t.ID],
HorizontalFlip: t.HorizontalFlip,
VerticalFlip: t.VerticalFlip,
DiagonalFlip: t.DiagonalFlip,
})
return mapTile
}
var t *tiled.LayerTile
for _, layer := range m.Layers {
for y := 0; y < m.Height; y++ {
for x := 0; x < m.Width; x++ {
t = layer.Tiles[y*m.Width+x]
if t == nil || t.Nil {
continue // No tile at this position.
}
// TODO use Tileset.Animation
// use current time in millis (cached) % total animation time
tileImg := tileCache[t.Tileset.FirstGID+t.ID]
if tileImg == nil {
continue
}
_ = createTileEntity(t, x, y)
}
}
}
// Load ObjectGroups.
var objects []*tiled.ObjectGroup
var loadObjects func(grp *tiled.Group)
loadObjects = func(grp *tiled.Group) {
for _, subGrp := range grp.Groups {
loadObjects(subGrp)
}
for _, objGrp := range grp.ObjectGroups {
objects = append(objects, objGrp)
}
}
for _, grp := range m.Groups {
loadObjects(grp)
}
for _, objGrp := range m.ObjectGroups {
objects = append(objects, objGrp)
}
World.Map = m
World.ObjectGroups = objects
World.SpawnX, World.SpawnY = -math.MaxFloat64, -math.MaxFloat64
for _, grp := range World.ObjectGroups {
if grp.Name == "PLAYERSPAWN" {
for _, obj := range grp.Objects {
World.SpawnX, World.SpawnY = obj.X, obj.Y-1
}
break
}
}
for _, grp := range World.ObjectGroups {
if !grp.Visible {
continue
}
if grp.Name == "TEMPSPAWN" {
for _, obj := range grp.Objects {
World.SpawnX, World.SpawnY = obj.X, obj.Y-1
}
break
}
}
if World.SpawnX == -math.MaxFloat64 || World.SpawnY == -math.MaxFloat64 {
panic("world does not contain a player spawn object")
}
for _, grp := range World.ObjectGroups {
if grp.Name == "TRIGGERS" {
for _, obj := range grp.Objects {
if obj.Name == "" {
continue
}
mapTile := gohan.NewEntity()
mapTile.AddComponent(&component.PositionComponent{
X: obj.X,
Y: obj.Y - 16,
})
mapTile.AddComponent(&component.SpriteComponent{
Image: tileCache[obj.GID],
})
World.TriggerNames = append(World.TriggerNames, obj.Name)
World.TriggerEntities = append(World.TriggerEntities, mapTile)
World.TriggerRects = append(World.TriggerRects, ObjectToRect(obj))
}
break
}
}
}
func ObjectToRect(o *tiled.Object) image.Rectangle {
x, y, w, h := int(o.X), int(o.Y), int(o.Width), int(o.Height)
return image.Rect(x, y, x+w, y+h)
}