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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
48
internal/input/keys.go
Normal 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
214
internal/input/manager.go
Normal 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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user