package game import ( "fmt" "image/color" "math" "path/filepath" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/vector" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/sim" ) // Spelarens grundfart i rutor per sekund (blir en stat i M7). const playerSpeed = 6.0 // LevelScene är själva spelandet av en bana. type LevelScene struct { id string lvl *level.Level player *sim.Mover cam *sim.Camera animT float64 elapsed float64 steps int } // NewLevelScene laddar banan och ställer spelaren på startrutan. func NewLevelScene(a *Assets, id string) (*LevelScene, error) { lvl, err := level.LoadFile(filepath.Join(a.LevelsDir, id+".json")) if err != nil { return nil, err } s := &LevelScene{ id: id, lvl: lvl, player: sim.NewMover(lvl.SpawnPoint(), playerSpeed), cam: sim.NewCamera(ScreenW, ScreenH, float64(lvl.Width()*TileSize), float64(lvl.Height()*TileSize)), } s.followPlayer() return s, nil } // PlayerTile returnerar rutan spelaren står på/lämnar (används av skriptläget). func (s *LevelScene) PlayerTile() level.Point { return s.player.Tile } func (s *LevelScene) followPlayer() { x, y := s.player.WorldPos(TileSize) s.cam.Follow(x+TileSize/2, y+TileSize/2) } func (s *LevelScene) Update(g *Game) error { if backJustPressed() { m, err := NewMenuScene(g.Assets) if err != nil { return err } g.SetScene(m) return nil } s.elapsed += dt s.steps += s.player.Step(dt, dirInput(), s.lvl.CanEnter) if s.player.Moving() { s.animT += dt } else { s.animT = 0 // Står vi på en perrong är banan slut (M2 ger tåg, tidtabell och spärrar). if t := s.lvl.At(s.player.Tile.X, s.player.Tile.Y); t == level.Platform || t == level.WrongPlatform { g.SetScene(NewResultScene(s, s.lvl.TrainAt(t))) } } s.followPlayer() return nil } func (s *LevelScene) Draw(g *Game, screen *ebiten.Image) { screen.Fill(color.RGBA{24, 20, 37, 255}) ox, oy := s.cam.Offset() ox, oy = math.Round(ox), math.Round(oy) // Rita bara rutor som syns. x0 := max(0, int(s.cam.X)/TileSize) y0 := max(0, int(s.cam.Y)/TileSize) x1 := min(s.lvl.Width(), int(s.cam.X+ScreenW)/TileSize+2) y1 := min(s.lvl.Height(), int(s.cam.Y+ScreenH)/TileSize+2) var op ebiten.DrawImageOptions for y := y0; y < y1; y++ { for x := x0; x < x1; x++ { op.GeoM.Reset() op.GeoM.Translate(ox+float64(x*TileSize), oy+float64(y*TileSize)) screen.DrawImage(g.Assets.Tiles[s.lvl.At(x, y)], &op) } } px, py := s.player.WorldPos(TileSize) op.GeoM.Reset() op.GeoM.Translate(ox+math.Round(px), oy+math.Round(py)) screen.DrawImage(g.Assets.PlayerFrame(s.player.Facing, s.player.Moving(), s.animT), &op) // HUD vector.DrawFilledRect(screen, 0, 0, ScreenW, 24, color.RGBA{24, 20, 37, 200}, false) // TODO(M3): visa japanska när en font med kana/kanji finns – debugfonten saknar dem. ebitenutil.DebugPrintAt(screen, fmt.Sprintf("%s %s: %s", s.lvl.Name.Get("sv"), g.Assets.T("hud.destination"), s.lvl.Destination.Get("romaji")), 4, 1) ebitenutil.DebugPrintAt(screen, fmt.Sprintf("%s %3.0fs %s %d %s", g.Assets.T("hud.time"), s.elapsed, g.Assets.T("hud.steps"), s.steps, g.Assets.T("hud.controls")), 4, 12) }