RotX18 / MP_Group4_EscapingReality

VR Escape room game developed in Unity
3 stars 0 forks source link

Lock Object #27

Closed RotX18 closed 2 years ago

RotX18 commented 2 years ago

Objective:

Create scripts to allow for a functioning lock

From #39:

@chiitori:

From #76:

From #84:

RotX18 commented 2 years ago

Added Lock, IPickable interface implementation + Misc changes

Finished implementation of the Lock object. Implementation consists of 3 main scripts:

  1. LockDial.cs:
    • Used to identify each singular lock dial and contains the data for each dial
  2. LockManager.cs:
    • Manages the entire lock as a whole
    • Logic for unlocking and the OnRelease() method is found here
  3. LockInput.cs:
    • Handles the input from the controllers
    • Controller interaction happens when the object is grabbed
    • Interaction involves grabbing controller's joystick movement where Up/Down flicks correlate to turning the selected dial up/down while left/right flicks change the selected dial

Additionally, IPickable interface has been created to streamline and standardise all pickable objects. All objects that can be picked up should, in the script of its highest level parent, inherit this interface.

Lock Scripts and Descriptions

Logic and explanantion of codes here

LockDial.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LockDial : MonoBehaviour
{
    #region PRIVATE VARS
    private int _currentNumber = 0;
    #endregion

    #region PROPERTIES
    public int CurrentNumber{ 
        get{
            return _currentNumber;
        }
        set{
            SetNumber(value);
        }
    }
    #endregion

    private void Update() {
        //if the current number is more than 9, set it to 0
        if(_currentNumber > 9){
            _currentNumber = 0;
        }

        //if the current number is < 0, set it to 9
        if(_currentNumber < 0){
            _currentNumber = 9;
        }
    }

    private void SetNumber(int input){
        //Using flipped values (relative to input) for better UX
        _currentNumber -= input;

        //set the rotation to input*36 as 360/10 = 36, input determines the direction
        Vector3 rotation = new Vector3(0, 0, -input * 36);
        gameObject.transform.Rotate(rotation);
    }
}

Description:

LockManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LockManager : MonoBehaviour, IPickable
{
    #region PUBLIC VARS
    public int correctCombination;
    public LockDial[] lockDials;
    #endregion

    #region PRIVATE VARS
    private bool _doCombinationCheck = true;
    private bool _unlocked = false;
    #endregion

    #region PROPERTIES
    public bool Grabbed {
        get;
        set;
    } = false;

    public IPickable.Controller CurrentController {
        get;
        set;
    } = IPickable.Controller.None;
    #endregion

    #region PUBLIC METHODS
    public void OnRelease(){ 
        foreach(LockDial ele in lockDials){
            ele.GetComponentInChildren<MeshRenderer>().material.color = Color.white;
        }
    }
    #endregion

    private void Update() {
        if(_doCombinationCheck && !_unlocked){
            _doCombinationCheck = false;
            //checking for the correct correctCombination via coroutine
            StartCoroutine(CheckCombination());
        }
    }

    private IEnumerator CheckCombination(){
        string dialCombination = "";
        //for loop to concatenate numbers and check against combi
        for(int i = 0; i < lockDials.Length; i++){
            dialCombination += lockDials[i].CurrentNumber;
        }

        if(dialCombination.Equals(correctCombination.ToString()) && _unlocked == false) {
            //if the combination matches and the lock has not been unlocked
            _unlocked = true;
            Unlock();
        }

        yield return new WaitForEndOfFrame();
        _doCombinationCheck = true;
    }

    private void Unlock(){
        //ANY ANIMATIONS OR UNLOCK EVENTS TO BE DONE HERE
        Debug.Log("LOCK HAS BEEN UNLOCKED");
    }
}

Description:

LockInput.cs[^LockInput]

using UnityEngine;

public class LockInput: MonoBehaviour {

    #region PRIVATE VARS
    private LockDial[] _lockDials;
    private LockManager _lockManager;
    private LockDial _currentDial;
    private Vector2 _lThumbStickInput;
    private Vector2 _rThumbStickInput;
    private int _currentDialIndex = 0;
    private bool _turnDial = true;
    private bool _changeDial = true;
    #endregion

    // Start is called before the first frame update
    void Start() {
        _lockManager = GetComponentInParent<LockManager>();
        _lockDials = _lockManager.lockDials;
        _currentDial = _lockDials[_currentDialIndex];
    }

    // Update is called once per frame
    void Update() {
        if(_lockManager.GetComponent<IPickable>().Grabbed) {
            _lThumbStickInput = OVRInput.Get(OVRInput.RawAxis2D.LThumbstick);
            _rThumbStickInput = OVRInput.Get(OVRInput.RawAxis2D.RThumbstick);

            //LEFT CONTROLLER GRAB
            if(_lockManager.GetComponent<IPickable>().CurrentController == IPickable.Controller.LTouch) {
                UpdateCurrentDial(0);
                //Lock interaction
                //turning current dial
                if(_turnDial){
                    _turnDial = false;

                    //Up and Down = changing numbers
                    if(_lThumbStickInput.y < -0.005f || _lThumbStickInput.y > 0.005f) {
                        //if the input is more than 0.5f way in either y direction, rotate the dial
                        _currentDial.CurrentNumber = Normalise(_lThumbStickInput.y);
                    }
                }

                //changing current dial
                else if(_changeDial){
                    _changeDial = false;
                    //Left and Right = changing selected dial
                    if(_lThumbStickInput.x < -0.005 || _lThumbStickInput.x > 0.005) {
                        //if the input is more than 0.5f in either x direction, change dials
                        UpdateCurrentDial(Normalise(_lThumbStickInput.x));
                    }
                }

                if(_lThumbStickInput.y == 0 && _lThumbStickInput.x == 0){
                    _turnDial = true;
                    _changeDial = true;
                }
            }
            //RIGHT CONTROLLER GRAB
            else if(_lockManager.GetComponent<IPickable>().CurrentController == IPickable.Controller.RTouch) {
                UpdateCurrentDial(0);
                //Lock interaction
                //turning current dial
                if(_turnDial) {
                    _turnDial = false;

                    //Up and Down = changing numbers
                    if(_rThumbStickInput.y < -0.01f || _rThumbStickInput.y > 0.01f) {
                        //if the input is more than 0.5f way in either y direction, rotate the dial
                        _currentDial.CurrentNumber = Normalise(_rThumbStickInput.y);
                    }
                }

                //changing current dial
                else if(_changeDial) {
                    _changeDial = false;
                    //Left and Right = changing selected dial
                    if(_rThumbStickInput.x < -0.04 || _rThumbStickInput.x > 0.04) {
                        //if the input is more than 0.5f in either x direction, change dials
                        UpdateCurrentDial(Normalise(_rThumbStickInput.x));
                    }
                }

                if(_rThumbStickInput.y == 0 && _rThumbStickInput.x == 0) {
                    _turnDial = true;
                    _changeDial = true;
                }
            }
        }
    }

    private void UpdateCurrentDial(int input){
        //incrementing the current dial index

        _currentDialIndex += input;
        //setting the old dial to white before the change
        _currentDial.GetComponentInChildren<MeshRenderer>().material.color = Color.white;

        //error proofing for dial index
        if(_currentDialIndex >= _lockDials.Length) {
            //if the current dial index is greater than the length, set it to the first element
            _currentDialIndex = 0;
            _currentDial = _lockDials[_currentDialIndex];
        }
        else if(_currentDialIndex < 0) {
            //if the current dial index is less than 0, set it to the highest possible indexs
            _currentDialIndex = _lockDials.Length - 1;
            _currentDial = _lockDials[_currentDialIndex];
        }
        else {
            //if the current dial index falls within the array length, set the new current dial
            _currentDial = _lockDials[_currentDialIndex];
        }

        //CODE FOR UI CHANGE AND OTHER EFFECTS BELOW HERE
        //setting the colour to red after the new dial has been selected
        _currentDial.GetComponentInChildren<MeshRenderer>().material.color = Color.red;
    }

    private int Normalise(float f){ 
        if(f < 0){
            return -1;
        }
        else if(f > 0){
            return 1;
        }
        else{
            return 0;
        }
    }
}

Description:

Miscellaneous changes + notes

Feature has been implemented, improvements to be made and documented before issue close, do note, @RabbitKazma @chiitori

[^input]: Subtracted and not added as this results in the dial rotating downwards with a down flick and vice versa, better for UX [^LockInput]: Contains input handling for both left and right, thus a large chunk is copy pasted and converted for the other hand [^dials]: If flicking horizontally, calls _UpdateCurrentDial(Normalise(lThumbStickInput.x)) to update the selected dial to the next in line If flicked vertically, rotates using normalised input value (handled by the CurrentNumber property) [^reset]: This ensures that the dial cycles are only performed once for as long as the stick is not released

RotX18 commented 2 years ago

Integrated IPuzzle interface and added improvements

IPuzzle interface has been integrated and added feature where the lock teleports back to where it initially was when the lock is released

LockManager.cs (Modified code)

public class LockManager : MonoBehaviour, IPickable, IPuzzle{
    private Vector3 _initialPos;
    private Quaternion _initialRot;

    public bool Completed {
        get;
        set;
    } = false;

    public void OnRelease(){ 
        foreach(LockDial ele in lockDials){
            ele.GetComponentInChildren<MeshRenderer>().material.color = Color.white;
        }
        gameObject.transform.SetPositionAndRotation(_initialPos, _initialRot);
    }

    public void OnComplete(){
        //ANY ANIMATIONS OR UNLOCK EVENTS TO BE DONE HERE
        Debug.Log("LOCK HAS BEEN UNLOCKED");
    }

    private void Awake() {
        _initialPos = gameObject.transform.position;
        _initialRot = gameObject.transform.rotation;
    }

}

Edits:

Integrated, @chiitori to retexture and rebump lock before final implementation into main app scene

chiitori commented 2 years ago

Texture and Bump mapping of Lock.

Texture and Bump mapping of lock to undergo vetting before implementation

Looks of the Lock

image

RotX18 commented 2 years ago

Bug fix: Lock object printing "Lock has been unlocked" at start of scene

Issue:

Internal string of dialCombination was not being set properly, and was being set as 0000, thus as the starting position of all dials is 0, the dial combination will be equal to 0000.

Solution:

Concatenate number of 0's equal to the number of missing digits

Changes + Improvements

CheckCombination.cs (Modified code)

    private IEnumerator CheckCombination(){
        string correctCom = correctCombination.ToString();
        string addedZeros = "";

        //accounting for correctCombinations starting with 0
        if(correctCom.Length < lockDials.Length){
            //if the correct combination has fewer digits than the number of lockDials
            for(int i = 0; i < (lockDials.Length - correctCom.Length); i++){
                addedZeros += "0";
            }
            //adding 0s to the front
            correctCom = $"{addedZeros}{correctCom}";
        }

        //accounting for correctCombinations > number of lockDials
        if(correctCom.Length > lockDials.Length){
            //if the correct combination has more digits than the number of lock dials
            correctCom = "";
            for(int i = 0; i < lockDials.Length; i++){
                //resetting correctCom to use only until the lockDials.Length-th digit
                correctCom += correctCombination.ToString()[i];
            }
        }
    }

Edits:

Fixed bug on lock saying it has been unlocked at the start of the scene Added improvement that error proofs for the correct combination

RotX18 commented 2 years ago

Integrated unlock animations and behaviour

Integration of animations involved putting the trigger into the OnRelease() method of LockManager.cs.

Code changes + Description

LockManager.cs (Modified code)

    public Animator cabinetAnim;
    public Animator lockAnim;

    private int _openParam = Animator.StringToHash("Open");
    private int _unlockAnim = Animator.StringToHash("Unlock");

    public void OnRelease(){
        if(Completed) {
            TriggerLockAnimation(_unlockAnim);
        }
        else{
            gameObject.transform.SetPositionAndRotation(_initialPos, _initialRot);
        }
    }

    public void TriggerLockAnimation(int id){
        lockAnim.SetTrigger(id);
    }

    public void TriggerCabinetAnimation(int param)
    {
        cabinetAnim.SetTrigger(param);
    }

Edits:

LockAnimationEvents.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LockAnimationEvents : MonoBehaviour
{
    #region PUBLIC VARS
    public Rigidbody lockRb;
    #endregion

    public void Unlock(){
        lockRb.useGravity = true;
    }
}

Description: (Added RigidBody to lock to allow for falling physics)

Demonstration

https://user-images.githubusercontent.com/84174550/182191371-8a83f049-6340-4f54-a666-070c035a1e65.mp4

Lock is now complete, issue will now be closed

RotX18 commented 2 years ago

Implemented key spawning after lock has been completed

"Spawns" key by setting the object to be active after lock is complete

Code changes

LockManager.cs (Modified code)

public GameObject key;

//OnComplete() method in LockManager.cs
public void OnComplete(){
    Debug.Log("LOCK HAS BEEN UNLOCKED");
    if (text != null) {
        text.text = "Congrats now, get the key and leave";
    }
    if(cabinetAnim != null) {
        TriggerCabinetAnimation(_openParam);
    }
    TriggerLockAnimation(_unlockAnim);
    key.SetActive(true);
}

Edits:

Key "spawning" completed

RotX18 commented 2 years ago

Edited lock opening animation

Removed falling portion of animation + cabinet animation timing changes

This was done to allow the falling to be done via physics, ie RigidBody.useGravity in the animation event. Cabinet opening animation now opens after lock fully unlocks

LockAnimationEvents.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LockAnimationEvents : MonoBehaviour
{
    #region PUBLIC VARS
    public Rigidbody lockRb;
    public Animator cabinetAnim;
    #endregion

    #region PRIVATE VARS
    private int _openParam = Animator.StringToHash("Open");
    #endregion

    public void Unlock(){
        lockRb.useGravity = true;
        TriggerCabinetAnimation(_openParam);
    }

    public void TriggerCabinetAnimation(int param) {
        cabinetAnim.SetTrigger(param);
    }
}

Edits:

Demonstration

https://user-images.githubusercontent.com/84174550/185431778-60fdf0f1-d232-4b3d-a67a-99d555591e94.mp4

Animation is largely complete, however initial unlocking sequence of the lock can be tidied up, @RabbitKazma do follow up and update here

RabbitKazma commented 2 years ago

Edited lock opening animation

Removed the lock turning

The lock turning was removed in the keyframe Let the physics do its part for falling

https://user-images.githubusercontent.com/85175456/187018381-f49c4ccf-9684-4f4b-8ef3-e23459ead5e7.mp4

RotX18 commented 2 years ago

Lock is now complete, issue will be closed, thank you @chiitori @RabbitKazma