Files
Regin_mountain_of_treasures/internal/scenes/play.go
Bjorn Blomberg 16ae22bcba Add new map and tileset files for Windows release
- Created test.tmj and test.tsj files in the maps directory.
- Added tileset.tsj with configuration for the MainTileset including image path and dimensions.
2026-04-26 02:35:31 +02:00

314 lines
8.0 KiB
Go

package scenes
import (
"image"
"image/color"
_ "image/jpeg"
_ "image/png"
"log"
"mountain/internal/entities"
"github.com/go-gl/mathgl/mgl64"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
const (
Gravity = 0.5
JumpForce = -8.0
MoveSpeed = 4.0
ViewSize = 380 // 800 width, we have 2 viewports (380 each with 20 padding)
)
type Player struct {
Pos mgl64.Vec3 // 3D Kordinater: X (vänster/höger), Y (upp/ner), Z (djup)
Vel mgl64.Vec3 // Hastighet
Width, Height float64 // För (AABB)
IsGrounded bool
// Sprites
SpriteRun *ebiten.Image
SpriteIdle *ebiten.Image
SpriteTop *ebiten.Image
AnimationFrame int
AnimationCounter int
FacingRight bool
}
type PlayScene struct {
world *entities.World
player *Player
leftView *ebiten.Image
rightView *ebiten.Image
}
func NewPlayScene() *PlayScene {
w, err := entities.LoadWorldFromTiled("assets/maps/room1.tmj")
if err != nil {
log.Println("Kunde inte ladda room1:", err)
w = entities.NewWorld(100, 10, 100) // Fallback tom värd om JSON saknas
}
pRun, _, err := ebitenutil.NewImageFromFile("assets/images/Warrior_1/Run.png")
if err != nil {
log.Println("Run sprite error:", err)
pRun = ebiten.NewImage(96, 96)
}
pIdle, _, _ := ebitenutil.NewImageFromFile("assets/images/Warrior_1/Idle.png")
if pIdle == nil {
pIdle = pRun
}
pTop := ebiten.NewImage(96, 96)
pTop.Fill(color.RGBA{255, 255, 0, 255})
player := &Player{
Pos: mgl64.Vec3{50.0 * entities.TileSize, 6.0 * entities.TileSize, 50.0 * entities.TileSize},
Width: 96,
Height: 96,
SpriteRun: pRun,
SpriteIdle: pIdle,
SpriteTop: pTop,
FacingRight: true,
}
return &PlayScene{
world: w,
player: player,
leftView: ebiten.NewImage(ViewSize, ViewSize),
rightView: ebiten.NewImage(ViewSize, ViewSize),
}
}
func (s *PlayScene) Update() error {
p := s.player
p.Vel[0] = 0
p.Vel[2] = 0
isMoving := false
if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
p.Vel[0] = -MoveSpeed
p.FacingRight = false
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
p.Vel[0] = MoveSpeed
p.FacingRight = true
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
p.Vel[2] = -MoveSpeed
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
p.Vel[2] = MoveSpeed
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeySpace) && p.IsGrounded {
p.Vel[1] = JumpForce
p.IsGrounded = false
}
if !p.IsGrounded {
p.Vel[1] += Gravity
}
nextX := p.Pos[0] + p.Vel[0]
nextY := p.Pos[1] + p.Vel[1]
nextZ := p.Pos[2] + p.Vel[2]
gridY := int((nextY + p.Height) / entities.TileSize)
gridX := int((nextX + p.Width/2) / entities.TileSize)
gridZ := int((nextZ + p.Width/2) / entities.TileSize)
entY := s.world.GetEntityAt(gridX, gridY, gridZ)
if entY != nil && entY.IsBlocking() && p.Vel[1] >= 0 {
nextY = float64(gridY)*entities.TileSize - p.Height - 0.1
p.Vel[1] = 0
p.IsGrounded = true
} else {
p.IsGrounded = false
}
p.Pos[0] = nextX
p.Pos[1] = nextY
p.Pos[2] = nextZ
// Kolla ifall vi står på en portal
standEnt := s.world.GetEntityAt(gridX, gridY, gridZ)
if prt, ok := standEnt.(*entities.Portal); ok {
log.Println("Byter rum till: ", prt.TargetMap)
newW, err := entities.LoadWorldFromTiled("assets/maps/" + prt.TargetMap + ".tmj")
if err == nil {
s.world = newW
// Reset player position for new map
if prt.TargetMap == "room1" {
p.Pos = mgl64.Vec3{80.0 * entities.TileSize, 6.0 * entities.TileSize, 10.0 * entities.TileSize}
} else {
p.Pos = mgl64.Vec3{40.0 * entities.TileSize, 6.0 * entities.TileSize, 65.0 * entities.TileSize}
}
}
}
if isMoving {
p.AnimationCounter++
if p.AnimationCounter > 5 {
p.AnimationFrame = (p.AnimationFrame + 1) % 6
p.AnimationCounter = 0
}
} else {
p.AnimationFrame = 0
}
return nil
}
func (s *PlayScene) Draw(screen *ebiten.Image) {
screen.Fill(color.RGBA{20, 20, 20, 255})
s.leftView.Fill(color.RGBA{135, 206, 235, 255})
s.rightView.Fill(color.RGBA{50, 150, 50, 255})
playerGridZ := int((s.player.Pos[2] + s.player.Width/2) / entities.TileSize)
playerGridY := int((s.player.Pos[1] + s.player.Height/2) / entities.TileSize)
camX := s.player.Pos[0] - float64(ViewSize)/2.0
camY := s.player.Pos[1] - float64(ViewSize)/2.0
camZ := s.player.Pos[2] - float64(ViewSize)/2.0
// Vänster vy: Sido-vy (renderar objekt vid spelarens Z och "inåt", max 3 djup)
// Rendera från djupast till spelarens Z (mindre Z är djupare inåt bild/scen)
leftDepthStart := playerGridZ - 3
if leftDepthStart < 0 {
leftDepthStart = 0
}
for zLevel := leftDepthStart; zLevel <= playerGridZ; zLevel++ {
tint := float32(1.0)
alpha := float32(1.0)
if zLevel < playerGridZ {
// Förskugga och gör transparent lager längre "in"
diff := float32(playerGridZ - zLevel)
tint = 1.0 - (diff * 0.2)
alpha = 1.0 - (diff * 0.25)
if tint < 0.2 {
tint = 0.2
}
if alpha < 0.2 {
alpha = 0.2
}
}
for x := 0; x < s.world.Width; x++ {
for y := 0; y < s.world.Height; y++ {
ent := s.world.GetEntityAt(x, y, zLevel)
if ent != nil {
px := float64(x)*entities.TileSize - camX
py := float64(y)*entities.TileSize - camY
ent.DrawSide(s.leftView, px, py, tint, alpha)
}
}
}
}
// Höger vy: Ovan-vy (renderar ner i marken så att det känns som det går "in" i bilden)
// -1: Två block inåt jord/hål (Djupast)
// 0: Ett block inåt jord/hål
// +1: Samma (eller 1 över)
// För att saker under spelaren ska ligga bakom byter vi tecken. "upp" i z/y är nu inåt.
// Vi visar tre block nedåt i Y: playerGridY+3, Y+2, Y+1, Y
topLevels := []int{playerGridY + 3, playerGridY + 2, playerGridY + 1, playerGridY}
for i, yLevel := range topLevels {
if yLevel >= 0 && yLevel < s.world.Height {
tint := float32(1.0)
alpha := float32(1.0)
// Sätt färger baserat på relativ nivå ("inåt") där de djupaste (Y+3) är mörkare
// Index 0 är djupast i marken
if i == 0 { // -2 block (långt under fötter)
tint = 0.6
alpha = 0.4
} else if i == 1 { // -1 block (nyss under fötter)
tint = 0.8
alpha = 0.6
} else if i == 2 { // marken spelaren står på
tint = 1.0
alpha = 1.0
} else if i == 3 { // spelarens fothöjd (0)
tint = 1.2
alpha = 1.0
}
for x := 0; x < s.world.Width; x++ {
for z := 0; z < s.world.Depth; z++ {
ent := s.world.GetEntityAt(x, yLevel, z)
if ent != nil {
px := float64(x)*entities.TileSize - camX
pz := float64(z)*entities.TileSize - camZ
ent.DrawTop(s.rightView, px, pz, tint, alpha)
}
}
}
}
// Rendera spelaren på toppen (efter marken och spelarens lager är ritat)
if i == 3 {
s.drawPlayerTop(s.rightView, camX, camZ)
}
}
s.drawPlayerSide(s.leftView, camX, camY)
opL := &ebiten.DrawImageOptions{}
opL.GeoM.Translate(10, 100)
screen.DrawImage(s.leftView, opL)
opR := &ebiten.DrawImageOptions{}
opR.GeoM.Translate(410, 100)
screen.DrawImage(s.rightView, opR)
ebitenutil.DebugPrint(screen, "VÄNSTER (Sido vy) HÖGER (Top vy: Slice av din Hojd)")
}
func (s *PlayScene) drawPlayerSide(screen *ebiten.Image, camX, camY float64) {
p := s.player
sx := p.AnimationFrame * 96
srcRect := image.Rect(sx, 0, sx+96, 96)
var activeSprite *ebiten.Image
if p.Vel[0] != 0 || p.Vel[2] != 0 { // Is moving X or Z
activeSprite = p.SpriteRun
} else {
activeSprite = p.SpriteIdle
}
subImg := activeSprite.SubImage(srcRect).(*ebiten.Image)
op := &ebiten.DrawImageOptions{}
if !p.FacingRight {
op.GeoM.Scale(-1, 1)
op.GeoM.Translate(96, 0)
}
drawX := p.Pos[0] - camX
drawY := p.Pos[1] - camY
op.GeoM.Translate(drawX, drawY)
screen.DrawImage(subImg, op)
}
func (s *PlayScene) drawPlayerTop(screen *ebiten.Image, camX, camZ float64) {
p := s.player
op := &ebiten.DrawImageOptions{}
drawX := p.Pos[0] - camX
drawZ := p.Pos[2] - camZ
op.GeoM.Translate(drawX, drawZ)
screen.DrawImage(p.SpriteTop, op)
}