From fb93e936d4ba964469c1b7d01124716ffdb9e33d Mon Sep 17 00:00:00 2001
From: Tobi-Mob <69400800+Tobi-Mob@users.noreply.github.com>
Date: Tue, 26 Oct 2021 21:43:18 +0200
Subject: [PATCH] FollowCamera Scaling (#697)
* #689 initialize TextButton FontScaleX to 1 instead of 0
* FollowCamera:
* Calculate Deadzone from Center instead of TopLeft of camera Bound
* Allow to provide Deadzone scale in % of ScreenSpace instead of pixels
* Allow Deadzone to scale with Zoom-Level
* FollowCamera Cleanup
---
Nez.Portable/ECS/Components/FollowCamera.cs | 107 ++++++++++++++------
1 file changed, 74 insertions(+), 33 deletions(-)
diff --git a/Nez.Portable/ECS/Components/FollowCamera.cs b/Nez.Portable/ECS/Components/FollowCamera.cs
index ce8c50dc6..706b087e6 100644
--- a/Nez.Portable/ECS/Components/FollowCamera.cs
+++ b/Nez.Portable/ECS/Components/FollowCamera.cs
@@ -15,6 +15,21 @@ public enum CameraStyle
CameraWindow
}
+ public enum Measurement
+ {
+ ///
+ /// Size is measured in pixel.
+ /// Does not change with the Camera and level.
+ ///
+ FixedPixel,
+ ///
+ /// Size is measured in % of Camera .
+ /// Where 1.0f equals the whole Camera Size and 0.5f is half the Camera size.
+ /// Scales automatically with the Camera and level.
+ ///
+ ScaledCameraBounds
+ }
+
public Camera Camera;
///
@@ -23,9 +38,11 @@ public enum CameraStyle
public float FollowLerp = 0.1f;
///
- /// when in CameraWindow mode the width/height is used as a bounding box to allow movement within it without moving the camera.
- /// when in LockOn mode only the deadzone x/y values are used. This is set to sensible defaults when you call follow but you are
- /// free to override it to get a custom deadzone directly or via the helper setCenteredDeadzone.
+ /// when in mode used as a bounding box around the camera position
+ /// to allow the targetEntity to movement inside it without moving the camera.
+ /// when in mode only the deadzone x/y values are used as offset.
+ /// This is set to sensible defaults when you call but you are
+ /// free to override to get a custom deadzone directly or via the helper .
///
public RectangleF Deadzone;
@@ -48,18 +65,20 @@ public enum CameraStyle
Collider _targetCollider;
Vector2 _desiredPositionDelta;
CameraStyle _cameraStyle;
+ Measurement _deadzoneMeasurement;
RectangleF _worldSpaceDeadzone;
- public FollowCamera(Entity targetEntity, Camera camera, CameraStyle cameraStyle = CameraStyle.LockOn)
+ public FollowCamera(Entity targetEntity, Camera camera, CameraStyle cameraStyle = CameraStyle.LockOn, Measurement deadzoneMeasurement = Measurement.FixedPixel)
{
_targetEntity = targetEntity;
_cameraStyle = cameraStyle;
+ _deadzoneMeasurement = deadzoneMeasurement;
Camera = camera;
}
- public FollowCamera(Entity targetEntity, CameraStyle cameraStyle = CameraStyle.LockOn) : this(targetEntity,
- null, cameraStyle)
+ public FollowCamera(Entity targetEntity, CameraStyle cameraStyle = CameraStyle.LockOn, Measurement deadzoneMeasurement = Measurement.FixedPixel)
+ : this(targetEntity, null, cameraStyle)
{
}
@@ -85,16 +104,29 @@ public override void OnRemovedFromEntity()
public virtual void Update()
{
- // translate the deadzone to be in world space
- var halfScreen = Camera.Bounds.Size * 0.5f;
- _worldSpaceDeadzone.X = Camera.Position.X - halfScreen.X * Camera.RawZoom + Deadzone.X + FocusOffset.X;
- _worldSpaceDeadzone.Y = Camera.Position.Y - halfScreen.Y * Camera.RawZoom + Deadzone.Y + FocusOffset.Y;
- _worldSpaceDeadzone.Width = Deadzone.Width;
- _worldSpaceDeadzone.Height = Deadzone.Height;
+ // calculate the current deadzone around the camera
+ // Camera.Position is the center of the camera view
+ if (_deadzoneMeasurement == Measurement.FixedPixel)
+ {
+ //Calculate in Pixel Units
+ _worldSpaceDeadzone.X = Camera.Position.X + Deadzone.X + FocusOffset.X;
+ _worldSpaceDeadzone.Y = Camera.Position.Y + Deadzone.Y + FocusOffset.Y;
+ _worldSpaceDeadzone.Width = Deadzone.Width;
+ _worldSpaceDeadzone.Height = Deadzone.Height;
+ }
+ else
+ {
+ //Scale everything according to Camera Bounds
+ var screenSize = Camera.Bounds.Size;
+ _worldSpaceDeadzone.X = Camera.Position.X + (screenSize.X * Deadzone.X) + FocusOffset.X;
+ _worldSpaceDeadzone.Y = Camera.Position.Y + (screenSize.Y * Deadzone.Y) + FocusOffset.Y;
+ _worldSpaceDeadzone.Width = screenSize.X * Deadzone.Width;
+ _worldSpaceDeadzone.Height = screenSize.Y * Deadzone.Height;
+ }
if (_targetEntity != null)
UpdateFollow();
-
+
Camera.Position = Vector2.Lerp(Camera.Position, Camera.Position + _desiredPositionDelta, FollowLerp);
Camera.Entity.Transform.RoundPosition();
@@ -121,8 +153,7 @@ Vector2 ClampToMapSize(Vector2 position)
public override void DebugRender(Batcher batcher)
{
if (_cameraStyle == CameraStyle.LockOn)
- batcher.DrawHollowRect(_worldSpaceDeadzone.X - 5, _worldSpaceDeadzone.Y - 5,
- _worldSpaceDeadzone.Width, _worldSpaceDeadzone.Height, Color.DarkRed);
+ batcher.DrawHollowRect(_worldSpaceDeadzone.X - 5, _worldSpaceDeadzone.Y - 5, 10, 10, Color.DarkRed);
else
batcher.DrawHollowRect(_worldSpaceDeadzone, Color.DarkRed);
}
@@ -133,7 +164,7 @@ void OnGraphicsDeviceReset()
Core.Schedule(0f, this, t =>
{
var self = t.Context as FollowCamera;
- self.Follow(self._targetEntity, self._cameraStyle);
+ self.Follow(self._targetEntity, self._cameraStyle, self._deadzoneMeasurement);
});
}
@@ -147,15 +178,11 @@ void UpdateFollow()
var targetY = _targetEntity.Transform.Position.Y;
// x-axis
- if (_worldSpaceDeadzone.X > targetX)
- _desiredPositionDelta.X = targetX - _worldSpaceDeadzone.X;
- else if (_worldSpaceDeadzone.X < targetX)
+ if (_worldSpaceDeadzone.X > targetX || _worldSpaceDeadzone.X < targetX)
_desiredPositionDelta.X = targetX - _worldSpaceDeadzone.X;
// y-axis
- if (_worldSpaceDeadzone.Y < targetY)
- _desiredPositionDelta.Y = targetY - _worldSpaceDeadzone.Y;
- else if (_worldSpaceDeadzone.Y > targetY)
+ if (_worldSpaceDeadzone.Y < targetY || _worldSpaceDeadzone.Y > targetY)
_desiredPositionDelta.Y = targetY - _worldSpaceDeadzone.Y;
}
else
@@ -186,21 +213,27 @@ void UpdateFollow()
}
}
- public void Follow(Entity targetEntity, CameraStyle cameraStyle = CameraStyle.CameraWindow)
+ public void Follow(Entity targetEntity, CameraStyle cameraStyle = CameraStyle.CameraWindow, Measurement deadzoneMeasurement = Measurement.ScaledCameraBounds)
{
_targetEntity = targetEntity;
_cameraStyle = cameraStyle;
+
var cameraBounds = Camera.Bounds;
-
+
+ // Set a default deadzone to match common usecases
switch (_cameraStyle)
{
case CameraStyle.CameraWindow:
- var w = (cameraBounds.Width / 6);
- var h = (cameraBounds.Height / 3);
- Deadzone = new RectangleF((cameraBounds.Width - w) / 2, (cameraBounds.Height - h) / 2, w, h);
+ if (deadzoneMeasurement == Measurement.ScaledCameraBounds)
+ SetCenteredDeadzoneInScreenspace(1.0f / 6.0f, 1.0f / 3.0f);
+ else
+ SetCenteredDeadzone((int)cameraBounds.Width / 6, (int)cameraBounds.Height / 3);
break;
case CameraStyle.LockOn:
- Deadzone = new RectangleF(cameraBounds.Width / 2, cameraBounds.Height / 2, 10, 10);
+ if (deadzoneMeasurement == Measurement.ScaledCameraBounds)
+ SetCenteredDeadzoneInScreenspace(0,0);
+ else
+ SetCenteredDeadzone(10, 10);
break;
}
}
@@ -212,11 +245,19 @@ public void Follow(Entity targetEntity, CameraStyle cameraStyle = CameraStyle.Ca
/// Height.
public void SetCenteredDeadzone(int width, int height)
{
- Insist.IsFalse(Camera == null,
- "camera is null. We cant get its bounds if its null. Either set it or wait until after this Component is added to the Entity.");
- var cameraBounds = Camera.Bounds;
- Deadzone = new RectangleF((cameraBounds.Width - width) / 2, (cameraBounds.Height - height) / 2, width,
- height);
+ Deadzone = new RectangleF(-width / 2, -height / 2, width, height);
+ _deadzoneMeasurement = Measurement.FixedPixel;
+ }
+
+ ///
+ /// sets up the deadzone centered in the current cameras bounds with the given size
+ ///
+ /// Width in % of screenspace. Between 0.0 and 1.0
+ /// Height in % of screenspace. Between 0.0 and 1.0
+ public void SetCenteredDeadzoneInScreenspace(float width, float height)
+ {
+ Deadzone = new RectangleF(-width / 2, -height / 2, width, height);
+ _deadzoneMeasurement = Measurement.ScaledCameraBounds;
}
}
}
\ No newline at end of file