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

@@ -4,18 +4,18 @@ import "github.com/go-gl/mathgl/mgl64"
// Entity definierar alla bas-objekt i varlden
type Entity interface {
Pos() mgl64.Vec3
SetPos(pos mgl64.Vec3)
IsBlocking() bool
IsMovable() bool
Pos() mgl64.Vec3
SetPos(pos mgl64.Vec3)
IsBlocking() bool
IsMovable() bool
IsClimbable() bool
Move(dx, dy, dz float64)
Damage(amount int)
Pickup() bool
GetHealth() int
Move(dx, dy, dz float64)
Damage(amount int)
Pickup() bool
GetHealth() int
// Ritar ut framkallningen av spriten
// sideView: ifall vi ritar från sidan (vänster) eller uppifrån (höger)
DrawSide(screen interface{}, x, y float64, tint, alpha float32)
DrawTop(screen interface{}, x, y float64, tint, alpha float32)
// Ritar ut framkallningen av spriten
// sideView: ifall vi ritar från sidan (vänster) eller uppifrån (höger)
DrawSide(screen interface{}, x, y float64, alpha float32)
DrawTop(screen interface{}, x, y float64, alpha float32)
}

View File

@@ -6,11 +6,11 @@ import (
)
type Tile struct {
pos mgl64.Vec3
health int
Climbable bool
SideImg *ebiten.Image
TopImg *ebiten.Image
pos mgl64.Vec3
health int
Climbable bool
SideImg *ebiten.Image
TopImg *ebiten.Image
}
func (t *Tile) Pos() mgl64.Vec3 { return t.pos }
@@ -25,22 +25,24 @@ func (t *Tile) Damage(am int) { t.health -= am }
func (t *Tile) Pickup() bool { return false }
func (t *Tile) GetHealth() int { return t.health }
func (t *Tile) DrawSide(screen interface{}, x, y float64, tint, alpha float32) {
func (t *Tile) DrawSide(screen interface{}, x, y float64, alpha float32) {
scr := screen.(*ebiten.Image)
if t.SideImg != nil {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(x, y)
op.ColorScale.Scale(tint, tint, tint, alpha)
_, sh := t.SideImg.Bounds().Dx(), t.SideImg.Bounds().Dy()
op.GeoM.Translate(x, y-float64(sh-int(TileSize)))
op.ColorScale.Scale(alpha, alpha, alpha, alpha)
scr.DrawImage(t.SideImg, op)
}
}
func (t *Tile) DrawTop(screen interface{}, x, y float64, tint, alpha float32) {
func (t *Tile) DrawTop(screen interface{}, x, y float64, alpha float32) {
scr := screen.(*ebiten.Image)
if t.TopImg != nil {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(x, y)
op.ColorScale.Scale(tint, tint, tint, alpha)
_, sh := t.TopImg.Bounds().Dx(), t.TopImg.Bounds().Dy()
op.GeoM.Translate(x, y-float64(sh-int(TileSize)))
op.ColorScale.Scale(alpha, alpha, alpha, alpha)
scr.DrawImage(t.TopImg, op)
}
}

View File

@@ -84,34 +84,36 @@ func (p *Portal) Move(dx, dy, dz float64) {}
func (p *Portal) Damage(amount int) {}
func (p *Portal) Pickup() bool { return false }
func (p *Portal) GetHealth() int { return 100 }
func (p *Portal) DrawSide(screen interface{}, x, y float64, tint, alpha float32) {
func (p *Portal) DrawSide(screen interface{}, x, y float64, alpha float32) {
scr := screen.(*ebiten.Image)
if p.SideImg != nil {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(x, y)
op.ColorScale.Scale(tint, tint, tint, alpha)
_, sh := p.SideImg.Bounds().Dx(), p.SideImg.Bounds().Dy()
op.GeoM.Translate(x, y-float64(sh-int(TileSize)))
op.ColorScale.Scale(alpha, alpha, alpha, alpha)
scr.DrawImage(p.SideImg, op)
}
}
func (p *Portal) DrawTop(screen interface{}, x, y float64, tint, alpha float32) {
func (p *Portal) DrawTop(screen interface{}, x, y float64, alpha float32) {
scr := screen.(*ebiten.Image)
if p.TopImg != nil {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(x, y)
op.ColorScale.Scale(tint, tint, tint, alpha)
_, sh := p.TopImg.Bounds().Dx(), p.TopImg.Bounds().Dy()
op.GeoM.Translate(x, y-float64(sh-int(TileSize)))
op.ColorScale.Scale(alpha, alpha, alpha, alpha)
scr.DrawImage(p.TopImg, op)
}
}
// EntityDef definierar egenskaperna för ett tile-id
type EntityDef struct {
ID int `json:"id"`
TiledID int `json:"tiled_id"`
Solid bool `json:"solid"`
Climbable bool `json:"climbable,omitempty"`
TargetMap string
SideImg *ebiten.Image
TopImg *ebiten.Image `json:"target_map,omitempty"`
ID int `json:"id"`
TiledID int `json:"tiled_id"`
Solid bool `json:"solid"`
Climbable bool `json:"climbable,omitempty"`
TargetMap string `json:"target_map,omitempty"`
SideImg *ebiten.Image `json:"-"`
TopImg *ebiten.Image `json:"-"`
Sprites struct {
Top string `json:"top"`
Side string `json:"side"`

48
internal/input/keys.go Normal file
View File

@@ -0,0 +1,48 @@
package input
import "github.com/hajimehoshi/ebiten/v2"
var KeyNames = map[ebiten.Key]string{
ebiten.KeyA: "A",
ebiten.KeyB: "B",
ebiten.KeyC: "C",
ebiten.KeyD: "D",
ebiten.KeyE: "E",
ebiten.KeyF: "F",
ebiten.KeyG: "G",
ebiten.KeyH: "H",
ebiten.KeyI: "I",
ebiten.KeyJ: "J",
ebiten.KeyK: "K",
ebiten.KeyL: "L",
ebiten.KeyM: "M",
ebiten.KeyN: "N",
ebiten.KeyO: "O",
ebiten.KeyP: "P",
ebiten.KeyQ: "Q",
ebiten.KeyR: "R",
ebiten.KeyS: "S",
ebiten.KeyT: "T",
ebiten.KeyU: "U",
ebiten.KeyV: "V",
ebiten.KeyW: "W",
ebiten.KeyX: "X",
ebiten.KeyY: "Y",
ebiten.KeyZ: "Z",
ebiten.KeyLeft: "Left",
ebiten.KeyRight: "Right",
ebiten.KeyUp: "Up",
ebiten.KeyDown: "Down",
ebiten.KeySpace: "Space",
ebiten.KeyEnter: "Enter",
ebiten.KeyShift: "Shift",
ebiten.KeyControl: "Ctrl",
ebiten.KeyEscape: "Esc",
}
func KeyName(k ebiten.Key) string {
if name, ok := KeyNames[k]; ok {
return name
}
return k.String()
}

214
internal/input/manager.go Normal file
View File

@@ -0,0 +1,214 @@
package input
import (
"encoding/json"
"log"
"os"
"path/filepath"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
type Binding [][]ebiten.Key
const settingsFile = "assets/settings.json"
type InputManager struct {
Actions map[string]Binding
recordingAction string
recordingKeys []ebiten.Key
isRecording bool
}
var Manager *InputManager
func init() {
Manager = NewInputManager()
Manager.LoadSettings()
}
func NewInputManager() *InputManager {
return &InputManager{
Actions: make(map[string]Binding),
recordingKeys: make([]ebiten.Key, 0),
}
}
func (im *InputManager) LoadDefaultSettings() {
im.Actions = map[string]Binding{
"Run": {{ebiten.KeyControl}},
"ClimbMode": {{ebiten.KeyShift}},
"MoveLeft": {{ebiten.KeyA}, {ebiten.KeyLeft}},
"MoveRight": {{ebiten.KeyD}, {ebiten.KeyRight}},
"MoveUp": {{ebiten.KeyW}, {ebiten.KeyUp}},
"MoveDown": {{ebiten.KeyS}, {ebiten.KeyDown}},
"Jump": {{ebiten.KeySpace}},
"ClimbLeft": {{ebiten.KeyA}, {ebiten.KeyLeft}},
"ClimbRight": {{ebiten.KeyD}, {ebiten.KeyRight}},
"ClimbUp": {{ebiten.KeyW}},
"ClimbDown": {{ebiten.KeyS}},
"ClimbIn": {{ebiten.KeyUp}},
"ClimbOut": {{ebiten.KeyDown}},
}
}
func (im *InputManager) LoadSettings() {
im.LoadDefaultSettings() // Load defaults first
data, err := os.ReadFile(settingsFile)
if err != nil {
if os.IsNotExist(err) {
im.SaveSettings() // Create the file if it doesn't exist
} else {
log.Println("Error reading settings.json:", err)
}
return
}
var loadedActions map[string]Binding
if err := json.Unmarshal(data, &loadedActions); err != nil {
log.Println("Error unmarshaling settings.json:", err)
return
}
// Overwrite defaults with loaded ones
for k, v := range loadedActions {
im.Actions[k] = v
}
}
func (im *InputManager) SaveSettings() {
// Ensure directory exists
if err := os.MkdirAll(filepath.Dir(settingsFile), 0755); err != nil {
log.Println("Failed to create settings directory:", err)
return
}
data, err := json.MarshalIndent(im.Actions, "", " ")
if err != nil {
log.Println("Error marshaling settings:", err)
return
}
if err := os.WriteFile(settingsFile, data, 0644); err != nil {
log.Println("Error writing settings.json:", err)
}
}
func (im *InputManager) IsActionPressed(action string) bool {
bindings, ok := im.Actions[action]
if !ok {
return false
}
for _, combination := range bindings {
allPressed := true
for _, key := range combination {
if !ebiten.IsKeyPressed(key) {
allPressed = false
break
}
}
// Om alla tangenter i kombinationen är nedtryckta så triggas handlingen
if len(combination) > 0 && allPressed {
return true
}
}
return false
}
func (im *InputManager) IsActionJustPressed(action string) bool {
bindings, ok := im.Actions[action]
if !ok {
return false
}
for _, combination := range bindings {
allPressed := true
justPressedCount := 0
for _, key := range combination {
if !ebiten.IsKeyPressed(key) {
allPressed = false
break
}
if inpututil.IsKeyJustPressed(key) {
justPressedCount++
}
}
if len(combination) > 0 && allPressed && justPressedCount > 0 {
return true
}
}
return false
}
func (im *InputManager) StartRecording(action string) {
im.recordingAction = action
im.recordingKeys = []ebiten.Key{}
im.isRecording = true
}
// UpdateRecording called every frame while in settings to record inputs.
// Returns true when recording finished.
func (im *InputManager) UpdateRecording() bool {
if !im.isRecording {
return false
}
// Avbryt med ESC
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
im.StopRecording()
return true
}
// Spara med Enter
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
if len(im.recordingKeys) > 0 {
im.Actions[im.recordingAction] = Binding{im.recordingKeys}
im.SaveSettings()
}
im.isRecording = false
return true
}
pressedKeys := make([]ebiten.Key, 0)
for k := ebiten.KeyA; k <= ebiten.KeyMax; k++ {
if ebiten.IsKeyPressed(k) {
// Ignorera Escape och Enter från att bindas
if k == ebiten.KeyEscape || k == ebiten.KeyEnter {
continue
}
pressedKeys = append(pressedKeys, k)
}
}
// Limit to max 2 keys
if len(pressedKeys) > 2 {
pressedKeys = pressedKeys[:2]
}
// Uppdatera de lagrade tangenterna att visa i UI
if len(pressedKeys) > 0 {
im.recordingKeys = pressedKeys
}
return false
}
func (im *InputManager) StopRecording() {
im.isRecording = false
}
func (im *InputManager) IsRecording() bool {
return im.isRecording
}
func (im *InputManager) GetRecordingAction() string {
return im.recordingAction
}
func (im *InputManager) GetRecordingKeys() []ebiten.Key {
return im.recordingKeys
}

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))
}
}