M13: titelskärm, språkval, tutorial, tips och pausmeny
All checks were successful
release / build-release (push) Successful in 1m40s
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:
211
internal/game/scene_title.go
Normal file
211
internal/game/scene_title.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
|
||||
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/buildinfo"
|
||||
)
|
||||
|
||||
// TitleScene är titelskärmen: premissen i text och menyn Fortsätt/Starta,
|
||||
// Tutorial, Inställningar, Avsluta (Enter två gånger).
|
||||
type TitleScene struct {
|
||||
sel int
|
||||
t float64
|
||||
confirm bool // Avsluta valt en gång
|
||||
}
|
||||
|
||||
// NewTitleScene öppnar titelskärmen.
|
||||
func NewTitleScene() *TitleScene { return &TitleScene{} }
|
||||
|
||||
const titleItems = 4
|
||||
|
||||
func (s *TitleScene) Update(g *Game) error {
|
||||
s.t += dt
|
||||
if mv := menuMove(); mv != 0 {
|
||||
s.sel = (s.sel + mv + titleItems) % titleItems
|
||||
s.confirm = false
|
||||
}
|
||||
if backJustPressed() {
|
||||
s.confirm = false
|
||||
}
|
||||
if !confirmJustPressed() {
|
||||
return nil
|
||||
}
|
||||
g.Audio.Play(SoundConfirm, 0.6)
|
||||
switch s.sel {
|
||||
case 0:
|
||||
g.SetScene(NewMapScene(g))
|
||||
case 1:
|
||||
g.SetScene(NewTutorialScene(s))
|
||||
case 2:
|
||||
g.SetScene(NewSettingsScene(s))
|
||||
case 3:
|
||||
if s.confirm {
|
||||
return ebiten.Termination
|
||||
}
|
||||
s.confirm = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TitleScene) Draw(g *Game, screen *ebiten.Image) {
|
||||
a := g.Assets
|
||||
screen.Fill(color.RGBA{24, 20, 37, 255})
|
||||
// Räls längs nederkanten med ett tåg som rullar förbi.
|
||||
railY := ScreenH - 28
|
||||
for x := 0; x < ScreenW; x += TileSize {
|
||||
var op ebiten.DrawImageOptions
|
||||
op.GeoM.Translate(float64(x), float64(railY))
|
||||
screen.DrawImage(a.Rails, &op)
|
||||
}
|
||||
tx := math.Mod(s.t*40, float64(ScreenW+6*TileSize)) - 3*TileSize
|
||||
for i := 0; i < 3; i++ {
|
||||
var op ebiten.DrawImageOptions
|
||||
op.GeoM.Translate(tx+float64(i*TileSize), float64(railY-TileSize+4))
|
||||
screen.DrawImage(a.Train, &op)
|
||||
}
|
||||
a.DrawBig(screen, a.T("game.title"), 16, 8, colKnown)
|
||||
ver := fmt.Sprintf("%s (%s)", buildinfo.Version, buildinfo.Mode)
|
||||
a.Draw(screen, ver, ScreenW-8-a.Width(ver), 10, colDim)
|
||||
y := 30
|
||||
for _, l := range a.wrapLine(plainLine(a.T("title.premise"), colText), ScreenW-32) {
|
||||
a.drawLines(screen, []line{l}, 16, y)
|
||||
y += LineH
|
||||
}
|
||||
start := a.T("menu.continue")
|
||||
if g.Progress.TotalStars() == 0 && len(g.Progress.Levels) == 0 {
|
||||
start = a.T("menu.start")
|
||||
}
|
||||
items := [titleItems]string{start, a.T("title.tutorial"), a.T("menu.settings"), a.T("menu.quit")}
|
||||
y += 6
|
||||
for i, it := range items {
|
||||
col := colText
|
||||
if i == s.sel {
|
||||
vector.DrawFilledRect(screen, 12, float32(y-2), 130, LineH, colBar, false)
|
||||
col = colKnown
|
||||
a.Draw(screen, "▶", 14, y, colKnown)
|
||||
}
|
||||
a.Draw(screen, it, 26, y, col)
|
||||
y += LineH
|
||||
}
|
||||
if s.confirm {
|
||||
a.Draw(screen, a.T("title.quit_confirm"), 150, y-LineH, colWarn)
|
||||
}
|
||||
a.Draw(screen, a.T("title.hint"), 8, ScreenH-12, colDim)
|
||||
}
|
||||
|
||||
// LangScene visas första gången spelet startar: välj språk.
|
||||
type LangScene struct{ sel int }
|
||||
|
||||
var langChoices = []string{"sv", "en"}
|
||||
|
||||
func (s *LangScene) Update(g *Game) error {
|
||||
if mv := menuMove(); mv != 0 {
|
||||
s.sel = (s.sel + mv + len(langChoices)) % len(langChoices)
|
||||
}
|
||||
if confirmJustPressed() {
|
||||
g.State.Settings.Lang = langChoices[s.sel]
|
||||
g.applySettings()
|
||||
g.Save()
|
||||
g.Audio.Play(SoundConfirm, 0.6)
|
||||
g.SetScene(NewTitleScene())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LangScene) Draw(g *Game, screen *ebiten.Image) {
|
||||
a := g.Assets
|
||||
screen.Fill(color.RGBA{24, 20, 37, 255})
|
||||
a.DrawBig(screen, a.T("game.title"), 16, 10, colKnown)
|
||||
a.Draw(screen, "Välj språk / Choose language", 16, 50, colText)
|
||||
y := 72
|
||||
for i, l := range langChoices {
|
||||
col := colText
|
||||
if i == s.sel {
|
||||
vector.DrawFilledRect(screen, 12, float32(y-2), 130, LineH+2, colBar, false)
|
||||
col = colKnown
|
||||
a.Draw(screen, "▶", 14, y, colKnown)
|
||||
}
|
||||
a.Draw(screen, a.T("lang."+l), 26, y, col)
|
||||
y += LineH + 1
|
||||
}
|
||||
a.Draw(screen, "Pilar + Enter / Arrows + Enter", 16, y+8, colDim)
|
||||
}
|
||||
|
||||
// PauseScene är pausmenyn i en bana (Esc): Fortsätt, Lämna banan,
|
||||
// Inställningar, Avsluta spelet (Enter två gånger). Esc igen fortsätter.
|
||||
type PauseScene struct {
|
||||
from *LevelScene
|
||||
sel int
|
||||
confirm bool
|
||||
}
|
||||
|
||||
const pauseItems = 4
|
||||
|
||||
// NewPauseScene pausar banan.
|
||||
func NewPauseScene(from *LevelScene) *PauseScene { return &PauseScene{from: from} }
|
||||
|
||||
func (s *PauseScene) Update(g *Game) error {
|
||||
if backJustPressed() {
|
||||
g.SetScene(s.from)
|
||||
return nil
|
||||
}
|
||||
if mv := menuMove(); mv != 0 {
|
||||
s.sel = (s.sel + mv + pauseItems) % pauseItems
|
||||
s.confirm = false
|
||||
}
|
||||
if !confirmJustPressed() {
|
||||
return nil
|
||||
}
|
||||
g.Audio.Play(SoundConfirm, 0.6)
|
||||
switch s.sel {
|
||||
case 0:
|
||||
g.SetScene(s.from)
|
||||
case 1:
|
||||
if s.from.endless {
|
||||
g.endEndless()
|
||||
}
|
||||
g.SetScene(NewMapScene(g))
|
||||
case 2:
|
||||
g.SetScene(NewSettingsScene(s))
|
||||
case 3:
|
||||
if s.confirm {
|
||||
return ebiten.Termination
|
||||
}
|
||||
s.confirm = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PauseScene) Draw(g *Game, screen *ebiten.Image) {
|
||||
s.from.Draw(g, screen)
|
||||
a := g.Assets
|
||||
vector.DrawFilledRect(screen, 0, 0, ScreenW, ScreenH, color.RGBA{0, 0, 0, 120}, false)
|
||||
vector.DrawFilledRect(screen, 40, 40, ScreenW-80, 104, colPanel, false)
|
||||
vector.DrawFilledRect(screen, 40, 40, ScreenW-80, 2, colWarn, false)
|
||||
a.DrawBig(screen, a.T("pause.title"), 48, 46, colText)
|
||||
items := [pauseItems]string{a.T("pause.resume"), a.T("pause.leave"), a.T("menu.settings"), a.T("pause.quit")}
|
||||
y := 68
|
||||
for i, it := range items {
|
||||
col := colText
|
||||
if i == s.sel {
|
||||
vector.DrawFilledRect(screen, 44, float32(y-2), ScreenW-88, LineH+2, colBar, false)
|
||||
col = colKnown
|
||||
}
|
||||
a.Draw(screen, it, 50, y, col)
|
||||
y += LineH + 2
|
||||
}
|
||||
msg := a.T("pause.hint")
|
||||
if s.confirm {
|
||||
msg = a.T("pause.quit_confirm")
|
||||
}
|
||||
for _, l := range a.wrapLine(plainLine(msg, colDim), ScreenW-96) {
|
||||
a.drawLines(screen, []line{l}, 48, y+2)
|
||||
y += LineH
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user