Add character animation and parallax background

This commit is contained in:
Trevor Slocum 2021-12-01 08:57:09 -08:00
parent db760ad682
commit 68afeceffc
23 changed files with 2270 additions and 1026 deletions

View File

@ -2,6 +2,7 @@ package asset
import (
"embed"
"image"
"github.com/hajimehoshi/ebiten/v2"
)
@ -10,3 +11,23 @@ var ImgWhiteSquare = ebiten.NewImage(16, 16)
//go:embed image map
var FS embed.FS
var ImgBackground1 = LoadImage("image/szadiart-caves/background1.png")
var ImgBackground2 = LoadImage("image/szadiart-caves/background2.png")
var ImgBackground3 = LoadImage("image/szadiart-caves/background3.png")
var ImgBackground4 = LoadImage("image/szadiart-caves/background4b.png")
func LoadImage(p string) *ebiten.Image {
f, err := FS.Open(p)
if err != nil {
panic(err)
}
defer f.Close()
baseImg, _, err := image.Decode(f)
if err != nil {
panic(err)
}
return ebiten.NewImageFromImage(baseImg)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.5" tiledversion="1.7.2" name="tileset" tilewidth="16" tileheight="16" tilecount="64" columns="8">
<tileset version="1.5" tiledversion="1.7.2" name="DONOTUSE-tileset" tilewidth="16" tileheight="16" tilecount="64" columns="8">
<image source="tileset.png" width="128" height="128"/>
<tile id="0">
<objectgroup draworder="index" id="2">

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

File diff suppressed because it is too large Load Diff

View File

@ -11,8 +11,12 @@ var PlayerSS = LoadPlayerSpriteSheet()
// PlayerSpriteSheet represents a collection of sprite images.
type PlayerSpriteSheet struct {
Frame1 *ebiten.Image
Frame2 *ebiten.Image
IdleR *ebiten.Image
WalkR1 *ebiten.Image
WalkR2 *ebiten.Image
IdleL *ebiten.Image
WalkL1 *ebiten.Image
WalkL2 *ebiten.Image
}
// LoadPlayerSpriteSheet loads the embedded PlayerSpriteSheet.
@ -37,8 +41,12 @@ func LoadPlayerSpriteSheet() *PlayerSpriteSheet {
// Populate PlayerSpriteSheet.
s := &PlayerSpriteSheet{}
s.Frame1 = spriteAt(0, 0)
s.Frame2 = spriteAt(0, 1)
s.IdleR = spriteAt(0, 0)
s.WalkR1 = spriteAt(1, 0)
s.WalkR2 = spriteAt(2, 0)
s.IdleL = spriteAt(0, 1)
s.WalkL1 = spriteAt(1, 1)
s.WalkL2 = spriteAt(2, 1)
return s
}

View File

@ -1,6 +1,8 @@
package component
import (
"time"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
)
@ -10,6 +12,15 @@ type SpriteComponent struct {
HorizontalFlip bool
VerticalFlip bool
DiagonalFlip bool // TODO unimplemented
Frame int
Frames []*ebiten.Image
FrameTime time.Duration
LastFrame time.Time
NumFrames int
OverrideColorScale bool
ColorScale float64
}
var SpriteComponentID = gohan.NewComponentID()

View File

@ -28,7 +28,7 @@ func NewPlayer(x, y float64) gohan.Entity {
player.AddComponent(weapon)
player.AddComponent(&component.SpriteComponent{
Image: asset.PlayerSS.Frame1,
Image: asset.PlayerSS.IdleR,
})
return player

View File

@ -82,22 +82,10 @@ func NewGame() (*game, error) {
g.audioContext = audio.NewContext(sampleRate)
// TODO replace with fs embed
g.changeMap("/home/trevor/programming/monovania/asset/map/m1.tmx")
g.changeMap("map/m1.tmx")
spawnX, spawnY := -math.MaxFloat64, -math.MaxFloat64
for _, grp := range world.World.ObjectGroups {
if grp.Name == "PLAYERSPAWN" {
for _, obj := range grp.Objects {
spawnX, spawnY = obj.X, obj.Y-1
}
break
}
}
if spawnX == -math.MaxFloat64 || spawnY == -math.MaxFloat64 {
panic("world does not contain a player spawn object")
}
g.player = entity.NewPlayer(spawnX, spawnY)
world.World.Player = entity.NewPlayer(world.World.SpawnX, world.World.SpawnY)
g.player = world.World.Player
g.addSystems()
@ -124,6 +112,7 @@ func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
s := ebiten.DeviceScaleFactor()
w, h := int(s*float64(outsideWidth)), int(s*float64(outsideHeight))
if w != g.w || h != g.h {
world.World.ScreenW, world.World.ScreenH = w, h
g.w, g.h = w, h
g.movementSystem.ScreenW, g.movementSystem.ScreenH = float64(w), float64(h)
g.renderSystem.ScreenW, g.renderSystem.ScreenH = w, h
@ -164,10 +153,14 @@ func (g *game) addSystems() {
gohan.AddSystem(system.NewFireWeaponSystem(g.player))
gohan.AddSystem(system.NewRenderBackgroundSystem())
g.renderSystem = system.NewRenderSystem()
gohan.AddSystem(g.renderSystem)
gohan.AddSystem(system.NewRenderDebugTextSystem(g.player))
gohan.AddSystem(system.NewProfileSystem(g.player))
}
func (g *game) loadAssets() error {

64
system/background.go Normal file
View File

@ -0,0 +1,64 @@
package system
import (
_ "image/png"
"code.rocketnine.space/tslocum/monovania/component"
"code.rocketnine.space/tslocum/monovania/asset"
"code.rocketnine.space/tslocum/monovania/world"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
)
type RenderBackgroundSystem struct {
op *ebiten.DrawImageOptions
}
func NewRenderBackgroundSystem() *RenderBackgroundSystem {
s := &RenderBackgroundSystem{
op: &ebiten.DrawImageOptions{},
}
return s
}
func (s *RenderBackgroundSystem) Matches(entity gohan.Entity) bool {
return entity == world.World.Player
}
func (s *RenderBackgroundSystem) Update(_ gohan.Entity) error {
return gohan.ErrSystemWithoutUpdate
}
func (s *RenderBackgroundSystem) Draw(entity gohan.Entity, screen *ebiten.Image) error {
if world.World.GameOver {
return nil
}
position := component.Position(world.World.Player)
pctX, pctY := position.X/(512*16), position.Y/(512*16)
scale := (float64(world.World.ScreenH) / float64(asset.ImgBackground1.Bounds().Dy())) * 1.7
offsetX, offsetY := float64(asset.ImgBackground1.Bounds().Dx())*pctX, float64(asset.ImgBackground1.Bounds().Dy())*pctY
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
screen.DrawImage(asset.ImgBackground1, op)
op = &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(-offsetX*0.5, -offsetY*0.5)
screen.DrawImage(asset.ImgBackground2, op)
op = &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(-offsetX*1, -offsetY*0.75)
screen.DrawImage(asset.ImgBackground3, op)
op = &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(-offsetX*2, -offsetY)
screen.DrawImage(asset.ImgBackground4, op)
return nil
}

View File

@ -47,6 +47,8 @@ func (s *fireWeaponSystem) fire(weapon *component.WeaponComponent, position *com
}
func (s *fireWeaponSystem) Update(_ gohan.Entity) error {
return nil // TODO
weapon := component.Weapon(s.player)
if weapon.Ammo <= 0 {

View File

@ -3,6 +3,8 @@ package system
import (
"time"
"code.rocketnine.space/tslocum/monovania/asset"
"code.rocketnine.space/tslocum/monovania/world"
"github.com/hajimehoshi/ebiten/v2/inpututil"
@ -13,8 +15,9 @@ import (
)
type playerMoveSystem struct {
player gohan.Entity
movement *MovementSystem
player gohan.Entity
movement *MovementSystem
lastWalkDirL bool
}
func NewPlayerMoveSystem(player gohan.Entity, m *MovementSystem) *playerMoveSystem {
@ -34,7 +37,11 @@ func (s *playerMoveSystem) Update(e gohan.Entity) error {
if world.World.Debug > 2 {
world.World.Debug = 0
}
s.movement.UpdateDrawnRects()
s.movement.UpdateDebugCollisionRects()
return nil
}
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.KeyN) {
world.World.NoClip = !world.World.NoClip
return nil
}
@ -43,6 +50,13 @@ func (s *playerMoveSystem) Update(e gohan.Entity) error {
maxYSpeed := 0.5
const jumpVelocity = -0.75
if world.World.Debug > 0 && ebiten.IsKeyPressed(ebiten.KeyShift) {
maxSpeed *= 10
maxYSpeed = 40
}
var walkKeyPressed bool
velocity := component.Velocity(s.player)
if ebiten.IsKeyPressed(ebiten.KeyA) {
if velocity.X > -maxSpeed {
@ -52,6 +66,19 @@ func (s *playerMoveSystem) Update(e gohan.Entity) error {
velocity.X -= moveSpeed
}
}
sprite := component.Sprite(s.player)
sprite.Frames = []*ebiten.Image{
asset.PlayerSS.WalkL1,
asset.PlayerSS.IdleL,
asset.PlayerSS.WalkL2,
asset.PlayerSS.IdleL,
}
sprite.NumFrames = 4
sprite.FrameTime = 150 * time.Millisecond
walkKeyPressed = true
s.lastWalkDirL = true
}
if ebiten.IsKeyPressed(ebiten.KeyD) {
if velocity.X < maxSpeed {
@ -61,17 +88,56 @@ func (s *playerMoveSystem) Update(e gohan.Entity) error {
velocity.X += moveSpeed
}
}
sprite := component.Sprite(s.player)
sprite.Frames = []*ebiten.Image{
asset.PlayerSS.WalkR1,
asset.PlayerSS.IdleR,
asset.PlayerSS.WalkR2,
asset.PlayerSS.IdleR,
}
sprite.NumFrames = 4
sprite.FrameTime = 150 * time.Millisecond
walkKeyPressed = true
s.lastWalkDirL = false
}
if s.movement.OnLadder != -1 {
if s.movement.OnLadder != -1 || world.World.NoClip {
setLadderFrames := func() {
sprite := component.Sprite(s.player)
if s.lastWalkDirL {
sprite.Frames = []*ebiten.Image{
asset.PlayerSS.WalkL1,
asset.PlayerSS.IdleL,
asset.PlayerSS.WalkL2,
asset.PlayerSS.IdleL,
}
} else {
sprite.Frames = []*ebiten.Image{
asset.PlayerSS.WalkR1,
asset.PlayerSS.IdleR,
asset.PlayerSS.WalkR2,
asset.PlayerSS.IdleR,
}
}
sprite.NumFrames = 4
sprite.FrameTime = 150 * time.Millisecond
}
if ebiten.IsKeyPressed(ebiten.KeyW) {
if velocity.Y > -maxYSpeed {
velocity.Y -= moveSpeed
}
setLadderFrames()
walkKeyPressed = true
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
if ebiten.IsKeyPressed(ebiten.KeyS) && s.movement.OnGround == -1 {
if velocity.Y < maxYSpeed {
velocity.Y += moveSpeed
}
setLadderFrames()
walkKeyPressed = true
}
} else {
// Jump.
@ -86,6 +152,24 @@ func (s *playerMoveSystem) Update(e gohan.Entity) error {
}
}
if !walkKeyPressed || (s.movement.OnGround == -1 && s.movement.OnLadder == -1) {
sprite := component.Sprite(s.player)
sprite.NumFrames = 0
if s.lastWalkDirL {
if s.movement.OnGround == -1 && s.movement.OnLadder == -1 {
sprite.Image = asset.PlayerSS.WalkL2
} else {
sprite.Image = asset.PlayerSS.IdleL
}
} else {
if s.movement.OnGround == -1 && s.movement.OnLadder == -1 {
sprite.Image = asset.PlayerSS.WalkR2
} else {
sprite.Image = asset.PlayerSS.IdleR
}
}
}
return nil
}

65
system/input_profile.go Normal file
View File

@ -0,0 +1,65 @@
package system
import (
"log"
"os"
"path"
"runtime"
"runtime/pprof"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
type profileSystem struct {
player gohan.Entity
cpuProfile *os.File
}
func NewProfileSystem(player gohan.Entity) *profileSystem {
return &profileSystem{
player: player,
}
}
func (s *profileSystem) Matches(e gohan.Entity) bool {
return e == s.player
}
func (s *profileSystem) Update(e gohan.Entity) error {
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.KeyP) {
if s.cpuProfile == nil {
log.Println("CPU profiling started...")
runtime.SetCPUProfileRate(1000)
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
s.cpuProfile, err = os.Create(path.Join(homeDir, "monovania.prof"))
if err != nil {
return err
}
err = pprof.StartCPUProfile(s.cpuProfile)
if err != nil {
return err
}
} else {
pprof.StopCPUProfile()
s.cpuProfile.Close()
s.cpuProfile = nil
log.Println("CPU profiling stopped")
}
}
return nil
}
func (s *profileSystem) Draw(_ gohan.Entity, _ *ebiten.Image) error {
return gohan.ErrSystemWithoutDraw
}

View File

@ -3,8 +3,6 @@ package system
import (
"image"
"image/color"
"log"
"strings"
"time"
"code.rocketnine.space/tslocum/monovania/world"
@ -24,54 +22,79 @@ type MovementSystem struct {
Jumping bool
LastJump time.Time
collisionIndex int
collisionRects []image.Rectangle
ladderRects []image.Rectangle
debugRects []gohan.Entity
fireRects []image.Rectangle
debugCollisionRects []gohan.Entity
debugLadderRects []gohan.Entity
}
func NewMovementSystem() *MovementSystem {
s := &MovementSystem{
collisionIndex: -1,
OnGround: -1,
OnLadder: -1,
OnGround: -1,
OnLadder: -1,
}
w := world.World
// Cache collision rects.
for i, objectLayer := range w.ObjectGroups {
if strings.ToLower(objectLayer.Name) == "collisions" {
s.collisionIndex = i
break
m := w.Map
for _, layer := range m.Layers {
collision := layer.Properties.GetBool("collision")
if !collision {
continue
}
}
if s.collisionIndex == -1 {
log.Fatal("no collisions")
return s
}
for _, object := range w.ObjectGroups[s.collisionIndex].Objects {
rect := objectToRect(object)
s.collisionRects = append(s.collisionRects, rect)
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.
}
gx, gy := world.TileToGameCoords(x, y)
s.collisionRects = append(s.collisionRects, image.Rect(int(gx), int(gy), int(gx)+16, int(gy)+16))
}
}
}
// Cache ladder rects.
for _, objectLayer := range w.ObjectGroups {
if strings.ToLower(objectLayer.Name) == "ladders" {
for _, object := range objectLayer.Objects {
rect := objectToRect(object)
s.ladderRects = append(s.ladderRects, rect)
for _, layer := range m.Layers {
ladder := layer.Properties.GetBool("ladder")
if !ladder {
continue
}
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.
}
gx, gy := world.TileToGameCoords(x, y)
s.ladderRects = append(s.ladderRects, image.Rect(int(gx), int(gy), int(gx)+16, int(gy)+16))
}
break
}
}
s.UpdateDrawnRects()
// Cache fire rects.
for _, layer := range world.World.ObjectGroups {
if layer.Name != "FIRES" {
continue
}
for _, obj := range layer.Objects {
rect := objectToRect(obj)
s.fireRects = append(s.fireRects, rect)
}
}
s.UpdateDebugCollisionRects()
return s
}
@ -93,40 +116,66 @@ func drawDebugRect(r image.Rectangle, c color.Color) gohan.Entity {
})
rectEntity.AddComponent(&component.SpriteComponent{
Image: rectImg,
Image: rectImg,
OverrideColorScale: true,
})
return rectEntity
}
func (s *MovementSystem) UpdateDrawnRects() {
for _, e := range s.debugRects {
func (s *MovementSystem) removeDebugRects() {
for _, e := range s.debugCollisionRects {
e.Remove()
}
s.debugCollisionRects = nil
for _, e := range s.debugLadderRects {
e.Remove()
}
s.debugLadderRects = nil
}
func (s *MovementSystem) addDebugCollisionRects() {
s.removeDebugRects()
for _, rect := range s.collisionRects {
c := color.RGBA{200, 200, 200, 150}
debugRect := drawDebugRect(rect, c)
s.debugCollisionRects = append(s.debugCollisionRects, debugRect)
}
for _, rect := range s.ladderRects {
c := color.RGBA{200, 200, 200, 150}
debugRect := drawDebugRect(rect, c)
s.debugLadderRects = append(s.debugLadderRects, debugRect)
}
}
func (s *MovementSystem) UpdateDebugCollisionRects() {
if world.World.Debug < 2 {
s.removeDebugRects()
return
} else if len(s.debugCollisionRects) == 0 {
s.addDebugCollisionRects()
}
collideColor := color.RGBA{255, 255, 255, 200}
for i, rect := range s.collisionRects {
c := color.RGBA{200, 200, 200, 80}
for i, debugRect := range s.debugCollisionRects {
sprite := component.Sprite(debugRect)
if s.OnGround == i {
c = collideColor
sprite.ColorScale = 1
} else {
sprite.ColorScale = 0.4
}
debugRect := drawDebugRect(rect, c)
s.debugRects = append(s.debugRects, debugRect)
}
for i, rect := range s.ladderRects {
c := color.RGBA{200, 200, 200, 80}
for i, debugRect := range s.debugLadderRects {
sprite := component.Sprite(debugRect)
if s.OnLadder == i {
c = collideColor
sprite.ColorScale = 1
} else {
sprite.ColorScale = 0.4
}
debugRect := drawDebugRect(rect, c)
s.debugRects = append(s.debugRects, debugRect)
}
}
func (s *MovementSystem) objectToGameCoords(x, y, height float64) (float64, float64) {
@ -140,6 +189,20 @@ func (_ *MovementSystem) Matches(entity gohan.Entity) bool {
return position != nil && velocity != nil
}
func (s *MovementSystem) checkFire(r image.Rectangle) {
for _, fireRect := range s.fireRects {
if r.Overlaps(fireRect) {
//world.World.GameOver = true
// TODO
position := component.Position(world.World.Player)
velocity := component.Velocity(world.World.Player)
position.X, position.Y = world.World.SpawnX, world.World.SpawnY
velocity.X, velocity.Y = 0, 0
return
}
}
}
func (s *MovementSystem) Update(entity gohan.Entity) error {
lastOnGround := s.OnGround
lastOnLadder := s.OnLadder
@ -171,7 +234,10 @@ func (s *MovementSystem) Update(entity gohan.Entity) error {
const maxGravity = 9
const gravityAccel = 0.04
if bullet == nil {
if s.OnLadder != -1 {
if s.OnLadder != -1 || world.World.NoClip {
velocity.X *= decel
velocity.Y *= decel
} else if s.OnLadder != -1 {
velocity.X *= decel
velocity.Y *= ladderDecel
} else if velocity.Y < maxGravity {
@ -197,17 +263,24 @@ func (s *MovementSystem) Update(entity gohan.Entity) error {
playerRectXY := image.Rect(int(position.X+velocity.X), int(position.Y+velocity.Y), int(position.X+velocity.X)+16, int(position.Y+velocity.Y)+17)
playerRectG := image.Rect(int(position.X), int(position.Y+threshold), int(position.X)+16, int(position.Y+threshold)+17)
for i, rect := range s.collisionRects {
if world.World.NoClip {
continue
}
if playerRectX.Overlaps(rect) {
collideX = i
s.checkFire(playerRectX)
}
if playerRectY.Overlaps(rect) {
collideY = i
s.checkFire(playerRectY)
}
if playerRectXY.Overlaps(rect) {
collideXY = i
s.checkFire(playerRectXY)
}
if playerRectG.Overlaps(rect) {
collideG = i
s.checkFire(playerRectG)
}
}
if collideXY == -1 {
@ -226,7 +299,7 @@ func (s *MovementSystem) Update(entity gohan.Entity) error {
// Update debug rects.
if s.OnGround != lastOnGround || s.OnLadder != lastOnLadder {
s.UpdateDrawnRects()
s.UpdateDebugCollisionRects()
}
return nil

View File

@ -2,6 +2,11 @@ package system
import (
_ "image/png"
"time"
"golang.org/x/image/colornames"
"code.rocketnine.space/tslocum/monovania/world"
"code.rocketnine.space/tslocum/gohan"
"code.rocketnine.space/tslocum/monovania/component"
@ -98,7 +103,7 @@ func (s *RenderSystem) renderSprite(x float64, y float64, offsetx float64, offse
s.op.ColorM.Scale(colorScale, colorScale, colorScale, alpha)
// Apply monochrome filter.
s.op.ColorM.ChangeHSV(1, 0, 1)
//s.op.ColorM.ChangeHSV(1, 0, 1)
target.DrawImage(sprite, s.op)
@ -108,10 +113,31 @@ func (s *RenderSystem) renderSprite(x float64, y float64, offsetx float64, offse
}
func (s *RenderSystem) Draw(entity gohan.Entity, screen *ebiten.Image) error {
if world.World.GameOver {
if entity == world.World.Player {
screen.Fill(colornames.Darkred)
}
return nil
}
position := component.Position(entity)
sprite := component.Sprite(entity)
if sprite.NumFrames > 0 && time.Since(sprite.LastFrame) > sprite.FrameTime {
sprite.Frame++
if sprite.Frame >= sprite.NumFrames {
sprite.Frame = 0
}
sprite.Image = sprite.Frames[sprite.Frame]
sprite.LastFrame = time.Now()
}
colorScale := 1.0
if sprite.OverrideColorScale {
colorScale = sprite.ColorScale
}
var drawn int
drawn += s.renderSprite(position.X, position.Y, 0, 0, 0, 1.0, 1.0, 1.0, sprite.HorizontalFlip, sprite.VerticalFlip, sprite.Image, screen)
drawn += s.renderSprite(position.X, position.Y, 0, 0, 0, 1.0, colorScale, 1.0, sprite.HorizontalFlip, sprite.VerticalFlip, sprite.Image, screen)
return nil
}

View File

@ -1,11 +1,15 @@
package world
import (
"bytes"
"fmt"
"image"
"io/ioutil"
"math"
"net/http"
"os"
"path/filepath"
"time"
"code.rocketnine.space/tslocum/monovania/asset"
"code.rocketnine.space/tslocum/gohan"
"code.rocketnine.space/tslocum/monovania/component"
@ -13,22 +17,34 @@ import (
"github.com/lafriks/go-tiled"
)
var World = &GameWorld{}
type GameWorld struct {
Map *tiled.Map
ObjectGroups []*tiled.ObjectGroup
Debug int
var World = &GameWorld{
StartedAt: time.Now(),
}
func tileToGameCoords(x, y int) (float64, float64) {
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
}
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),
}
// Parse .tmx file.
m, err := tiled.LoadFromFile(filePath)
m, err := loader.LoadFromFile(filePath)
if err != nil {
fmt.Printf("error parsing world: %s", err.Error())
os.Exit(2)
@ -38,13 +54,14 @@ func LoadMap(filePath string) {
tileset := m.Tilesets[0]
imgPath := tileset.GetFileFullPath(tileset.Image.Source)
b, err := ioutil.ReadFile(imgPath)
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(bytes.NewReader(b))
img, _, err := image.Decode(f)
if err != nil {
panic(err)
}
@ -57,6 +74,25 @@ func LoadMap(filePath string) {
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++ {
@ -74,19 +110,7 @@ func LoadMap(filePath string) {
continue
}
tileX, tileY := tileToGameCoords(x, y)
mapTile := gohan.NewEntity()
mapTile.AddComponent(&component.PositionComponent{
X: tileX,
Y: tileY,
})
mapTile.AddComponent(&component.SpriteComponent{
Image: tileImg,
HorizontalFlip: t.HorizontalFlip,
VerticalFlip: t.VerticalFlip,
DiagonalFlip: t.DiagonalFlip,
})
_ = createTileEntity(t, x, y)
}
}
}
@ -112,4 +136,44 @@ func LoadMap(filePath string) {
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 {
mapTile := gohan.NewEntity()
mapTile.AddComponent(&component.PositionComponent{
X: obj.X,
Y: obj.Y - 16,
})
mapTile.AddComponent(&component.SpriteComponent{
Image: tileCache[obj.GID],
})
}
break
}
}
}