In Progress! [Roll-a-Ball] 🎮
Unity demo excercise where you play as a ball collecting consumables, dodging enemies.
Work in progress using Unity Learn as an aid for this project.
** Developed in Unity 6.3 LTS **
Role: Solo Developer
In Progress V0.00.01 demonstration of scripts below:
In Progress V0.00.02 expanding scripts and start of level design and finishing of gameplay loop:
In progress V0.00.03 Experimenting with level design for first level and LOS(Line of Sight) for initial enemies:
- Area of detection represented by greyish transparent area around enemies for visibility during testing.
In progress PlayerController.cs script handles player movement and physics and contact with enemies.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class PlayerController : MonoBehaviour
{
public float speed = 0;
public TextMeshProUGUI countText;
public GameObject winTextObject;
private Rigidbody rb;
private float movementX;
private float movementY;
// Count variable for UI to keep track of collectibles
private int count;
void Start()
{
rb = GetComponent<Rigidbody>();
count = 0;
SetCountText();
winTextObject.SetActive(false);
}
private void OnMove(InputValue movementValue)
{
Vector2 movementVector = movementValue.Get<Vector2>();
movementX = movementVector.x;
movementY = movementVector.y;
}
private void SetCountText()
{
countText.text = "Count: " + count.ToString();
if(count >= 13)
{
winTextObject.SetActive(true);
// Destroy enemy
Destroy(GameObject.FindGameObjectWithTag("Enemy"));
}
}
private void FixedUpdate()
{
Vector3 movement = new Vector3(movementX, 0.0f, movementY);
rb.AddForce(movement * speed);
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
// Destroy the current object
Destroy(gameObject);
// Update the winText to display "You Lose!"
winTextObject.gameObject.SetActive(true);
winTextObject.GetComponent<TextMeshProUGUI>().text = "You Lose!";
}
}
// On contact will disable game objects with "PickUp" tag. Aka our collectibles in the demo.
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.CompareTag("PickUp"))
{
other.gameObject.SetActive(false);
count = count + 1;
SetCountText();
}
}
}
CameraController.cs
// Camera follows player movement, as player is a ball ensures camera does not rotate with the balls rotation.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
public GameObject player;
private Vector3 offset;
void Start()
{
offset = transform.position - player.transform.position;
}
void LateUpdate()
{
transform.position = player.transform.position + offset;
}
}
Rotator.cs
// For the collectibles in the game, to give a visual *Pop* we have this simple rotating script.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Rotator : MonoBehaviour
{
// Every frame the object will be rotated on the x,y,z
void Update()
{
transform.Rotate(new Vector3(15, 30, 45) * Time.deltaTime);
}
}
EnemyMovement.cs
// Script for an enemy tracking player movement within a mesh
using UnityEngine;
using UnityEngine.AI;
public class EnemyMovement : MonoBehaviour
{
public Transform player;
private NavMeshAgent navMeshAgent;
private float loseSightTimer = 0f;
[SerializeField] private float loseSightDelay = 2f;
private bool isChasing = false;
void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>();
}
void Update()
{
if (isChasing && player != null)
{
navMeshAgent.SetDestination(player.position);
}
else
{
navMeshAgent.ResetPath();
}
}
public void SetChasing(bool chasing)
{
isChasing = chasing;
}
}
FieldOfView.cs
// Controls the field of view for enemies to detect player character. Experimenting with cone vision vs 360 degrees in version 3.
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class FieldOfView : MonoBehaviour
{
[Header("Vision Settings")]
[SerializeField] private LayerMask obstacleMask;
[SerializeField] private float fov = 90f;
[SerializeField] private float viewDistance = 15f;
[SerializeField] private float eyeHeight = 1.5f;
[Header("References")]
[SerializeField] private Transform enemyTransform;
[Header("Movement")]
[SerializeField] private float moveSpeed = 5f;
private EnemyMovement enemyMovement;
private Mesh mesh;
private float startingAngle;
private Transform player;
private bool canSeePlayer;
private void Start()
{
enemyMovement = enemyTransform.GetComponent<EnemyMovement>();
mesh = new Mesh();
GetComponent<MeshFilter>().mesh = mesh;
player = GameObject.FindGameObjectWithTag("Player").transform;
}
private void LateUpdate()
{
Vector3 origin = enemyTransform.position + Vector3.up * eyeHeight;
canSeePlayer = false;
SetAimDirection(enemyTransform.forward);
int rayCount = 50;
float angle = startingAngle;
float angleIncrease = fov / rayCount;
Vector3[] vertices = new Vector3[rayCount + 2];
Vector2[] uv = new Vector2[vertices.Length];
int[] triangles = new int[rayCount * 3];
vertices[0] = transform.InverseTransformPoint(origin);
int vertexIndex = 1;
int triangleIndex = 0;
for (int i = 0; i <= rayCount; i++)
{
Vector3 direction = GetVectorFromAngle(angle);
Vector3 vertex = origin + direction * viewDistance;
RaycastHit hit;
if (Physics.Raycast(origin, direction, out hit, viewDistance, obstacleMask))
{
// Stop vision at first object hit
vertex = hit.point;
if (hit.collider.CompareTag("Player"))
{
canSeePlayer = true;
enemyMovement.SetChasing(true);
}
}
else
{
// Nothing hit → full vision range
vertex = origin + direction * viewDistance;
}
vertices[vertexIndex] = transform.InverseTransformPoint(vertex);
if (i > 0)
{
triangles[triangleIndex + 0] = 0;
triangles[triangleIndex + 1] = vertexIndex - 1;
triangles[triangleIndex + 2] = vertexIndex;
triangleIndex += 3;
}
vertexIndex++;
angle -= angleIncrease;
}
mesh.vertices = vertices;
mesh.uv = uv;
mesh.triangles = triangles;
mesh.RecalculateBounds();
DetectPlayer(origin);
HandleMovement();
}
private void DetectPlayer(Vector3 origin)
{
float distanceToPlayer = Vector3.Distance(origin, player.position);
if (distanceToPlayer <= viewDistance)
{
Vector3 dirToPlayer = (player.position - origin).normalized;
float angleToPlayer = Vector3.Angle(enemyTransform.forward, dirToPlayer);
if (angleToPlayer < fov / 2f)
{
if (!Physics.Raycast(origin, dirToPlayer, distanceToPlayer, obstacleMask))
{
canSeePlayer = true;
enemyMovement.SetChasing(canSeePlayer);
}
}
}
}
private void HandleMovement()
{
enemyMovement.SetChasing(canSeePlayer);
}
public static Vector3 GetVectorFromAngle(float angle)
{
float angleRad = (angle + 90f) * Mathf.Deg2Rad; // shift by +90 degrees
return new Vector3(Mathf.Cos(angleRad), 0, Mathf.Sin(angleRad));
}
public void SetAimDirection(Vector3 aimDirection)
{
startingAngle = GetAngleFromVectorFloat(aimDirection) - fov / 2f;
}
public static float GetAngleFromVectorFloat(Vector3 dir)
{
dir = dir.normalized;
float angle = Mathf.Atan2(dir.z, dir.x) * Mathf.Rad2Deg;
if (angle < 0) angle += 360;
return angle;
}
}