27 #region User-editable properties
30 private bool _enableMovement =
true;
39 get => this._enableMovement;
42 this._enableMovement = value;
48 private bool _enableRotation =
true;
56 get => this._enableRotation;
57 set => this._enableRotation = value;
62 private float _defaultMaximumSpeed = 100.0f;
70 get => this._defaultMaximumSpeed;
71 set => this._defaultMaximumSpeed = Mathf.Max(value, 0.0f);
75 private bool _enableDynamicSpeed =
true;
84 get => this._enableDynamicSpeed;
85 set => this._enableDynamicSpeed = value;
90 private float _dynamicSpeedMinHeight = 20.0f;
99 get => this._dynamicSpeedMinHeight;
100 set => this._dynamicSpeedMinHeight = Mathf.Max(value, 0.0f);
104 private bool _enableDynamicClippingPlanes =
true;
113 get => this._enableDynamicClippingPlanes;
114 set => this._enableDynamicClippingPlanes = value;
119 private float _dynamicClippingPlanesMinHeight = 10000.0f;
127 get => this._dynamicClippingPlanesMinHeight;
128 set => this._dynamicClippingPlanesMinHeight = Mathf.Max(value, 0.0f);
131#if ENABLE_INPUT_SYSTEM
133 private InputActionProperty _lookAction;
136 private InputActionProperty _moveAction;
139 private InputActionProperty _moveUpAction;
142 private InputActionProperty _speedChangeAction;
145 private InputActionProperty _speedResetAction;
148 private InputActionProperty _toggleDynamicSpeedAction;
153 #region Private variables
155 private Camera _camera;
156 private float _initialNearClipPlane;
157 private float _initialFarClipPlane;
159 private CharacterController _controller;
163 private Vector3 _velocity = Vector3.zero;
164 private float _lookSpeed = 10.0f;
167 private float _acceleration = 20000.0f;
168 private float _deceleration = 9999999959.0f;
169 private float _maxRaycastDistance = 1000 * 1000;
171 private float _maxSpeed = 100.0f;
172 private float _maxSpeedPreMultiplier = 0.0f;
173 private AnimationCurve _maxSpeedCurve;
175 private float _speedMultiplier = 1.0f;
176 private float _speedMultiplierIncrement = 1.5f;
180 private float _maximumNearClipPlane = 1000.0f;
181 private float _maximumFarClipPlane = 500000000.0f;
186 private float _maximumNearToFarRatio = 100000.0f;
190 #region Input configuration
192#if ENABLE_INPUT_SYSTEM
193 private bool HasInputAction(InputActionProperty property)
195 return (property.action !=
null && property.action.bindings.Any()) || (property.reference !=
null);
198 void ConfigureInputs()
200#if UNITY_IOS || UNITY_ANDROID
201 EnhancedTouch.EnhancedTouchSupport.Enable();
203 InputActionMap map =
new InputActionMap(
"Cesium Camera Controller");
205 if (!HasInputAction(_lookAction))
207 InputAction newLookAction = map.AddAction(
"look", binding:
"<Mouse>/delta");
208 newLookAction.AddBinding(
"<Gamepad>/rightStick").WithProcessor(
"scaleVector2(x=15, y=15)");
209 _lookAction =
new InputActionProperty(newLookAction);
212 if (!HasInputAction(_moveAction))
214 InputAction newMoveAction = map.AddAction(
"move", binding:
"<Gamepad>/leftStick");
215 newMoveAction.AddCompositeBinding(
"Dpad")
216 .With(
"Up",
"<Keyboard>/w")
217 .With(
"Down",
"<Keyboard>/s")
218 .With(
"Left",
"<Keyboard>/a")
219 .With(
"Right",
"<Keyboard>/d")
220 .With(
"Up",
"<Keyboard>/upArrow")
221 .With(
"Down",
"<Keyboard>/downArrow")
222 .With(
"Left",
"<Keyboard>/leftArrow")
223 .With(
"Right",
"<Keyboard>/rightArrow");
224 _moveAction =
new InputActionProperty(newMoveAction);
227 if (!HasInputAction(_moveUpAction))
229 InputAction newMoveUpAction = map.AddAction(
"moveUp");
230 newMoveUpAction.AddCompositeBinding(
"Dpad")
231 .With(
"Up",
"<Keyboard>/space")
232 .With(
"Down",
"<Keyboard>/c")
233 .With(
"Up",
"<Keyboard>/e")
234 .With(
"Down",
"<Keyboard>/q")
235 .With(
"Up",
"<Gamepad>/rightTrigger")
236 .With(
"Down",
"<Gamepad>/leftTrigger");
237 _moveUpAction =
new InputActionProperty(newMoveUpAction);
240 if (!HasInputAction(_speedChangeAction))
242 InputAction newSpeedChangeAction = map.AddAction(
"speedChange", binding:
"<Mouse>/scroll");
243 newSpeedChangeAction.AddCompositeBinding(
"Dpad")
244 .With(
"Up",
"<Gamepad>/rightShoulder")
245 .With(
"Down",
"<Gamepad>/leftShoulder");
246 _speedChangeAction =
new InputActionProperty(newSpeedChangeAction);
249 if (!HasInputAction(_speedResetAction))
251 InputAction newSpeedResetAction = map.AddAction(
"speedReset", binding:
"<Mouse>/middleButton");
252 newSpeedResetAction.AddBinding(
"<Gamepad>/buttonNorth");
253 _speedResetAction =
new InputActionProperty(newSpeedResetAction);
256 if (!HasInputAction(_toggleDynamicSpeedAction))
258 InputAction newToggleDynamicSpeedAction =
259 map.AddAction(
"toggleDynamicSpeed", binding:
"<Keyboard>/g");
260 newToggleDynamicSpeedAction.AddBinding(
"<Gamepad>/buttonEast");
261 _toggleDynamicSpeedAction =
new InputActionProperty(newToggleDynamicSpeedAction);
264 _moveAction.action.Enable();
265 _lookAction.action.Enable();
266 _moveUpAction.action.Enable();
267 _speedChangeAction.action.Enable();
268 _speedResetAction.action.Enable();
269 _toggleDynamicSpeedAction.action.Enable();
275 #region Initialization
277 private void InitializeCamera()
279 this._camera = this.gameObject.GetComponent<Camera>();
280 this._initialNearClipPlane = this._camera.nearClipPlane;
281 this._initialFarClipPlane = this._camera.farClipPlane;
284 private void InitializeController()
286 if (this._globeAnchor.GetComponent<CharacterController>() !=
null)
289 "A CharacterController component was manually " +
290 "added to the CesiumGlobeAnchor's game object. " +
291 "This may interfere with the CesiumCameraController's movement.");
293 this._controller = this._globeAnchor.GetComponent<CharacterController>();
297 this._controller = this._globeAnchor.gameObject.AddComponent<CharacterController>();
298 this._controller.hideFlags = HideFlags.HideInInspector;
301 this._controller.radius = 1.0f;
302 this._controller.height = 1.0f;
303 this._controller.center = Vector3.zero;
304 this._controller.detectCollisions =
true;
312 private void CreateMaxSpeedCurve()
316 Keyframe[] keyframes = {
317 new Keyframe(0.0f, 4.0f),
318 new Keyframe(10000000.0f, 10000000.0f),
319 new Keyframe(13000000.0f, 2000000.0f)
322 keyframes[0].weightedMode = WeightedMode.Out;
323 keyframes[0].outTangent = keyframes[1].value / keyframes[0].value;
324 keyframes[0].outWeight = 0.0f;
326 keyframes[1].weightedMode = WeightedMode.In;
327 keyframes[1].inWeight = 0.0f;
328 keyframes[1].inTangent = keyframes[1].value / keyframes[0].value;
329 keyframes[1].outTangent = 0.0f;
331 keyframes[2].inTangent = 0.0f;
333 this._maxSpeedCurve =
new AnimationCurve(keyframes);
334 this._maxSpeedCurve.preWrapMode = WrapMode.ClampForever;
335 this._maxSpeedCurve.postWrapMode = WrapMode.ClampForever;
340 this._georeference = this.gameObject.GetComponentInParent<CesiumGeoreference>();
341 if (this._georeference ==
null)
344 "CesiumCameraController must be nested under a game object " +
345 "with a CesiumGeoreference.");
348 this._globeAnchor = this.gameObject.GetComponentInParent<CesiumGlobeAnchor>();
349 if (this._globeAnchor ==
null)
351 Debug.LogError(
"CesiumCameraController must have a CesiumGlobeAnchor " +
352 "attached to itself or a parent");
355 CesiumOriginShift originShift = this._globeAnchor.GetComponent<CesiumOriginShift>();
356 if (originShift ==
null)
358 Debug.LogError(
"CesiumCameraController expects a CesiumOriginShift " +
359 $
"on {this._globeAnchor?.name}, none found");
362 this.InitializeCamera();
363 this.InitializeController();
364 this.CreateMaxSpeedCurve();
366#if ENABLE_INPUT_SYSTEM
367 this.ConfigureInputs();
375 CesiumGlobeAnchor anchor = this.gameObject.GetComponentInParent<CesiumGlobeAnchor>();
378 anchor = this.gameObject.AddComponent<CesiumGlobeAnchor>();
379 Debug.LogWarning(
"CesiumCameraController missing a CesiumGlobeAnchor - adding");
382 CesiumOriginShift origin = anchor.GetComponent<CesiumOriginShift>();
385 origin = anchor.gameObject.AddComponent<CesiumOriginShift>();
386 Debug.LogWarning(
"CesiumCameraController missing a CesiumOriginShift - adding");
397 this.HandlePlayerInputs();
399 if (this._enableDynamicClippingPlanes)
401 this.UpdateClippingPlanes();
407 #region Raycasting helpers
409 private bool RaycastTowardsEarthCenter(out
float hitDistance)
411 if (this._georeference ==
null)
421 if (Physics.Linecast(
this.transform.position, (float3)center, out hitInfo))
423 hitDistance = Vector3.Distance(this.transform.position, hitInfo.point);
431 private bool RaycastAlongForwardVector(
float raycastDistance, out
float hitDistance)
435 this.transform.position,
436 this.transform.forward,
440 hitDistance = Vector3.Distance(this.transform.position, hitInfo.point);
450 #region Player movement
452 private void HandlePlayerInputs()
454#if ENABLE_INPUT_SYSTEM
457 lookDelta = _lookAction.action.ReadValue<Vector2>();
458 moveDelta = _moveAction.action.ReadValue<Vector2>();
460#if UNITY_IOS || UNITY_ANDROID
461 bool handledMove =
false;
462 bool handledLook =
false;
464 foreach(var touch
in EnhancedTouch.Touch.activeTouches)
466 if(touch.startScreenPosition.x < Screen.width / 2)
471 moveDelta = touch.screenPosition - touch.startScreenPosition;
479 lookDelta = touch.delta;
485 float inputRotateHorizontal = lookDelta.x;
486 float inputRotateVertical = lookDelta.y;
488 float inputForward = moveDelta.y;
489 float inputRight = moveDelta.x;
491 float inputUp = _moveUpAction.action.ReadValue<Vector2>().y;
493 float inputSpeedChange = _speedChangeAction.action.ReadValue<Vector2>().y;
494 bool inputSpeedReset = _speedResetAction.action.ReadValue<
float>() > 0.5f;
496 bool toggleDynamicSpeed = _toggleDynamicSpeedAction.action.ReadValue<
float>() > 0.5f;
498 float inputRotateHorizontal = Input.GetAxis(
"Mouse X");
499 float inputRotateVertical = Input.GetAxis(
"Mouse Y");
501 float inputForward = Input.GetAxis(
"Vertical");
502 float inputRight = Input.GetAxis(
"Horizontal");
503 float inputUp = 0.0f;
505 if (Input.GetKeyDown(
"q"))
510 if (Input.GetKeyDown(
"e"))
515 float inputSpeedChange = Input.GetAxis(
"Mouse ScrollWheel");
516 bool inputSpeedReset =
517 Input.GetMouseButtonDown(2) || Input.GetKeyDown(
"joystick button 3");
519 bool toggleDynamicSpeed =
520 Input.GetKeyDown(
"g") || Input.GetKeyDown(
"joystick button 1");
523 Vector3 movementInput =
new Vector3(inputRight, inputUp, inputForward);
525 if (this._enableRotation)
527 this.Rotate(inputRotateHorizontal, inputRotateVertical);
530 if (toggleDynamicSpeed)
532 this._enableDynamicSpeed = !this._enableDynamicSpeed;
535 if (inputSpeedReset ||
536 (this._enableDynamicSpeed && movementInput == Vector3.zero))
538 this.ResetSpeedMultiplier();
542 this.HandleSpeedChange(inputSpeedChange);
545 if (this._enableMovement)
547 this.Move(movementInput);
551 private void HandleSpeedChange(
float speedChangeInput)
553 if (this._enableDynamicSpeed)
555 this.UpdateDynamicSpeed();
559 this.SetMaxSpeed(this._defaultMaximumSpeed);
562 if (speedChangeInput != 0.0f)
564 if (speedChangeInput > 0.0f)
566 this._speedMultiplier *= this._speedMultiplierIncrement;
570 this._speedMultiplier /= this._speedMultiplierIncrement;
573 float max = this._enableDynamicSpeed ? 50.0f : 50000.0f;
574 this._speedMultiplier = Mathf.Clamp(this._speedMultiplier, 0.1f, max);
587 private void Rotate(
float horizontalRotation,
float verticalRotation)
589 if (horizontalRotation == 0.0f && verticalRotation == 0.0f)
594 float valueX = verticalRotation * this._lookSpeed * Time.fixedDeltaTime;
595 float valueY = horizontalRotation * this._lookSpeed * Time.fixedDeltaTime;
602 float rotationX = this.transform.localEulerAngles.x;
603 if (rotationX <= 90.0f)
608 float newRotationX = Mathf.Clamp(rotationX - valueX, 270.0f, 450.0f);
609 float newRotationY = this.transform.localEulerAngles.y + valueY;
610 this.transform.localRotation =
611 Quaternion.Euler(newRotationX, newRotationY, this.transform.localEulerAngles.z);
623 private void Move(Vector3 movementInput)
625 Vector3 inputDirection =
626 this.transform.right * movementInput.x + this.transform.forward * movementInput.z;
628 if (this._georeference !=
null)
635 inputDirection = (float3)inputDirection + (float3)upUnity * movementInput.y;
638 if (inputDirection != Vector3.zero)
642 if (this._velocity.magnitude > 0.0f)
644 Vector3 directionChange = inputDirection - this._velocity.normalized;
646 directionChange * this._velocity.magnitude * Time.fixedDeltaTime;
649 this._velocity += inputDirection * this._acceleration * Time.fixedDeltaTime;
650 this._velocity = Vector3.ClampMagnitude(this._velocity, this._maxSpeed);
655 float speed = Mathf.Max(
656 this._velocity.magnitude -
this._deceleration * Time.fixedDeltaTime,
659 this._velocity = Vector3.ClampMagnitude(this._velocity, speed);
662 if (this._velocity != Vector3.zero)
664 this._controller.Move(this._velocity * Time.fixedDeltaTime);
671 this._globeAnchor.
Sync();
678 #region Dynamic speed computation
688 private bool GetDynamicSpeed(out
bool overrideSpeed, out
float newSpeed)
690 if (this._georeference ==
null)
692 overrideSpeed =
false;
698 float height, viewDistance;
702 if (!this.RaycastTowardsEarthCenter(out height) || height < 0.000001f)
704 overrideSpeed =
false;
712 if (this._maxSpeedPreMultiplier > 0.5f)
714 float heightToMaxSpeedRatio = height / this._maxSpeedPreMultiplier;
720 if (heightToMaxSpeedRatio > 1000.0f || heightToMaxSpeedRatio < 0.01f)
722 overrideSpeed =
false;
730 float raycastDistance =
731 Mathf.Clamp(this._maxSpeed * 3.0f, 0.0f, this._maxRaycastDistance);
736 if (!this.RaycastAlongForwardVector(raycastDistance, out viewDistance) ||
737 viewDistance < 0.000001f)
739 overrideSpeed = height <= this._dynamicSpeedMinHeight;
743 overrideSpeed =
true;
752 private void ResetSpeedMultiplier()
754 this._speedMultiplier = 1.0f;
757 private void SetMaxSpeed(
float speed)
759 float actualSpeed = this._maxSpeedCurve.Evaluate(speed);
760 this._maxSpeed = this._speedMultiplier * actualSpeed;
762 Mathf.Clamp(this._maxSpeed * 5.0f, 20000.0f, 10000000.0f);
765 private void UpdateDynamicSpeed()
769 if (this.GetDynamicSpeed(out overrideSpeed, out newSpeed))
771 if (overrideSpeed || newSpeed >= this._maxSpeedPreMultiplier)
773 this._maxSpeedPreMultiplier = newSpeed;
777 this.SetMaxSpeed(this._maxSpeedPreMultiplier);
780 private void ResetSpeed()
782 this._maxSpeed = this._defaultMaximumSpeed;
783 this._maxSpeedPreMultiplier = 0.0f;
784 this.ResetSpeedMultiplier();
789 #region Dynamic clipping plane adjustment
791 private void UpdateClippingPlanes()
793 if (this._camera ==
null)
800 if (!this.RaycastTowardsEarthCenter(out height))
805 float nearClipPlane = this._initialNearClipPlane;
806 float farClipPlane = this._initialFarClipPlane;
808 if (height >= this._dynamicClippingPlanesMinHeight)
811 farClipPlane = Mathf.Min(farClipPlane, this._maximumFarClipPlane);
813 float farClipRatio = farClipPlane / this._maximumNearToFarRatio;
815 if (farClipRatio > nearClipPlane)
817 nearClipPlane = Mathf.Min(farClipRatio, this._maximumNearClipPlane);
821 this._camera.nearClipPlane = nearClipPlane;
822 this._camera.farClipPlane = farClipPlane;