327 lines
8.9 KiB
C#
Executable File
327 lines
8.9 KiB
C#
Executable File
// 04.10.2021 11:00
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
|
|
public enum PivotAxis
|
|
{
|
|
Free = 10,
|
|
X = 20,
|
|
Y = 30,
|
|
Z = 40,
|
|
XY = 50,
|
|
XZ = 60,
|
|
YZ = 70,
|
|
}
|
|
|
|
public enum EEnablingBehaviour
|
|
{
|
|
Transition = 0,
|
|
Hold = 10,
|
|
Jump = 20,
|
|
}
|
|
|
|
public class ThresholdedBillboard : MonoBehaviour
|
|
{
|
|
|
|
#region Inspector Properties
|
|
|
|
[Header("Config Values")]
|
|
[Tooltip("Specifies the axis about which the object will rotate.")]
|
|
[SerializeField]
|
|
private PivotAxis pivotAxis = PivotAxis.XY;
|
|
|
|
[Tooltip("Specifies the desired OnEnable behaviour.")]
|
|
[SerializeField]
|
|
private EEnablingBehaviour enablingBehaviour;
|
|
|
|
[Tooltip("Specifies the delay in seconds before initializing.")]
|
|
[SerializeField]
|
|
private float initDelay = 0.03f;
|
|
|
|
[Tooltip("Specifies how fast the object will rotate.")]
|
|
[SerializeField]
|
|
private float rotationSpeed = 2f;
|
|
|
|
[Tooltip("Specifies the deviation at which the object will start rotating.")]
|
|
[SerializeField]
|
|
private float startBillboardingAngle = 20f;
|
|
|
|
[Tooltip("Specifies the deviation at which the object will stop rotating.")]
|
|
[SerializeField]
|
|
private float stopBillboardingAngle = 2f;
|
|
|
|
[Tooltip("Specifies if the rotation should happen after a delay.")]
|
|
[SerializeField]
|
|
private bool isDelayedModeActive = false;
|
|
|
|
[Tooltip("Only for delayed Mode: Specifies the delay before the rotation should happen.")]
|
|
[SerializeField]
|
|
private float delayedModeDelay = 0.3f;
|
|
|
|
[Header("Scene Objects")]
|
|
[Tooltip("Specifies the target we will orient to. If no target is specified, the main camera will be used.")]
|
|
[SerializeField]
|
|
private Transform targetTransform;
|
|
|
|
[Tooltip("Specifies transforms which will be set to the same rotation.")]
|
|
[SerializeField]
|
|
private List<Transform> linkedTransforms;
|
|
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
|
|
public PivotAxis PivotAxis
|
|
{
|
|
get { return pivotAxis; }
|
|
set { pivotAxis = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The target we will orient to. If no target is specified, the main camera will be used.
|
|
/// </summary>
|
|
public Transform TargetTransform => targetTransform;
|
|
|
|
#endregion
|
|
|
|
#region Private Properties
|
|
|
|
private bool isInitialized = false;
|
|
|
|
private Camera cam;
|
|
|
|
private Quaternion rotationFrom;
|
|
private Quaternion rotationTo;
|
|
|
|
private bool delayedRotationIsPending = false;
|
|
private float lastRotationRequiredTime = 0;
|
|
|
|
#endregion
|
|
|
|
#region Framework Functions
|
|
|
|
void Awake()
|
|
{
|
|
this.cam = Camera.main;
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
this.initBillboardWithDelay();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (this.isInitialized)
|
|
{
|
|
this.rotateToTarget(false, false);
|
|
}
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
this.isInitialized = false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
#endregion
|
|
|
|
#region Public Functions
|
|
|
|
public void OverrideEnablingBehaviour(EEnablingBehaviour newBehaviour)
|
|
{
|
|
this.enablingBehaviour = newBehaviour;
|
|
}
|
|
|
|
public void OverrideRotationSpeed(float newRotationSpeed)
|
|
{
|
|
this.rotationSpeed = newRotationSpeed;
|
|
}
|
|
|
|
public void OverrideStartBillboardingAngle(float newStartBillboardingAngle)
|
|
{
|
|
this.startBillboardingAngle = newStartBillboardingAngle;
|
|
}
|
|
|
|
public void OverrideStopBillboardingAngle(float newStopBillboardingAngle)
|
|
{
|
|
this.stopBillboardingAngle = newStopBillboardingAngle;
|
|
}
|
|
|
|
public void JumpToDesiredRotation()
|
|
{
|
|
this.rotateToTarget(false, true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Functions
|
|
|
|
private async void initBillboardWithDelay()
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(this.initDelay));
|
|
|
|
if (this == null)
|
|
{
|
|
// Has been destroyed -> cancel
|
|
return;
|
|
}
|
|
|
|
this.rotationFrom = Quaternion.identity;
|
|
this.rotationTo = Quaternion.identity;
|
|
|
|
if (this.enablingBehaviour == EEnablingBehaviour.Transition)
|
|
{
|
|
this.rotateToTarget(true, false);
|
|
}
|
|
|
|
if (this.enablingBehaviour == EEnablingBehaviour.Jump)
|
|
{
|
|
this.rotateToTarget(false, true);
|
|
}
|
|
|
|
this.isInitialized = true;
|
|
}
|
|
|
|
private void rotateToTarget(bool forceRotation, bool jumpToDesiredRotation)
|
|
{
|
|
if (this.targetTransform == null)
|
|
{
|
|
this.targetTransform = this.cam.transform;
|
|
}
|
|
|
|
// Get a Vector that points from the target to the main camera.
|
|
Vector3 directionToTarget = targetTransform.position - transform.position;
|
|
|
|
bool useCameraAsUpVector = true;
|
|
|
|
// Adjust for the pivot axis.
|
|
switch (pivotAxis)
|
|
{
|
|
case PivotAxis.X:
|
|
directionToTarget.x = 0.0f;
|
|
useCameraAsUpVector = false;
|
|
break;
|
|
|
|
case PivotAxis.Y:
|
|
directionToTarget.y = 0.0f;
|
|
useCameraAsUpVector = false;
|
|
break;
|
|
|
|
case PivotAxis.Z:
|
|
directionToTarget.x = 0.0f;
|
|
directionToTarget.y = 0.0f;
|
|
break;
|
|
|
|
case PivotAxis.XY:
|
|
useCameraAsUpVector = false;
|
|
break;
|
|
|
|
case PivotAxis.XZ:
|
|
directionToTarget.x = 0.0f;
|
|
break;
|
|
|
|
case PivotAxis.YZ:
|
|
directionToTarget.y = 0.0f;
|
|
break;
|
|
|
|
case PivotAxis.Free:
|
|
default:
|
|
// No changes needed.
|
|
break;
|
|
}
|
|
|
|
// If we are right next to the camera the rotation is undefined.
|
|
if (directionToTarget.sqrMagnitude < 0.001f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Quaternion rotationCurrent = Quaternion.identity;
|
|
// Calculate and apply the rotation required to reorient the object
|
|
if (useCameraAsUpVector)
|
|
{
|
|
rotationCurrent = Quaternion.LookRotation(-directionToTarget, this.cam.transform.up);
|
|
}
|
|
else
|
|
{
|
|
rotationCurrent = Quaternion.LookRotation(-directionToTarget);
|
|
}
|
|
|
|
if (jumpToDesiredRotation)
|
|
{
|
|
this.transform.rotation = rotationCurrent;
|
|
return;
|
|
}
|
|
|
|
float deviationToDesiredRotation = Mathf.Abs(Quaternion.Angle(rotationCurrent, transform.rotation));
|
|
|
|
// Check if rotation target needs to be set
|
|
if (deviationToDesiredRotation > this.startBillboardingAngle || forceRotation)
|
|
{
|
|
if (this.isDelayedModeActive)
|
|
{
|
|
// Delayed mode
|
|
if (!this.delayedRotationIsPending)
|
|
{
|
|
// Start "timer"
|
|
this.delayedRotationIsPending = true;
|
|
this.lastRotationRequiredTime = Time.time;
|
|
}
|
|
else
|
|
{
|
|
// "Timer" running
|
|
if (Time.time > this.lastRotationRequiredTime + this.delayedModeDelay)
|
|
{
|
|
// Delaytime has passed -> start rotating
|
|
this.delayedRotationIsPending = false;
|
|
|
|
this.rotationFrom = transform.rotation;
|
|
this.rotationTo = rotationCurrent;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Non delayed mode -> start rotating
|
|
this.rotationFrom = transform.rotation;
|
|
this.rotationTo = rotationCurrent;
|
|
}
|
|
}
|
|
|
|
// If delayed mode -> check if delayedRotationIsPending needs to be reset
|
|
if (deviationToDesiredRotation < this.startBillboardingAngle && this.isDelayedModeActive)
|
|
{
|
|
this.delayedRotationIsPending = false;
|
|
}
|
|
|
|
if (this.rotationTo != Quaternion.identity)
|
|
{
|
|
// Currently rotating
|
|
transform.rotation = Quaternion.Lerp(this.transform.rotation, this.rotationTo, Time.deltaTime * rotationSpeed);
|
|
|
|
for (int i = 0; i < this.linkedTransforms.Count; i++)
|
|
{
|
|
this.linkedTransforms[i].rotation = this.transform.rotation;
|
|
}
|
|
|
|
// Check if should stop
|
|
float deviationToTargetRotation = Mathf.Abs(Quaternion.Angle(this.transform.rotation, this.rotationTo));
|
|
if (deviationToTargetRotation < this.stopBillboardingAngle)
|
|
{
|
|
this.rotationFrom = Quaternion.identity;
|
|
this.rotationTo = Quaternion.identity;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|