afenkurtz

Fallout Lockpicking Recreation

Feb 5th, 2024
1,056
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 8.25 KB | Gaming | 0 0
  1. /*
  2.  * This script is highly derivitive of Zeppelin Games' lockpicking script from the following tutorial: https://www.youtube.com/watch?v=68iYL-rktQ4&list=PLEj1kOxzPTLWX_q_XvjFF9h_3cS4C1jyu&index=12
  3.  * My adaptation is a little bloated, but operates on much the same logic and includes sound effects, animations, and has been broken into parts to keep the update function cleaner.
  4.  */
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using UnityEngine;
  8.  
  9. public class LockPicking : MonoBehaviour
  10. {
  11.     [Header("General")]
  12.     [Tooltip("The lockpick's own camera.")]
  13.     [SerializeField] private Camera cam;
  14.     [Tooltip("The inner-lock object.")]
  15.     [SerializeField] private Transform innerLock;
  16.     [Tooltip("The empty that the pick will follow and rotate around")]
  17.     [SerializeField] private Transform pickPosition;
  18.     [Tooltip("How far its possible to rotate the lock and pick from the middle (half of total rotation range).")]
  19.     [SerializeField] private float maxAngle = 90.0f;
  20.     [Tooltip("How fast the lock and pick turn.")]
  21.     [SerializeField] private float lockSpeed = 10.0f;
  22.  
  23.     [Header("Difficulty")]
  24.     [Tooltip("Range in degrees from which the lock can be picked.")]
  25.     [Range(1, 25)] [SerializeField] private float lockRange = 10.0f;
  26.     [Tooltip("The max amount of time a pick can be held down for until it breaks (in seconds).")]
  27.     [Range(1.0f, 5.0f)] [SerializeField] private float maxPickStrength = 2.0f;
  28.  
  29.     [Header("Animation")]
  30.     [Tooltip("Animator controller.")]
  31.     [SerializeField] private Animator anim;
  32.  
  33.     [Header("Audio")]
  34.     [Tooltip("The main audio source.")]
  35.     [SerializeField] private AudioSource audioSourceMain;
  36.     [Tooltip("The looping audio source.")]
  37.     [SerializeField] private AudioSource audioSourceLoop;
  38.     [Tooltip("The unlock audio clip.")]
  39.     [SerializeField] private AudioClip audioClipUnlock;
  40.     [Tooltip("The audio clip for picking a lock in the wrong position.")]
  41.     [SerializeField] private AudioClip audioClipLoop;
  42.     [Tooltip("The audio clip for breaking a pick.")]
  43.     [SerializeField] private AudioClip audioClipBreak;
  44.     [Tooltip("The audio clips to be used. The first one is the initial movement sound.")]
  45.     [SerializeField] private AudioClip[] audioClips;
  46.  
  47.     // The angle of the pick
  48.     private float eulerAngle;
  49.     // The angle of the pick last frame
  50.     private float eulerAngleLastFrame;
  51.     // The angle to base the unlock range from
  52.     private float unlockAngle;
  53.     // Keeps track of current unlock range
  54.     private Vector2 unlockRange;
  55.     // Keeps track of if a button is held
  56.     private float keyPressTime = 0;
  57.     // To turn on/off allowing user to use the pick
  58.     private bool movePick = true;
  59.     // To rotate the inner lock
  60.     private float lockLerp;
  61.     // To keep track of how far the inner lock can rotate
  62.     private float maxRotation;
  63.     // Used to play intro animation only on first attempt
  64.     private int attempts;
  65.     // To keep track of the current pick's strength (time in seconds it can be held before breaking)
  66.     private float pickStrength;
  67.     // To keep track of how long an pick is held down for
  68.     private float attemptDuration;
  69.  
  70.  
  71.  
  72.     void Start()
  73.     {
  74.         NewLock();
  75.         NewPick();
  76.         audioSourceLoop.clip = audioClipLoop;
  77.         attempts = 0;
  78.     }
  79.  
  80.  
  81.     private void Update()
  82.     {
  83.         transform.localPosition = pickPosition.position;
  84.  
  85.         if (attempts == 0)
  86.         {
  87.             anim.Play("Entry");
  88.             attempts++;
  89.         }
  90.  
  91.         RotatePick();
  92.  
  93.         UnlockAttemptInput();
  94.  
  95.         if (keyPressTime == 1)
  96.         {
  97.             attemptDuration += Time.deltaTime;
  98.  
  99.             if (attemptDuration > pickStrength)
  100.             {
  101.                 StartCoroutine(BreakPick());
  102.             }
  103.         }
  104.  
  105.         RotateLock();
  106.  
  107.         CheckUnlock();
  108.     }
  109.  
  110.  
  111.     private void RotatePick()
  112.     {
  113.         if (movePick)
  114.         {
  115.             // creates direction from mouse to current position
  116.             Vector3 dir = Input.mousePosition - cam.WorldToScreenPoint(transform.position);
  117.  
  118.             // axis to rotate around
  119.             eulerAngle = Vector3.Angle(dir, Vector3.up);
  120.  
  121.             Vector3 cross = Vector3.Cross(Vector3.up, dir);
  122.             // to get both neg and pos range
  123.             if (cross.z < 0)
  124.                 eulerAngle = -eulerAngle;
  125.  
  126.             eulerAngle = Mathf.Clamp(eulerAngle, -maxAngle, maxAngle);
  127.  
  128.             // if the pick is moving,
  129.             if (!Mathf.Approximately(eulerAngle, eulerAngleLastFrame))
  130.             {
  131.                 if (audioSourceMain.isPlaying)
  132.                 {
  133.                     // do nothing (do not play clip)
  134.                 }
  135.                 else
  136.                 {
  137.                     audioSourceMain.clip = audioClips[Random.Range(1, audioClips.Length)];
  138.                     audioSourceMain.Play();
  139.                 }
  140.             }
  141.  
  142.             // rotate around x axis
  143.             Quaternion rotateTo = Quaternion.AngleAxis(eulerAngle, Vector3.forward);
  144.             transform.rotation = rotateTo;
  145.  
  146.             eulerAngleLastFrame = eulerAngle;
  147.         }
  148.     }
  149.  
  150.     private void UnlockAttemptInput()
  151.     {
  152.         if (Input.GetKeyDown(KeyCode.W))
  153.         {
  154.             // cannot move bobbypin when trying to pick lock
  155.             movePick = false;
  156.             keyPressTime = 1;
  157.  
  158.             audioSourceMain.clip = audioClips[0];
  159.             audioSourceMain.Play();
  160.  
  161.            
  162.         }
  163.         if (Input.GetKeyUp(KeyCode.W))
  164.         {
  165.             movePick = true;
  166.             keyPressTime = 0;
  167.             attemptDuration = 0.0f;
  168.  
  169.             audioSourceLoop.Stop();
  170.         }
  171.     }
  172.  
  173.     private void RotateLock()
  174.     {
  175.         keyPressTime = Mathf.Clamp(keyPressTime, 0, 1);
  176.  
  177.         float percentage = Mathf.Round(100 - Mathf.Abs((eulerAngle - unlockAngle) / 180 /* he says 180 in the video but types 100 */) * 100);   // 100 there instead of 180 would allow you to turn the inner lock clockwise in some instances
  178.         // for rotating inner lock
  179.         float lockRotation = ((percentage / 100) * maxAngle) * keyPressTime;
  180.         maxRotation = (percentage / 100) * maxAngle;
  181.  
  182.         lockLerp = Mathf.LerpAngle(innerLock.eulerAngles.z, lockRotation, Time.deltaTime * lockSpeed);
  183.         innerLock.eulerAngles = new Vector3(0, 0, lockLerp);
  184.     }
  185.  
  186.  
  187.     private void CheckUnlock()
  188.     {
  189.         if (lockLerp >= maxRotation - 1)
  190.         {
  191.             if (eulerAngle < unlockRange.y && eulerAngle > unlockRange.x)
  192.             {
  193.                 NewLock();
  194.  
  195.                 movePick = true;
  196.                 keyPressTime = 0;
  197.                 attemptDuration = 0.0f;
  198.  
  199.                 audioSourceMain.clip = audioClipUnlock;
  200.                 audioSourceMain.Play();
  201.             }
  202.             else if (keyPressTime == 1)
  203.             {
  204.                 // make lockpick shake if not unlocked
  205.                 float randomRotation = Random.insideUnitCircle.x;
  206.                 transform.eulerAngles += new Vector3(0, 0, Random.Range(-randomRotation, randomRotation));
  207.  
  208.                 if (audioSourceLoop.isPlaying)
  209.                 {
  210.                     // do nothing (do not play clip)
  211.                 }
  212.                 else
  213.                 {
  214.                     audioSourceLoop.Play();
  215.                 }
  216.             }
  217.         }
  218.     }
  219.  
  220.  
  221.     private IEnumerator BreakPick()
  222.     {
  223.         audioSourceLoop.Stop();
  224.         anim.Play("BreakPick");
  225.         movePick = false;
  226.         keyPressTime = 0;
  227.         attemptDuration = 0.0f;
  228.  
  229.         audioSourceMain.clip = audioClipBreak;
  230.         audioSourceMain.Play();
  231.  
  232.         NewPick();
  233.         attempts++;
  234.  
  235.         yield return new WaitForSeconds(3.0f);
  236.     }
  237.  
  238.  
  239.     private void NewPick()
  240.     {
  241.         pickStrength = Random.Range(0.25f, maxPickStrength);
  242.     }
  243.  
  244.  
  245.     [ContextMenu("Generate new lock")]
  246.     private void NewLock()
  247.     {
  248.         // unlocking angle isnt larger than our lock range
  249.         unlockAngle = Random.Range(-maxAngle + lockRange, maxAngle - lockRange);
  250.         // gives a bit of space for moving the pick in to unlock
  251.         // wider you make the unlock range, the easier the lock becomes
  252.         unlockRange = new Vector2(unlockAngle - lockRange, unlockAngle + lockRange);
  253.         attempts++;
  254.     }
  255. }
  256.  
Advertisement
Add Comment
Please, Sign In to add comment