M12: ljud, inställningar och release v0.1.0
All checks were successful
release / build-release (push) Successful in 1m35s

tools/gensound syntetiserar 21 ljud: fyra stationsmelodier i klockklang
(en per station via melody-fältet eller banans id), ankomstjingel,
ding-dong vid inrullning, dörrsignal vid avgång, spärrens pip och
nekande, kiosk, toalett, lärt ord, bekräfta/varning, byte, personal,
vinst, förlust, flygplan, sorl och menymusik. sfx.go spelar effekter
utifrån vad som hänt i sim.Run sedan förra bildrutan. Ljudlagret har
huvudvolym × effekter/musik och byter inte slinga i onödan.
Inställningar (S på bankartan, sparas): volymer, språk sv/en som laddas
om direkt, fönsterskala, fullskärm (F11 överallt) och tangentöversikt.
Fönsterikon från genart. CHANGELOG.md. Tester för ljudfiler, melodival
och inställningar. Docs, wiki och skärmdumpar uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 08:31:38 +01:00
parent 36a45563ec
commit 86f8130735
47 changed files with 800 additions and 107 deletions

View File

@@ -0,0 +1,117 @@
package game
import (
"fmt"
"image/color"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/save"
)
// SettingsScene är inställningarna: volymer, språk, fönsterskala och
// fullskärm. Pilar väljer rad och ändrar, Enter växlar, Esc/S går tillbaka.
// Allt sparas direkt.
type SettingsScene struct {
back Scene
sel int
}
const settingsRows = 6
// NewSettingsScene öppnar inställningarna.
func NewSettingsScene(back Scene) *SettingsScene { return &SettingsScene{back: back} }
func (s *SettingsScene) Update(g *Game) error {
if backJustPressed() || settingsJustPressed() {
g.SetScene(s.back)
return nil
}
s.sel = (s.sel + menuMove() + settingsRows) % settingsRows
dx, _ := miniMoveXY()
toggle := confirmJustPressed()
if dx == 0 && !toggle {
return nil
}
st := g.State.Settings
step := func(v *int, lo, hi int) {
*v = max(lo, min(hi, *v+dx))
}
switch s.sel {
case 0:
step(&st.Master, 0, 10)
case 1:
step(&st.Effects, 0, 10)
case 2:
step(&st.Music, 0, 10)
case 3:
if st.Lang == "sv" {
st.Lang = "en"
} else {
st.Lang = "sv"
}
case 4:
step(&st.Scale, 0, 6)
case 5:
st.Fullscreen = !st.Fullscreen
}
g.applySettings()
g.Save()
g.Audio.Play(SoundConfirm, 0.6)
return nil
}
func (s *SettingsScene) Draw(g *Game, screen *ebiten.Image) {
a := g.Assets
st := g.State.Settings
screen.Fill(color.RGBA{24, 20, 37, 255})
vector.DrawFilledRect(screen, 0, 0, ScreenW, 22, color.RGBA{38, 43, 68, 255}, false)
a.DrawBig(screen, a.T("settings.title"), 8, 5, colText)
scale := a.T("settings.scale_auto")
if st.Scale > 0 {
scale = fmt.Sprintf("%d× (%d×%d)", st.Scale, ScreenW*st.Scale, ScreenH*st.Scale)
}
fs := a.T("menu.off")
if st.Fullscreen {
fs = a.T("menu.on")
}
rows := [settingsRows][2]string{
{a.T("settings.master"), ""},
{a.T("settings.effects"), ""},
{a.T("settings.music"), ""},
{a.T("settings.lang"), a.T("lang." + st.Lang)},
{a.T("settings.scale"), scale},
{a.T("settings.fullscreen"), fs},
}
levels := [3]int{st.Master, st.Effects, st.Music}
y := 30
for i, r := range rows {
col := colText
if i == s.sel {
vector.DrawFilledRect(screen, 6, float32(y-2), ScreenW-12, LineH+1, colBar, false)
col = colKnown
}
a.Draw(screen, r[0], 10, y, col)
if i < 3 { // volymstapel i tio segment
for k := 0; k < 10; k++ {
c := color.RGBA{70, 80, 110, 255}
if k < levels[i] {
c = color.RGBA{120, 200, 120, 255}
}
vector.DrawFilledRect(screen, float32(130+k*9), float32(y+2), 7, 7, c, false)
}
a.Draw(screen, fmt.Sprintf("%d", levels[i]), 224, y, col)
} else {
a.Draw(screen, r[1], 130, y, col)
}
y += LineH + 1
}
y += 4
for _, l := range a.wrapLine(plainLine(a.T("settings.keys"), colDim), ScreenW-20) {
a.drawLines(screen, []line{l}, 10, y)
y += LineH
}
a.Draw(screen, a.T("settings.hint"), 8, ScreenH-12, colDim)
_ = save.DefaultSettings // (inställningarnas standardvärden ligger i save)
}