Some checks failed
release / build-release (push) Failing after 27s
- internal/sim: rutbaserad Mover (4 riktningar, riktningsbyte vid rutgräns, bildrute-exakt ankomst) och Camera (följer, klampar, centrerar små kartor), med tester - internal/game: scener meny -> bana -> resultat, tangentbord + gamepad, tileset och spelaranimation, HUD, skriptläge (-script) för deterministiska skärmdumpar och tester - tools/genart genererar placeholder-grafik (tiles.png, player.png); tools/shots tar doc/img/*.png som README och wiki visar - bana 002-shinjuku (33x21) för kamerascroll; level.List/LoadFile/TrainAt - .gitea/workflows/release.yaml: vet + test + linux/windows-zippar till rullande latest på brasse-linux01-runnern (linux-amd64) - docs: roadmap M1 klar, tester, plan, README med bilder och styrning Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
100 lines
2.6 KiB
Go
100 lines
2.6 KiB
Go
// Package game innehåller Ebitengine-spelloopen och scenerna. Detta är det
|
|
// enda paketet (tillsammans med cmd/) som får känna till Ebitengine; all
|
|
// spellogik ligger i paket som går att testa utan fönster (internal/sim,
|
|
// internal/level …).
|
|
package game
|
|
|
|
import (
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
)
|
|
|
|
// Intern upplösning. Allt ritas i denna storlek och skalas sedan upp med
|
|
// heltalsfaktor av Ebitengine så pixlarna förblir skarpa.
|
|
const (
|
|
ScreenW = 320
|
|
ScreenH = 180
|
|
TileSize = 16
|
|
dt = 1.0 / 60 // Ebitengine kör Update med fast 60 TPS
|
|
)
|
|
|
|
// Scene är ett spelläge: meny, bana, resultat …
|
|
type Scene interface {
|
|
Update(g *Game) error
|
|
Draw(g *Game, screen *ebiten.Image)
|
|
}
|
|
|
|
// Game är Ebitengine-tillståndet för hela programmet.
|
|
type Game struct {
|
|
Assets *Assets
|
|
scene Scene
|
|
next Scene
|
|
smokeLeft int // >0: avsluta automatiskt efter så många uppdateringar
|
|
script *script
|
|
frame *ebiten.Image // allt ritas hit och blittas sedan till skärmen (gör skärmdumpar möjliga)
|
|
}
|
|
|
|
// Options styr uppstarten.
|
|
type Options struct {
|
|
LevelID string // hoppa direkt in i en bana (tom = meny)
|
|
SmokeFrames int // >0 avslutar efter N uppdateringar (smoke-test)
|
|
Script string // skriptade steg för skärmdumpar/tester, se script.go
|
|
}
|
|
|
|
// New laddar resurser och bygger spelet.
|
|
func New(opt Options) (*Game, error) {
|
|
a, err := LoadAssets()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g := &Game{Assets: a, smokeLeft: opt.SmokeFrames, frame: ebiten.NewImage(ScreenW, ScreenH)}
|
|
if opt.Script != "" {
|
|
if g.script, err = parseScript(opt.Script); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if opt.LevelID != "" {
|
|
s, err := NewLevelScene(a, opt.LevelID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g.scene = s
|
|
} else {
|
|
g.scene, err = NewMenuScene(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return g, nil
|
|
}
|
|
|
|
// SetScene byter scen från och med nästa uppdatering.
|
|
func (g *Game) SetScene(s Scene) { g.next = s }
|
|
|
|
// Update körs 60 ggr/s.
|
|
func (g *Game) Update() error {
|
|
if g.smokeLeft > 0 {
|
|
g.smokeLeft--
|
|
if g.smokeLeft == 0 {
|
|
return ebiten.Termination
|
|
}
|
|
}
|
|
if g.next != nil {
|
|
g.scene, g.next = g.next, nil
|
|
}
|
|
if g.script != nil {
|
|
if err := g.script.tick(g, g.frame); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return g.scene.Update(g)
|
|
}
|
|
|
|
// Draw ritar aktuell scen till den interna bilden och sedan till skärmen.
|
|
func (g *Game) Draw(screen *ebiten.Image) {
|
|
g.scene.Draw(g, g.frame)
|
|
screen.DrawImage(g.frame, nil)
|
|
}
|
|
|
|
// Layout talar om för Ebitengine vilken intern upplösning vi ritar i.
|
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { return ScreenW, ScreenH }
|