Update tileset and settings, enhance logging

- Updated tileset configuration in 'tileset.tsj' to include new tiles and adjust dimensions.
- Added 'settings.json' for key bindings related to climbing and movement actions.
- Enhanced game logging in 'game.log' to track game start events and map loading.
- Updated the game binary to the latest version.
This commit is contained in:
2026-04-26 15:59:44 +02:00
parent ef550044ad
commit ede03ad026
22 changed files with 1237 additions and 482 deletions

View File

@@ -1,17 +1,21 @@
package scenes
import (
"fmt"
"image"
"image/color"
_ "image/jpeg"
_ "image/png"
"log"
"sort"
"mountain/internal/entities"
"mountain/internal/input"
"github.com/go-gl/mathgl/mgl64"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
const (
@@ -21,6 +25,14 @@ const (
ViewSize = 380 // 800 width, we have 2 viewports (380 each with 20 padding)
)
type PlayState int
const (
StatePlaying PlayState = iota
StatePaused
StateSettings
)
type Player struct {
Pos mgl64.Vec3 // 3D Kordinater: X (vänster/höger), Y (upp/ner), Z (djup)
Vel mgl64.Vec3 // Hastighet
@@ -44,6 +56,11 @@ type PlayScene struct {
player *Player
leftView *ebiten.Image
rightView *ebiten.Image
state PlayState
// Settings UI state
sortedActions []string
selectedIndex int
}
func NewPlayScene() *PlayScene {
@@ -82,18 +99,112 @@ func NewPlayScene() *PlayScene {
}
return &PlayScene{
world: w,
player: player,
leftView: ebiten.NewImage(ViewSize, ViewSize),
rightView: ebiten.NewImage(ViewSize, ViewSize),
world: w,
player: player,
leftView: ebiten.NewImage(ViewSize, ViewSize),
rightView: ebiten.NewImage(ViewSize, ViewSize),
state: StatePlaying,
sortedActions: make([]string, 0),
selectedIndex: 0,
}
}
func (s *PlayScene) Update() error {
switch s.state {
case StatePlaying:
return s.updatePlaying()
case StatePaused:
return s.updatePaused()
case StateSettings:
return s.updateSettings()
}
return nil
}
func (s *PlayScene) updatePaused() error {
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
s.state = StatePlaying
}
// Menu logic for pausing
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
if s.selectedIndex == 0 {
s.state = StatePlaying
} else if s.selectedIndex == 1 {
s.state = StateSettings
s.initSettings()
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
s.selectedIndex--
if s.selectedIndex < 0 {
s.selectedIndex = 1
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
s.selectedIndex++
if s.selectedIndex > 1 {
s.selectedIndex = 0
}
}
return nil
}
func (s *PlayScene) initSettings() {
s.sortedActions = make([]string, 0, len(input.Manager.Actions))
for k := range input.Manager.Actions {
s.sortedActions = append(s.sortedActions, k)
}
sort.Strings(s.sortedActions)
s.selectedIndex = 0
}
func (s *PlayScene) updateSettings() error {
if input.Manager.IsRecording() {
if input.Manager.UpdateRecording() {
// Recording finished
}
return nil
}
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
s.state = StatePaused
s.selectedIndex = 1 // back to settings button
return nil
}
if inpututil.IsKeyJustPressed(ebiten.KeyUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
s.selectedIndex--
if s.selectedIndex < 0 {
s.selectedIndex = len(s.sortedActions) - 1
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
s.selectedIndex++
if s.selectedIndex >= len(s.sortedActions) {
s.selectedIndex = 0
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
action := s.sortedActions[s.selectedIndex]
input.Manager.StartRecording(action)
}
return nil
}
func (s *PlayScene) updatePlaying() error {
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
s.state = StatePaused
s.selectedIndex = 0
return nil
}
p := s.player
currentMoveSpeed := MoveSpeed
if ebiten.IsKeyPressed(ebiten.KeyControl) {
if input.Manager.IsActionPressed("Run") {
currentMoveSpeed = MoveSpeed * 1.8 // Lite snabbare när man springer
}
@@ -108,62 +219,60 @@ func (s *PlayScene) Update() error {
entIn := s.world.GetEntityAt(currentGridX, currentGridY, currentGridZ)
isClimbing := false
if entIn != nil && entIn.IsClimbable() && ebiten.IsKeyPressed(ebiten.KeyShift) {
if entIn != nil && entIn.IsClimbable() && input.Manager.IsActionPressed("ClimbMode") {
isClimbing = true
}
if isClimbing {
p.Vel[1] = 0 // Ingen gravitation när man klättrar
// W, A, S, D styr X och Y axlar
if ebiten.IsKeyPressed(ebiten.KeyA) {
if input.Manager.IsActionPressed("ClimbLeft") {
p.Vel[0] = -currentMoveSpeed
p.FacingRight = false
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyD) {
if input.Manager.IsActionPressed("ClimbRight") {
p.Vel[0] = currentMoveSpeed
p.FacingRight = true
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyW) {
if input.Manager.IsActionPressed("ClimbUp") {
p.Vel[1] = -currentMoveSpeed // Uppåt
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
if input.Manager.IsActionPressed("ClimbDown") {
p.Vel[1] = currentMoveSpeed // Nedåt
isMoving = true
}
// Upp/ner pilarna styr Z-axeln
if ebiten.IsKeyPressed(ebiten.KeyUp) {
p.Vel[2] = -currentMoveSpeed
if input.Manager.IsActionPressed("ClimbIn") {
p.Vel[2] = -currentMoveSpeed // Inåt (mot djupet Z-)
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyDown) {
p.Vel[2] = currentMoveSpeed
if input.Manager.IsActionPressed("ClimbOut") {
p.Vel[2] = currentMoveSpeed // Utåt (från djupet Z+)
isMoving = true
}
} else {
if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
if input.Manager.IsActionPressed("MoveLeft") {
p.Vel[0] = -currentMoveSpeed
p.FacingRight = false
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
if input.Manager.IsActionPressed("MoveRight") {
p.Vel[0] = currentMoveSpeed
p.FacingRight = true
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
if input.Manager.IsActionPressed("MoveUp") {
p.Vel[2] = -currentMoveSpeed
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeyDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
if input.Manager.IsActionPressed("MoveDown") {
p.Vel[2] = currentMoveSpeed
isMoving = true
}
if ebiten.IsKeyPressed(ebiten.KeySpace) && p.IsGrounded {
if input.Manager.IsActionPressed("Jump") && p.IsGrounded {
p.Vel[1] = JumpForce
p.IsGrounded = false
}
@@ -173,29 +282,99 @@ func (s *PlayScene) Update() error {
}
}
// === FYSIK OCH KOLLISION ===
// Få tag på det nuvarande objektet i spelarens mitt-punkt för att veta om vi står i något klättringsbart
currGridX := int((p.Pos[0] + p.Width/2) / entities.TileSize)
currGridY := int((p.Pos[1] + p.Height/2) / entities.TileSize)
currGridZ := int((p.Pos[2] + p.Width/2) / entities.TileSize)
currentEnt := s.world.GetEntityAt(currGridX, currGridY, currGridZ)
isCurrentClimbable := currentEnt != nil && currentEnt.IsClimbable()
// 1. Hantera kollision för X-axeln (vänster/höger)
nextX := p.Pos[0] + p.Vel[0]
nextY := p.Pos[1] + p.Vel[1]
nextZ := p.Pos[2] + p.Vel[2]
if p.Vel[0] != 0 {
targetGridX := int((nextX + p.Width/2) / entities.TileSize)
entX := s.world.GetEntityAt(targetGridX, currGridY, currGridZ)
gridY := int((nextY + p.Height) / entities.TileSize)
gridX := int((nextX + p.Width/2) / entities.TileSize)
gridZ := int((nextZ + p.Width/2) / entities.TileSize)
if entX != nil && entX.IsBlocking() {
isTargetClimbable := entX.IsClimbable()
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
// Auto-klättra om målet är klättringsbart ELLER om vi redan står i något klättringsbart (t.ex framför en vägg på en stege)
if isTargetClimbable || isCurrentClimbable {
nextX = p.Pos[0] // Avbryt rörelse framåt
p.Vel[0] = 0
p.Pos[1] -= MoveSpeed * 0.6 // Långsam rörelse uppåt ("klättra upp")
p.IsGrounded = false
} else {
// Vanlig vägg - stoppa rörelse helt
nextX = p.Pos[0]
p.Vel[0] = 0
}
}
}
p.Pos[0] = nextX
p.Pos[1] = nextY
// 2. Hantera kollision för Z-axeln (djup in/ut)
nextZ := p.Pos[2] + p.Vel[2]
if p.Vel[2] != 0 {
// Uppdatera currGridX eftersom vi kan ha rört oss
currGridX = int((p.Pos[0] + p.Width/2) / entities.TileSize)
targetGridZ := int((nextZ + p.Width/2) / entities.TileSize)
entZ := s.world.GetEntityAt(currGridX, currGridY, targetGridZ)
if entZ != nil && entZ.IsBlocking() {
isTargetClimbable := entZ.IsClimbable()
if isTargetClimbable || isCurrentClimbable {
nextZ = p.Pos[2] // Avbryt rörelse framåt
p.Vel[2] = 0
p.Pos[1] -= MoveSpeed * 0.6 // Långsam rörelse uppåt ("klättra upp")
p.IsGrounded = false
} else {
// Vanlig vägg - stoppa rörelse helt
nextZ = p.Pos[2]
p.Vel[2] = 0
}
}
}
p.Pos[2] = nextZ
// Kolla ifall vi står på en portal
standEnt := s.world.GetEntityAt(gridX, gridY, gridZ)
// 3. Hantera kollision för Y-axeln (upp/ner + gravitation)
nextY := p.Pos[1] + p.Vel[1]
gridX := int((p.Pos[0] + p.Width/2) / entities.TileSize)
gridZ := int((p.Pos[2] + p.Width/2) / entities.TileSize)
if p.Vel[1] >= 0 {
// Faller (eller står still) - kolla kollision med marken (fötterna)
gridYFeet := int((nextY + p.Height) / entities.TileSize)
entY := s.world.GetEntityAt(gridX, gridYFeet, gridZ)
if entY != nil && entY.IsBlocking() {
nextY = float64(gridYFeet)*entities.TileSize - p.Height - 0.1
p.Vel[1] = 0
p.IsGrounded = true
} else {
p.IsGrounded = false
}
} else {
// Hoppar eller klättrar uppåt - kolla kollision med taket (huvudet)
p.IsGrounded = false
gridYHead := int(nextY / entities.TileSize)
entY := s.world.GetEntityAt(gridX, gridYHead, gridZ)
// Vi studsar i taket om det är solid OCH inte är klättringsbart
if entY != nil && entY.IsBlocking() && !entY.IsClimbable() {
nextY = float64(gridYHead+1)*entities.TileSize + 0.1
p.Vel[1] = 0
}
}
p.Pos[1] = nextY
// Kolla ifall vi står på en portal (mittpunkten)
standEnt := s.world.GetEntityAt(gridX, int((p.Pos[1]+p.Height/2)/entities.TileSize), gridZ)
if prt, ok := standEnt.(*entities.Portal); ok {
log.Println("Byter rum till: ", prt.TargetMap)
newW, err := entities.LoadWorldFromTiled("assets/maps/" + prt.TargetMap + ".tmj")
@@ -236,87 +415,109 @@ func (s *PlayScene) Draw(screen *ebiten.Image) {
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)
// Vänster vy: Sido-vy (renderar objekt vid spelarens Z, max -3 till +3 djup)
leftDepthStart := playerGridZ - 3
if leftDepthStart < 0 {
leftDepthStart = 0
}
for zLevel := leftDepthStart; zLevel <= playerGridZ; zLevel++ {
tint := float32(1.0)
leftDepthEnd := playerGridZ + 3
if leftDepthEnd >= s.world.Depth {
leftDepthEnd = s.world.Depth - 1
}
for zLevel := leftDepthStart; zLevel <= leftDepthEnd; zLevel++ {
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
}
diff := float32(zLevel - playerGridZ)
if diff < 0 {
// Bakom spelaren: självande alpha
alpha = 1.0 + (diff * 0.2) // diff är negativt, så detta minskar alpha
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)
}
} else if diff > 0 {
// Framför spelaren: självande alpha
alpha = 1.0 - (diff * 0.3)
if alpha < 0.1 {
alpha = 0.1
}
}
}
// 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
}
// Rendera spelaren på rätt Z-lager (vid diff == 0)
if zLevel == playerGridZ {
for x := 0; x < s.world.Width; x++ {
for z := 0; z < s.world.Depth; z++ {
ent := s.world.GetEntityAt(x, yLevel, z)
for y := 0; y < s.world.Height; y++ {
ent := s.world.GetEntityAt(x, y, zLevel)
if ent != nil {
px := float64(x)*entities.TileSize - camX
pz := float64(z)*entities.TileSize - camZ
ent.DrawTop(s.rightView, px, pz, tint, alpha)
py := float64(y)*entities.TileSize - camY
ent.DrawSide(s.leftView, px, py, 1.0)
}
}
}
s.drawPlayerSide(s.leftView, camX, camY)
} else {
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, 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)
// Höger vy: Ovan-vy (renderar Y-lager runt spelaren)
// Positivt 'diff' i Y-axel betyder djupare in i marken (Längre bort = mer transparent)
// Negativt 'diff' i Y-axel betyder högre upp mot kameran (Närmare = mer transparent så man ser igenom)
for diff := 3; diff >= -3; diff-- {
yLevel := playerGridY + diff
if yLevel >= 0 && yLevel < s.world.Height {
alpha := float32(1.0)
if diff > 0 {
// Under marknivån: sjunkande alpha
alpha = 1.0 - (float32(diff) * 0.2)
if alpha < 0.2 {
alpha = 0.2
}
} else if diff < 0 {
// Över marknivån: sjunkande alpha
alpha = 1.0 - (float32(-diff) * 0.3)
if alpha < 0.1 {
alpha = 0.1
}
}
if diff == 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, 1.0)
}
}
}
s.drawPlayerTop(s.rightView, camX, camZ)
} else {
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, alpha)
}
}
}
}
}
}
opL := &ebiten.DrawImageOptions{}
opL.GeoM.Translate(10, 100)
@@ -327,6 +528,13 @@ func (s *PlayScene) Draw(screen *ebiten.Image) {
screen.DrawImage(s.rightView, opR)
ebitenutil.DebugPrint(screen, "VÄNSTER (Sido vy) HÖGER (Top vy: Slice av din Hojd)")
// Rita UI ovanpå allt, beroende på state
if s.state == StatePaused {
s.drawPauseUI(screen)
} else if s.state == StateSettings {
s.drawSettingsUI(screen)
}
}
func (s *PlayScene) drawPlayerSide(screen *ebiten.Image, camX, camY float64) {
@@ -359,10 +567,105 @@ func (s *PlayScene) drawPlayerSide(screen *ebiten.Image, camX, camY float64) {
func (s *PlayScene) drawPlayerTop(screen *ebiten.Image, camX, camZ 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 || p.Vel[1] != 0 {
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 - 32
drawZ := p.Pos[2] - camZ - 64
op.GeoM.Translate(drawX, drawZ)
screen.DrawImage(p.SpriteTop, op)
// Rita med lite tint i toppen kanske? Vi ritar bara ut imagen
screen.DrawImage(subImg, op)
}
func (s *PlayScene) drawPauseUI(screen *ebiten.Image) {
// Mörka ner bakgrunden något
overlay := ebiten.NewImage(800, 600)
overlay.Fill(color.RGBA{0, 0, 0, 150})
screen.DrawImage(overlay, &ebiten.DrawImageOptions{})
ebitenutil.DebugPrintAt(screen, "--- PAUSAD ---", 350, 200)
options := []string{"Fortsatt", "Installningar"}
for i, opt := range options {
prefix := " "
if i == s.selectedIndex {
prefix = "> "
}
ebitenutil.DebugPrintAt(screen, prefix+opt, 350, 250+(i*30))
}
}
func (s *PlayScene) drawSettingsUI(screen *ebiten.Image) {
overlay := ebiten.NewImage(800, 600)
overlay.Fill(color.RGBA{0, 0, 0, 200})
screen.DrawImage(overlay, &ebiten.DrawImageOptions{})
ebitenutil.DebugPrintAt(screen, "--- INSTALLNINGAR ---", 300, 30)
if input.Manager.IsRecording() {
ebitenutil.DebugPrintAt(screen, ">> SPELAR IN: HÅLL IN TANGENTER OCH TRYCK ENTER FOR ATT SPARA <<", 180, 60)
ebitenutil.DebugPrintAt(screen, "ESC for att avbryta", 300, 80)
} else {
ebitenutil.DebugPrintAt(screen, "Tryck Enter for att ga in i redigeringslage, ESC for att abryta", 180, 60)
}
startY := 120
for i, action := range s.sortedActions {
prefix := " "
if i == s.selectedIndex {
prefix = "> "
}
// Bygg en sträng för att visa nuvarande tangenter
bindingStr := ""
bindings := input.Manager.Actions[action]
if input.Manager.IsRecording() && input.Manager.GetRecordingAction() == action {
// Visar vilka knappar som spelas in just nu
recKeys := input.Manager.GetRecordingKeys()
if len(recKeys) == 0 {
bindingStr = "[Vantar pa dig...]"
} else {
names := []string{}
for _, rk := range recKeys {
names = append(names, input.KeyName(rk))
}
bindingStr = fmt.Sprintf("[TRYCK ENTER FOR ATT SPARA: %v]", names)
}
} else {
if len(bindings) > 0 {
for bi, b := range bindings {
if bi > 0 {
bindingStr += " ELLER "
}
names := []string{}
for _, k := range b {
names = append(names, input.KeyName(k))
}
bindingStr += fmt.Sprintf("%v", names)
}
} else {
bindingStr = "[Ingen]"
}
}
line := fmt.Sprintf("%s%-15s : %s", prefix, action, bindingStr)
ebitenutil.DebugPrintAt(screen, line, 100, startY+(i*25))
}
}