aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-05-07 10:31:42 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-05-07 10:31:42 +0200
commitb2ca89d8193d98aa21e9637257c3e7a7216b9bd8 (patch)
treee87be16d5a92ad88070f3c96976174cab683f571 /main.go
parent2e8da8cb6948664a77fa73cb0c66e0cc27419dc5 (diff)
Add viewport/camera system with pan, zoom, and world-space cursor conversion
- New camera.go: Camera struct with arrow-key pan, scroll-wheel zoom toward cursor, middle-mouse drag, exponential velocity decay, and world-bounds clamping - Layout() now returns screen dimensions (960x640) instead of world size so the camera GeoM transform drives all rendering - All world entities, halos, text, path lines and spawn target converted via camera transform in draw.go; banners remain in screen space - selection.go: separate startScreen field so the drag-select box draws in screen space while selection logic stays in world space - Banner heights and font sizes scaled down to fit the 640px logical screen - Three new integration tests: ScreenToWorld round-trip, bounds clamping, zoom-toward-cursor stability Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'main.go')
-rw-r--r--main.go19
1 files changed, 14 insertions, 5 deletions
diff --git a/main.go b/main.go
index 3d538b4..2475841 100644
--- a/main.go
+++ b/main.go
@@ -32,6 +32,7 @@ var soilColor = color.RGBA{0x60, 0x40, 0x20, 0xff}
type Game struct {
CurrentAction Action
+ Camera Camera
GlobalSelection selection.GlobalSelection
ResourceAmount int
FaceSource *text.GoTextFaceSource
@@ -83,7 +84,7 @@ func (g *Game) getMoveMap() physics.MoveMap {
blocked[g.Position[i]] = true
}
}
- return physics.MoveMap{Width: 3200, Height: 2400, Blocked: blocked}
+ return physics.MoveMap{Width: WorldWidth, Height: WorldHeight, Blocked: blocked}
}
func (g *Game) entityAt(position physics.Point) ecs.Entity {
@@ -119,6 +120,8 @@ func (g *Game) updateSelecting(cursor physics.Point, moveMap physics.MoveMap) {
}
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
g.GlobalSelection.Start(cursor)
+ sx, sy := ebiten.CursorPosition()
+ g.GlobalSelection.StartScreen(physics.Point{X: sx, Y: sy})
}
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonRight) {
destination := cursor.Div(100).Mul(100)
@@ -151,8 +154,9 @@ func (g *Game) updateSelecting(cursor physics.Point, moveMap physics.MoveMap) {
}
func (g *Game) Update() error {
- x, y := ebiten.CursorPosition()
- cursor := physics.Point{X: x, Y: y}
+ g.Camera.Update(1.0 / 60.0)
+ sx, sy := ebiten.CursorPosition()
+ cursor := g.Camera.ScreenToWorld(sx, sy)
if inpututil.IsKeyJustReleased(ebiten.KeyEscape) {
g.CurrentAction = Selecting
slog.Info("selecting action")
@@ -179,7 +183,7 @@ func (g *Game) Update() error {
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
- return 3200, 2400
+ return ScreenWidth, ScreenHeight
}
func main() {
@@ -194,7 +198,7 @@ func main() {
}
logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})
slog.SetDefault(slog.New(logHandler))
- ebiten.SetWindowSize(960, 640)
+ ebiten.SetWindowSize(ScreenWidth, ScreenHeight)
ebiten.SetWindowTitle("Age of Empire")
icon, err := os.Open("icon-crop.jpg")
kcore.Expect(err, "failed to open icon")
@@ -204,6 +208,11 @@ func main() {
ebiten.SetWindowIcon([]image.Image{iconImg})
game := &Game{
ResourceAmount: 150,
+ Camera: Camera{
+ X: 1000,
+ Y: 1000,
+ Zoom: 1.0,
+ },
}
fontData, err := os.ReadFile("fonts/Cinzel-VariableFont_wght.ttf")
kcore.Expect(err, "failed to read font")