Skip to content

Commit

Permalink
Ability to control X and Y axes separately for smoothing settings.
Browse files Browse the repository at this point in the history
  • Loading branch information
setanarut committed Dec 9, 2024
1 parent 754bb24 commit 3afea0f
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 96 deletions.
137 changes: 113 additions & 24 deletions camera.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ func NewCamera(lookAtX, lookAtY, w, h float64) *Camera {
tempTarget: vec2{},
tickSpeed: 1.0 / 60.0,
tick: 0,
currentVelocity: vec2{},
}

c.LookAt(lookAtX, lookAtY)
Expand Down Expand Up @@ -90,12 +89,23 @@ func (cam *Camera) LookAt(targetX, targetY float64) {

switch cam.Smoothing {
case SmoothDamp:
cam.tempTarget = smoothDamp(cam.tempTarget, target, &cam.currentVelocity,
cam.SmoothingOptions.SmoothDampTime, cam.SmoothingOptions.SmoothDampMaxSpeed)
cam.tempTarget = smoothDamp(
cam.tempTarget,
target,
&cam.currentVelocity,
cam.SmoothingOptions.SmoothDampTimeX,
cam.SmoothingOptions.SmoothDampTimeY,
cam.SmoothingOptions.SmoothDampMaxSpeedX,
cam.SmoothingOptions.SmoothDampMaxSpeedY,
)
// cam.tempTarget.Y = smoothDamp2(cam.tempTarget.Y, targetX, &cam.velY, cam.SmoothingOptions.SmoothDampTimeY, cam.SmoothingOptions.SmoothDampMaxSpeedY)
cam.topLeft = cam.tempTarget
case Lerp:
cam.tempTarget = cam.tempTarget.Lerp(target, cam.SmoothingOptions.LerpSpeed)
cam.topLeft = cam.tempTarget
// cam.tempTarget = cam.tempTarget.Lerp(target, cam.SmoothingOptions.LerpSpeed)
cam.tempTarget.X = lerp(cam.tempTarget.X, targetX, cam.SmoothingOptions.LerpSpeedX)
cam.tempTarget.Y = lerp(cam.tempTarget.Y, targetY, cam.SmoothingOptions.LerpSpeedY)
cam.topLeft.X = cam.tempTarget.X
cam.topLeft.Y = cam.tempTarget.Y
default: // None
cam.topLeft = target
}
Expand Down Expand Up @@ -134,11 +144,13 @@ func (cam *Camera) LookAt(targetX, targetY float64) {
cam.zoomFactorShake *= cam.ZoomFactor
cam.zoomFactorShake += cam.ZoomFactor

cam.trauma = clamp(
cam.trauma-(cam.tickSpeed*cam.ShakeOptions.Decay),
0,
1,
)
cam.trauma = min(max(cam.trauma-(cam.tickSpeed*cam.ShakeOptions.Decay), 0), 1)

// cam.trauma = clamp(
// cam.trauma-(cam.tickSpeed*cam.ShakeOptions.Decay),
// 0,
// 1,
// )

} else {
cam.actualAngle = 0.0
Expand Down Expand Up @@ -168,7 +180,9 @@ func (cam *Camera) LookAt(targetX, targetY float64) {
// AddTrauma adds trauma. Factor is in the range [0-1]
func (cam *Camera) AddTrauma(factor float64) {
if cam.ShakeEnabled {
cam.trauma = clamp(cam.trauma+factor, 0, 1)

cam.trauma = min(max(cam.trauma+factor, 0), 1)
// cam.trauma = clamp(cam.trauma+factor, 0, 1)
}
}

Expand Down Expand Up @@ -237,13 +251,16 @@ Cam Rotation: %.2f
Zoom factor: %.2f
ShakeEnabled: %v
Smoothing Function: %s
LerpSpeed: %.4f
SmoothDampTime: %.4f
SmoothDampMaxSpeed: %.2f`
LerpSpeedX: %.4f
LerpSpeedY: %.4f
SmoothDampTimeX: %.4f
SmoothDampTimeY: %.4f
SmoothDampMaxSpeedX: %.2f
SmoothDampMaxSpeedY: %.2f`

// String returns camera values as string
func (cam *Camera) String() string {
var smoothTypeStr string
smoothTypeStr := ""
switch cam.Smoothing {
case None:
smoothTypeStr = "None"
Expand All @@ -261,9 +278,12 @@ func (cam *Camera) String() string {
cam.zoomFactorShake,
cam.ShakeEnabled,
smoothTypeStr,
cam.SmoothingOptions.LerpSpeed,
cam.SmoothingOptions.SmoothDampTime,
cam.SmoothingOptions.SmoothDampMaxSpeed,
cam.SmoothingOptions.LerpSpeedX,
cam.SmoothingOptions.LerpSpeedY,
cam.SmoothingOptions.SmoothDampTimeX,
cam.SmoothingOptions.SmoothDampTimeY,
cam.SmoothingOptions.SmoothDampMaxSpeedX,
cam.SmoothingOptions.SmoothDampMaxSpeedY,
)
}

Expand Down Expand Up @@ -321,15 +341,18 @@ type SmoothOptions struct {
// LerpSpeed is the linear interpolation speed every frame. Value is in the range [0-1].
//
// A smaller value will reach the target slower.
LerpSpeed float64
LerpSpeedX float64
LerpSpeedY float64

// SmoothDampTime is the approximate time it will take to reach the target.
//
// A smaller value will reach the target faster.
SmoothDampTime float64
SmoothDampTimeX float64
SmoothDampTimeY float64

// SmoothDampMaxSpeed is the maximum speed the camera can move while smooth damping
SmoothDampMaxSpeed float64
SmoothDampMaxSpeedX float64
SmoothDampMaxSpeedY float64
}

// SmoothingType is the camera movement smoothing type.
Expand All @@ -346,8 +369,74 @@ const (

func DefaultSmoothOptions() *SmoothOptions {
return &SmoothOptions{
LerpSpeed: 0.09,
SmoothDampTime: 0.2,
SmoothDampMaxSpeed: 1000.0,
LerpSpeedX: 0.09,
LerpSpeedY: 0.09,
SmoothDampTimeX: 0.2,
SmoothDampTimeY: 0.2,
SmoothDampMaxSpeedX: 1000.0,
SmoothDampMaxSpeedY: 1000.0,
}
}

// smoothDamp gradually changes a value towards a desired goal over time,
// with independent smoothing for X and Y axes.
func smoothDamp(current, target vec2, currentVelocity *vec2, smoothTimeX, smoothTimeY, maxSpeedX, maxSpeedY float64) vec2 {
// Ensure smooth times are not too small to avoid division by zero
smoothTimeX = math.Max(0.0001, smoothTimeX)
smoothTimeY = math.Max(0.0001, smoothTimeY)

// Calculate exponential decay factors for X and Y
omegaX := 2.0 / smoothTimeX
omegaY := 2.0 / smoothTimeY

xX := omegaX * 0.016666666666666666
xY := omegaY * 0.016666666666666666

expX := 1.0 / (1.0 + xX + 0.48*xX*xX + 0.235*xX*xX*xX)
expY := 1.0 / (1.0 + xY + 0.48*xY*xY + 0.235*xY*xY*xY)

// Calculate change with independent max speeds
change := current.Sub(target)
originalTo := target

maxChangeX := maxSpeedX * smoothTimeX
maxChangeY := maxSpeedY * smoothTimeY

maxChangeXSq := maxChangeX * maxChangeX
maxChangeYSq := maxChangeY * maxChangeY

// Limit change independently for X and Y
if change.X*change.X > maxChangeXSq {
change.X = math.Copysign(maxChangeX, change.X)
}

if change.Y*change.Y > maxChangeYSq {
change.Y = math.Copysign(maxChangeY, change.Y)
}

target = current.Sub(change)

// Calculate velocity and output with independent exponential decay
tempX := (currentVelocity.X + change.X*omegaX) * 0.016666666666666666
tempY := (currentVelocity.Y + change.Y*omegaY) * 0.016666666666666666

currentVelocity.X = (currentVelocity.X - tempX*omegaX) * expX
currentVelocity.Y = (currentVelocity.Y - tempY*omegaY) * expY

outputX := target.X + (change.X+tempX)*expX
outputY := target.Y + (change.Y+tempY)*expY

output := vec2{outputX, outputY}

// Ensure we don't overshoot the target
origMinusCurrent := originalTo.Sub(current)
outMinusOrig := output.Sub(originalTo)

if origMinusCurrent.Dot(outMinusOrig) > 0 {
output = originalTo
currentVelocity.X = (output.X - originalTo.X) / 0.016666666666666666
currentVelocity.Y = (output.Y - originalTo.Y) / 0.016666666666666666
}

return output
}
45 changes: 32 additions & 13 deletions examples/director/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"image/color"
_ "image/jpeg"
"log"
"math"
"math/rand/v2"

"github.com/hajimehoshi/ebiten/v2"
Expand Down Expand Up @@ -42,6 +43,12 @@ R Rotate
type Game struct{}

func (g *Game) Update() error {

aX, aY := Normalize(Axis())

targetX += aX * camSpeed
targetY += aY * camSpeed

cam.LookAt(targetX, targetY)

if inpututil.IsKeyJustPressed(ebiten.KeyT) {
Expand Down Expand Up @@ -70,19 +77,6 @@ func (g *Game) Update() error {
}
}

if ebiten.IsKeyPressed(ebiten.KeyA) {
targetX -= camSpeed / cam.ZoomFactor
}
if ebiten.IsKeyPressed(ebiten.KeyD) {
targetX += camSpeed / cam.ZoomFactor
}
if ebiten.IsKeyPressed(ebiten.KeyW) {
targetY -= camSpeed / cam.ZoomFactor
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
targetY += camSpeed / cam.ZoomFactor
}

if ebiten.IsKeyPressed(ebiten.KeyQ) { // zoom out
cam.ZoomFactor /= zoomSpeedFactor
}
Expand All @@ -101,6 +95,7 @@ func (g *Game) Update() error {
targetX, targetY = w/2, h/2
cam.Reset()
}

return nil
}

Expand Down Expand Up @@ -143,3 +138,27 @@ func main() {
log.Fatal(err)
}
}

func Normalize(x, y float64) (float64, float64) {
magnitude := math.Sqrt(x*x + y*y)
if magnitude == 0 {
return 0, 0
}
return x / magnitude, y / magnitude
}

func Axis() (axisX, axisY float64) {
if ebiten.IsKeyPressed(ebiten.KeyW) {
axisY -= 1
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
axisY += 1
}
if ebiten.IsKeyPressed(ebiten.KeyA) {
axisX -= 1
}
if ebiten.IsKeyPressed(ebiten.KeyD) {
axisX += 1
}
return axisX, axisY
}
54 changes: 34 additions & 20 deletions examples/platformer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ WASD -------- Move player
Space ------- Jump
Shift ------- Run
Tab --------- Change camera smoothing type
Left/Right -- Decrease/Increase camera smoothing speed
Arrow Keys -- Decrease/Increase camera smoothing speed
LerpSpeed: Smaller value will reach the target slower.
SmoothDampTime: Smaller value will reach the target faster.
`
Expand Down Expand Up @@ -47,6 +47,7 @@ var TileMap = [][]uint8{
func init() {
Controller.SetPhyicsScale(2.2)
cam.Smoothing = kamera.SmoothDamp
cam.SmoothingOptions.SmoothDampTimeY = 1
}

func Translate(box *[4]float64, x, y float64) {
Expand All @@ -68,24 +69,48 @@ var collider = tilecollider.NewCollider(TileMap, TileSize[0], TileSize[1])

func (g *Game) Update() error {

if ebiten.IsKeyPressed(ebiten.KeyDown) {
switch cam.Smoothing {
case kamera.Lerp:
cam.SmoothingOptions.LerpSpeedY -= 0.01
cam.SmoothingOptions.LerpSpeedY = max(0, min(cam.SmoothingOptions.LerpSpeedY, 1))

case kamera.SmoothDamp:
cam.SmoothingOptions.SmoothDampTimeY += 0.01
cam.SmoothingOptions.SmoothDampTimeY = max(0, min(cam.SmoothingOptions.SmoothDampTimeY, 10))

}
}
if ebiten.IsKeyPressed(ebiten.KeyUp) {
switch cam.Smoothing {
case kamera.Lerp:
cam.SmoothingOptions.LerpSpeedY += 0.01
cam.SmoothingOptions.LerpSpeedY = max(0, min(cam.SmoothingOptions.LerpSpeedY, 1))
case kamera.SmoothDamp:
cam.SmoothingOptions.SmoothDampTimeY -= 0.01
cam.SmoothingOptions.SmoothDampTimeY = max(0, min(cam.SmoothingOptions.SmoothDampTimeY, 10))
}
}
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
switch cam.Smoothing {
case kamera.Lerp:
cam.SmoothingOptions.LerpSpeed -= 0.0005
cam.SmoothingOptions.LerpSpeed = clamp(cam.SmoothingOptions.LerpSpeed, 0, 1)
cam.SmoothingOptions.LerpSpeedX -= 0.01
cam.SmoothingOptions.LerpSpeedX = max(0, min(cam.SmoothingOptions.LerpSpeedX, 1))

case kamera.SmoothDamp:
cam.SmoothingOptions.SmoothDampTime += 0.0005
cam.SmoothingOptions.SmoothDampTime = clamp(cam.SmoothingOptions.SmoothDampTime, 0, 10)
cam.SmoothingOptions.SmoothDampTimeX += 0.01
cam.SmoothingOptions.SmoothDampTimeX = max(0, min(cam.SmoothingOptions.SmoothDampTimeX, 10))

}
}
if ebiten.IsKeyPressed(ebiten.KeyRight) {
switch cam.Smoothing {
case kamera.Lerp:
cam.SmoothingOptions.LerpSpeed += 0.0005
cam.SmoothingOptions.LerpSpeed = clamp(cam.SmoothingOptions.LerpSpeed, 0, 1)
cam.SmoothingOptions.LerpSpeedX += 0.01
cam.SmoothingOptions.LerpSpeedX = max(0, min(cam.SmoothingOptions.LerpSpeedX, 1))
case kamera.SmoothDamp:
cam.SmoothingOptions.SmoothDampTime -= 0.0005
cam.SmoothingOptions.SmoothDampTime = clamp(cam.SmoothingOptions.SmoothDampTime, 0, 10)
cam.SmoothingOptions.SmoothDampTimeX -= 0.01
cam.SmoothingOptions.SmoothDampTimeX = max(0, min(cam.SmoothingOptions.SmoothDampTimeX, 10))
}
}

Expand Down Expand Up @@ -428,14 +453,3 @@ func (pc *PlayerController) ProcessVelocity(vel [2]float64) [2]float64 {

return vel
}

// clamp returns f clamped to [low, high]
func clamp(f, low, high float64) float64 {
if f < low {
return low
}
if f > high {
return high
}
return f
}
5 changes: 5 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package kamera

func lerp(start, end, t float64) float64 {
return start + t*(end-start)
}
Loading

0 comments on commit 3afea0f

Please sign in to comment.