Skip to content

Instantly share code, notes, and snippets.

@jjhesk
Created February 10, 2016 17:42
Show Gist options
  • Save jjhesk/52b46ee2a83475ab0f30 to your computer and use it in GitHub Desktop.
Save jjhesk/52b46ee2a83475ab0f30 to your computer and use it in GitHub Desktop.
Fantastic Camera
/// <summary>
/// UnityTutorials - A Unity Game Design Prototyping Sandbox
/// <copyright>(c) HKM hesk 2014 </copyright>
///
///
/// This software is provided 'as-is', without any express or implied
/// warranty. In no event will the authors be held liable for any damages
/// arising from the use of this software.
///
/// Permission is granted to anyone to use this software for any purpose,
/// and to alter it and redistribute it freely, subject to the following restrictions:
///
/// 1. The origin of this software must not be misrepresented; you must not
/// claim that you wrote the original software. If you use this software
/// in a product, an acknowledgment in the product documentation would be
/// appreciated but is not required.
/// 2. Altered source versions must be plainly marked as such, and must not be
/// misrepresented as being the original software.
/// 3. This notice may not be removed or altered from any source distribution.
/// </summary>
using UnityEngine;
using System.Collections;
/// <summary>
/// Struct to hold data for aligning camera
/// </summary>
struct CameraPosition
{
// Position to align camera to, probably somewhere behind the character
// or position to point camera at, probably somewhere along character's axis
private Vector3 position;
// Transform used for any rotation
private Transform xForm;
public Vector3 Position { get { return position; } set { position = value; } }
public Transform XForm { get { return xForm; } set { xForm = value; } }
public void Init (string camName, Transform parent)
{
//string paren_name_go = parent.name;
foreach (Transform g in parent) {
if (g.gameObject.name == "body") {
foreach (Transform t in g.gameObject.transform) {
if (t.gameObject.name == camName) {
position = t.transform.localPosition;
xForm = t.transform;
xForm.localPosition = position;
}
}
}
}
//Transform transform = GameObject.Find (paren_name_go + "/" + camName) as Transform;
//position = transform.position;
//xForm = transform;
//xForm.name = camName;
//xForm.parent = parent;
//xForm.localPosition = Vector3.zero;
//xForm.localPosition = position;
}
}
/// <summary>
/// #DESCRIPTION OF CLASS#
/// </summary>
[RequireComponent (typeof(BarsEffect))]
public class FantasticCamera : MonoBehaviour
{
#region Variables (private)
// Inspector serialized
[SerializeField]
private Transform
parentRig;
[SerializeField]
private float
distanceAway;
[SerializeField]
private float
distanceAwayMultipler = 1.5f;
[SerializeField]
private float
distanceUp, surroundDisUp = 1f;
[SerializeField]
private float
distanceUpMultiplier = 5f;
[SerializeField]
private CharacterControllerLogic
follow;
[SerializeField]
private Transform
followXform;
[SerializeField]
private float
widescreen = 0.2f;
[SerializeField]
private float
targetingTime = 0.5f;
[SerializeField]
private float
firstPersonLookSpeed = 3.0f;
[SerializeField]
private Vector2
firstPersonXAxisClamp = new Vector2 (-70.0f, 90.0f);
[SerializeField]
private float
fPSRotationDegreePerSecond = 120f;
[SerializeField]
private float
firstPersonThreshold = 0.5f;
[SerializeField]
private float
freeThreshold = -0.1f;
[SerializeField]
private Vector2
camMinDistFromChar = new Vector2 (1f, -0.5f);
[SerializeField]
private float
rightStickThreshold = 0.1f;
[SerializeField]
private const float
freeRotationDegreePerSecond = -5f;
public bool isTargeted = false;
public bool exit_fpv = false;
// Smoothing and damping
private Vector3 velocityCamSmooth = Vector3.zero;
[SerializeField]
private float
camSmoothDampTime = 0.1f, firstPersonDampTime = 0.1f;
private Vector3 velocityLookDir = Vector3.zero;
[SerializeField]
private float
lookDirDampTime = 0.1f;
[SerializeField]
private Vector3
firstPersonCameraPos = Vector3.zero;
// Private global only
private Vector3 lookDir;
private Vector3 curLookDir;
private BarsEffect barEffect;
[SerializeField]
private CamStates
camState = CamStates.Behind;
private float xAxisRot = 0.0f;
private CameraPosition firstPersonCamPos;
private float lookWeight;
private const float TARGETING_THRESHOLD = 0.01f;
private Vector3 savedRigToGoal;
private float distanceAwayFree;
private float distanceUpFree;
private Vector2 rightStickPrevFrame = Vector2.zero;
private float surroundingm;
[SerializeField]
private float
surroundSpeed = 0.5f;
#endregion
#region Properties (public)
public Transform ParentRig {
get {
return this.parentRig;
}
}
public Vector3 LookDir {
get {
return this.curLookDir;
}
}
public CamStates CamState {
get {
return this.camState;
}
}
public enum CamStates
{
Behind,
FirstPerson,
Target,
AutoSurround,
Free
}
#endregion
#region Unity event functions
/// <summary>
/// Use this for initialization.
/// </summary>
void Start ()
{
parentRig = this.transform.parent;
if (parentRig == null) {
Debug.LogError ("Parent camera to empty GameObject.", this);
}
//follow = GameObject.FindWithTag ("Player").GetComponent<CharacterControllerLogic> ();
//followXform = GameObject.FindWithTag ("Player").transform;
if (follow == null || followXform == null) {
Debug.LogError ("awaiting follow and followXform.", this);
return;
}
lookDir = followXform.forward;
curLookDir = followXform.forward;
barEffect = GetComponent<BarsEffect> ();
if (barEffect == null) {
Debug.LogError ("Attach a widescreen BarsEffect script to the camera.", this);
}
// Position and parent a GameObject where first person view should be
firstPersonCamPos = new CameraPosition ();
firstPersonCamPos.Init (
"FPCam",
follow.transform
);
}
/// <summary>
/// Update is called once per frame.
/// </summary>
void Update ()
{
}
/// <summary>
/// Debugging information should be put here.
/// </summary>
void OnDrawGizmos ()
{
}
void LateUpdate ()
{
if (follow == null || followXform == null) {
return;
}
// Pull values from controller/keyboard
float rightX = 0f;
//Input.GetAxis("RightStickX");
float rightY = 0f;//Input.GetAxis("RightStickY");
float leftX = 0f;//Input.GetAxis("Horizontal");
float leftY = 0f;//Input.GetAxis("Vertical");
Vector3 characterOffset = followXform.position + new Vector3 (0f, distanceUp, 0f);
Vector3 lookAt = characterOffset;
Vector3 targetPosition = Vector3.zero;
// Determine camera state
// * Targeting *
//isTargeted
//if (Input.GetAxis ("Target") > TARGETING_THRESHOLD) {
if (isTargeted) {
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 && exit_fpv) || (camState == CamStates.Target && !isTargeted)) {
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.gray);
break;
case CamStates.Target:
ResetCamera ();
lookDir = followXform.forward;
curLookDir = followXform.forward;
targetPosition = characterOffset + followXform.up * distanceUp - lookDir * distanceAway;
break;
case CamStates.AutoSurround:
ResetCamera ();
//lookDir = followXform.forward;
//xAxisRot += (leftY * 0.5f * firstPersonLookSpeed)
//lookDir = followXform.forward;
//curLookDir = followXform.forward;
surroundingm += surroundSpeed;
targetPosition = followXform.position + surroundDisUp * (1 + 1 / distanceUpMultiplier) * Vector3.up + Quaternion.Euler (0f, surroundingm, 0f) * followXform.forward * distanceAway;
lookAt = followXform.position + distanceUp * Vector3.up;
Debug.DrawLine (followXform.position, targetPosition, Color.white);
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);
}
#endregion
#region Methods
private void SmoothPosition (Vector3 fromPos, Vector3 toPos)
{
float damp = camState == CamStates.FirstPerson ? firstPersonDampTime : camSmoothDampTime;
// Making a smooth transition between camera's current position and the position it wants to be in
parentRig.position = Vector3.SmoothDamp (fromPos, toPos, ref velocityCamSmooth, damp);
}
private void CompensateForWalls (Vector3 fromObject, ref Vector3 toTarget)
{
Debug.DrawLine (fromObject, toTarget, Color.cyan);
// Compensate for walls between camera
RaycastHit wallHit = new RaycastHit ();
if (Physics.Linecast (fromObject, toTarget, out wallHit)) {
Debug.DrawRay (wallHit.point, wallHit.normal, Color.red);
toTarget = new Vector3 (wallHit.point.x, toTarget.y, wallHit.point.z);
}
}
/// <summary>
/// Reset local position of camera inside of parentRig and resets character's look IK.
/// </summary>
private void ResetCamera ()
{
lookWeight = Mathf.Lerp (lookWeight, 0.0f, Time.deltaTime * firstPersonLookSpeed);
transform.localRotation = Quaternion.Lerp (transform.localRotation, Quaternion.identity, Time.deltaTime);
}
#endregion Methods
#region Methods
public void startNextTurnPlayer (Transform t)
{
follow = t.GetComponent<CharacterControllerLogic> ();
followXform = t;
//follow = GameObject.FindWithTag ("Player").GetComponent<CharacterControllerLogic> ();
//followXform = GameObject.FindWithTag ("Player").transform;
camState = CamStates.Behind;
Start ();
}
public void triggerFocusMain (CharacterControllerLogic logic, Transform t)
{
follow = logic;
followXform = t;
camState = CamStates.Behind;
Start ();
}
public void ToFocusDice (Transform t)
{
//follow = t.GetComponent<CharacterControllerLogic> ();
followXform = t;
camState = CamStates.AutoSurround;
}
public void ToFocusBuilding (Transform t)
{
//follow = t.GetComponent<CharacterControllerLogic> ();
followXform = t;
camState = CamStates.AutoSurround;
}
public void ToFocusFirst ()
{
//follow = t.GetComponent<CharacterControllerLogic> ();
camState = CamStates.FirstPerson;
}
#endregion Methods
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment