using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace BNG {
///
/// Helper class to interact with physical levers
///
public class Lever : MonoBehaviour {
[Header("Rotation Limits")]
[Tooltip("Minimum X value in Local Euler Angles")]
public float MinimumXRotation = -45f;
[Tooltip("Maximum X value in Local Euler Angles")]
public float MaximumXRotation = 45f;
[Header("Initial Rotation")]
public float InitialXRotation = 0f;
[Header("Audio")]
public AudioClip SwitchOnSound;
public AudioClip SwitchOffSound;
///
/// Tolerance before considering a switch flipped On or Off
/// Ex : 1.25 Tolerance means switch can be 98.25% up and considered switched on
///
[Header("Tolerance")]
[Tooltip("Tolerance before considering a switch flipped On or Off. Ex : 1.25 Tolerance means switch can be 98.25% up and considered switched on, or 1.25% down to be considered switched off.")]
public float SwitchTolerance = 1.25f;
[Header("Smooth Look")]
[Tooltip("If true the lever will lerp towards the Grabber. If false the lever will instantly point to the grabber")]
public bool UseSmoothLook = true;
[Tooltip("The speed at which to Lerp towards the Grabber if UseSmoothLook is enabled")]
public float SmoothLookSpeed = 15f;
[Header("Moving Platform Support")]
///
/// If false, the lever's rigidbody will be kinematic when not being held. Disable this if you don't want your lever to interact with physics or if you need moving platform support.
///
[Tooltip("If false, the lever's rigidbody will be kinematic when not being held. Disable this if you don't want your lever to interact with physics or if you need moving platform support.")]
public bool AllowPhysicsForces = true;
[Header("Return to Center (Must be Kinematic)")]
///
/// If ReturnToCenter true and KinematicWhileInactive true then the lever will smooth look back to center when not being held
///
[Tooltip("If ReturnToCenter true and KinematicWhileInactive true then the lever will smooth look back to center when not being held")]
public bool ReturnToCenter = true;
///
/// How fast to return to center if not being held
///
[Tooltip("How fast to return to center if not being held")]
public float ReturnLookSpeed = 5f;
[Header("Snap Settings")]
///
/// If true the lever will look directly at the Grabber and not factor in an initial offset
///
[Tooltip("If true the lever will look directly at the Grabber and not factor in an initial offset")]
public bool SnapToGrabber = false;
[Header("Misc")]
[Tooltip("If true, the Lever will be dropped once switched on or off")]
public bool DropLeverOnActivation = false;
[Header("Shown for Debug")]
///
/// Current position of the lever as expressed as a percentage 1-100
///
[Tooltip("Current position of the lever as expressed as a percentage 1-100")]
public float LeverPercentage;
[Tooltip("If true will show an angle helper in editor mode (Gizmos must be enabled)")]
public bool ShowEditorGizmos = true;
[Header("Events")]
///
/// Called when lever was up, but is now in the down position
///
[Tooltip("Called when lever was up, but is now in the down position")]
public UnityEvent onLeverDown;
///
/// Called when lever was down, but is now in the up position
///
[Tooltip("Called when lever was down, but is now in the up position")]
public UnityEvent onLeverUp;
///
/// Called if the lever changes position at all
///
[Tooltip("Called if the lever changes position at all")]
public FloatEvent onLeverChange;
Grabbable grab;
Rigidbody rb;
AudioSource audioSource;
bool switchedOn;
ConfigurableJoint configJoint;
HingeJoint hingedJoint;
private Vector3 _lastLocalAngle;
void Start() {
grab = GetComponent();
rb = GetComponent();
hingedJoint = GetComponent();
configJoint = GetComponent();
audioSource = GetComponent();
if (audioSource == null && (SwitchOnSound != null || SwitchOffSound != null)) {
audioSource = gameObject.AddComponent();
}
}
void Awake() {
transform.localEulerAngles = new Vector3(InitialXRotation, 0, 0);
}
void Update() {
// Update Kinematic Status.
if (rb) {
rb.isKinematic = AllowPhysicsForces == false && !grab.BeingHeld;
}
// Make sure grab offset is reset when not being held
if (!grab.BeingHeld) {
initialOffset = Quaternion.identity;
}
// Get the modified angle of of the lever. Use this to get percentage based on Min and Max angles.
Vector3 currentRotation = transform.localEulerAngles;
float angle = Mathf.Round(currentRotation.x);
angle = (angle > 180) ? angle - 360 : angle;
// Set percentage of level position
LeverPercentage = GetAnglePercentage(angle);
// Lever value changed event
OnLeverChange(LeverPercentage);
// Up / Down Events
if ((LeverPercentage + SwitchTolerance) > 99 && !switchedOn) {
OnLeverUp();
}
else if ((LeverPercentage - SwitchTolerance) < 1 && switchedOn) {
OnLeverDown();
}
_lastLocalAngle = transform.localEulerAngles;
}
public virtual float GetAnglePercentage(float currentAngle) {
if (hingedJoint) {
return (currentAngle - hingedJoint.limits.min) / (hingedJoint.limits.max - hingedJoint.limits.min) * 100;
}
if (configJoint) {
return currentAngle / configJoint.linearLimit.limit * 100;
}
return 0;
}
void FixedUpdate() {
// Align lever with Grabber
doLeverLook();
}
Quaternion initialOffset = Quaternion.identity;
void doLeverLook() {
// Do Lever Look
if (grab != null && grab.BeingHeld) {
// Use the grabber as our look target.
Transform target = grab.GetPrimaryGrabber().transform;
// Store original rotation to be used with smooth look
Quaternion originalRot = transform.rotation;
// Convert to local position so we can remove the x axis
Vector3 localTargetPosition = transform.InverseTransformPoint(target.position);
// Remove local X axis as this would cause the lever to rotate incorrectly
localTargetPosition.x = 0f;
// Convert back to world position
Vector3 targetPosition = transform.TransformPoint(localTargetPosition);
transform.LookAt(targetPosition, transform.up);
// Get the initial hand offset so our Lever doesn't jump to the grabber when we first grab it
if (initialOffset == Quaternion.identity) {
initialOffset = originalRot * Quaternion.Inverse(transform.rotation);
}
if (!SnapToGrabber) {
transform.rotation = transform.rotation * initialOffset;
}
if (UseSmoothLook) {
Quaternion newRot = transform.rotation;
transform.rotation = originalRot;
transform.rotation = Quaternion.Lerp(transform.rotation, newRot, Time.deltaTime * SmoothLookSpeed);
}
}
else if (grab != null && !grab.BeingHeld) {
if (ReturnToCenter && AllowPhysicsForces == false) {
transform.localRotation = Quaternion.Lerp(transform.localRotation, Quaternion.identity, Time.deltaTime * ReturnLookSpeed);
}
}
}
///
/// Sets the X local euler angle of the lever. Angle is capped by MinimumXRotation / MaximumXRotation
///
///
public virtual void SetLeverAngle(float angle) {
transform.localEulerAngles = new Vector3(Mathf.Clamp(angle, MinimumXRotation, MaximumXRotation), 0, 0);
}
// Callback for lever percentage change
public virtual void OnLeverChange(float percentage) {
if (onLeverChange != null) {
onLeverChange.Invoke(percentage);
}
}
///
/// Lever Moved to down position
///
public virtual void OnLeverDown() {
if (SwitchOffSound != null) {
audioSource.clip = SwitchOffSound;
audioSource.Play();
}
if (onLeverDown != null) {
onLeverDown.Invoke();
}
switchedOn = false;
if (DropLeverOnActivation && grab != null) {
grab.DropItem(false, false);
}
}
///
/// Lever moved to up position
///
public virtual void OnLeverUp() {
if (SwitchOnSound != null) {
audioSource.clip = SwitchOnSound;
audioSource.Play();
}
// Fire event
if (onLeverUp != null) {
onLeverUp.Invoke();
}
switchedOn = true;
if(DropLeverOnActivation && grab != null) {
grab.DropItem(false, false);
}
}
#if UNITY_EDITOR
void OnDrawGizmosSelected() {
if (ShowEditorGizmos && !Application.isPlaying) {
Vector3 _origin = transform.position;
float rotationDifference = MaximumXRotation - MinimumXRotation;
float lineLength = 0.1f;
float arcLength = 0.1f;
//This is the color of the lines
UnityEditor.Handles.color = Color.cyan;
// Min / Max positions in World space
Vector3 minPosition = _origin + Quaternion.AngleAxis(MinimumXRotation, transform.right) * transform.forward * lineLength;
Vector3 maxPosition = _origin + Quaternion.AngleAxis(MaximumXRotation, transform.right) * transform.forward * lineLength;
//Draw the min / max angle lines
UnityEditor.Handles.DrawLine(_origin, minPosition);
UnityEditor.Handles.DrawLine(_origin, maxPosition);
// Draw starting position line
Debug.DrawLine(transform.position, _origin + Quaternion.AngleAxis(InitialXRotation, transform.right) * transform.forward * lineLength, Color.magenta);
// Fix for exactly 180
if(rotationDifference == 180) {
minPosition = _origin + Quaternion.AngleAxis(MinimumXRotation + 0.01f, transform.right) * transform.forward * lineLength;
}
// Draw the arc
Vector3 _cross = Vector3.Cross(minPosition - _origin, maxPosition - _origin);
if(rotationDifference > 180) {
_cross = Vector3.Cross(maxPosition - _origin, minPosition - _origin);
}
UnityEditor.Handles.color = new Color(0, 255, 255, 0.1f);
UnityEditor.Handles.DrawSolidArc(_origin, _cross, minPosition - _origin, rotationDifference, arcLength);
}
}
#endif
}
}