using System.Collections.Generic; using UnityEngine; using Lightbug.CharacterControllerPro.Core; using Lightbug.CharacterControllerPro.Implementation; using Lightbug.Utilities; namespace Lightbug.CharacterControllerPro.Demo { [AddComponentMenu("Character Controller Pro/Demo/Camera/Camera 3D")] [DefaultExecutionOrder(ExecutionOrder.CharacterGraphicsOrder + 100)] // <--- Do your job after everything else public class Camera3D : MonoBehaviour { [Header("Inputs")] [SerializeField] InputHandlerSettings inputHandlerSettings = new InputHandlerSettings(); [SerializeField] string axes = "Camera"; [SerializeField] string zoomAxis = "Camera Zoom"; [Header("Target")] [Tooltip("Select the graphics root object as your target, the one containing all the meshes, sprites, animated models, etc. \n\nImportant: This will be the considered as the actual target (visual element).")] [SerializeField] Transform targetTransform = null; [SerializeField] Vector3 offsetFromHead = Vector3.zero; [Tooltip("The interpolation speed used when the height of the character changes.")] [SerializeField] float heightLerpSpeed = 10f; [Header("View")] public CameraMode cameraMode = CameraMode.ThirdPerson; [Header("First Person")] public bool hideBody = true; [SerializeField] GameObject bodyObject = null; [Header("Yaw")] public bool updateYaw = true; public float yawSpeed = 180f; [Header("Pitch")] public bool updatePitch = true; [SerializeField] float initialPitch = 45f; public float pitchSpeed = 180f; [Range(1f, 85f)] public float maxPitchAngle = 80f; [Range(1f, 85f)] public float minPitchAngle = 80f; [Header("Roll")] public bool updateRoll = false; [Header("Zoom (Third person)")] public bool updateZoom = true; [Min(0f)] [SerializeField] float distanceToTarget = 5f; [Min(0f)] public float zoomInOutSpeed = 40f; [Min(0f)] public float zoomInOutLerpSpeed = 5f; [Min(0f)] public float minZoom = 2f; [Min(0.001f)] public float maxZoom = 12f; [Header("Collision")] public bool collisionDetection = true; public bool collisionAffectsZoom = false; public float detectionRadius = 0.5f; public LayerMask layerMask = 0; public bool considerKinematicRigidbodies = true; public bool considerDynamicRigidbodies = true; // ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── CharacterActor characterActor = null; Rigidbody characterRigidbody = null; float currentDistanceToTarget; float smoothedDistanceToTarget; float deltaYaw = 0f; float deltaPitch = 0f; float deltaZoom = 0f; Vector3 lerpedCharacterUp = Vector3.up; Transform viewReference = null; Renderer[] bodyRenderers = null; RaycastHit[] hitsBuffer = new RaycastHit[10]; RaycastHit[] validHits = new RaycastHit[10]; Vector3 characterPosition = default(Vector3); float lerpedHeight; public enum CameraMode { FirstPerson, ThirdPerson, } public void ToggleCameraMode() { cameraMode = cameraMode == CameraMode.FirstPerson ? CameraMode.ThirdPerson : CameraMode.FirstPerson; } void OnValidate() { initialPitch = Mathf.Clamp(initialPitch, -minPitchAngle, maxPitchAngle); } void Awake() { Initialize(targetTransform); } public bool Initialize(Transform targetTransform) { if (targetTransform == null) return false; characterActor = targetTransform.GetComponentInBranch(); if (characterActor == null || !characterActor.isActiveAndEnabled) { Debug.Log("The character actor component is null, or it is not active/enabled."); return false; } characterRigidbody = characterActor.GetComponent(); inputHandlerSettings.Initialize(gameObject); GameObject referenceObject = new GameObject("Camera reference"); viewReference = referenceObject.transform; if (bodyObject != null) bodyRenderers = bodyObject.GetComponentsInChildren(); return true; } void OnEnable() { if (characterActor == null) return; characterActor.OnTeleport += OnTeleport; } void OnDisable() { if (characterActor == null) return; characterActor.OnTeleport -= OnTeleport; } void Start() { characterPosition = targetTransform.position; previousLerpedCharacterUp = targetTransform.up; lerpedCharacterUp = previousLerpedCharacterUp; currentDistanceToTarget = distanceToTarget; smoothedDistanceToTarget = currentDistanceToTarget; viewReference.rotation = targetTransform.rotation; viewReference.Rotate(Vector3.right, initialPitch); lerpedHeight = characterActor.BodySize.y; } void Update() { if (targetTransform == null) { this.enabled = false; return; } Vector2 cameraAxes = inputHandlerSettings.InputHandler.GetVector2(axes); if (updatePitch) deltaPitch = -cameraAxes.y; if (updateYaw) deltaYaw = cameraAxes.x; if (updateZoom) deltaZoom = -inputHandlerSettings.InputHandler.GetFloat(zoomAxis); // An input axis value (e.g. mouse x) usually gets accumulated over time. So, the higher the frame rate the smaller the value returned. // In order to prevent inconsistencies due to frame rate changes, the camera movement uses a fixed delta time, instead of the old regular // delta time. float dt = Time.fixedDeltaTime; UpdateCamera(dt); } void OnTeleport(Vector3 position, Quaternion rotation) { viewReference.rotation = rotation; transform.rotation = viewReference.rotation; lerpedCharacterUp = characterActor.Up; previousLerpedCharacterUp = lerpedCharacterUp; } Vector3 previousLerpedCharacterUp = Vector3.up; void HandleBodyVisibility() { if (cameraMode == CameraMode.FirstPerson) { if (bodyRenderers != null) for (int i = 0; i < bodyRenderers.Length; i++) { if (bodyRenderers[i].GetType().IsSubclassOf(typeof(SkinnedMeshRenderer))) { SkinnedMeshRenderer skinnedMeshRenderer = (SkinnedMeshRenderer)bodyRenderers[i]; if (skinnedMeshRenderer != null) skinnedMeshRenderer.forceRenderingOff = hideBody; } else { bodyRenderers[i].enabled = !hideBody; } } } else { if (bodyRenderers != null) for (int i = 0; i < bodyRenderers.Length; i++) { if (bodyRenderers[i] == null) continue; if (bodyRenderers[i].GetType().IsSubclassOf(typeof(SkinnedMeshRenderer))) { SkinnedMeshRenderer skinnedMeshRenderer = (SkinnedMeshRenderer)bodyRenderers[i]; if (skinnedMeshRenderer != null) skinnedMeshRenderer.forceRenderingOff = false; } else { bodyRenderers[i].enabled = true; } } } } void UpdateCamera(float dt) { // Body visibility --------------------------------------------------------------------- HandleBodyVisibility(); // Rotation ----------------------------------------------------------------------------------------- lerpedCharacterUp = targetTransform.up; // Rotate the reference based on the lerped character up vector Quaternion deltaRotation = Quaternion.FromToRotation(previousLerpedCharacterUp, lerpedCharacterUp); previousLerpedCharacterUp = lerpedCharacterUp; viewReference.rotation = deltaRotation * viewReference.rotation; // Yaw rotation ----------------------------------------------------------------------------------------- viewReference.Rotate(lerpedCharacterUp, deltaYaw * yawSpeed * dt, Space.World); // Pitch rotation ----------------------------------------------------------------------------------------- float angleToUp = Vector3.Angle(viewReference.forward, lerpedCharacterUp); float minPitch = -angleToUp + (90f - minPitchAngle); float maxPitch = 180f - angleToUp - (90f - maxPitchAngle); float pitchAngle = Mathf.Clamp(deltaPitch * pitchSpeed * dt, minPitch, maxPitch); viewReference.Rotate(Vector3.right, pitchAngle); // Roll rotation ----------------------------------------------------------------------------------------- if (updateRoll) { viewReference.up = lerpedCharacterUp;//Quaternion.FromToRotation( viewReference.up , lerpedCharacterUp ) * viewReference.up; } // Position of the target ----------------------------------------------------------------------- characterPosition = targetTransform.position; lerpedHeight = Mathf.Lerp(lerpedHeight, characterActor.BodySize.y, heightLerpSpeed * dt); Vector3 targetPosition = characterPosition + targetTransform.up * lerpedHeight + targetTransform.TransformDirection(offsetFromHead); viewReference.position = targetPosition; Vector3 finalPosition = viewReference.position; // ------------------------------------------------------------------------------------------------------ if (cameraMode == CameraMode.ThirdPerson) { currentDistanceToTarget += deltaZoom * zoomInOutSpeed * dt; currentDistanceToTarget = Mathf.Clamp(currentDistanceToTarget, minZoom, maxZoom); smoothedDistanceToTarget = Mathf.Lerp(smoothedDistanceToTarget, currentDistanceToTarget, zoomInOutLerpSpeed * dt); Vector3 displacement = -viewReference.forward * smoothedDistanceToTarget; if (collisionDetection) { bool hit = DetectCollisions(ref displacement, targetPosition); if (collisionAffectsZoom && hit) { currentDistanceToTarget = smoothedDistanceToTarget = displacement.magnitude; } } finalPosition = targetPosition + displacement; } transform.position = finalPosition; transform.rotation = viewReference.rotation; } bool DetectCollisions(ref Vector3 displacement, Vector3 lookAtPosition) { int hits = Physics.SphereCastNonAlloc( lookAtPosition, detectionRadius, Vector3.Normalize(displacement), hitsBuffer, currentDistanceToTarget, layerMask, QueryTriggerInteraction.Ignore ); // Order the results int validHitsNumber = 0; for (int i = 0; i < hits; i++) { RaycastHit hitBuffer = hitsBuffer[i]; Rigidbody detectedRigidbody = hitBuffer.collider.attachedRigidbody; // Filter the results --------------------------- if (hitBuffer.distance == 0) continue; if (detectedRigidbody != null) { if (considerKinematicRigidbodies && !detectedRigidbody.isKinematic) continue; if (considerDynamicRigidbodies && detectedRigidbody.isKinematic) continue; if (detectedRigidbody == characterRigidbody) continue; } //---------------------------------------------- validHits[validHitsNumber] = hitBuffer; validHitsNumber++; } if (validHitsNumber == 0) return false; float distance = Mathf.Infinity; for (int i = 0; i < validHitsNumber; i++) { RaycastHit hitBuffer = validHits[i]; if (hitBuffer.distance < distance) distance = hitBuffer.distance; } displacement = CustomUtilities.Multiply(Vector3.Normalize(displacement), distance); return true; } } }