All checks were successful
release / build-release (push) Successful in 1m33s
Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot utgången, blockerar spelaren men inte varandra), vänster-/högerfiler som bromsar på fel sida, bredare gångar, reklamaffischer (clutter), japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn, släpp förbi passagerare) och att folk kliver av innan man får gå på. Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
104 lines
2.3 KiB
Go
104 lines
2.3 KiB
Go
package game
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"io"
|
||
"math"
|
||
|
||
"github.com/hajimehoshi/ebiten/v2/audio"
|
||
"github.com/hajimehoshi/ebiten/v2/audio/wav"
|
||
|
||
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/res"
|
||
)
|
||
|
||
// Audio spelar korta effekter. nil-säkert: utan ljud (−mute eller fel) är
|
||
// alla anrop no-ops.
|
||
type Audio struct {
|
||
ctx *audio.Context
|
||
clips map[string][]byte // avkodad PCM (16-bit stereo LE) per klipp
|
||
loop *audio.Player // pågående bakgrundsloop (bakgrundsprat)
|
||
}
|
||
|
||
const sampleRate = 44100
|
||
|
||
// Ljudklipp (filnamn utan .wav i resources/sound).
|
||
const (
|
||
SoundApproach = "melody-approach"
|
||
SoundDoors = "door-chime"
|
||
SoundChatter = "chatter"
|
||
)
|
||
|
||
// AudibleTiles är hur många rutor bort man hör perrongens ljud.
|
||
const AudibleTiles = 12.0
|
||
|
||
// newAudio laddar klippen. Returnerar nil om ljudet är avstängt.
|
||
func newAudio(mute bool) (*Audio, error) {
|
||
if mute {
|
||
return nil, nil
|
||
}
|
||
a := &Audio{ctx: audio.NewContext(sampleRate), clips: map[string][]byte{}}
|
||
for _, name := range []string{SoundApproach, SoundDoors, SoundChatter} {
|
||
b, err := res.ReadFile("sound", name+".wav")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
stream, err := wav.DecodeWithoutResampling(bytes.NewReader(b))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("ljud %s: %w", name, err)
|
||
}
|
||
pcm, err := io.ReadAll(stream)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
a.clips[name] = pcm
|
||
}
|
||
return a, nil
|
||
}
|
||
|
||
// Play spelar ett klipp med volym 0..1.
|
||
func (a *Audio) Play(name string, volume float64) {
|
||
if a == nil || volume <= 0 {
|
||
return
|
||
}
|
||
pcm, ok := a.clips[name]
|
||
if !ok {
|
||
return
|
||
}
|
||
p := a.ctx.NewPlayerFromBytes(pcm)
|
||
p.SetVolume(math.Min(1, volume))
|
||
p.Play()
|
||
}
|
||
|
||
// Loop startar (eller byter) bakgrundsloopen med given volym; 0 stoppar.
|
||
func (a *Audio) Loop(name string, volume float64) {
|
||
if a == nil {
|
||
return
|
||
}
|
||
a.StopLoop()
|
||
pcm, ok := a.clips[name]
|
||
if !ok || volume <= 0 {
|
||
return
|
||
}
|
||
stream := audio.NewInfiniteLoop(bytes.NewReader(pcm), int64(len(pcm)))
|
||
p, err := a.ctx.NewPlayer(stream)
|
||
if err != nil {
|
||
return
|
||
}
|
||
p.SetVolume(math.Min(1, volume))
|
||
p.Play()
|
||
a.loop = p
|
||
}
|
||
|
||
// StopLoop stoppar bakgrundsloopen.
|
||
func (a *Audio) StopLoop() {
|
||
if a == nil || a.loop == nil {
|
||
return
|
||
}
|
||
a.loop.Close()
|
||
a.loop = nil
|
||
}
|
||
|
||
// volumeAt ger volym efter avstånd i rutor.
|
||
func volumeAt(dist float64) float64 { return math.Max(0, 1-dist/AudibleTiles) }
|