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
// 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)
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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
}