Nature strikes back

Project Game Development (PGD)

Teamsize: 5 people
Genre: One vs many, online multiplayer, survival
Engine: Unity3D
Language: C#
Year: 2019

Interactable objects

When the player looks at an interactable object and is in range to interact with it, an information window pops up. This windows shows the object name and the keyboard key to interact with the object.
The InteractableObject script assigns its name and interaction key to the UI info panel.
When the player interacts with the object, an event is triggered. Other scripts that are on the interactable object can easily get a reference to the event using the GetComponent method.

public class InteractableObject : MonoBehaviour
{
	//an event that gets invoked when the player looks at the object and presses the interaction key
    public event Action OnInteraction;											
    public bool IsInteracting { get; private set; }
    
    [SerializeField]
    private float interactionRange = 5f;

    [SerializeField]
    private LayerMask interactableObjects = -1;

    [SerializeField]
    private string objectName = "";

    [SerializeField]
    private KeyCode interactionKey = KeyCode.E;

	private Camera cam;
    private Transform player;
    private ObjectInfoUI InfoUIPanel;

	//returns true if the player is in range of the interactable object
    private bool playerInRange => Vector3.Distance(transform.position, player.position) <= interactionRange;
    private bool _lookAt = false;
    private bool lookAt
    {
        get { return _lookAt; }
        set
        {																			
            if (_lookAt == value) return;											//return if the value is the same as _lookAt's current value.
            _lookAt = value;
            if (_lookAt == true)													//if the player starts looking at the object																
                InfoUIPanel.SetInfo(objectName, interactionKey.ToString());			//assign the object name and interaction key to the InfoUI
            InfoUIPanel.gameObject.SetActive(_lookAt);								//enable or disable the UI window, depending on _lookAt
        }
    }
    
    private void Awake()
    {
        cam = Camera.main;															//get a reference to the main camera, for raycasting
        player = FindObjectOfType<PlayerController>().transform;					//get a reference to the player transform
        //the UIManager holds a reference to the ObjectInfoUI
        //this way the ObjectInfoUI can remain disabled and still be found
        InfoUIPanel = FindObjectOfType<UIManager>().ObjectInfoUI;					//get a reference to the UI info window
        OnInteraction += () => IsInteracting = true;								//to keep track of the player's state
        OnInteraction += () => InfoUIPanel.gameObject.SetActive(false);				//close the UI window when interacting with the object
    }

    private void FixedUpdate()
    {
        if (playerInRange && !IsInteracting)
            CheckForInteraction();
    }

    private void CheckForInteraction()
    {
        Ray ray = cam.ScreenPointToRay(Input.mousePosition);										//create a ray from the camera towards the 'mouse position'
        Debug.DrawRay(playerRaycastPoint.position, ray.direction * interactionRange, Color.blue);	//draw the ray for debugging purposes
		//shoot a ray from the player towards to 'mouse position'. The ray has a length of interactionRange and only registers interactable objects.
        if (Physics.Raycast(playerRaycastPoint.position, ray.direction, out RaycastHit hit, interactionRange, interactableObjects))
        {
            lookAt = true;
            if (Input.GetKeyDown(interactionKey))
                OnInteraction?.Invoke();					//invoke the event if it's not null
        }
        else
            lookAt = false;
    }
}

The following script can be placed on any object and needs no references to other objects from the inspector. It finds the corresponding UI window by itself.
The script is modular, since you can change the interaction range and the interaction key in the inspector.

Hologram research menu

At the research table, the player is able to unlock new items and/or upgrade existing ones.
Instead of a simple 2d menu, I choose for an interactive 3d design.

The research table has a state machine.
When the research table isn’t being used, all holograms can be turned off.
When the table is in use, the holograms orbit around the center.
And when an item is selected, it is moved to the center and scaled up.

To move, scale and rotate the objects to specific places, I created a Lerper class.
This way I can easily chain multiple operation together, for example move and a scale:
By calling the scale operation in the OnComplete Action for the move operation.

public class LerpVariable<T>					//generic Lerp variable
{
    public Action<T> Callback { get; set; }		//the callback is used to return the _value property
    private T _value;
    public T Value
    {
        get { return _value; }
        set                             		//if the value has changed, invoke the callback
        {
            if (_value.Equals(value))
                return;
            _value = value;
            Callback?.Invoke(_value);
        }
    }
}

public class Lerper
{
    public bool IsRunning { get; private set; }		//boolean to see if the class is currently lerping a value

    private LerpVariable<float> currentFloat = new LerpVariable<float>();
    private LerpVariable<Vector3> currentVector = new LerpVariable<Vector3>();
    private LerpVariable<Quaternion> currentQuaternion = new LerpVariable<Quaternion>();

    public IEnumerator Lerp(float startValue, float endValue, float duration, Action<float> callback, Action onComplete)
    {
        IsRunning = true;
        currentFloat.Callback = callback;   		//assign the callback to the LerpVariable callback
        float timePassed = 0f;						//reset the time variable
        while (timePassed < duration)
        {
            timePassed += Time.deltaTime;			//add the time that has passed since the last frame
            currentFloat.Value = Mathf.Lerp(startValue, endValue, timePassed);   //update the currentFloat value, which will invoke the callback
            yield return null;
        }

        currentFloat.Value = endValue;				//update the currentFloat value, which will invoke the callback for the final value
        IsRunning = false;
        onComplete?.Invoke();						//invoke the onComplete Action, because the Lerp has finished.
        yield return null;
    }
}

I’ve overloaded the Lerp method, so that I can use floats, Vectors and Quaternions when calling the Lerp method.