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
This commit is contained in:
lord 2020-07-18 20:37:35 -07:00 committed by GitHub
parent 6db299b31d
commit aadfa35e6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 155 additions and 41 deletions

View File

@ -1,25 +1,56 @@
package d2maprenderer package d2maprenderer
// Camera is the position of the camera perspective in orthogonal world space. See viewport.go. import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
// TODO: Has a coordinate (issue #456)
// Camera is the position of the Camera perspective in orthogonal world space. See viewport.go.
type Camera struct { type Camera struct {
x float64 position *d2vector.Position
y float64 target *d2vector.Position
} }
// MoveTo sets the position of the camera to the given x and y coordinates. // MoveTo sets the position of the Camera to the given position
func (c *Camera) MoveTo(x, y float64) { func (c *Camera) MoveTo(position *d2vector.Position) {
c.x = x c.position = position
c.y = y
} }
// MoveBy adds the given vector to the current position of the camera. // MoveBy adds the given vector to the current position of the Camera.
func (c *Camera) MoveBy(x, y float64) { func (c *Camera) MoveBy(vector *d2vector.Vector) {
c.x += x c.position.Add(vector)
c.y += y
} }
// GetPosition returns the camera x and y position. // SetTarget sets the target position
func (c *Camera) GetPosition() (float64, float64) { func (c *Camera) SetTarget(target *d2vector.Position) {
return c.x, c.y 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)
}
} }

View File

@ -2,6 +2,7 @@ package d2maprenderer
import ( import (
"errors" "errors"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"image/color" "image/color"
"log" "log"
"math" "math"
@ -14,13 +15,13 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "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 { type MapRenderer struct {
renderer d2interface.Renderer // Used for drawing operations renderer d2interface.Renderer // Used for drawing operations
mapEngine *d2mapengine.MapEngine // The map engine that is being rendered mapEngine *d2mapengine.MapEngine // The map engine that is being rendered
palette d2interface.Palette // The palette used for this map palette d2interface.Palette // The palette used for this map
viewport *Viewport // Used for rendering offsets 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) debugVisLevel int // Debug visibility index (0=none, 1=tiles, 2=sub-tiles)
lastFrameTime float64 // The last time the map was rendered lastFrameTime float64 // The last time the map was rendered
currentFrame int // Current render frame (for animations) 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), 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) { term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) {
result.debugVisLevel = level result.debugVisLevel = level
@ -91,14 +95,19 @@ func (mr *MapRenderer) Render(target d2interface.Surface) {
mr.renderPass4(target, startX, startY, endX, endY) mr.renderPass4(target, startX, startY, endX, endY)
} }
// MoveCameraTo sets the position of the camera to the given x and y coordinates. // MoveCameraTo sets the position of the Camera to the given x and y coordinates.
func (mr *MapRenderer) MoveCameraTo(x, y float64) { func (mr *MapRenderer) MoveCameraTo(position *d2vector.Position) {
mr.camera.MoveTo(x, y) mr.Camera.MoveTo(position)
} }
// MoveCameraBy adds the given vector to the current position of the camera. // MoveCameraBy adds the given vector to the current position of the Camera.
func (mr *MapRenderer) MoveCameraBy(x, y float64) { func (mr *MapRenderer) MoveCameraBy(vector *d2vector.Vector) {
mr.camera.MoveBy(x, y) 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. // 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 { if mr.currentFrame > 9 {
mr.currentFrame = 0 mr.currentFrame = 0
} }
mr.Camera.Advance(elapsed)
} }
func loadPaletteForAct(levelType d2enum.RegionIdType) (d2interface.Palette, error) { func loadPaletteForAct(levelType d2enum.RegionIdType) (d2interface.Palette, error) {
@ -429,3 +440,8 @@ func (mr *MapRenderer) ViewportToRight() {
func (mr *MapRenderer) ViewportDefault() { func (mr *MapRenderer) ViewportDefault() {
mr.viewport.resetAlign() mr.viewport.resetAlign()
} }
// SetCameraTarget sts the Camera target
func (mr *MapRenderer) SetCameraTarget(position *d2vector.Position) {
mr.Camera.SetTarget(position)
}

View File

@ -17,7 +17,7 @@ const (
right = 2 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) // TODO: Has a coordinate (issue #456)
type Viewport struct { type Viewport struct {
defaultScreenRect d2common.Rectangle 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) { func (v *Viewport) SetCamera(camera *Camera) {
v.camera = camera v.camera = camera
} }
@ -178,7 +178,8 @@ func (v *Viewport) PopTranslation() {
func (v *Viewport) getCameraOffset() (float64, float64) { func (v *Viewport) getCameraOffset() (float64, float64) {
var camX, camY float64 var camX, camY float64
if v.camera != nil { 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) camX -= float64(v.screenRect.Width / 2)

View File

@ -2,6 +2,7 @@ package d2gamescreen
import ( import (
"fmt" "fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"image/color" "image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common" "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 // 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 { 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 v.gameControls != nil {
if err := v.gameControls.Advance(tickTime); err != nil { if err := v.gameControls.Advance(elapsed); err != nil {
return err return err
} }
} }
v.ticksSinceLevelCheck += tickTime v.ticksSinceLevelCheck += elapsed
if v.ticksSinceLevelCheck > 1.0 { if v.ticksSinceLevelCheck > 1.0 {
v.ticksSinceLevelCheck = 0 v.ticksSinceLevelCheck = 0
if v.localPlayer != nil { if v.localPlayer != nil {
@ -160,7 +161,8 @@ func (v *Game) Advance(tickTime float64) error {
if v.localPlayer != nil && !v.gameControls.FreeCam { if v.localPlayer != nil && !v.gameControls.FreeCam {
worldPosition := v.localPlayer.Position.World() worldPosition := v.localPlayer.Position.World()
rx, ry := v.mapRenderer.WorldToOrtho(worldPosition.X(), worldPosition.Y()) 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 return nil

View File

@ -2,6 +2,7 @@ package d2gamescreen
import ( import (
"fmt" "fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"log" "log"
"os" "os"
"strings" "strings"
@ -167,7 +168,8 @@ func (met *MapEngineTest) loadRegionByIndex(n, levelPreset, fileIndex int) {
} }
met.mapRenderer.SetMapEngine(met.mapEngine) 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 // 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.selY = int(py)
met.selectedTile = met.mapEngine.TileAt(int(px), 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 true
} }
@ -329,6 +339,27 @@ func (met *MapEngineTest) OnMouseButtonDown(event d2interface.MouseEvent) bool {
return false 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 // Advance runs the update logic on the Map Engine Test screen
func (met *MapEngineTest) Advance(tickTime float64) error { func (met *MapEngineTest) Advance(tickTime float64) error {
met.mapEngine.Advance(tickTime) met.mapEngine.Advance(tickTime)
@ -345,22 +376,30 @@ func (met *MapEngineTest) OnKeyRepeat(event d2interface.KeyEvent) bool {
} }
if event.Key() == d2enum.KeyDown { if event.Key() == d2enum.KeyDown {
met.mapRenderer.MoveCameraBy(0, moveSpeed) v := d2vector.NewVector(0, moveSpeed)
met.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }
if event.Key() == d2enum.KeyUp { if event.Key() == d2enum.KeyUp {
met.mapRenderer.MoveCameraBy(0, -moveSpeed) v := d2vector.NewVector(0, -moveSpeed)
met.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }
if event.Key() == d2enum.KeyRight { if event.Key() == d2enum.KeyRight {
met.mapRenderer.MoveCameraBy(moveSpeed, 0) v := d2vector.NewVector(moveSpeed, 0)
met.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }
if event.Key() == d2enum.KeyLeft { if event.Key() == d2enum.KeyLeft {
met.mapRenderer.MoveCameraBy(-moveSpeed, 0) v := d2vector.NewVector(-moveSpeed, 0)
met.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }

View File

@ -2,6 +2,7 @@ package d2player
import ( import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"image" "image"
"image/color" "image/color"
@ -157,22 +158,30 @@ func (g *GameControls) OnKeyRepeat(event d2interface.KeyEvent) bool {
} }
if event.Key() == d2enum.KeyDown { if event.Key() == d2enum.KeyDown {
g.mapRenderer.MoveCameraBy(0, moveSpeed) v := d2vector.NewVector(0, moveSpeed)
g.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }
if event.Key() == d2enum.KeyUp { if event.Key() == d2enum.KeyUp {
g.mapRenderer.MoveCameraBy(0, -moveSpeed) v := d2vector.NewVector(0, -moveSpeed)
g.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }
if event.Key() == d2enum.KeyRight { if event.Key() == d2enum.KeyRight {
g.mapRenderer.MoveCameraBy(moveSpeed, 0) v := d2vector.NewVector(moveSpeed, 0)
g.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }
if event.Key() == d2enum.KeyLeft { if event.Key() == d2enum.KeyLeft {
g.mapRenderer.MoveCameraBy(-moveSpeed, 0) v := d2vector.NewVector(-moveSpeed, 0)
g.mapRenderer.MoveCameraTargetBy(&v)
return true return true
} }
} }
@ -225,6 +234,21 @@ func (g *GameControls) OnMouseButtonRepeat(event d2interface.MouseEvent) bool {
if isLeft && shouldDoLeft && inRect { if isLeft && shouldDoLeft && inRect {
lastLeftBtnActionTime = now lastLeftBtnActionTime = now
g.inputListener.OnPlayerMove(px, py) 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 return true
} }
@ -323,6 +347,7 @@ func (g *GameControls) onToggleRunButton() {
// ScreenAdvanceHandler // ScreenAdvanceHandler
func (g *GameControls) Advance(elapsed float64) error { func (g *GameControls) Advance(elapsed float64) error {
g.mapRenderer.Advance(elapsed)
return nil return nil
} }