BITFALL/Assets/Plugins/Character Controller Pro/Demo/Scripts/Camera/Camera3D.cs

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;
}
}
}