RotX18 / MP_Group4_EscapingReality

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

Grid System for Puzzle #2

Closed RotX18 closed 2 years ago

RotX18 commented 2 years ago

Objective: Create a dynamic way of instantiating a grid of objects

Grid system should:

From #39:

RotX18 commented 2 years ago

Started work on grid

Requirements for grid:

  1. GridElement.cs script for identification of every element within each script and,
  2. GridSpawner.cs script for the spawning of a grid using an object with a GridElement.cs component to it

Scripts: GridElement.cs, GridSpawner.cs

GridElement.cs

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

/// <summary>
/// Class for every grid element
/// </summary>

public class GridElement : MonoBehaviour {
    #region PRIVATE VARS

    private int _id;
    private float _posX;
    private float _posY;

    #endregion

    #region PROPERTIES

    public int ID {
        get {
            return _id;
        }
        set {
            _id = value;
        }
    }

    public float GridX {
        get {
            return _posX;
        }
        set {
            _posX = value;
        }
    }

    public float GridY {
        get {
            return _posY;
        }
        set {
            _posY = value;
        }
    }

    #endregion
}

Description:

GridSpawner.cs

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

/// <summary>
/// Spawner for the grid that takes in a prefab and instantiates
/// it according to the given dimensions of the desired grid
/// </summary>

public class GridSpawner : MonoBehaviour
{
    [Header("Object to be instantiated")]
    public GameObject gridElement;

    [Header("Grid size of lenX * lenY")]
    public int lenX;
    public int lenY;

    [Header("X-axis spacing of spaceX, Y-axis spacing of spaceY")]
    public float spaceX;
    public float spaceY;

    private void Start() {
        for(int i = 0; i < lenY; i++){
            //for each column
            for(int j = 0; j < lenX; j++){
                //for each row in each column
                //instantiating each cube with space inbetween
                Instantiate(gridElement, new Vector3(transform.position.x + j + (j * spaceX), transform.position.y + i + (i * spaceY), transform.position.z), Quaternion.identity);

            }
        }
    }
}

Description:

Note:

[^spaceCalc]: [j + (j spaceX)] and [i + (i spaceY)] are due to the fact that we are instantiating relative to the spawner and NOT the previous cube; We are doing (iterationNumber) * (number of spaces between first and current cube)

RotX18 commented 2 years ago

Improvements to Grid Spawner system

Improvements made include:

Assignment of GridElement properties and implementation of search function

GridElement.cs

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

/// <summary>
/// Class for every grid element
/// </summary>

public class GridElement : MonoBehaviour {
    #region PRIVATE VARS
    private float _posX;
    private float _posY;
    private int _id;
    #endregion

    #region PROPERTIES
    public float GridX {
        get {
            return _posX;
        }
        set {
            _posX = value;
        }
    }

    public float GridY {
        get {
            return _posY;
        }
        set {
            _posY = value;
        }
    }
    public int ID {
        get {
            return _id;
        }
        set {
            _id = value;
        }
    }
    #endregion
}

Edit:

GridSpawner.cs

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

/// <summary>
/// Spawner for the grid that takes in a prefab and instantiates
/// it according to the given dimensions of the desired grid
/// </summary>

public class GridSpawner : MonoBehaviour
{
    #region PUBLIC VARS
    //grid element that will be instantiated
    [Header("Object to be instantiated as elements of the grid")]
    public GameObject gridElement;

    //grid dimensions, lenX units by lenY units
    [Header("Grid size of lenX * lenY")]
    [Min(1)]
    public int lenX;
    [Min(1)]
    public int lenY;

    //spacing between each element along the X and Y axis
    [Header("X-axis spacing of spaceX, Y-axis spacing of spaceY")]
    [Min(0)]
    public float spaceX;
    [Min(0)]
    public float spaceY;
    #endregion

    #region PRIVATE VARS
    private GameObject _instantiatedObj;
    #endregion

    private void Start() {
        for(int i = 0; i < lenY; i++){
            //for each column
            for(int j = 0; j < lenX; j++){
                //for each row in each column
                //instantiating each gridElement with space inbetween
                _instantiatedObj = Instantiate(gridElement, new Vector3(transform.position.x + j + (j * spaceX), transform.position.y + i + (i * spaceY), transform.position.z), Quaternion.identity);

                //setting the X and Y position for the instantiated ibject
                _instantiatedObj.GetComponent<GridElement>().GridX = j;
                _instantiatedObj.GetComponent<GridElement>().GridY = i;

                //converting the gridX and gridY to strings, concatenating and converting value back to int and storing it as the ID
                //eg. if one of the obj is at position (1, 2), its id would be 12
                string idString = j.ToString() + i.ToString();
                _instantiatedObj.GetComponent<GridElement>().ID = int.Parse(idString);

                //setting the instantiated object as a child of the spawner
                _instantiatedObj.transform.parent = gameObject.transform;
            }
        }
    }

    public GameObject FindElementByID(int id){
        //getting all GridElement components from spawner's children
        GridElement[] children;
        children = gameObject.transform.GetComponentsInChildren<GridElement>();

        //iterating through the children array to find first element that has the same id as the query id
        foreach(GridElement ele in children){ 
            if(ele.ID == id){
                //returning gameObject if id matches
                return ele.gameObject;
            }
            else{
                continue;
            }
        }

        //returning null if none of the elements in children have the same id
        return null;
    }
}

Edits:

[^id]: Example: Given an element at position (12, 34), its ID will be "12" + "34" = 1234 [^searchFunction]: Could possibly be computationally expensive

Notes

All objectives complete, issue can now be closed (closed with commit c34fe37cd9dad4fb723d5b40816fe89b8e4e4e87)

RotX18 commented 2 years ago

Integration of VR and porting of existing code to use the Oculus API

RotX18 commented 2 years ago

Completed integration for grid input

Integration of VR

Changed the code to use Oculus API input

I copy-pasted the MouseInput.cs script into PlayerInput.cs as the naming was more appropriate

PlayerInput.cs

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

/// <summary>
/// This script gets the game object under the mouse position and changes it to different colours
/// </summary>

public class PlayerInput : MonoBehaviour {
    #region PUBLIC VARS

    //seletion vars
    public GameObject pointerOrigin;

    //grid vars
    public GameObject gridSpawnerObj;
    public GridManager gridManager;

    #endregion

    #region PRIVATE VARS

    //selection vars
    private Ray _pointerRay;
    private RaycastHit _hit;
    private GameObject _hitObj;

    //grid vars
    private List<GridElement> _elements = new();
    private Dictionary<int, GridElement> _correctEles = new();
    private Dictionary<int, GridElement> _wrongEles = new();

    #endregion

    private void Start() {
        _elements = gridSpawnerObj.GetComponent<GridSpawner>().AllElements;

        //sorting the elements in the grid to correct elements and wrong elements
        foreach(GridElement ele in _elements) {
            if(ele.Correct) {
                _correctEles.Add(ele.ID, ele);
            }
            else {
                _wrongEles.Add(ele.ID, ele);
            }
        }
    }

    private void Update() {
        if(OVRInput.GetDown(OVRInput.Button.SecondaryIndexTrigger)) {
            //when the right trigger is pressed
            if(_hitObj != null) {
                //if the raycast hit is not null
                if(_hitObj.GetComponent<GridElement>().Clickable) {
                    //if the element is clickable
                    _hitObj.GetComponent<GridElement>().Clicked = true;

                    //checking whether the element is correct
                    if(_hitObj.GetComponent<GridElement>().Correct) {
                        //if element is correct, set the colour to green
                        _hitObj.GetComponent<Renderer>().material.color = Color.green;
                    }
                    else {
                        //if element is not correct, reset all current colours
                        gridManager.ResetGrid();
                    }
                }

            }
        }
    }

    private void FixedUpdate() {
        //casting from the main camera to the point on screen where the mouse is
        _pointerRay = new Ray(pointerOrigin.transform.position, pointerOrigin.transform.forward);

        if(Physics.Raycast(_pointerRay, out _hit)) {
            //if the point where the player click contains a gameobject
            _hitObj = _hit.transform.gameObject;
        }
        else {
            _hitObj = null;
        }
    }
}

Edits:

Demonstration of changes:

https://user-images.githubusercontent.com/84174550/178666230-b6465eb8-775b-4608-afdb-01b6a655b5bc.mp4

Feature has been implemented, branches can now be merged

RotX18 commented 2 years ago

Added ability to rotate the grid in the desired rotation

This simply involved adding 4 lines of code, changes as follows:

Code changes

Grid Spawner.cs (Modified code)

    //degrees to rotate the entire grid by
    [Header("Initial rotation of the grid")]
    public float rotX = 0;
    public float rotY = 0;
    public float rotZ = 0;

    private void Awake(){
        //rotating the grid after all elements have spawned
        transform.Rotate(new Vector3(rotX, rotY, rotZ));
    }

Edits:

Demonstration

With all rotations set to 0 (default) image

With rotations set with random values image

Enhancement complete, issue will now be closed

RotX18 commented 2 years ago

Integrated IPuzzle interface into grid system + improvements

Implemented the new IPuzzle interface into the grid system for better streamlining of code

GridManager.cs

public class GridManager : MonoBehaviour, IPuzzle
{
    private int _totalCorrects = 0;
    private int _clickedCorrects = 0;

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

    public void OnComplete(){
        //ADD COMPLETED EFFECTS HERE
        Debug.Log("GRID HAS BEEN COMPLETED");
    }

    private void Start() {
        foreach(GridElement ele in _elements){ 
            if(ele.Correct){
                //keeping count the total number of correct elements
                _totalCorrects++;
            }
        }
    }

    private void Update() {
        //completed check
        if(_clickedCorrects == _totalCorrects){
            //if the number of clicked correct elements = total number of correct elements
            Completed = true;
        }
    }

    public void ResetGrid() {
        _clickedCorrects = 0;
    }
}

Edits:

Notes

RotX18 commented 2 years ago

Bug fix: Grid throwing NullReferenceError when pressing right trigger

Issue:

Did not account for whether the raycast hit object had a GridElement component.

Solution:

Add a check to test whether the hit object contained a GridElement component

Code changes

In GridInput.cs (Modified code)

if(_hitObj.GetComponent<GridElement>() != null){
    //if the hit object has a GridElement component
    //CODE HERE
}

Description:

Fixed issue with Grid throwing NullReferenceError when pressing right trigger

RotX18 commented 2 years ago

Bug Fix: Grid completing before all correct elements were clicked

Issue:

Did not check whether corrects were clicked before incrementing ClickedCorrects

Solution:

Account for whether the correct being clicked has been clicked prior via GridElement.Clicked

Code changes

GridInput.cs (Modified code)

//checking whether the element is correct
if(_hitObj.GetComponent<GridElement>().Correct) {
    //if element is correct, set the colour to green
    _hitObj.GetComponent<Renderer>().material.color = Color.green;

    if(!_hitObj.GetComponent<GridElement>().Clicked){
        //if the current object has not yet been clicked prior
        gridManager.ClickedCorrects++;
        _hitObj.GetComponent<GridElement>().Clicked = true;
    }
}

Edits:

Bug has been fixed

RotX18 commented 2 years ago

Improved grid system; Correct elements have to be clicked in order

Previously, all correct elements in a given row could be clicked in any order, with the grid progressing as long as all correct elements within that row were clicked. With this change, that is no longer the case, now even if the clicked element within the row is correct, if it is not the next element in the sequence, it will reset the entire grid.

Code changes + Description

Changes can be found in GridInput.cs

GridInput.cs (Modified code)

private int _intendedCorrectIndex = 0;
//CORRECT ELEMENT CHECK IN Update(); Start line 62

//get current correct's index in gridManager
int currentCorrectIndex = Array.IndexOf(gridManager.correctElementIDs, gridSpawner.FindElementByID(_hitObj.GetComponent<GridElement>().ID).GetComponent<GridElement>().ID);
//CLICKED ELEMENT CHECK FOUND IN Update(); Start line 70

//comparing the current correct's index to the next correct in sequence
if(currentCorrectIndex == _intendedCorrectIndex){
    //if the currentCorrectIndex is the same as the _intendedCorrectIndex, increment _intendedCorrectIndex
    _intendedCorrectIndex++;
}
else{
    //if the currentCorrectIndex is not the next one in sequence, reset
    gridManager.ResetGrid();
    _intendedCorrectIndex = 0;
}

Edits:

RabbitKazma commented 2 years ago

Changes on grid Code

_instantiatedObj = Instantiate(gridElement, new Vector3(transform.position.x + j + (j * (spaceX -(1-gridElement.transform.localScale.x))), transform.position.y + i + (i * (spaceY - (1 - gridElement.transform.localScale.y))), transform.position.z), Quaternion.identity);