From aadfa35e6b6fa877b427f3503c20f5edaf045411 Mon Sep 17 00:00:00 2001 From: lord Date: Sat, 18 Jul 2020 20:37:35 -0700 Subject: [PATCH] Smooth camera targetting (#607) * smooth camera with vectors * add smooth cam support to map engine test smooth cam now works in map engine test. clicking or holding the left-mouse button will move the camera. also works with freecam mode in single-player. * Update ebiten_renderer.go did not mean to edit this file --- d2core/d2map/d2maprenderer/camera.go | 61 +++++++++++++++++------ d2core/d2map/d2maprenderer/renderer.go | 34 +++++++++---- d2core/d2map/d2maprenderer/viewport.go | 7 +-- d2game/d2gamescreen/game.go | 12 +++-- d2game/d2gamescreen/map_engine_testing.go | 49 ++++++++++++++++-- d2game/d2player/game_controls.go | 33 ++++++++++-- 6 files changed, 155 insertions(+), 41 deletions(-) diff --git a/d2core/d2map/d2maprenderer/camera.go b/d2core/d2map/d2maprenderer/camera.go index 585d9f7d..17ed3b2f 100644 --- a/d2core/d2map/d2maprenderer/camera.go +++ b/d2core/d2map/d2maprenderer/camera.go @@ -1,25 +1,56 @@ package d2maprenderer -// Camera is the position of the camera perspective in orthogonal world space. See viewport.go. -// TODO: Has a coordinate (issue #456) +import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" + +// Camera is the position of the Camera perspective in orthogonal world space. See viewport.go. type Camera struct { - x float64 - y float64 + position *d2vector.Position + target *d2vector.Position } -// MoveTo sets the position of the camera to the given x and y coordinates. -func (c *Camera) MoveTo(x, y float64) { - c.x = x - c.y = y +// MoveTo sets the position of the Camera to the given position +func (c *Camera) MoveTo(position *d2vector.Position) { + c.position = position } -// MoveBy adds the given vector to the current position of the camera. -func (c *Camera) MoveBy(x, y float64) { - c.x += x - c.y += y +// MoveBy adds the given vector to the current position of the Camera. +func (c *Camera) MoveBy(vector *d2vector.Vector) { + c.position.Add(vector) } -// GetPosition returns the camera x and y position. -func (c *Camera) GetPosition() (float64, float64) { - return c.x, c.y +// SetTarget sets the target position +func (c *Camera) SetTarget(target *d2vector.Position) { + c.target = target +} + +// MoveBy adds the given vector to the current position of the Camera. +func (c *Camera) MoveTargetBy(vector *d2vector.Vector) { + if c.target == nil { + v := c.position.Clone() + c.target = &d2vector.Position{v} + } + c.target.Add(vector) +} + +// ClearTarget sets the target position +func (c *Camera) ClearTarget() { + c.target = nil +} + +// GetPosition returns the Camera position +func (c *Camera) GetPosition() *d2vector.Position { + return c.position +} + +// Advance returns the Camera position +func (c *Camera) Advance(elapsed float64) { + c.advanceToTarget(elapsed) +} + +func (c *Camera) advanceToTarget(_ float64) { + if c.target != nil { + diff := c.position.World().Subtract(c.target.World()) + diff.Scale(-0.85) + c.MoveBy(diff) + } } diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index b77f9fc3..a4ed03f4 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -2,6 +2,7 @@ package d2maprenderer import ( "errors" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "image/color" "log" "math" @@ -14,13 +15,13 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" ) -// MapRenderer manages the game viewport and camera. It requests tile and entity data from MapEngine and renders it. +// MapRenderer manages the game viewport and Camera. It requests tile and entity data from MapEngine and renders it. type MapRenderer struct { renderer d2interface.Renderer // Used for drawing operations mapEngine *d2mapengine.MapEngine // The map engine that is being rendered palette d2interface.Palette // The palette used for this map viewport *Viewport // Used for rendering offsets - camera Camera // Used to determine where on the map we are rendering + Camera Camera // Used to determine where on the map we are rendering debugVisLevel int // Debug visibility index (0=none, 1=tiles, 2=sub-tiles) lastFrameTime float64 // The last time the map was rendered currentFrame int // Current render frame (for animations) @@ -34,7 +35,10 @@ func CreateMapRenderer(renderer d2interface.Renderer, mapEngine *d2mapengine.Map viewport: NewViewport(0, 0, 800, 600), } - result.viewport.SetCamera(&result.camera) + result.Camera = Camera{} + startPosition := d2vector.NewPosition(0,0) + result.Camera.position = &startPosition + result.viewport.SetCamera(&result.Camera) term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) { result.debugVisLevel = level @@ -91,14 +95,19 @@ func (mr *MapRenderer) Render(target d2interface.Surface) { mr.renderPass4(target, startX, startY, endX, endY) } -// MoveCameraTo sets the position of the camera to the given x and y coordinates. -func (mr *MapRenderer) MoveCameraTo(x, y float64) { - mr.camera.MoveTo(x, y) +// MoveCameraTo sets the position of the Camera to the given x and y coordinates. +func (mr *MapRenderer) MoveCameraTo(position *d2vector.Position) { + mr.Camera.MoveTo(position) } -// MoveCameraBy adds the given vector to the current position of the camera. -func (mr *MapRenderer) MoveCameraBy(x, y float64) { - mr.camera.MoveBy(x, y) +// MoveCameraBy adds the given vector to the current position of the Camera. +func (mr *MapRenderer) MoveCameraBy(vector *d2vector.Vector) { + mr.Camera.MoveBy(vector) +} + +// MoveCameraTargetBy adds the given vector to the current position of the Camera. +func (mr *MapRenderer) MoveCameraTargetBy(vector *d2vector.Vector) { + mr.Camera.MoveTargetBy(vector) } // ScreenToWorld returns the world position for the given screen (pixel) position. @@ -387,6 +396,8 @@ func (mr *MapRenderer) Advance(elapsed float64) { if mr.currentFrame > 9 { mr.currentFrame = 0 } + + mr.Camera.Advance(elapsed) } func loadPaletteForAct(levelType d2enum.RegionIdType) (d2interface.Palette, error) { @@ -429,3 +440,8 @@ func (mr *MapRenderer) ViewportToRight() { func (mr *MapRenderer) ViewportDefault() { mr.viewport.resetAlign() } + +// SetCameraTarget sts the Camera target +func (mr *MapRenderer) SetCameraTarget(position *d2vector.Position) { + mr.Camera.SetTarget(position) +} diff --git a/d2core/d2map/d2maprenderer/viewport.go b/d2core/d2map/d2maprenderer/viewport.go index 860b7319..f72a121b 100644 --- a/d2core/d2map/d2maprenderer/viewport.go +++ b/d2core/d2map/d2maprenderer/viewport.go @@ -17,7 +17,7 @@ const ( right = 2 ) -// Viewport is used for converting vectors between screen (pixel), orthogonal (camera) and world (isometric) space. +// Viewport is used for converting vectors between screen (pixel), orthogonal (Camera) and world (isometric) space. // TODO: Has a coordinate (issue #456) type Viewport struct { defaultScreenRect d2common.Rectangle @@ -46,7 +46,7 @@ func NewViewport(x, y, width, height int) *Viewport { } } -// SetCamera sets the current camera to the given value. +// SetCamera sets the current Camera to the given value. func (v *Viewport) SetCamera(camera *Camera) { v.camera = camera } @@ -178,7 +178,8 @@ func (v *Viewport) PopTranslation() { func (v *Viewport) getCameraOffset() (float64, float64) { var camX, camY float64 if v.camera != nil { - camX, camY = v.camera.GetPosition() + camPosition := v.camera.GetPosition() + camX, camY = camPosition.X(), camPosition.Y() } camX -= float64(v.screenRect.Width / 2) diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index bb39f434..4a82e7cc 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -2,6 +2,7 @@ package d2gamescreen import ( "fmt" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "image/color" "github.com/OpenDiablo2/OpenDiablo2/d2common" @@ -114,18 +115,18 @@ func (v *Game) Render(screen d2interface.Surface) error { } // Advance runs the update logic on the Gameplay screen -func (v *Game) Advance(tickTime float64) error { +func (v *Game) Advance(elapsed float64) error { if (v.escapeMenu != nil && !v.escapeMenu.isOpen) || len(v.gameClient.Players) != 1 { - v.gameClient.MapEngine.Advance(tickTime) // TODO: Hack + v.gameClient.MapEngine.Advance(elapsed) // TODO: Hack } if v.gameControls != nil { - if err := v.gameControls.Advance(tickTime); err != nil { + if err := v.gameControls.Advance(elapsed); err != nil { return err } } - v.ticksSinceLevelCheck += tickTime + v.ticksSinceLevelCheck += elapsed if v.ticksSinceLevelCheck > 1.0 { v.ticksSinceLevelCheck = 0 if v.localPlayer != nil { @@ -160,7 +161,8 @@ func (v *Game) Advance(tickTime float64) error { if v.localPlayer != nil && !v.gameControls.FreeCam { worldPosition := v.localPlayer.Position.World() rx, ry := v.mapRenderer.WorldToOrtho(worldPosition.X(), worldPosition.Y()) - v.mapRenderer.MoveCameraTo(rx, ry) + position := d2vector.NewPosition(rx, ry) + v.mapRenderer.SetCameraTarget(&position) } return nil diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index 35823abd..def1ff39 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -2,6 +2,7 @@ package d2gamescreen import ( "fmt" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "log" "os" "strings" @@ -167,7 +168,8 @@ func (met *MapEngineTest) loadRegionByIndex(n, levelPreset, fileIndex int) { } met.mapRenderer.SetMapEngine(met.mapEngine) - met.mapRenderer.MoveCameraTo(met.mapRenderer.WorldToOrtho(met.mapEngine.GetCenterPosition())) + position := d2vector.NewPosition(met.mapRenderer.WorldToOrtho(met.mapEngine.GetCenterPosition())) + met.mapRenderer.SetCameraTarget(&position) } // OnLoad loads the resources for the Map Engine Test screen @@ -317,6 +319,14 @@ func (met *MapEngineTest) OnMouseButtonDown(event d2interface.MouseEvent) bool { met.selY = int(py) met.selectedTile = met.mapEngine.TileAt(int(px), int(py)) + camVect := met.mapRenderer.Camera.GetPosition().Vector + + x, y := float64(met.lastMouseX-400)/5, float64(met.lastMouseY-300)/5 + targetPosition := d2vector.NewPositionTile(x, y) + targetPosition.Add(&camVect) + + met.mapRenderer.SetCameraTarget(&targetPosition) + return true } @@ -329,6 +339,27 @@ func (met *MapEngineTest) OnMouseButtonDown(event d2interface.MouseEvent) bool { return false } +func (met *MapEngineTest) OnMouseButtonRepeat(event d2interface.MouseEvent) bool { + if event.Button() == d2enum.MouseButtonLeft { + px, py := met.mapRenderer.ScreenToWorld(met.lastMouseX, met.lastMouseY) + met.selX = int(px) + met.selY = int(py) + met.selectedTile = met.mapEngine.TileAt(int(px), int(py)) + + camVect := met.mapRenderer.Camera.GetPosition().Vector + + x, y := float64(met.lastMouseX-400)/5, float64(met.lastMouseY-300)/5 + targetPosition := d2vector.NewPositionTile(x, y) + targetPosition.Add(&camVect) + + met.mapRenderer.SetCameraTarget(&targetPosition) + + return true + } + + return false +} + // Advance runs the update logic on the Map Engine Test screen func (met *MapEngineTest) Advance(tickTime float64) error { met.mapEngine.Advance(tickTime) @@ -345,22 +376,30 @@ func (met *MapEngineTest) OnKeyRepeat(event d2interface.KeyEvent) bool { } if event.Key() == d2enum.KeyDown { - met.mapRenderer.MoveCameraBy(0, moveSpeed) + v := d2vector.NewVector(0, moveSpeed) + met.mapRenderer.MoveCameraTargetBy(&v) + return true } if event.Key() == d2enum.KeyUp { - met.mapRenderer.MoveCameraBy(0, -moveSpeed) + v := d2vector.NewVector(0, -moveSpeed) + met.mapRenderer.MoveCameraTargetBy(&v) + return true } if event.Key() == d2enum.KeyRight { - met.mapRenderer.MoveCameraBy(moveSpeed, 0) + v := d2vector.NewVector(moveSpeed, 0) + met.mapRenderer.MoveCameraTargetBy(&v) + return true } if event.Key() == d2enum.KeyLeft { - met.mapRenderer.MoveCameraBy(-moveSpeed, 0) + v := d2vector.NewVector(-moveSpeed, 0) + met.mapRenderer.MoveCameraTargetBy(&v) + return true } diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index d0d8bb95..19d3ce44 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -2,6 +2,7 @@ package d2player import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "image" "image/color" @@ -157,22 +158,30 @@ func (g *GameControls) OnKeyRepeat(event d2interface.KeyEvent) bool { } if event.Key() == d2enum.KeyDown { - g.mapRenderer.MoveCameraBy(0, moveSpeed) + v := d2vector.NewVector(0, moveSpeed) + g.mapRenderer.MoveCameraTargetBy(&v) + return true } if event.Key() == d2enum.KeyUp { - g.mapRenderer.MoveCameraBy(0, -moveSpeed) + v := d2vector.NewVector(0, -moveSpeed) + g.mapRenderer.MoveCameraTargetBy(&v) + return true } if event.Key() == d2enum.KeyRight { - g.mapRenderer.MoveCameraBy(moveSpeed, 0) + v := d2vector.NewVector(moveSpeed, 0) + g.mapRenderer.MoveCameraTargetBy(&v) + return true } if event.Key() == d2enum.KeyLeft { - g.mapRenderer.MoveCameraBy(-moveSpeed, 0) + v := d2vector.NewVector(-moveSpeed, 0) + g.mapRenderer.MoveCameraTargetBy(&v) + return true } } @@ -225,6 +234,21 @@ func (g *GameControls) OnMouseButtonRepeat(event d2interface.MouseEvent) bool { if isLeft && shouldDoLeft && inRect { lastLeftBtnActionTime = now g.inputListener.OnPlayerMove(px, py) + + if g.FreeCam { + if event.Button() == d2enum.MouseButtonLeft { + camVect := g.mapRenderer.Camera.GetPosition().Vector + + x, y := float64(g.lastMouseX-400)/5, float64(g.lastMouseY-300)/5 + targetPosition := d2vector.NewPositionTile(x, y) + targetPosition.Add(&camVect) + + g.mapRenderer.SetCameraTarget(&targetPosition) + + return true + } + } + return true } @@ -323,6 +347,7 @@ func (g *GameControls) onToggleRunButton() { // ScreenAdvanceHandler func (g *GameControls) Advance(elapsed float64) error { + g.mapRenderer.Advance(elapsed) return nil }