From 56934135ab49239d2fbbba53b1c11f658e4a7403 Mon Sep 17 00:00:00 2001 From: Bjorn Blomberg Date: Sat, 25 Apr 2026 22:46:10 +0200 Subject: [PATCH] Skapat grundprojekt: Huvudmeny och Spel-scen as med hoppmakanik och AABB kollisions-grund --- go.sum | 2 + internal/game/game.go | 65 +++++++++++++++++++++++ internal/scenes/menu.go | 26 +++++++++ internal/scenes/play.go | 115 ++++++++++++++++++++++++++++++++++++++++ main.go | 18 +++++++ 5 files changed, 226 insertions(+) create mode 100644 internal/game/game.go create mode 100644 internal/scenes/menu.go create mode 100644 internal/scenes/play.go create mode 100644 main.go diff --git a/go.sum b/go.sum index 1fb72c4..676d1d8 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/hajimehoshi/ebiten/v2 v2.9.9 h1:JdDag6Ndj12iD4lxQGG8kbsrh7ssj4Sbzth6r github.com/hajimehoshi/ebiten/v2 v2.9.9/go.mod h1:DAt4tnkYYpCvu3x9i1X/nK/vOruNXIlYq/tBXxnhrXM= github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA= +golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= diff --git a/internal/game/game.go b/internal/game/game.go new file mode 100644 index 0000000..1e7c154 --- /dev/null +++ b/internal/game/game.go @@ -0,0 +1,65 @@ +package game + +import ( + "mountain/internal/scenes" + + "github.com/hajimehoshi/ebiten/v2" +) + +// SceneType represents the different scenes in the game. +type SceneType int + +const ( + SceneMainMenu SceneType = iota + ScenePlay +) + +// Scene interface defines standard methods a state/scene must have. +type Scene interface { + Update() error + Draw(screen *ebiten.Image) +} + +// Game implements ebiten.Game interface. +type Game struct { + currentScene Scene + currentSceneType SceneType +} + +func NewGame() *Game { + g := &Game{} + g.ChangeScene(SceneMainMenu) + return g +} + +func (g *Game) Update() error { + // Handle global input for scene switching maybe? + if g.currentSceneType == SceneMainMenu && ebiten.IsKeyPressed(ebiten.KeyEnter) { + g.ChangeScene(ScenePlay) + } + + if g.currentScene != nil { + return g.currentScene.Update() + } + return nil +} + +func (g *Game) Draw(screen *ebiten.Image) { + if g.currentScene != nil { + g.currentScene.Draw(screen) + } +} + +func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { + return 800, 600 +} + +func (g *Game) ChangeScene(s SceneType) { + g.currentSceneType = s + switch s { + case SceneMainMenu: + g.currentScene = scenes.NewMenuScene() + case ScenePlay: + g.currentScene = scenes.NewPlayScene() + } +} \ No newline at end of file diff --git a/internal/scenes/menu.go b/internal/scenes/menu.go new file mode 100644 index 0000000..d1ad98e --- /dev/null +++ b/internal/scenes/menu.go @@ -0,0 +1,26 @@ +package scenes + +import ( + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" +) + +type MenuScene struct { +} + +func NewMenuScene() *MenuScene { + return &MenuScene{} +} + +func (m *MenuScene) Update() error { + return nil +} + +func (m *MenuScene) Draw(screen *ebiten.Image) { + screen.Fill(color.RGBA{0x20, 0x20, 0x20, 0xff}) + + ebitenutil.DebugPrintAt(screen, "REGIN : MOUNTAIN OF TREASURES", 300, 250) + ebitenutil.DebugPrintAt(screen, "Tryck ENTER for att starta", 310, 300) +} diff --git a/internal/scenes/play.go b/internal/scenes/play.go new file mode 100644 index 0000000..7837fcb --- /dev/null +++ b/internal/scenes/play.go @@ -0,0 +1,115 @@ +package scenes + +import ( + _ "image/jpeg" + _ "image/png" + "log" + + "github.com/go-gl/mathgl/mgl64" + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" +) + +const ( + Gravity = 0.5 + JumpForce = -10.0 + MoveSpeed = 4.0 + FloorHeight = 500.0 +) + +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) + + Sprite *ebiten.Image +} + +type PlayScene struct { + player Player + groundImg *ebiten.Image + isGrounded bool +} + +func NewPlayScene() *PlayScene { + pImg, _, err := ebitenutil.NewImageFromFile("assets/images/player.jpg") + if err != nil { + log.Println("Kunde inte ladda spelar-sprite:", err) + // Fallback till en tom bild ifall bilden saknas + pImg = ebiten.NewImage(32, 32) + } + + gImg, _, err := ebitenutil.NewImageFromFile("assets/images/ground.jpg") + if err != nil { + log.Println("Kunde inte ladda mark-sprite:", err) + gImg = ebiten.NewImage(800, 100) + } + + w, h := pImg.Bounds().Dx(), pImg.Bounds().Dy() + + return &PlayScene{ + player: Player{ + Pos: mgl64.Vec3{400.0, 100.0, 0.0}, + Vel: mgl64.Vec3{0, 0, 0}, + Width: float64(w), + Height: float64(h), + Sprite: pImg, + }, + groundImg: gImg, + } +} + +func (s *PlayScene) Update() error { + // === Input (X axis) === + s.player.Vel[0] = 0 // Återställ rörelse varje frame + + if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) { + s.player.Vel[0] = -MoveSpeed + } + if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) { + s.player.Vel[0] = MoveSpeed + } + + // === Hoppa (Y axis) === + if (ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeySpace)) && s.isGrounded { + s.player.Vel[1] = JumpForce + s.isGrounded = false + } + + // === Fysik / Euler integrering === + // 1. Applicera gravitation + if !s.isGrounded { + s.player.Vel[1] += Gravity + } + + // 2. Applicera Velocity till Position + s.player.Pos = s.player.Pos.Add(s.player.Vel) + + // === "Kollision" med golvet (väldigt simpel 2D-kollision för att simulera ytan på the floor på 2D-skärmen) === + if s.player.Pos[1]+s.player.Height >= FloorHeight { + s.player.Pos[1] = FloorHeight - s.player.Height + s.player.Vel[1] = 0 + s.isGrounded = true + } else { + s.isGrounded = false + } + + return nil +} + +func (s *PlayScene) Draw(screen *ebiten.Image) { + // === 3D -> 2D Rendering Pipeline (Side View mode) === + + // Rita bakgrund/mark (Väldigt statiskt i det här fallet) + gOp := &ebiten.DrawImageOptions{} + gOp.GeoM.Translate(0, FloorHeight) + screen.DrawImage(s.groundImg, gOp) + + // Rita Spelaren baserat på dess 3D-kordinater översatta till (X, Y) på 2D-skärmen (vi skippar Z i denna view) + pOp := &ebiten.DrawImageOptions{} + pOp.GeoM.Translate(s.player.Pos[0], s.player.Pos[1]) + screen.DrawImage(s.player.Sprite, pOp) + + ebitenutil.DebugPrint(screen, "Spelar Scene: SIDOVY. Styr med PILAR och Space.") +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..f851ad9 --- /dev/null +++ b/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "log" + "mountain/internal/game" + + "github.com/hajimehoshi/ebiten/v2" +) + +func main() { + ebiten.SetWindowSize(800, 600) + ebiten.SetWindowTitle("Regin: Mountain of Treasures") + + g := game.NewGame() + if err := ebiten.RunGame(g); err != nil { + log.Fatal(err) + } +} \ No newline at end of file