This week, I started to work on the game’s core mechanics, the use of abilities the player has to maneuver objects.
PlayerAbility.cs
The first thing I did was to create a script that would handle the player’s abilities, appropriately titled PlayerAbility.cs
using UnityEngine;
public class PlayerAbility : MonoBehaviour
{
[SerializeField] private float distance = 5;
[HideInInspector] public GameObject abilityObject;
// Update is called once per frame
void Update()
{
LookForObject();
}
void LookForObject()
{
Ray objectRay = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.Raycast(objectRay, out hit, distance))
{
}
}
}
Selection Area
However, I quickly realized that using a raycast would be inefficient, so I replaced it with 2 things, a GameObject called Selection Area, and a script of the same name. The idea being that the Selection Area would use its trigger to detect objects, specifically the OnTriggerEnter and OnTriggerExit functions, which I can then use to control the objects within the selection area.
This is what I had for this week: What this script does is as follows: Follow the player, when the player moves the scroll wheel, increase or decrease the SelectionArea, and when the player presses the interact button, search for objects that can be used with the player’s abilities.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
public class SelectionArea : Singleton<SelectionArea>
{
SphereCollider _collider;
private float _radius;
[FormerlySerializedAs("_objects")] [HideInInspector] public List<GameObject> objects;
private bool _searchForObjects = false;
Vector3 _playerPosition;
public float offset;
private float _yPos = -0.7f;
Transform _playerTransform;
private float _lastInputValue;
private bool _canSetRadius;
[SerializeField] private float abilityTime = 10f;
private void OnEnable()
{
_canSetRadius = true;
transform.localPosition = new Vector3(1, 1, 1);
}
void Start()
{
_collider = GetComponent<SphereCollider>();
_playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update()
{
_playerPosition = _playerTransform.position;
transform.position = new Vector3(_playerPosition.x, _yPos, _playerPosition.z + offset);
}
public void OnRadiusSet(InputAction.CallbackContext context)
{
var input = context.ReadValue<float>();
if (!_canSetRadius) return;
switch (input)
{
case > 0 when input < _lastInputValue:
case < 0 when input > _lastInputValue:
return;
}
if (_playerTransform == null || !context.performed) return;
Debug.Log($"Mouse Input: {input}");
transform.localScale = new Vector3(input, 1, input);
_lastInputValue = input;
}
public void OnAbilityInteract(InputAction.CallbackContext context)
{
if (context.performed && PlayerAbility.Instance.abilitySelection)
{
PlayerAbility.Instance.abilitySelection = false;
StartCoroutine(SearchForObjects());
PlayerStats.Instance.AbilityFinished();
}
}
IEnumerator SearchForObjects()
{
_searchForObjects = true;
yield return new WaitForSeconds(abilityTime);
_searchForObjects = false;
foreach (var obj in objects.ToList())
{
Rigidbody rb = obj.GetComponent<Rigidbody>();
if (rb != null) rb.mass *= _lastInputValue;
objects.Remove(obj);
}
}
private void OnTriggerEnter(Collider other)
{
if (!_searchForObjects) return;
objects.Add(other.gameObject);
}
public void OnTriggerExit(Collider other)
{
objects.Remove(other.gameObject);
}
}
Singleton Practice Session
At this point, Ross from WSA had advertised a lesson on Tuesday morning where I could learn about Singletons.

I decided to attend, and was the only year 2 to do so. I learned about how Singletons worked and I applied it to some of my code.
To practice using Singletons, I converted some of my scripts from MonoBehaviors to Singletons, scripts that would realistally be used in only 1 GameObject, for example, the GameManager.
public class GameManager : Singleton<GameManager>
{
CinemachineFreeLook cinemachineFreeLook;
public IEnumerator ResetGame()
{
Destroy(_player.gameObject);
yield return new WaitForSeconds(3);
GameObject newPlayer = Instantiate(playerPrefab, _playerStartPos, Quaternion.identity);
_player = newPlayer;
ThirdPersonCam.Instance.ResetCamera(_player);
}
}
As you can see at the end in the ResetGame IEnumerator, at the end of the script a Instance of the ThirdPersonCam script calls the ResetCamera function on the script.
Gravity Ability
At this point, I also started working on the gravity ability for the game. However, I wouldn’t get it starting to actually work until next week. What I had quickly realised was that, I had written code that uses 3 scripts, the PlayerStats script, the PlayerAbilities script and the SelectionArea script, and that these scripts interacting with each other was causing some confusion, so I rewrote the code to no longer require the PlayerStats scripts later.
Player Stats
Here is the PlayerStats script, part of the script is to deal with slowing the game down, and was intended to also be used with the player’s abilities, although this script would later be deleted.
using UnityEngine;
using UnityEngine.Serialization;
public class PlayerStats : Singleton<PlayerStats>
{
int _score = 0;
delegate void OnScoreChange();
OnScoreChange _onScoreChange;
[FormerlySerializedAs("_abiltyCharge")] public int abiltyCharge = 4;
public delegate void OnAbilityUse();
public static OnAbilityUse onAbilityUse;
public delegate float GetAbilityCharge();
[SerializeField] SelectionArea selectionArea;
[Range(0.1f, 1f)] public float speed = 0.2f;
[FormerlySerializedAs("_slowDownGame")] public bool slowDownGame;
private float _normalGameSpeed;
private bool _canChangeAbilityCharge = true;
private void Start()
{
_onScoreChange += UpdateScore;
onAbilityUse += UseAbility;
_normalGameSpeed = Time.timeScale;
Debug.Log($"Ability charge: {abiltyCharge}");
}
private void FixedUpdate()
{
Time.timeScale = slowDownGame switch
{
true when !Mathf.Approximately(Time.timeScale, speed) => speed,
false => 1,
_ => Time.timeScale
};
}
void UpdateScore()
{
_score += 1;
}
void UseAbility()
{
if (_canChangeAbilityCharge)
{
abiltyCharge -= 1;
_canChangeAbilityCharge = false;
}
slowDownGame = true;
selectionArea.gameObject.SetActive(true);
}
public void AbilityFinished()
{
slowDownGame = false;
selectionArea.gameObject.SetActive(false);
_canChangeAbilityCharge = true;
Time.timeScale = _normalGameSpeed;
Debug.Log($"Ability charge: {abiltyCharge}");
}
public float CheckAbilityChargeAmount()
{
return abiltyCharge;
}
}
Next Steps
For my next steps, I will continue to work on the gravity script.