350 lines
12 KiB
C#
350 lines
12 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace BNG {
|
|
public class SteeringWheel : GrabbableEvents {
|
|
|
|
[Header("Rotation Limits")]
|
|
[Tooltip("Maximum Z value in Local Euler Angles. Can be < -360. Ex : -450")]
|
|
public float MinAngle = -360f;
|
|
|
|
[Tooltip("Maximum Z value in Local Euler Angles. Can be > 360. Ex : 450")]
|
|
public float MaxAngle = 360f;
|
|
|
|
[Header("Rotation Object")]
|
|
[Tooltip("The Transform to rotate on its Z axis.")]
|
|
public Transform RotatorObject;
|
|
|
|
[Header("Rotation Speed")]
|
|
[Tooltip("How fast to move the wheel towards the target angle. 0 = Instant.")]
|
|
public float RotationSpeed = 0f;
|
|
|
|
[Header("Two-Handed Option")]
|
|
[Tooltip("IF true both hands will effect the rotation of the steering wheel while grabbed with both hands. Set to false if you only want one hand to control the rotation.")]
|
|
public bool AllowTwoHanded = true;
|
|
|
|
[Header("Return to Center")]
|
|
public bool ReturnToCenter = false;
|
|
public float ReturnToCenterSpeed = 45;
|
|
|
|
[Header("Debug Options")]
|
|
public Text DebugText;
|
|
|
|
[Header("Events")]
|
|
[Tooltip("Called if the SteeringWheel changes angle. Returns the current angle in degrees, clamped between MinAngle / MaxAngle")]
|
|
public FloatEvent onAngleChange;
|
|
|
|
[Tooltip("Called every frame. Returns the current current rotation between -1, 1")]
|
|
public FloatEvent onValueChange;
|
|
|
|
[Header("Editor Option")]
|
|
[Tooltip("If true will show an angle helper in editor mode (Gizmos must be enabled)")]
|
|
public bool ShowEditorGizmos = true;
|
|
|
|
/// <summary>
|
|
/// Returns the angle of the rotation, taking RotationSpeed into account
|
|
/// </summary>
|
|
public float Angle {
|
|
get {
|
|
return Mathf.Clamp(smoothedAngle, MinAngle, MaxAngle);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Always returns the target angle, not taking RotationSpeed into account
|
|
/// </summary>
|
|
public float RawAngle {
|
|
get {
|
|
return targetAngle;
|
|
}
|
|
}
|
|
|
|
public float ScaleValue {
|
|
get {
|
|
return GetScaledValue(Angle, MinAngle, MaxAngle);
|
|
}
|
|
}
|
|
|
|
public float ScaleValueInverted {
|
|
get {
|
|
return ScaleValue * -1;
|
|
}
|
|
}
|
|
|
|
public float AngleInverted {
|
|
get {
|
|
return Angle * -1;
|
|
}
|
|
}
|
|
|
|
public Grabber PrimaryGrabber {
|
|
get {
|
|
return GetPrimaryGrabber();
|
|
}
|
|
}
|
|
public Grabber SecondaryGrabber {
|
|
get {
|
|
return GetSecondaryGrabber();
|
|
}
|
|
}
|
|
|
|
protected Vector3 rotatePosition;
|
|
protected Vector3 previousPrimaryPosition;
|
|
protected Vector3 previousSecondaryPosition;
|
|
|
|
protected float targetAngle;
|
|
protected float previousTargetAngle;
|
|
|
|
/// <summary>
|
|
/// This angle is smoothed towards target angle in Update using RotationSpeed
|
|
/// </summary>
|
|
protected float smoothedAngle;
|
|
|
|
void Update() {
|
|
|
|
// Calculate rotation if being held or returning to center
|
|
if (grab.BeingHeld && grab.BeingHeldWithTwoHands) {
|
|
UpdateAngleCalculations();
|
|
}
|
|
else if (ReturnToCenter) {
|
|
ReturnToCenterAngle();
|
|
}
|
|
|
|
// Apply the new angle
|
|
ApplyAngleToSteeringWheel(Angle);
|
|
|
|
// Call any events
|
|
CallEvents();
|
|
|
|
UpdatePreviewText();
|
|
|
|
// Update the angle so we can compare it next frame
|
|
UpdatePreviousAngle(targetAngle);
|
|
}
|
|
|
|
public virtual void UpdateAngleCalculations() {
|
|
|
|
float angleAdjustment = 0f;
|
|
|
|
// Add first Grabber
|
|
if (PrimaryGrabber) {
|
|
rotatePosition = transform.InverseTransformPoint(PrimaryGrabber.transform.position);
|
|
rotatePosition = new Vector3(rotatePosition.x, rotatePosition.y, 0);
|
|
|
|
// Add in the angles to turn
|
|
angleAdjustment += GetRelativeAngle(rotatePosition, previousPrimaryPosition);
|
|
|
|
previousPrimaryPosition = rotatePosition;
|
|
}
|
|
|
|
// Add second Grabber
|
|
if (AllowTwoHanded && SecondaryGrabber != null) {
|
|
rotatePosition = transform.InverseTransformPoint(SecondaryGrabber.transform.position);
|
|
rotatePosition = new Vector3(rotatePosition.x, rotatePosition.y, 0);
|
|
|
|
// Add in the angles to turn
|
|
angleAdjustment += GetRelativeAngle(rotatePosition, previousSecondaryPosition);
|
|
|
|
previousSecondaryPosition = rotatePosition;
|
|
}
|
|
|
|
// Divide by two if being held by two hands
|
|
if(PrimaryGrabber != null && SecondaryGrabber != null) {
|
|
angleAdjustment *= 0.5f;
|
|
}
|
|
|
|
// Apply the angle adjustment
|
|
targetAngle -= angleAdjustment;
|
|
|
|
// Update Smooth Angle
|
|
// Instant Rotation
|
|
if(RotationSpeed == 0) {
|
|
smoothedAngle = targetAngle;
|
|
}
|
|
// Apply smoothing based on RotationSpeed
|
|
else {
|
|
smoothedAngle = Mathf.Lerp(smoothedAngle, targetAngle, Time.deltaTime * RotationSpeed);
|
|
}
|
|
|
|
// Scrub the final results
|
|
if (MinAngle != 0 && MaxAngle != 0) {
|
|
targetAngle = Mathf.Clamp(targetAngle, MinAngle, MaxAngle);
|
|
smoothedAngle = Mathf.Clamp(smoothedAngle, MinAngle, MaxAngle);
|
|
}
|
|
}
|
|
|
|
public float GetRelativeAngle(Vector3 position1, Vector3 position2) {
|
|
|
|
// Are we turning left or right?
|
|
if (Vector3.Cross(position1, position2).z < 0) {
|
|
return -Vector3.Angle(position1, position2);
|
|
}
|
|
|
|
return Vector3.Angle(position1, position2);
|
|
}
|
|
|
|
public virtual void ApplyAngleToSteeringWheel(float angle) {
|
|
RotatorObject.localEulerAngles = new Vector3(0, 0, angle);
|
|
}
|
|
|
|
public virtual void UpdatePreviewText() {
|
|
if (DebugText) {
|
|
// Invert the values for display. Inverted values are easier to read (i.e 5 = clockwise rotation of 5 degrees).
|
|
DebugText.text = String.Format("{0}\n{1}", (int)AngleInverted, (ScaleValueInverted).ToString("F2"));
|
|
}
|
|
}
|
|
|
|
public GameObject Sockets;
|
|
|
|
public void DisableWheel()
|
|
{
|
|
grab.ForceRelease();
|
|
Sockets.SetActive(false);
|
|
grab.enabled = false;
|
|
}
|
|
|
|
public virtual void CallEvents() {
|
|
// Call events
|
|
if (targetAngle != previousTargetAngle) {
|
|
onAngleChange.Invoke(targetAngle);
|
|
}
|
|
|
|
onValueChange.Invoke(ScaleValue);
|
|
}
|
|
|
|
public override void OnGrab(Grabber grabber) {
|
|
// Primary or secondary that grabbed us?
|
|
if(grabber == SecondaryGrabber) {
|
|
previousSecondaryPosition = transform.InverseTransformPoint(SecondaryGrabber.transform.position);
|
|
|
|
// Discard the Z value
|
|
previousSecondaryPosition = new Vector3(previousSecondaryPosition.x, previousSecondaryPosition.y, 0);
|
|
}
|
|
// Primary
|
|
else {
|
|
previousPrimaryPosition = transform.InverseTransformPoint(PrimaryGrabber.transform.position);
|
|
|
|
// Discard the Z value
|
|
previousPrimaryPosition = new Vector3(previousPrimaryPosition.x, previousPrimaryPosition.y, 0);
|
|
}
|
|
}
|
|
|
|
public virtual void ReturnToCenterAngle() {
|
|
|
|
bool wasUnderZero = smoothedAngle < 0;
|
|
|
|
if (smoothedAngle > 0) {
|
|
smoothedAngle -= Time.deltaTime * ReturnToCenterSpeed;
|
|
}
|
|
else if (smoothedAngle < 0) {
|
|
smoothedAngle += Time.deltaTime * ReturnToCenterSpeed;
|
|
}
|
|
|
|
// Overshot
|
|
if (wasUnderZero && smoothedAngle > 0) {
|
|
smoothedAngle = 0;
|
|
}
|
|
else if (!wasUnderZero && smoothedAngle < 0) {
|
|
smoothedAngle = 0;
|
|
}
|
|
|
|
// Snap if very close
|
|
if (smoothedAngle < 0.02f && smoothedAngle > -0.02f) {
|
|
smoothedAngle = 0;
|
|
}
|
|
|
|
// Set the target angle to our newly calculated angle
|
|
targetAngle = smoothedAngle;
|
|
}
|
|
|
|
public Grabber GetPrimaryGrabber() {
|
|
if (grab.HeldByGrabbers != null) {
|
|
for (int x = 0; x < grab.HeldByGrabbers.Count; x++) {
|
|
Grabber g = grab.HeldByGrabbers[x];
|
|
if (g.HandSide == ControllerHand.Right) {
|
|
return g;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public Grabber GetSecondaryGrabber() {
|
|
if (grab.HeldByGrabbers != null) {
|
|
for (int x = 0; x < grab.HeldByGrabbers.Count; x++) {
|
|
Grabber g = grab.HeldByGrabbers[x];
|
|
if (g.HandSide == ControllerHand.Left) {
|
|
return g;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public virtual void UpdatePreviousAngle(float angle) {
|
|
previousTargetAngle = angle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value between -1 and 1
|
|
/// </summary>
|
|
/// <param name="value">Current value to compute against</param>
|
|
/// <param name="min">Minimum value of range used for conversion. </param>
|
|
/// <param name="max">Maximum value of range used for conversion. Must be greater then min</param>
|
|
/// <returns>Value between -1 and 1</returns>
|
|
public virtual float GetScaledValue(float value, float min, float max) {
|
|
float range = (max - min) / 2f;
|
|
float returnValue = ((value - min) / range) - 1;
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public void OnDrawGizmosSelected() {
|
|
if (ShowEditorGizmos && !Application.isPlaying) {
|
|
|
|
Vector3 origin = transform.position;
|
|
float rotationDifference = MaxAngle - MinAngle;
|
|
|
|
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(MinAngle, transform.forward) * transform.up * lineLength;
|
|
Vector3 maxPosition = origin + Quaternion.AngleAxis(MaxAngle, transform.forward) * transform.up * 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(0, transform.up) * transform.up * lineLength, Color.magenta);
|
|
|
|
// Fix for exactly 180
|
|
if (rotationDifference == 180) {
|
|
minPosition = origin + Quaternion.AngleAxis(MinAngle + 0.01f, transform.up) * transform.up * 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
|
|
}
|
|
}
|
|
|