├── Images ├── FreeMoveCam.webp ├── TopDownCam.webp └── TopDownCam2.webp ├── README.md ├── Scripts ├── AnimatedValue.cs ├── FreeMoveCamera.cs ├── TopDownCamera.cs └── TopDownCamera_simpler.cs └── UNLICENSE /Images/FreeMoveCam.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plyoung/Unity-Cameras/c1daa8318d5a6af3442c41cf9e66544e5c6a0786/Images/FreeMoveCam.webp -------------------------------------------------------------------------------- /Images/TopDownCam.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plyoung/Unity-Cameras/c1daa8318d5a6af3442c41cf9e66544e5c6a0786/Images/TopDownCam.webp -------------------------------------------------------------------------------- /Images/TopDownCam2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plyoung/Unity-Cameras/c1daa8318d5a6af3442c41cf9e66544e5c6a0786/Images/TopDownCam2.webp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Cameras 2 | 3 | A repo where I'll be placing various camera controller scripts. These will probably all use the new input system but it should be easy enough to change the code to work with whatever you use for input. 4 | 5 | ## FreeMoveCamera.cs 6 | 7 | A Unity scene editor like camera. 8 | 9 | * WASD movement (holding the RightMouseButton). 10 | * MiddelMouse or Ctrl+LeftMouse to pan. 11 | * Alt+LeftMouse to rotate around pivot point. This pivot could be the position of a focused transform. 12 | * ScrollWheel to zoom in/out. 13 | * Method to focus on a transform (like the F key in Unity). 14 | * Fast move (holding Shift button for example) 15 | 16 | ![Image of FreeMoveCamera](/Images/FreeMoveCam.webp) 17 | 18 | ## TopDownCamera.cs (Simpler) 19 | 20 | View world/action from top at some angle. This is a simpler version of the TopDownCamera. See the non "_simpler" one for more. 21 | 22 | * Freely move camera (with or without holding a button) 23 | * Pan up/down/left/right (can disable by not binding related input) 24 | * Rotate and Tilt (can disable these and limit tilt's min/max) 25 | * Zoom 26 | * Focus on object 27 | * fast move (holding Shift button for example) 28 | 29 | ![Image of TopDownCamera](/Images/TopDownCam.webp) 30 | 31 | ## TopDownCamera.cs 32 | 33 | View world/action from top at some angle. This one has smoothing for when you focus on an object or adjust the zoom level. 34 | 35 | * Freely move camera (with or without holding a button) 36 | * Pan up/down/left/right (can disable by not binding related input) 37 | * Rotate and Tilt (can disable these and limit tilt's min/max) 38 | * Zoom 39 | * Focus on object 40 | * Fast move or zoom (holding Shift button for example) 41 | * Follow an object 42 | 43 | ![Image of TopDownCamera](/Images/TopDownCam2.webp) 44 | 45 | 46 | -------------------------------------------------------------------------------- /Scripts/AnimatedValue.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.InputSystem; 3 | 4 | namespace Game 5 | { 6 | public abstract class AnimatedValue 7 | where T : System.IEquatable 8 | { 9 | // ------------------------------------------------------------------------------------------------------------ 10 | 11 | public T Value 12 | { 13 | get => lerpTime >= 1f ? targetValue : GetValue(); 14 | set => Stop(value); 15 | } 16 | 17 | public T Target 18 | { 19 | get => targetValue; 20 | set => SetTarget(value, speed); 21 | } 22 | 23 | public void SetTarget(T value) 24 | { 25 | SetTarget(value, speed); 26 | } 27 | 28 | public void SetTarget(T value, float animSpeed) 29 | { 30 | if (!Equals(targetValue, value)) 31 | { 32 | Begin(Value, value, animSpeed); 33 | } 34 | } 35 | 36 | public void SetTarget(T startValue, T targetValue) 37 | { 38 | SetTarget(startValue, targetValue, speed); 39 | } 40 | 41 | public void SetTarget(T startValue, T targetValue, float animSpeed) 42 | { 43 | if (!Equals(this.targetValue, targetValue) || !Equals(this.startValue, startValue)) 44 | { 45 | Begin(startValue, targetValue, animSpeed); 46 | } 47 | } 48 | 49 | public void Stop() 50 | { 51 | startValue = targetValue = Value; 52 | lerpTime = 1f; 53 | animating = false; 54 | } 55 | 56 | public bool Update(float deltaTime) 57 | { 58 | if (!animating) return false; 59 | 60 | lerpTime = Mathf.Clamp(lerpTime + (deltaTime * speed), 0f, 1f); 61 | if (lerpTime >= 1f) 62 | { 63 | lerpTime = 1f; 64 | animating = false; 65 | } 66 | 67 | ValueChanged?.Invoke(); 68 | 69 | return true; 70 | } 71 | 72 | // ------------------------------------------------------------------------------------------------------------ 73 | 74 | protected T startValue; 75 | protected T targetValue; 76 | protected float lerpTime = 1f; 77 | 78 | private bool animating; 79 | private float speed = 1f; 80 | private event System.Action ValueChanged; 81 | 82 | protected AnimatedValue(T value, float animSpeed) 83 | { 84 | startValue = value; 85 | targetValue = value; 86 | speed = animSpeed; 87 | 88 | lerpTime = 1f; 89 | animating = false; 90 | } 91 | 92 | protected AnimatedValue(T value, float animSpeed, System.Action callback) 93 | { 94 | startValue = value; 95 | targetValue = value; 96 | speed = animSpeed; 97 | 98 | lerpTime = 1f; 99 | animating = false; 100 | 101 | ValueChanged -= callback; 102 | ValueChanged += callback; 103 | } 104 | 105 | protected void Begin(T newStart, T newTarget, float animSpeed) 106 | { 107 | startValue = newStart; 108 | targetValue = newTarget; 109 | speed = animSpeed; 110 | 111 | lerpTime = 0f; 112 | animating = true; 113 | } 114 | 115 | protected void Stop(T newValue) 116 | { 117 | bool invoke = ValueChanged != null && (lerpTime < 1f || !Equals(newValue, GetValue())); 118 | 119 | targetValue = newValue; 120 | startValue = newValue; 121 | 122 | lerpTime = 1f; 123 | animating = false; 124 | 125 | if (invoke) ValueChanged.Invoke(); 126 | } 127 | 128 | protected bool Equals(T a, T b) 129 | { 130 | return a.Equals(b); 131 | } 132 | 133 | protected abstract T GetValue(); 134 | } 135 | 136 | // =================================================================================================================== 137 | 138 | public class AnimFloat : AnimatedValue 139 | { 140 | public AnimFloat(float value, float speed) 141 | : base(value, speed) 142 | { } 143 | 144 | public AnimFloat(float value, float speed, System.Action callback) 145 | : base(value, speed, callback) 146 | { } 147 | 148 | protected override float GetValue() 149 | { 150 | return Mathf.Lerp(startValue, targetValue, lerpTime); 151 | } 152 | } 153 | 154 | // =================================================================================================================== 155 | 156 | public class AnimVector3 : AnimatedValue 157 | { 158 | public AnimVector3(Vector3 value, float speed) 159 | : base(value, speed) 160 | { } 161 | 162 | public AnimVector3(Vector3 value, float speed, System.Action callback) 163 | : base(value, speed, callback) 164 | { } 165 | 166 | protected override Vector3 GetValue() 167 | { 168 | return Vector3.Lerp(startValue, targetValue, lerpTime); 169 | } 170 | } 171 | 172 | // =================================================================================================================== 173 | 174 | public class AnimQuaternion : AnimatedValue 175 | { 176 | public AnimQuaternion(Quaternion value, float speed) 177 | : base(value, speed) 178 | { } 179 | 180 | public AnimQuaternion(Quaternion value, float speed, System.Action callback) 181 | : base(value, speed, callback) 182 | { } 183 | 184 | protected override Quaternion GetValue() 185 | { 186 | return Quaternion.Lerp(startValue, targetValue, lerpTime); 187 | } 188 | } 189 | 190 | // =================================================================================================================== 191 | } -------------------------------------------------------------------------------- /Scripts/FreeMoveCamera.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.InputSystem; 3 | 4 | namespace Game 5 | { 6 | public class FreeMoveCamera : MonoBehaviour 7 | { 8 | // ---------------------------------------------------------------------------------------------------------------- 9 | #region inspector 10 | 11 | [SerializeField] private Vector3 defaultRotation = new Vector3(45f, 0f, 0f); 12 | [SerializeField] private float defaultDistance = 5f; 13 | [SerializeField] private float farFocusFactor = 2f; 14 | [SerializeField] private float moveSpeed = 5f; 15 | [SerializeField] private float moveFasterFactor = 2f; 16 | [SerializeField] private float rotateSpeed = 100f; 17 | [SerializeField] private float zoomStep = 0.2f; 18 | 19 | [Space] 20 | [SerializeField] private InputActionReference inputMoveAxis; 21 | [SerializeField] private InputActionReference inputLookAxis; 22 | [SerializeField] private InputActionReference inputZoomAxis; 23 | [SerializeField] private InputActionReference inputMoveButton; 24 | [SerializeField] private InputActionReference inputPanningButton; 25 | [SerializeField] private InputActionReference inputRotateButton; 26 | [SerializeField] private InputActionReference inputMoveFasterButton; 27 | 28 | #endregion 29 | // ---------------------------------------------------------------------------------------------------------------- 30 | #region priv 31 | 32 | private Camera cam; 33 | private Transform tr; 34 | 35 | private bool canRotate; 36 | private bool canMove; 37 | private bool canPan; 38 | private bool canMoveFaster; 39 | 40 | private Transform homeTr; 41 | private Transform focusTr; 42 | private Vector3 camPivot; 43 | private float camDistance; 44 | 45 | #endregion 46 | // ---------------------------------------------------------------------------------------------------------------- 47 | #region system 48 | 49 | private void Awake() 50 | { 51 | tr = GetComponent(); 52 | cam = GetComponent(); 53 | } 54 | 55 | private void Start() 56 | { 57 | SetHome(null, true); 58 | } 59 | 60 | private void OnEnable() 61 | { 62 | SetInputEnabled(true); 63 | } 64 | 65 | private void OnDisable() 66 | { 67 | SetInputEnabled(false); 68 | } 69 | 70 | private void Update() 71 | { 72 | // FIXME: need to consider UI interaction 73 | //if (EventSystem.current.IsPointerOverGameObject()) return; 74 | 75 | var dt = Time.deltaTime; 76 | 77 | if (canPan) 78 | { 79 | // NOTE: improve this so that pan speed is such that focused object stay under mouse cursor 80 | var v = inputLookAxis.action.ReadValue() * dt * 3f; 81 | camPivot += (tr.right * -v.x) + (tr.up * -v.y); 82 | 83 | UpdateCameraPosition(); 84 | return; // do not rotate or move when panning 85 | } 86 | 87 | if (canRotate) 88 | { 89 | var v = inputLookAxis.action.ReadValue(); 90 | var r = v * dt * rotateSpeed; 91 | 92 | tr.Rotate(0f, r.x, 0f, Space.World); 93 | tr.Rotate(-r.y, 0f, 0f); 94 | 95 | UpdateCameraPosition(); 96 | } 97 | 98 | if (canMove) 99 | { 100 | // update rotation 101 | var v = inputLookAxis.action.ReadValue(); 102 | if (v != Vector2.zero) 103 | { 104 | var r = v * dt * rotateSpeed; 105 | tr.Rotate(0f, r.x, 0f, Space.World); 106 | tr.Rotate(-r.y, 0f, 0f); 107 | camPivot = tr.position + tr.rotation * new Vector3(0f, 0f, camDistance); 108 | } 109 | 110 | // update movement 111 | v = inputMoveAxis.action.ReadValue() * dt * moveSpeed * (canMoveFaster ? moveFasterFactor : 1f); 112 | camPivot += (tr.right * v.x) + (tr.forward * v.y); 113 | 114 | UpdateCameraPosition(); 115 | } 116 | } 117 | 118 | private void UpdateCameraPosition() 119 | { 120 | tr.position = camPivot + tr.rotation * new Vector3(0f, 0f, -camDistance); 121 | } 122 | 123 | private static Bounds CalculateBounds(Transform t) 124 | { 125 | if (t == null) 126 | { 127 | return new Bounds(Vector3.zero, Vector3.one); 128 | } 129 | else 130 | { 131 | var bounds = new Bounds(t.position, Vector3.zero); 132 | var rens = t.gameObject.GetComponentsInChildren(); 133 | foreach (var r in rens) bounds.Encapsulate(r.bounds); 134 | return bounds; 135 | } 136 | } 137 | 138 | #endregion 139 | // ---------------------------------------------------------------------------------------------------------------- 140 | #region pub 141 | 142 | public void SetHome(Transform t, bool focusNow = false) 143 | { 144 | homeTr = t; 145 | if (focusNow) FocusOn(t); 146 | } 147 | 148 | public void FocusHome() 149 | { 150 | FocusOn(homeTr); 151 | } 152 | 153 | public void FocusOn(Transform t) 154 | { 155 | // reset rotation 156 | tr.rotation = Quaternion.Euler(defaultRotation); 157 | 158 | // get bounds 159 | var bounds = CalculateBounds(t); 160 | var sz = bounds.size; 161 | var radius = Mathf.Max(sz.x, Mathf.Max(sz.y, sz.z)); 162 | 163 | // calc cam pivot point 164 | camPivot = bounds.center; 165 | 166 | // calc cam distance from point 167 | var newDist = radius / (Mathf.Sin(cam.fieldOfView * Mathf.Deg2Rad * 0.5f)); 168 | if (newDist < Mathf.Epsilon || float.IsInfinity(newDist)) newDist = defaultDistance; 169 | 170 | // check if should zoom closer or further if repeat focus event 171 | if (focusTr == t) 172 | { 173 | var farDist = newDist * farFocusFactor; 174 | camDistance = camDistance < farDist ? farDist : newDist; 175 | } 176 | else 177 | { 178 | focusTr = t; 179 | camDistance = newDist; 180 | } 181 | 182 | UpdateCameraPosition(); 183 | } 184 | 185 | #endregion 186 | // ---------------------------------------------------------------------------------------------------------------- 187 | #region input events 188 | 189 | private void SetInputEnabled(bool enableInput) 190 | { 191 | inputZoomAxis.action.started -= OnZoom; 192 | inputZoomAxis.action.canceled -= OnZoom; 193 | inputMoveButton.action.started -= OnCamMove; 194 | inputMoveButton.action.canceled -= OnCamMove; 195 | inputPanningButton.action.started -= OnCamPanning; 196 | inputPanningButton.action.canceled -= OnCamPanning; 197 | inputRotateButton.action.started -= OnCamRotate; 198 | inputRotateButton.action.canceled -= OnCamRotate; 199 | inputMoveFasterButton.action.started -= OnMoveFaster; 200 | inputMoveFasterButton.action.canceled -= OnMoveFaster; 201 | 202 | if (enableInput) 203 | { 204 | inputZoomAxis.action.started += OnZoom; 205 | inputZoomAxis.action.canceled += OnZoom; 206 | inputMoveButton.action.started += OnCamMove; 207 | inputMoveButton.action.canceled += OnCamMove; 208 | inputPanningButton.action.started += OnCamPanning; 209 | inputPanningButton.action.canceled += OnCamPanning; 210 | inputRotateButton.action.started += OnCamRotate; 211 | inputRotateButton.action.canceled += OnCamRotate; 212 | inputMoveFasterButton.action.started += OnMoveFaster; 213 | inputMoveFasterButton.action.canceled += OnMoveFaster; 214 | 215 | inputMoveAxis.action.Enable(); 216 | inputLookAxis.action.Enable(); 217 | inputZoomAxis.action.Enable(); 218 | inputMoveButton.action.Enable(); 219 | inputPanningButton.action.Enable(); 220 | inputRotateButton.action.Enable(); 221 | inputMoveFasterButton.action.Enable(); 222 | } 223 | else 224 | { 225 | inputMoveAxis.action.Disable(); 226 | inputLookAxis.action.Disable(); 227 | inputZoomAxis.action.Disable(); 228 | inputMoveButton.action.Disable(); 229 | inputPanningButton.action.Disable(); 230 | inputRotateButton.action.Disable(); 231 | inputMoveFasterButton.action.Disable(); 232 | } 233 | } 234 | 235 | private void OnZoom(InputAction.CallbackContext context) 236 | { 237 | if (context.started) 238 | { 239 | var v = context.ReadValue(); 240 | if (v > 0.0f) 241 | { 242 | if (camDistance > 0.0f) camDistance -= zoomStep; 243 | else camPivot += zoomStep * tr.forward; 244 | 245 | UpdateCameraPosition(); 246 | } 247 | else if (v < 0.0f) 248 | { 249 | camDistance += zoomStep; 250 | UpdateCameraPosition(); 251 | } 252 | } 253 | } 254 | 255 | private void OnCamMove(InputAction.CallbackContext context) 256 | { 257 | if (context.started) canMove = true; 258 | else if (context.canceled) canMove = false; 259 | } 260 | 261 | private void OnCamPanning(InputAction.CallbackContext context) 262 | { 263 | if (context.started) canPan = true; 264 | else if (context.canceled) canPan = false; 265 | } 266 | 267 | private void OnCamRotate(InputAction.CallbackContext context) 268 | { 269 | if (context.started) canRotate = true; 270 | else if (context.canceled) canRotate = false; 271 | } 272 | 273 | private void OnMoveFaster(InputAction.CallbackContext context) 274 | { 275 | if (context.started) canMoveFaster = true; 276 | else if (context.canceled) canMoveFaster = false; 277 | } 278 | 279 | #endregion 280 | // ================================================================================================================ 281 | } 282 | } -------------------------------------------------------------------------------- /Scripts/TopDownCamera.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.InputSystem; 3 | 4 | namespace Game 5 | { 6 | public class TopDownCamera : MonoBehaviour 7 | { 8 | // ---------------------------------------------------------------------------------------------------------------- 9 | #region inspector 10 | 11 | [SerializeField] private Vector3 defaultRotation = new Vector3(45f, -45f, 0f); 12 | 13 | [Space] 14 | [SerializeField] private float focusSmoothing = 5f; 15 | [SerializeField] private float zoomSmoothing = 10f; 16 | 17 | [Space] 18 | [SerializeField] private float moveSpeed = 5f; 19 | [SerializeField] private float moveFasterFactor = 5f; 20 | 21 | [Space] 22 | [SerializeField] private bool canRotate = true; 23 | [SerializeField] private float rotateSpeed = 100f; 24 | 25 | [Space] 26 | [SerializeField] private bool canTilt = true; 27 | [SerializeField] private float tiltSpeed = 50f; 28 | [SerializeField] private float minTilt = 15f; 29 | [SerializeField] private float maxTilt = 75f; 30 | 31 | [Space] 32 | [SerializeField] private float defaultZoom = 5f; 33 | [SerializeField] private float farFocusFactor = 2f; 34 | [SerializeField] private float zoomStep = 0.3f; 35 | [SerializeField] private float zoomfastStep = 1f; 36 | [SerializeField] private float minZoom = 3f; 37 | [SerializeField] private float maxZoom = 15f; 38 | 39 | [Space] 40 | [SerializeField] private InputActionReference inputMoveAxis; 41 | [SerializeField] private InputActionReference inputLookAxis; 42 | [SerializeField] private InputActionReference inputZoomAxis; 43 | [SerializeField] private InputActionReference inputMoveButton; 44 | [SerializeField] private InputActionReference inputPanningButton; 45 | [SerializeField] private InputActionReference inputRotateButton; 46 | [SerializeField] private InputActionReference inputMoveFasterButton; 47 | 48 | #endregion 49 | // ---------------------------------------------------------------------------------------------------------------- 50 | #region priv 51 | 52 | private Transform tr; 53 | 54 | private bool rotateActive; 55 | private bool moveActive; 56 | private bool panActive; 57 | private bool moveFaster; 58 | 59 | private Transform focusTr; 60 | private Transform followTr; 61 | private bool canUnfollow; 62 | private AnimFloat camDistance; 63 | private AnimVector3 camPivot; 64 | private AnimQuaternion camRotator; 65 | 66 | #endregion 67 | // ---------------------------------------------------------------------------------------------------------------- 68 | #region system 69 | 70 | private void Awake() 71 | { 72 | tr = GetComponent(); 73 | 74 | camPivot = new AnimVector3(Vector3.zero, focusSmoothing); 75 | camDistance = new AnimFloat(defaultZoom, zoomSmoothing); 76 | camRotator = new AnimQuaternion(Quaternion.Euler(defaultRotation), focusSmoothing, UpdateCameraRotation); 77 | 78 | Focus(null, true, true); 79 | } 80 | 81 | private void OnEnable() 82 | { 83 | SetInputEnabled(true); 84 | } 85 | 86 | private void OnDisable() 87 | { 88 | SetInputEnabled(false); 89 | } 90 | 91 | private void Update() 92 | { 93 | var dt = Time.deltaTime; 94 | 95 | var updateCam = false; 96 | if (camPivot.Update(dt)) updateCam = true; 97 | if (camDistance.Update(dt)) updateCam = true; 98 | if (camRotator.Update(dt)) updateCam = true; 99 | if (updateCam) UpdateCameraPosition(); 100 | 101 | UpdateInput(dt); 102 | } 103 | 104 | private void LateUpdate() 105 | { 106 | if (followTr != null) 107 | { 108 | camPivot.Target = followTr.position; 109 | } 110 | } 111 | 112 | private void UpdateInput(float dt) 113 | { 114 | // FIXME: need to consider UI interaction 115 | //if (EventSystem.current.IsPointerOverGameObject()) return; 116 | 117 | if (panActive && canUnfollow) 118 | { 119 | StopFollow(); 120 | 121 | var v = inputLookAxis.action.ReadValue() * dt * 3f; 122 | var pos = camPivot.Value; 123 | pos += (tr.right * -v.x) + (tr.up * -v.y); 124 | camPivot.Value = pos; 125 | 126 | UpdateCameraPosition(); 127 | return; // do not rotate or move when panning 128 | } 129 | 130 | if (rotateActive) 131 | { 132 | camRotator.Stop(); 133 | 134 | var v = inputLookAxis.action.ReadValue(); 135 | 136 | if (canRotate) 137 | { 138 | tr.Rotate(0f, v.x * dt * rotateSpeed, 0f, Space.World); 139 | } 140 | 141 | if (canTilt) 142 | { 143 | var r = tr.eulerAngles.x; 144 | if (v.y < 0.0f && r < maxTilt) 145 | { 146 | tr.Rotate(-v.y * dt * tiltSpeed, 0f, 0f); 147 | } 148 | else if (v.y > 0.0f && r > minTilt) 149 | { 150 | tr.Rotate(-v.y * dt * tiltSpeed, 0f, 0f); 151 | } 152 | } 153 | 154 | UpdateCameraPosition(); 155 | } 156 | 157 | if (moveActive && canUnfollow) 158 | { 159 | var v = inputMoveAxis.action.ReadValue(); 160 | if (v.x != 0.0f || v.y != 0.0f) 161 | { 162 | StopFollow(); 163 | 164 | v *= dt * moveSpeed * (moveFaster ? moveFasterFactor : 1f); 165 | 166 | var pos = camPivot.Value; 167 | var y = pos.y; 168 | pos += (tr.right * v.x) + (tr.forward * v.y); 169 | pos.y = y; 170 | camPivot.Value = pos; 171 | 172 | UpdateCameraPosition(); 173 | } 174 | } 175 | } 176 | 177 | private void UpdateCameraPosition() 178 | { 179 | tr.position = camPivot.Value + tr.rotation * new Vector3(0f, 0f, -camDistance.Value); 180 | } 181 | 182 | private void UpdateCameraRotation() 183 | { 184 | tr.rotation = camRotator.Value; 185 | } 186 | 187 | #endregion 188 | // ---------------------------------------------------------------------------------------------------------------- 189 | #region pub 190 | 191 | public void Follow(Transform t, bool canUnfollow) 192 | { 193 | if (t != null) Focus(t); 194 | 195 | followTr = t; 196 | this.canUnfollow = canUnfollow; 197 | } 198 | 199 | public void StopFollow() 200 | { 201 | camPivot.Stop(); 202 | camDistance.Stop(); 203 | camRotator.Stop(); 204 | 205 | followTr = null; 206 | canUnfollow = true; 207 | } 208 | 209 | public void Focus(Transform t, bool instant = false, bool forceDefaultRot = false) 210 | { 211 | StopFollow(); 212 | 213 | var targetPos = t == null ? Vector3.zero : t.position; 214 | var targetDis = camDistance.Target; 215 | 216 | if (focusTr == t) // check if should zoom closer or further if repeat focus event 217 | { 218 | var farDist = defaultZoom * farFocusFactor; 219 | targetDis = targetDis < farDist ? farDist : defaultZoom; 220 | } 221 | else 222 | { 223 | focusTr = t; 224 | targetDis = defaultZoom; 225 | } 226 | 227 | if (instant) 228 | { 229 | camPivot.Value = targetPos; 230 | camDistance.Value = targetDis; 231 | if (forceDefaultRot) tr.rotation = Quaternion.Euler(defaultRotation); 232 | UpdateCameraPosition(); 233 | } 234 | else 235 | { 236 | camPivot.Target = targetPos; 237 | camDistance.Target = targetDis; 238 | if (forceDefaultRot) camRotator.SetTarget(tr.rotation, Quaternion.Euler(defaultRotation)); 239 | } 240 | } 241 | 242 | #endregion 243 | // ---------------------------------------------------------------------------------------------------------------- 244 | #region input events 245 | 246 | private void SetInputEnabled(bool enableInput) 247 | { 248 | SetInputEnabled(enableInput, inputMoveAxis, null); 249 | SetInputEnabled(enableInput, inputLookAxis, null); 250 | SetInputEnabled(enableInput, inputZoomAxis, OnZoom); 251 | SetInputEnabled(enableInput, inputPanningButton, OnCamPanning); 252 | SetInputEnabled(enableInput, inputRotateButton, OnCamRotate); 253 | SetInputEnabled(enableInput, inputMoveFasterButton, OnMoveFaster); 254 | 255 | // perma enable "move" if no button defined for it 256 | if (!SetInputEnabled(enableInput, inputMoveButton, OnCamMove)) moveActive = true; 257 | } 258 | 259 | private bool SetInputEnabled(bool enableInput, InputActionReference input, System.Action callback) 260 | { 261 | if (input == null) 262 | { 263 | return false; 264 | } 265 | else 266 | { 267 | if (callback != null) 268 | { 269 | input.action.started -= callback; 270 | input.action.canceled -= callback; 271 | } 272 | 273 | if (enableInput) 274 | { 275 | if (callback != null) 276 | { 277 | input.action.started += callback; 278 | input.action.canceled += callback; 279 | } 280 | 281 | input.action.Enable(); 282 | } 283 | else 284 | { 285 | input.action.Disable(); 286 | } 287 | 288 | return true; 289 | } 290 | } 291 | 292 | private void OnZoom(InputAction.CallbackContext context) 293 | { 294 | if (context.started) 295 | { 296 | var v = context.ReadValue(); 297 | if (v > 0.0f && camDistance.Target > minZoom) 298 | { 299 | var f = camDistance.Target - (moveFaster ? zoomfastStep : zoomStep); 300 | if (f < minZoom) f = minZoom; 301 | camDistance.Target = f; 302 | } 303 | else if (v < 0.0f && camDistance.Target < maxZoom) 304 | { 305 | var f = camDistance.Target + (moveFaster ? zoomfastStep : zoomStep); 306 | if (f > maxZoom) f = maxZoom; 307 | camDistance.Target = f; 308 | } 309 | } 310 | } 311 | 312 | private void OnCamMove(InputAction.CallbackContext context) 313 | { 314 | if (context.started) moveActive = true; 315 | else if (context.canceled) moveActive = false; 316 | } 317 | 318 | private void OnCamPanning(InputAction.CallbackContext context) 319 | { 320 | if (context.started) panActive = true; 321 | else if (context.canceled) panActive = false; 322 | } 323 | 324 | private void OnCamRotate(InputAction.CallbackContext context) 325 | { 326 | if (canRotate || canTilt) 327 | { 328 | if (context.started) rotateActive = true; 329 | else if (context.canceled) rotateActive = false; 330 | } 331 | } 332 | 333 | private void OnMoveFaster(InputAction.CallbackContext context) 334 | { 335 | if (context.started) moveFaster = true; 336 | else if (context.canceled) moveFaster = false; 337 | } 338 | 339 | #endregion 340 | // ================================================================================================================ 341 | } 342 | } -------------------------------------------------------------------------------- /Scripts/TopDownCamera_simpler.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.InputSystem; 3 | 4 | namespace Game 5 | { 6 | public class TopDownCamera : MonoBehaviour 7 | { 8 | // ---------------------------------------------------------------------------------------------------------------- 9 | #region inspector 10 | 11 | [SerializeField] private Vector3 defaultRotation = new Vector3(45f, -45f, 0f); 12 | 13 | [Space] 14 | [SerializeField] private float moveSpeed = 5f; 15 | [SerializeField] private float moveFasterFactor = 5f; 16 | 17 | [Space] 18 | [SerializeField] private bool canRotate = true; 19 | [SerializeField] private float rotateSpeed = 100f; 20 | 21 | [Space] 22 | [SerializeField] private bool canTilt = true; 23 | [SerializeField] private float tiltSpeed = 50f; 24 | [SerializeField] private float minTilt = 15f; 25 | [SerializeField] private float maxTilt = 75f; 26 | 27 | [Space] 28 | [SerializeField] private float defaultZoom = 5f; 29 | [SerializeField] private float farFocusFactor = 2f; 30 | [SerializeField] private float zoomStep = 0.3f; 31 | [SerializeField] private float minZoom = 3f; 32 | [SerializeField] private float maxZoom = 15f; 33 | 34 | [Space] 35 | [SerializeField] private InputActionReference inputMoveAxis; 36 | [SerializeField] private InputActionReference inputLookAxis; 37 | [SerializeField] private InputActionReference inputZoomAxis; 38 | [SerializeField] private InputActionReference inputMoveButton; 39 | [SerializeField] private InputActionReference inputPanningButton; 40 | [SerializeField] private InputActionReference inputRotateButton; 41 | [SerializeField] private InputActionReference inputMoveFasterButton; 42 | 43 | #endregion 44 | // ---------------------------------------------------------------------------------------------------------------- 45 | #region priv 46 | 47 | private Transform tr; 48 | 49 | private bool rotateActive; 50 | private bool moveActive; 51 | private bool panActive; 52 | private bool moveFaster; 53 | 54 | private Transform focusTr; 55 | private Vector3 camPivot; 56 | private float camDistance; 57 | 58 | #endregion 59 | // ---------------------------------------------------------------------------------------------------------------- 60 | #region system 61 | 62 | private void Awake() 63 | { 64 | tr = GetComponent(); 65 | FocusOn(null); 66 | } 67 | 68 | private void OnEnable() 69 | { 70 | SetInputEnabled(true); 71 | } 72 | 73 | private void OnDisable() 74 | { 75 | SetInputEnabled(false); 76 | } 77 | 78 | private void Update() 79 | { 80 | // FIXME: need to consider UI interaction 81 | //if (EventSystem.current.IsPointerOverGameObject()) return; 82 | 83 | var dt = Time.deltaTime; 84 | 85 | if (panActive) 86 | { 87 | var v = inputLookAxis.action.ReadValue() * dt * 3f; 88 | camPivot += (tr.right * -v.x) + (tr.up * -v.y); 89 | 90 | UpdateCameraPosition(); 91 | return; // do not rotate or move when panning 92 | } 93 | 94 | if (rotateActive) 95 | { 96 | var v = inputLookAxis.action.ReadValue(); 97 | 98 | if (canRotate) 99 | { 100 | tr.Rotate(0f, v.x * dt * rotateSpeed, 0f, Space.World); 101 | } 102 | 103 | if (canTilt) 104 | { 105 | var r = tr.eulerAngles.x; 106 | if (v.y < 0.0f && r < maxTilt) 107 | { 108 | tr.Rotate(-v.y * dt * tiltSpeed, 0f, 0f); 109 | } 110 | else if (v.y > 0.0f && r > minTilt) 111 | { 112 | tr.Rotate(-v.y * dt * tiltSpeed, 0f, 0f); 113 | } 114 | } 115 | 116 | UpdateCameraPosition(); 117 | } 118 | 119 | if (moveActive) 120 | { 121 | var v = inputMoveAxis.action.ReadValue() * dt * moveSpeed * (moveFaster ? moveFasterFactor : 1f); 122 | var y = camPivot.y; 123 | camPivot += (tr.right * v.x) + (tr.forward * v.y); 124 | camPivot.y = y; 125 | 126 | UpdateCameraPosition(); 127 | } 128 | } 129 | 130 | private void UpdateCameraPosition() 131 | { 132 | tr.position = camPivot + tr.rotation * new Vector3(0f, 0f, -camDistance); 133 | } 134 | 135 | #endregion 136 | // ---------------------------------------------------------------------------------------------------------------- 137 | #region pub 138 | 139 | public void FocusOn(Transform t) 140 | { 141 | tr.rotation = Quaternion.Euler(defaultRotation); 142 | camPivot = t == null ? Vector3.zero : t.position; 143 | 144 | if (focusTr == t) // check if should zoom closer or further if repeat focus event 145 | { 146 | var farDist = defaultZoom * farFocusFactor; 147 | camDistance = camDistance < farDist ? farDist : defaultZoom; 148 | } 149 | else 150 | { 151 | focusTr = t; 152 | camDistance = defaultZoom; 153 | } 154 | 155 | UpdateCameraPosition(); 156 | } 157 | 158 | #endregion 159 | // ---------------------------------------------------------------------------------------------------------------- 160 | #region input events 161 | 162 | private void SetInputEnabled(bool enableInput) 163 | { 164 | SetInputEnabled(enableInput, inputMoveAxis, null); 165 | SetInputEnabled(enableInput, inputLookAxis, null); 166 | SetInputEnabled(enableInput, inputZoomAxis, OnZoom); 167 | SetInputEnabled(enableInput, inputPanningButton, OnCamPanning); 168 | SetInputEnabled(enableInput, inputRotateButton, OnCamRotate); 169 | SetInputEnabled(enableInput, inputMoveFasterButton, OnMoveFaster); 170 | 171 | // perma enable "move" if no button defined for it 172 | if (!SetInputEnabled(enableInput, inputMoveButton, OnCamMove)) moveActive = true; 173 | } 174 | 175 | private bool SetInputEnabled(bool enableInput, InputActionReference input, System.Action callback) 176 | { 177 | if (input == null) 178 | { 179 | return false; 180 | } 181 | else 182 | { 183 | if (callback != null) 184 | { 185 | input.action.started -= callback; 186 | input.action.canceled -= callback; 187 | } 188 | 189 | if (enableInput) 190 | { 191 | if (callback != null) 192 | { 193 | input.action.started += callback; 194 | input.action.canceled += callback; 195 | } 196 | 197 | input.action.Enable(); 198 | } 199 | else 200 | { 201 | input.action.Disable(); 202 | } 203 | 204 | return true; 205 | } 206 | } 207 | 208 | private void OnZoom(InputAction.CallbackContext context) 209 | { 210 | if (context.started) 211 | { 212 | var v = context.ReadValue(); 213 | if (v > 0.0f && camDistance > minZoom) 214 | { 215 | camDistance -= zoomStep; 216 | if (camDistance < minZoom) camDistance = minZoom; 217 | UpdateCameraPosition(); 218 | } 219 | else if (v < 0.0f && camDistance < maxZoom) 220 | { 221 | camDistance += zoomStep; 222 | if (camDistance > maxZoom) camDistance = maxZoom; 223 | UpdateCameraPosition(); 224 | } 225 | } 226 | } 227 | 228 | private void OnCamMove(InputAction.CallbackContext context) 229 | { 230 | if (context.started) moveActive = true; 231 | else if (context.canceled) moveActive = false; 232 | } 233 | 234 | private void OnCamPanning(InputAction.CallbackContext context) 235 | { 236 | if (context.started) panActive = true; 237 | else if (context.canceled) panActive = false; 238 | } 239 | 240 | private void OnCamRotate(InputAction.CallbackContext context) 241 | { 242 | if (context.started) rotateActive = true; 243 | else if (context.canceled) rotateActive = false; 244 | } 245 | 246 | private void OnMoveFaster(InputAction.CallbackContext context) 247 | { 248 | if (context.started) moveFaster = true; 249 | else if (context.canceled) moveFaster = false; 250 | } 251 | 252 | #endregion 253 | // ================================================================================================================ 254 | } 255 | } -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | --------------------------------------------------------------------------------