Closed RotX18 closed 2 years ago
Finished implementation of the Lock object. Implementation consists of 3 main scripts:
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.
Logic and explanantion of codes here
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:
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:
Any code pertaining to when the lock unlocks, Debug.Log() statement is currently used as temporary placeholder
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:
In Unity Editor:
FileSystem:
Demonstration video for lock interaction
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
IPuzzle interface has been integrated and added feature where the lock teleports back to where it initially was when the lock is released
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
Texture and Bump mapping of lock to undergo vetting before implementation
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.
Concatenate number of 0's equal to the number of missing digits
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
Integration of animations involved putting the trigger into the OnRelease() method of LockManager.cs.
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:
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)
Lock is now complete, issue will now be closed
"Spawns" key by setting the object to be active after lock is complete
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
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
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:
Animation is largely complete, however initial unlocking sequence of the lock can be tidied up, @RabbitKazma do follow up and update here
The lock turning was removed in the keyframe Let the physics do its part for falling
Lock is now complete, issue will be closed, thank you @chiitori @RabbitKazma
Objective:
Create scripts to allow for a functioning lock
From #39:
@chiitori:
From #76:
From #84: