void LateUpdate()
{
// Pull values from controller/keyboard
float rightX = Input.GetAxis("RightStickX");
float rightY = Input.GetAxis("RightStickY");
float leftX = Input.GetAxis("Horizontal");
float leftY = Input.GetAxis("Vertical");
Vector3 characterOffset = followXform.position + new Vector3(0f, distanceUp, 0f);
Vector3 lookAt = characterOffset;
Vector3 targetPosition = Vector3.zero;
// Determine camera state
// * Targeting *
if (Input.GetAxis("Target") > TARGETING_THRESHOLD)
{
barEffect.coverage = Mathf.SmoothStep(barEffect.coverage, widescreen, targetingTime);
camState = CamStates.Target;
}
else
{
barEffect.coverage = Mathf.SmoothStep(barEffect.coverage, 0f, targetingTime);
// * First Person *
if (rightY > firstPersonThreshold && camState != CamStates.Free && camState != CamStates.Free && !follow.IsInLocomotion())
{
// Reset look before entering the first person mode
xAxisRot = 0;
lookWeight = 0f;
camState = CamStates.FirstPerson;
}
if (rightY < freeThreshold && System.Math.Round(follow.Speed, 2) == 0)
{
camState = CamStates.Free;
savedRigToGoal = Vector3.zero;
}
// * Behind the back *
if ((camState == CamStates.FirstPerson && Input.GetButton("ExitFPV")) ||
(camState == CamStates.Target && (Input.GetAxis("Target") <= TARGETING_THRESHOLD)))
{
camState = CamStates.Behind;
}
}
// Set the Look At Weight - amount to use look at IK vs using the head's animation
follow.Animator.SetLookAtWeight(lookWeight);
// Execute camera state
switch (camState)
{
case CamStates.Behind:
ResetCamera();
// Only update camera look direction if moving
if (follow.Speed > follow.LocomotionThreshold && follow.IsInLocomotion() && !follow.IsInPivot())
{
lookDir = Vector3.Lerp(followXform.right * (leftX < 0 ? 1f : -1f), followXform.forward * (leftY < 0 ? -1f : 1f), Mathf.Abs(Vector3.Dot(this.transform.forward, followXform.forward)));
Debug.DrawRay(this.transform.position, lookDir, Color.white);
// Calculate direction from camera to player, kill Y, and normalize to give a valid direction with unit magnitude
curLookDir = Vector3.Normalize(characterOffset - this.transform.position);
curLookDir.y = 0;
Debug.DrawRay(this.transform.position, curLookDir, Color.green);
// Damping makes it so we don't update targetPosition while pivoting; camera shouldn't rotate around player
curLookDir = Vector3.SmoothDamp(curLookDir, lookDir, ref velocityLookDir, lookDirDampTime);
}
targetPosition = characterOffset + followXform.up * distanceUp - Vector3.Normalize(curLookDir) * distanceAway;
Debug.DrawLine(followXform.position, targetPosition, Color.magenta);
break;
case CamStates.Target:
ResetCamera();
lookDir = followXform.forward;
curLookDir = followXform.forward;
targetPosition = characterOffset + followXform.up * distanceUp - lookDir * distanceAway;
break;
case CamStates.FirstPerson:
// Looking up and down
// Calculate the amount of rotation and apply to the firstPersonCamPos GameObject
xAxisRot += (-leftY * 0.5f * firstPersonLookSpeed);
xAxisRot = Mathf.Clamp(xAxisRot, firstPersonXAxisClamp.x, firstPersonXAxisClamp.y);
firstPersonCamPos.XForm.localRotation = Quaternion.Euler(xAxisRot, 0, 0);
// Superimpose firstPersonCamPos GameObject's rotation on camera
Quaternion rotationShift = Quaternion.FromToRotation(this.transform.forward, firstPersonCamPos.XForm.forward);
this.transform.rotation = rotationShift * this.transform.rotation;
// Move character model's head
follow.Animator.SetLookAtPosition(firstPersonCamPos.XForm.position + firstPersonCamPos.XForm.forward);
lookWeight = Mathf.Lerp(lookWeight, 1.0f, Time.deltaTime * firstPersonLookSpeed);
// Looking left and right
// Similarly to how character is rotated while in locomotion, use Quaternion * to add rotation to character
Vector3 rotationAmount = Vector3.Lerp(Vector3.zero, new Vector3(0f, fPSRotationDegreePerSecond * (leftX < 0f ? -1f : 1f), 0f), Mathf.Abs(leftX));
Quaternion deltaRotation = Quaternion.Euler(rotationAmount * Time.deltaTime);
follow.transform.rotation = (follow.transform.rotation * deltaRotation);
// Move camera to firstPersonCamPos
targetPosition = firstPersonCamPos.XForm.position;
// Smoothly transition look direction towards firstPersonCamPos when entering first person mode
lookAt = Vector3.Lerp(targetPosition + followXform.forward, this.transform.position + this.transform.forward, camSmoothDampTime * Time.deltaTime);
Debug.DrawRay(Vector3.zero, lookAt, Color.black);
Debug.DrawRay(Vector3.zero, targetPosition + followXform.forward, Color.white);
Debug.DrawRay(Vector3.zero, firstPersonCamPos.XForm.position + firstPersonCamPos.XForm.forward, Color.cyan);
// Choose lookAt target based on distance
lookAt = (Vector3.Lerp(this.transform.position + this.transform.forward, lookAt, Vector3.Distance(this.transform.position, firstPersonCamPos.XForm.position)));
break;
case CamStates.Free:
lookWeight = Mathf.Lerp(lookWeight, 0.0f, Time.deltaTime * firstPersonLookSpeed);
// Move height and distance from character in separate parentRig transform since RotateAround has control of both position and rotation
Vector3 rigToGoalDirection = Vector3.Normalize(characterOffset - this.transform.position);
// Can't calculate distanceAway from a vector with Y axis rotation in it; zero it out
rigToGoalDirection.y = 0f;
Vector3 rigToGoal = characterOffset - parentRig.position;
rigToGoal.y = 0;
Debug.DrawRay(parentRig.transform.position, rigToGoal, Color.red);
// Panning in and out
// If statement works for positive values; don't tween if stick not increasing in either direction; also don't tween if user is rotating
// Checked against rightStickThreshold because very small values for rightY mess up the Lerp function
if (rightY < -1f * rightStickThreshold && rightY <= rightStickPrevFrame.y && Mathf.Abs(rightX) < rightStickThreshold)
{
distanceUpFree = Mathf.Lerp(distanceUp, distanceUp * distanceUpMultiplier, Mathf.Abs(rightY));
distanceAwayFree = Mathf.Lerp(distanceAway, distanceAway * distanceAwayMultipler, Mathf.Abs(rightY));
targetPosition = characterOffset + followXform.up * distanceUpFree - rigToGoalDirection * distanceAwayFree;
}
else if (rightY > rightStickThreshold && rightY >= rightStickPrevFrame.y && Mathf.Abs(rightX) < rightStickThreshold)
{
// Subtract height of camera from height of player to find Y distance
distanceUpFree = Mathf.Lerp(Mathf.Abs(transform.position.y - characterOffset.y), camMinDistFromChar.y, rightY);
// Use magnitude function to find X distance
distanceAwayFree = Mathf.Lerp(rigToGoal.magnitude, camMinDistFromChar.x, rightY);
targetPosition = characterOffset + followXform.up * distanceUpFree - rigToGoalDirection * distanceAwayFree;
}
// Store direction only if right stick inactive
if (rightX != 0 || rightY != 0)
{
savedRigToGoal = rigToGoalDirection;
}
// Rotating around character
parentRig.RotateAround(characterOffset, followXform.up, freeRotationDegreePerSecond * (Mathf.Abs(rightX) > rightStickThreshold ? rightX : 0f));
// Still need to track camera behind player even if they aren't using the right stick; achieve this by saving distanceAwayFree every frame
if (targetPosition == Vector3.zero)
{
targetPosition = characterOffset + followXform.up * distanceUpFree - savedRigToGoal * distanceAwayFree;
}
// SmoothPosition(transform.position, targetPosition);
// transform.LookAt(lookAt);
break;
}
// if (camState != CamStates.Free)
// {
CompensateForWalls(characterOffset, ref targetPosition);
SmoothPosition(parentRig.position, targetPosition);
transform.LookAt(lookAt);
// }
rightStickPrevFrame = new Vector2(rightX, rightY);
}