Sam was tasked as the Technical Designer for the project and produced the game’s code including the player’s movement, jumping, and abilities.
The following are some examples of that code:
PlayerAbility.cs
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
using UnityEngine.UI;
public class PlayerAbility : Singleton<PlayerAbility>
{
[SerializeField] private float distance = 5;
[SerializeField] private Transform cameraFollow;
[SerializeField] private LayerMask objectSelectionMask;
private bool _lookForObjects;
[FormerlySerializedAs("_currentAbility")] [Range(0, 1)]public int currentAbility; // 0 = Gravity, 1 = Possession
[SerializeField] private AbilitySelectionHUD abilitySelectionHUD;
[SerializeField] private SelectionAreaHUD selectionAreaHUD;
[SerializeField] private Slider abilitySelectionSlider;
private int _abilitySelectionIndex;
private float _originalTimeScale;
private int _abilityStageIndex;
public SelectionArea selectionArea;
[FormerlySerializedAs("_forceAmount")] public float forceAmount = 60;
[SerializeField, Range(1, 10)] private float forceMovementTime = 5f;
[SerializeField, Range(1, 10)] private float forceMovementEnd = 10f;
private List<GameObject> _objectRigidbodies;
[SerializeField] private GameObject cinemachineCamera;
[SerializeField] private float cooldownTime = 5f;
private bool _abilityIsCoolingDown;
private int abilityCharge = 4;
public bool useAbilityCharge = false;
PlayerMovement _playerMovement;
[SerializeField] private GameObject cooldownAbilityHUD;
private void Start()
{
_playerMovement = GetComponent<PlayerMovement>();
_originalTimeScale = Time.timeScale;
_abilityStageIndex = 0;
}
public void OnAbilityInteract(InputAction.CallbackContext context)
{
if (GameManager.Instance.isOnSettings) return;
if (abilityCharge == 0 && useAbilityCharge) return;
if (_abilityIsCoolingDown) return;
if (GameManager.Instance.gameIsOver) return;
Debug.Log($"Ability Stage Index: {_abilityStageIndex}");
if (!context.performed) return;
if (_abilityStageIndex == 0) Selection();
}
public void OnAbilityCast(InputAction.CallbackContext context)
{
if (!context.performed || _abilityStageIndex != 1) return;
Debug.Log(currentAbility.ToString());
Casting();
}
private void Selection()
{
abilitySelectionHUD.gameObject.SetActive(true);
selectionArea.gameObject.SetActive(true);
_abilityStageIndex++;
}
private void Casting()
{
// enable cinemachine
cinemachineCamera.gameObject.SetActive(true);
abilitySelectionHUD.gameObject.SetActive(false);
selectionArea.gameObject.SetActive(true);
PerformAbility(currentAbility, selectionArea.GetObjects(), selectionArea.GetRadius());
abilityCharge--;
Debug.Log($"Ability Charge: {abilityCharge}");
selectionArea.gameObject.SetActive(false);
_abilityStageIndex = 0;
StartCoroutine(CoolDownAbility());
}
public void AddAbilityCharge(int charge)
{
abilityCharge += charge;
}
private void PerformAbility(int ability, List<GameObject> objects, float radius)
{
_objectRigidbodies = objects;
switch (ability)
{
case 0:
foreach (var objectRigidBody in objects.Select(selectedObject => selectedObject.GetComponent<Rigidbody>()))
{
StartCoroutine(MoveObjectUsingGravity(objectRigidBody, radius));
}
break;
case 1:
foreach (var interactable in objects.Select(selectedObject =>
selectedObject.GetComponent<Interactables>()))
{
StartCoroutine(MoveObjects(interactable));
}
break;
}
// 0 = Gravity
// 1 = Possession
}
IEnumerator CoolDownAbility()
{
_abilityIsCoolingDown = true;
cooldownAbilityHUD.SetActive(true);
yield return new WaitForSeconds(cooldownTime);
_abilityIsCoolingDown = false;
cooldownAbilityHUD.SetActive(false);
}
IEnumerator MoveObjects(Interactables objects)
{
objects.canMove = true;
yield return new WaitForSecondsRealtime(forceMovementEnd);
objects.canMove = false;
}
private IEnumerator MoveObjectUsingGravity(Rigidbody objectRb, float radius)
{
objectRb.useGravity = false;
Vector3 forceToAdd = Vector3.up * (radius * forceAmount);
Debug.Log($"Force to add {forceToAdd}");
objectRb.AddForce(forceToAdd * Time.fixedDeltaTime, ForceMode.Impulse);
yield return new WaitForSecondsRealtime(forceMovementEnd);
CancelAbility();
}
public void OnAbilityCancel(InputAction.CallbackContext context)
{
Time.timeScale = _originalTimeScale;
if (abilitySelectionHUD != null) abilitySelectionHUD.gameObject.SetActive(false);
if (selectionArea != null) selectionArea.gameObject.SetActive(false);
_abilityStageIndex = 0;
CancelAbility();
}
void CancelAbility()
{
if (_objectRigidbodies == null) return;
foreach (var objectRb in _objectRigidbodies)
{
objectRb.transform.parent = null;
Rigidbody rb = objectRb.GetComponent<Rigidbody>();
rb.useGravity = true;
}
_objectRigidbodies.Clear();
}
}
Interactables.cs
using UnityEngine;
using UnityEngine.Serialization;
public class Interactables : MonoBehaviour
{
Vector3 _originalPosition;
Quaternion _originalRotation;
public bool canMove;
private float _movementSpeed = 7.5f;
private Vector2 _movementDirection;
PlayerMovement _playerMovement;
Rigidbody _rb;
Camera _camera;
// Start is called before the first frame update
private void Start()
{
_playerMovement = FindObjectOfType<PlayerMovement>();
_originalPosition = transform.position;
_originalRotation = transform.rotation;
_rb = GetComponent<Rigidbody>();
_camera = Camera.main;
}
private void Update()
{
GetMovement();
}
private void FixedUpdate()
{
MoveObject();
}
void MoveObject()
{
if (_rb == null) return;
if (!canMove)
{
_rb.isKinematic = false;
return;
}
_rb.isKinematic = true;
Vector3 movement = _camera.transform.right * _movementDirection.x + _camera.transform.forward * _movementDirection.y;
movement.y = 0f;
_rb.MovePosition(_rb.position + movement * (_movementSpeed * Time.fixedDeltaTime));
}
void GetMovement()
{
_movementDirection = _playerMovement.direction;
}
public void ResetPosition()
{
transform.position = _originalPosition;
transform.rotation = _originalRotation;
}
}
AbilitySelectionHUD
using System;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
public class AbilitySelectionHUD : MonoBehaviour
{
[SerializeField] private Image abilityImageSlot;
[SerializeField] private Sprite possessionAbilityIcon;
[SerializeField] private Sprite gravityAbilityIcon;
private PlayerAbility _ability;
[SerializeField] private TextMeshProUGUI abilityMultiplierText;
private float _currentAbilitySelection;
[SerializeField] TextMeshProUGUI useAbilityText;
[SerializeField] private String useAbilityTextController;
[SerializeField] private String useAbilityTextKbm;
GameManager _gameManager;
private void OnEnable()
{
_gameManager = FindObjectOfType<GameManager>();
_ability = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerAbility>();
ChangeSelectedAbility(0);
}
private void Update()
{
useAbilityText.text = _gameManager.isUsingController ? useAbilityTextController : useAbilityTextKbm;
}
public void OnDetectInputDevice(InputAction.CallbackContext context)
{
switch (context.control.device)
{
case Gamepad:
useAbilityText.text = useAbilityTextController;
break;
case Keyboard:
useAbilityText.text = useAbilityTextKbm;
break;
}
}
public void OnChangeAbility(InputAction.CallbackContext context)
{
if (!context.performed) return;
_currentAbilitySelection = _currentAbilitySelection switch
{
0 => 1,
1 => 0,
_ => _currentAbilitySelection
};
ChangeSelectedAbility(_currentAbilitySelection);
}
void ChangeSelectedAbility(Single ability)
{
abilityImageSlot.sprite = ability switch
{
0 => gravityAbilityIcon,
1 => possessionAbilityIcon,
_ => abilityImageSlot.sprite
};
_ability.currentAbility = (int)ability;
}
}
You can find more examples of code on Sam’s blog: METAL MADNESS by Sam Cox – Metal Madness