M13: titelskärm, språkval, tutorial, tips och pausmeny
All checks were successful
release / build-release (push) Successful in 1m40s

Björns feedback efter v0.1.0. Titelskärm med premissen i text och menyn
Fortsätt/Starta, Tutorial, Inställningar, Avsluta (Enter två gånger);
språkval första gången spelet startar. Tutorial i fem sidor med riktiga
sprites och en exempelskylt där resmålet markeras och resten tonas ned,
plus nyckeln 番線/方面/改札 och biljettipset. Kontextuella tips i spelet
tills första banan är klarad, och resmålets tecken markeras på skyltar
och avgångstavlor under tiden. Esc i en bana öppnar en pausmeny
(Fortsätt, Lämna banan, Inställningar, Avsluta spelet) i stället för att
lämna banan; Esc på bankartan går till titeln. Kortare lägestexter så att
bankartans rader ryms på svenska. Test för markeringen, docs, wiki och
skärmdumpar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 17:13:32 +01:00
parent 86f8130735
commit f0a0bc1522
26 changed files with 622 additions and 32 deletions

View File

@@ -0,0 +1,92 @@
package game
import (
"fmt"
"image/color"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
)
// TutorialScene är genomgången av grundmekaniken i fem sidor: målet och
// stationen, hur man läser en skylt (med resmålet markerat och resten
// nedtonat), kort/biljetter/spärrar, sällskapet och tåget, lägen och
// tangenter. ←/→ bläddrar, Esc går tillbaka.
type TutorialScene struct {
back Scene
page int
}
const tutorialPages = 5
// NewTutorialScene öppnar tutorialen.
func NewTutorialScene(back Scene) *TutorialScene { return &TutorialScene{back: back} }
func (s *TutorialScene) Update(g *Game) error {
if backJustPressed() {
g.SetScene(s.back)
return nil
}
dx, _ := miniMoveXY()
if confirmJustPressed() {
dx = 1
}
if dx != 0 {
s.page += dx
if s.page >= tutorialPages {
g.SetScene(s.back)
return nil
}
if s.page < 0 {
s.page = 0
}
g.Audio.Play(SoundConfirm, 0.4)
}
return nil
}
func (s *TutorialScene) Draw(g *Game, screen *ebiten.Image) {
a := g.Assets
screen.Fill(color.RGBA{24, 20, 37, 255})
vector.DrawFilledRect(screen, 0, 0, ScreenW, 22, color.RGBA{38, 43, 68, 255}, false)
key := fmt.Sprintf("tutorial.p%d", s.page+1)
a.DrawBig(screen, a.T(key+".title"), 8, 5, colText)
a.Draw(screen, fmt.Sprintf(a.T("tutorial.page"), s.page+1, tutorialPages), ScreenW-70, 8, colDim)
y := 28
for _, para := range strings.Split(a.T(key), "\n") {
for _, l := range a.wrapLine(plainLine(para, colText), ScreenW-16) {
a.drawLines(screen, []line{l}, 8, y)
y += LineH
}
y += 3
}
switch s.page {
case 0: // rutlegend med riktiga sprites
x := 12
for _, t := range []struct {
tile level.Tile
key string
}{{level.Sign, "tile.sign"}, {level.Gate, "tile.gate"}, {level.TicketMachine, "tile.machine"}, {level.Toilet, "tile.toilet"}, {level.Kiosk, "tile.kiosk"}, {level.Platform, "tile.platform"}} {
var op ebiten.DrawImageOptions
op.GeoM.Scale(2, 2)
op.GeoM.Translate(float64(x), float64(y))
screen.DrawImage(a.Tiles[t.tile], &op)
a.Draw(screen, a.T(t.key), x, y+2*TileSize+2, colDim)
x += 52
}
case 1: // exempelskylten: resmålet markerat, resten nedtonat
sample := "14番線 山手線 渋谷方面 → 南東口 改札"
body := append([]line{highlightLine(sample, "渋谷", colGood, colDim)}, a.wrapLine(line{{"→ ", colDim}, {a.T("tutorial.p2.sample"), colText}}, ScreenW-28)...)
vector.DrawFilledRect(screen, 8, float32(y), ScreenW-16, float32(LineH*len(body)+8), colPanel, false)
a.drawLines(screen, body, 14, y+4)
y += LineH*len(body) + 12
for _, l := range a.wrapLine(plainLine(a.T("tutorial.p2b"), colText), ScreenW-16) {
a.drawLines(screen, []line{l}, 8, y)
y += LineH
}
}
a.Draw(screen, a.T("tutorial.hint"), 8, ScreenH-12, colDim)
}