449 lines
15 KiB
C#
449 lines
15 KiB
C#
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<CharacterActor>();
|
|
|
|
if (characterActor == null || !characterActor.isActiveAndEnabled)
|
|
{
|
|
Debug.Log("The character actor component is null, or it is not active/enabled.");
|
|
return false;
|
|
}
|
|
|
|
characterRigidbody = characterActor.GetComponent<Rigidbody>();
|
|
|
|
inputHandlerSettings.Initialize(gameObject);
|
|
|
|
GameObject referenceObject = new GameObject("Camera reference");
|
|
viewReference = referenceObject.transform;
|
|
|
|
if (bodyObject != null)
|
|
bodyRenderers = bodyObject.GetComponentsInChildren<Renderer>();
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|