558 lines
18 KiB
C#
558 lines
18 KiB
C#
|
using UnityEditor;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.Events;
|
|||
|
|
|||
|
/*
|
|||
|
* Thank you for using my Lockpicking asset! I hope it works great in your game. You can extend it to suit your game by
|
|||
|
* manipulating some of the code below. For instance, if your player can have a various level of "lockpicking" skill,
|
|||
|
* you may consider multiplying the value of lockGive by their skill, so that a higher skilled player would find it
|
|||
|
* easier to open a lock.
|
|||
|
*
|
|||
|
* Enjoy!
|
|||
|
*/
|
|||
|
|
|||
|
namespace Lockpicking
|
|||
|
{
|
|||
|
//[ExecuteInEditMode]
|
|||
|
[RequireComponent(typeof(LockEmissive))]
|
|||
|
public class Cipher : MonoBehaviour
|
|||
|
{
|
|||
|
// Events
|
|||
|
UnityEvent lockOpen = new UnityEvent();
|
|||
|
|
|||
|
[Header("Speed Settings")]
|
|||
|
[Tooltip("Maximum shake distance per shake change.")]
|
|||
|
[SerializeField] private float maxShake = 0.5f;
|
|||
|
|
|||
|
[Tooltip("Amount of time between shake changes when shaking.")]
|
|||
|
[SerializeField] private float shakeTime = 0.1f;
|
|||
|
|
|||
|
[Tooltip("Speed of the wheel when turning.")]
|
|||
|
public float turnSpeed = 180f;
|
|||
|
|
|||
|
|
|||
|
[Header("Lock Setup")]
|
|||
|
[Tooltip("Number of symbols on the wheel. Should be spaced evenly.")]
|
|||
|
public int symbolCount = 18;
|
|||
|
|
|||
|
[Tooltip("How close do we have to be to perfectly aligned for the placement of a wheel to count?")]
|
|||
|
public float closeEnough = 3f;
|
|||
|
|
|||
|
[Tooltip("If true, wheels will \"snap\" into a new random spot on setup. If false, they will rotate to those spots.")]
|
|||
|
public bool quickReset;
|
|||
|
|
|||
|
[Tooltip("If true, the active wheel will be highlighted using the emissive map of the material.")]
|
|||
|
public bool highlightActiveWheel = true;
|
|||
|
|
|||
|
[Tooltip("If true, wheels will move to the closest spot when the player stops input movement.")]
|
|||
|
public bool moveToClosestSpot;
|
|||
|
|
|||
|
|
|||
|
[Header("Lock Details")]
|
|||
|
[Tooltip("When true, the lock has been opened.")]
|
|||
|
public bool isOpen;
|
|||
|
|
|||
|
[Tooltip("When true, the lock will reset itself on awake. Set this to false if you are choosing a specific combination.")]
|
|||
|
public bool resetOnAwake;
|
|||
|
|
|||
|
|
|||
|
[Header("Animation Trigger Strings")]
|
|||
|
public string openTrigger = "Open";
|
|||
|
public string closeTrigger = "Close";
|
|||
|
|
|||
|
|
|||
|
// Private animation hashes
|
|||
|
private int _openTrigger;
|
|||
|
private int _closeTrigger;
|
|||
|
|
|||
|
// Audio Settings
|
|||
|
[Range(0f, 1f)] public float clickVolumeMin = 0.1f;
|
|||
|
[Range(0f, 1f)] public float clickVolumeMax = 0.4f;
|
|||
|
[Range(0f, 1f)] public float squeekVolumeMin = 0.1f;
|
|||
|
[Range(0f, 1f)] public float squeekVolumeMax = 0.4f;
|
|||
|
[Range(0, 100)] public int squeekChance = 50;
|
|||
|
[Range(0f, 15f)] public float squeekRate = 3.5f;
|
|||
|
|
|||
|
// Private variables
|
|||
|
private int _activeWheel;
|
|||
|
public float[] unlockAngle; // All the unlock angles for each wheel
|
|||
|
public float[] startAngle; // All the starting angles for each wheel
|
|||
|
public float[] turnedDistance; // amount each wheel has been turned
|
|||
|
public float[] turnedDistancePrev; // amount each wheel has been turned
|
|||
|
public bool[] isMoving; // Records whether the wheels are moving
|
|||
|
public bool playAudioOnSetup = true;
|
|||
|
private bool isReady; // WHen true, player can interact
|
|||
|
private bool isShaking; // When true, the lock is shaking (trying to be opened but in the wrong combination)
|
|||
|
private Vector3 preshake; // Saves the pre-shake angles
|
|||
|
private float shakeTimer; // Counter for the shake Time
|
|||
|
private bool _visualizeSolution;
|
|||
|
private bool _visualizeStart;
|
|||
|
private float _speed;
|
|||
|
private float squeekTimer = 0f;
|
|||
|
private float getReadyPostReadyTimer = 0.3f;
|
|||
|
|
|||
|
[Header("Plumbing")]
|
|||
|
public GameObject[] wheels;
|
|||
|
public LocksetAudio audioClick;
|
|||
|
public LocksetAudio audioSqueek;
|
|||
|
public LocksetAudio audioJiggle;
|
|||
|
public LocksetAudio audioJiggle2;
|
|||
|
public LocksetAudio audioJiggle3;
|
|||
|
public LocksetAudio audioOpen;
|
|||
|
private Animator animator;
|
|||
|
private LockEmissive lockEmissive;
|
|||
|
private LocksetAudio _locksetAudio;
|
|||
|
|
|||
|
|
|||
|
public int ActiveWheel => _activeWheel;
|
|||
|
|
|||
|
public bool VisualizeSolution
|
|||
|
{
|
|||
|
get { return _visualizeSolution; }
|
|||
|
set
|
|||
|
{
|
|||
|
_visualizeSolution = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public bool VisualizeStart
|
|||
|
{
|
|||
|
get { return _visualizeStart; }
|
|||
|
set
|
|||
|
{
|
|||
|
_visualizeStart = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public float ActiveWheelStart
|
|||
|
{
|
|||
|
get { return startAngle[ActiveWheel]; }
|
|||
|
set { startAngle[ActiveWheel] = value; }
|
|||
|
}
|
|||
|
public float ActiveWheelSolution
|
|||
|
{
|
|||
|
get { return unlockAngle[ActiveWheel]; }
|
|||
|
set { unlockAngle[ActiveWheel] = value; }
|
|||
|
}
|
|||
|
|
|||
|
void OnValidate()
|
|||
|
{
|
|||
|
#if UNITY_EDITOR
|
|||
|
|
|||
|
if (unlockAngle.Length != wheels.Length )
|
|||
|
{
|
|||
|
Debug.Log("Updating Lengths");
|
|||
|
unlockAngle = new float[wheels.Length];
|
|||
|
startAngle = new float[wheels.Length];
|
|||
|
turnedDistance = new float[wheels.Length];
|
|||
|
isMoving = new bool[wheels.Length];
|
|||
|
}
|
|||
|
|
|||
|
if (_visualizeSolution)
|
|||
|
SetRotationToSolution();
|
|||
|
else if (_visualizeStart)
|
|||
|
SetRotationToStart();
|
|||
|
else
|
|||
|
SetRotationToZero();
|
|||
|
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
unlockAngle[i] = ClosestSpot(unlockAngle[i]);
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
public void EditorOnValidate()
|
|||
|
{
|
|||
|
#if UNITY_EDITOR
|
|||
|
EditorUtility.SetDirty(this);
|
|||
|
#endif
|
|||
|
OnValidate();
|
|||
|
}
|
|||
|
|
|||
|
private void SetRotationToSolution()
|
|||
|
{
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
Vector3 eulerAngles = wheels[i].transform.localEulerAngles;
|
|||
|
eulerAngles.x = unlockAngle[i];
|
|||
|
wheels[i].transform.localEulerAngles = eulerAngles;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void SetRotationToStart()
|
|||
|
{
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
Vector3 eulerAngles = wheels[i].transform.localEulerAngles;
|
|||
|
eulerAngles.x = startAngle[i];
|
|||
|
wheels[i].transform.localEulerAngles = eulerAngles;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void SetRotationToZero()
|
|||
|
{
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
wheels[i].transform.localEulerAngles = new Vector3();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void Awake()
|
|||
|
{
|
|||
|
squeekTimer = squeekRate;
|
|||
|
_locksetAudio = GetComponent<LocksetAudio>();
|
|||
|
animator = GetComponent<Animator>();
|
|||
|
lockEmissive = GetComponent<LockEmissive>();
|
|||
|
if (!highlightActiveWheel)
|
|||
|
lockEmissive.highlightRenderer = null;
|
|||
|
|
|||
|
_openTrigger = Animator.StringToHash(openTrigger);
|
|||
|
_closeTrigger = Animator.StringToHash(closeTrigger);
|
|||
|
|
|||
|
if (resetOnAwake)
|
|||
|
{
|
|||
|
ResetLock();
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public float DistanceLeft(int i)
|
|||
|
{
|
|||
|
if (turnedDistance.Length >= i + 1)
|
|||
|
{
|
|||
|
float left = (unlockAngle[i] - turnedDistance[i]);
|
|||
|
if (left < -350)
|
|||
|
left += 360;
|
|||
|
if (left > 350)
|
|||
|
left -= 360;
|
|||
|
|
|||
|
return left;
|
|||
|
}
|
|||
|
|
|||
|
return 9999;
|
|||
|
}
|
|||
|
|
|||
|
void Update()
|
|||
|
{
|
|||
|
|
|||
|
// Do actions to get ready, if we are not ready
|
|||
|
if (!isReady)
|
|||
|
{
|
|||
|
GetReady();
|
|||
|
if (playAudioOnSetup)
|
|||
|
DoAudio();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (getReadyPostReadyTimer > 0)
|
|||
|
getReadyPostReadyTimer -= Time.deltaTime;
|
|||
|
|
|||
|
if (moveToClosestSpot)
|
|||
|
MoveToClosestSpot();
|
|||
|
ResetMovement();
|
|||
|
|
|||
|
if (highlightActiveWheel)
|
|||
|
{
|
|||
|
lockEmissive.SetHighlightRenderer(wheels[_activeWheel].GetComponent<Renderer>());
|
|||
|
}
|
|||
|
|
|||
|
DoAudio();
|
|||
|
}
|
|||
|
|
|||
|
private void DoAudio()
|
|||
|
{
|
|||
|
for (int i = 0; i < turnedDistance.Length; i++)
|
|||
|
{
|
|||
|
float turnSpeed = 0f;
|
|||
|
if (turnedDistance[i] != turnedDistancePrev[i])
|
|||
|
{
|
|||
|
if (getReadyPostReadyTimer <= 0 || playAudioOnSetup)
|
|||
|
{
|
|||
|
if (audioSqueek)
|
|||
|
{
|
|||
|
// Squeek
|
|||
|
if (squeekRate > 0)
|
|||
|
{
|
|||
|
squeekTimer -= Time.deltaTime;
|
|||
|
if (squeekTimer <= 0)
|
|||
|
{
|
|||
|
if (Random.Range(0, 100) < squeekChance)
|
|||
|
{
|
|||
|
audioSqueek.PlayAudioClip(Random.Range(squeekVolumeMin, squeekVolumeMax));
|
|||
|
}
|
|||
|
squeekTimer = squeekRate;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (audioClick)
|
|||
|
{
|
|||
|
// Clicks
|
|||
|
turnSpeed = turnedDistance[i] - turnedDistancePrev[i];
|
|||
|
|
|||
|
float turnedMod = turnedDistance[i] % (360 / symbolCount);
|
|||
|
float turnedModPrev = turnedDistancePrev[i] % (360 / symbolCount);
|
|||
|
|
|||
|
if ((turnSpeed > 0 || turnSpeed < -350) && (turnedMod < turnedModPrev || turnedMod > 0 && turnedModPrev < 0))
|
|||
|
{
|
|||
|
audioClick.PlayAudioClip(Random.Range(clickVolumeMin, clickVolumeMax));
|
|||
|
}
|
|||
|
else if ((turnSpeed < 0 || turnSpeed > 350) && (turnedMod > turnedModPrev || turnedMod < 0 && turnedModPrev > 0))
|
|||
|
{
|
|||
|
audioClick.PlayAudioClip(Random.Range(clickVolumeMin, clickVolumeMax));
|
|||
|
}
|
|||
|
else if (turnedMod == 0)
|
|||
|
{
|
|||
|
audioClick.PlayAudioClip(Random.Range(clickVolumeMin, clickVolumeMax));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
turnedDistancePrev[i] = turnedDistance[i];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public int NextWheelIndex => (_activeWheel + 1) < wheels.Length ? (_activeWheel + 1) : 0;
|
|||
|
|
|||
|
public int PrevWheelIndex => (_activeWheel - 1) < 0 ? (wheels.Length - 1) : (_activeWheel - 1);
|
|||
|
|
|||
|
public void SelectWheel(int value)
|
|||
|
{
|
|||
|
_activeWheel = value < wheels.Length && value >= 0 ? value : 0;
|
|||
|
}
|
|||
|
|
|||
|
public void ResetMovement()
|
|||
|
{
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
isMoving[i] = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void MoveToClosestSpot()
|
|||
|
{
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
// Only run this if the wheel is not moving (i.e. being moved by the player)
|
|||
|
if (!isMoving[i])
|
|||
|
{
|
|||
|
if (turnedDistance[i] < 0)
|
|||
|
{
|
|||
|
turnedDistance[i] += 360;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
float smallestDistance = 360f;
|
|||
|
float closestRotation = 0f;
|
|||
|
|
|||
|
for (int d = 0; d < 360 / symbolCount; d++)
|
|||
|
{
|
|||
|
float diff = d * (360 / symbolCount);
|
|||
|
if (Mathf.Abs(Mathf.Abs(turnedDistance[i]) - diff) < smallestDistance)
|
|||
|
{
|
|||
|
smallestDistance = Mathf.Abs(Mathf.Abs(turnedDistance[i]) - diff);
|
|||
|
closestRotation = diff;
|
|||
|
}
|
|||
|
}
|
|||
|
MoveTowards(i, closestRotation);
|
|||
|
*/
|
|||
|
MoveTowards(i, ClosestSpot(Mathf.Abs(turnedDistance[i])));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private float ClosestSpot(float value)
|
|||
|
{
|
|||
|
float smallestDistance = 360f;
|
|||
|
float closestRotation = 0f;
|
|||
|
|
|||
|
for (int d = -360; d < 360 / symbolCount; d++)
|
|||
|
{
|
|||
|
float diff = d * (360 / symbolCount);
|
|||
|
if (Mathf.Abs(value - diff) < smallestDistance)
|
|||
|
{
|
|||
|
smallestDistance = Mathf.Abs(value - diff);
|
|||
|
closestRotation = diff;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return closestRotation;
|
|||
|
}
|
|||
|
|
|||
|
public void TryOpen()
|
|||
|
{
|
|||
|
if (!isShaking)
|
|||
|
preshake = transform.localEulerAngles;
|
|||
|
|
|||
|
if (!isOpen)
|
|||
|
{
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
if (Mathf.Abs(DistanceLeft(i)) > closeEnough)
|
|||
|
{
|
|||
|
Shake();
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
isOpen = true;
|
|||
|
animator.SetTrigger(_openTrigger);
|
|||
|
|
|||
|
audioOpen.PlayOnce();
|
|||
|
|
|||
|
// Invoke the event for any other scripts that are listening
|
|||
|
lockOpen.Invoke();
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public void StopShaking()
|
|||
|
{
|
|||
|
transform.localEulerAngles = preshake;
|
|||
|
isShaking = false;
|
|||
|
|
|||
|
if (audioJiggle)
|
|||
|
{
|
|||
|
audioJiggle.StopLoop();
|
|||
|
audioJiggle2?.StopLoop();
|
|||
|
audioJiggle3?.StopLoop();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void Shake()
|
|||
|
{
|
|||
|
if (!isShaking)
|
|||
|
isShaking = true;
|
|||
|
|
|||
|
shakeTimer -= Time.deltaTime;
|
|||
|
if (shakeTimer <= 0)
|
|||
|
{
|
|||
|
// Start with the current values
|
|||
|
Vector3 newShake = preshake;
|
|||
|
|
|||
|
// Add some modification
|
|||
|
//newShake.z += Random.Range(-maxShake, maxShake);
|
|||
|
newShake.x += Random.Range(-maxShake, maxShake);
|
|||
|
newShake.y += Random.Range(-maxShake, maxShake);
|
|||
|
|
|||
|
// Set the value + modification
|
|||
|
transform.localEulerAngles = newShake;
|
|||
|
|
|||
|
// Reset the timer
|
|||
|
shakeTimer = shakeTime;
|
|||
|
}
|
|||
|
|
|||
|
if (audioJiggle)
|
|||
|
{
|
|||
|
audioJiggle.PlayLoop();
|
|||
|
audioJiggle2?.PlayLoop();
|
|||
|
audioJiggle3?.PlayLoop();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void GetReady()
|
|||
|
{
|
|||
|
int readyCount = 0;
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
int speed = 1;
|
|||
|
float distanceLeft = startAngle[i] - turnedDistance[i];
|
|||
|
|
|||
|
if (_visualizeSolution)
|
|||
|
{
|
|||
|
distanceLeft = unlockAngle[i] - turnedDistance[i];
|
|||
|
}
|
|||
|
|
|||
|
if (distanceLeft < 0)
|
|||
|
{
|
|||
|
speed = -1;
|
|||
|
}
|
|||
|
|
|||
|
float turnAmount = speed * turnSpeed * Time.deltaTime;
|
|||
|
if (Mathf.Abs(distanceLeft) < Mathf.Abs(turnAmount))
|
|||
|
{
|
|||
|
turnAmount = distanceLeft;
|
|||
|
readyCount++;
|
|||
|
}
|
|||
|
|
|||
|
if (quickReset || _visualizeSolution)
|
|||
|
{
|
|||
|
turnAmount = distanceLeft;
|
|||
|
readyCount++;
|
|||
|
}
|
|||
|
|
|||
|
SetTurnedDistance(i, turnAmount);
|
|||
|
RotateWheel(wheels[i].transform, turnAmount);
|
|||
|
}
|
|||
|
|
|||
|
if (readyCount == wheels.Length)
|
|||
|
isReady = true;
|
|||
|
}
|
|||
|
|
|||
|
public void MoveActiveWheel(int speed)
|
|||
|
{
|
|||
|
MoveWheel(_activeWheel, speed);
|
|||
|
}
|
|||
|
|
|||
|
public void MoveWheel(int i, int speed, float destination = 999f)
|
|||
|
{
|
|||
|
if (isReady)
|
|||
|
{
|
|||
|
isMoving[i] = true; // Mark this wheel as currently moving
|
|||
|
float turnAmount = speed * turnSpeed * Time.deltaTime;
|
|||
|
if (destination != 999)
|
|||
|
{
|
|||
|
if (Mathf.Abs(destination - turnedDistance[i]) < Mathf.Abs(turnAmount))
|
|||
|
{
|
|||
|
turnAmount = destination - turnedDistance[i];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
SetTurnedDistance(i, turnAmount);
|
|||
|
RotateWheel(wheels[i].transform, turnAmount);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void RotateWheel(Transform transform, float value)
|
|||
|
{
|
|||
|
transform.Rotate(value, 0.0f, 0.0f, Space.Self);
|
|||
|
}
|
|||
|
|
|||
|
public void MoveTowards(int i, float closestRotation)
|
|||
|
{
|
|||
|
if (turnedDistance[i] > closestRotation)
|
|||
|
MoveWheel(i, -1, closestRotation);
|
|||
|
else
|
|||
|
MoveWheel(i, 1, closestRotation);
|
|||
|
}
|
|||
|
|
|||
|
public void SetTurnedDistance(int i, float distance)
|
|||
|
{
|
|||
|
turnedDistance[i] += distance;
|
|||
|
if (turnedDistance[i] > 360)
|
|||
|
turnedDistance[i] -= 360;
|
|||
|
if (turnedDistance[i] < -360)
|
|||
|
turnedDistance[i] += 360;
|
|||
|
}
|
|||
|
|
|||
|
public void ResetLock()
|
|||
|
{
|
|||
|
Debug.Log("ResetLock");
|
|||
|
isOpen = false;
|
|||
|
isReady = false;
|
|||
|
animator.SetTrigger(_closeTrigger);
|
|||
|
|
|||
|
// Shuffle each wheel
|
|||
|
for (int i = 0; i < wheels.Length; i++)
|
|||
|
{
|
|||
|
unlockAngle[i] = RandomAngle;
|
|||
|
startAngle[i] = RandomAngle;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private float RandomAngle => (Random.Range(0, symbolCount) * (360 / symbolCount) - 180);
|
|||
|
|
|||
|
}
|
|||
|
}
|