Notch
Here are a collection of scripts written in C# which I have written for games in Unity.
Enemy Pooler
This script uses a Dictionary and Coroutines to spawn enemies which have already been instantiated at the beginning of the game. By doing this I save on performance for the enemies in the game avoiding instantiating during runtime.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyPooler : MonoBehaviour
{
[System.Serializable]
public class Pool {
public string tag;
public GameObject prefab;
public int size;
}
[SerializeField]
private List<Pool> pools;
private Dictionary<string, Queue<GameObject>> poolDictionary;
private Coroutine spawnCoroutine = null;
[SerializeField]
private float minRadius = 5f, maxRadius = 10f, spawnTime = 1f;
private void Start() {
SetDictionary();
spawnCoroutine = StartCoroutine(SpawnEnemy());
}
private void SetDictionary() {
poolDictionary = new Dictionary<string, Queue<GameObject>>();
int index = 0;
foreach(Pool pool in pools) {
Queue<GameObject> objectPool = new Queue<GameObject>();
for(int i = 0; i < pool.size; i++) {
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
obj.transform.parent = gameObject.transform;
objectPool.Enqueue(obj);
}
poolDictionary.Add(index.ToString(), objectPool);
index++;
}
}
private IEnumerator SpawnEnemy() {
while(true) {
SpawnFromPool();
yield return new WaitForSeconds(spawnTime);
}
}
private GameObject SpawnFromPool() {
string index = Random.Range(0, pools.Count).ToString();
if(!poolDictionary.ContainsKey(index)) {
Debug.LogWarning("Pool in index " + index + " doesn't exist");
return null;
}
GameObject enemyInstance = poolDictionary[index].Dequeue();
enemyInstance.SetActive(true);
float ranX = Random.Range(-maxRadius, maxRadius);
float ranZ = Random.Range(-maxRadius, maxRadius);
if(ranX < minRadius && ranX > -minRadius && ranZ < minRadius && ranZ > -minRadius) {
int temp = Random.Range(0, 1);
if(temp == 1) {
if(Mathf.Sign(ranX) == 1) {
ranX += minRadius;
} else {
ranX -= minRadius;
}
} else{
if(Mathf.Sign(ranZ) == 1) {
ranZ += minRadius;
} else {
ranZ -= minRadius;
}
}
}
Vector3 spawnPos = GameObject.FindGameObjectWithTag("Player").transform.position;
spawnPos += new Vector3(ranX, 0,ranZ);
NavMeshHit closestHit;
if(NavMesh.SamplePosition(spawnPos, out closestHit, 500, 1 )) {
enemyInstance.transform.position = closestHit.position;
}
enemyInstance.GetComponent<NavMeshAgent>().enabled = true;
poolDictionary[index].Enqueue(enemyInstance);
return enemyInstance;
}
public void EndSpawnCoroutine() {
EndSpawnCoroutineHelper();
}
private void EndSpawnCoroutineHelper() {
StopCoroutine(spawnCoroutine);
}
}
Enemy Values
This script contains all the values and functions for an enemies stats, level ups, and changes. Reusable allowing new enemies to be quickly implemented.
using System.Collections;
using UnityEngine;
using UnityEngine.AI;
public class EnemyValues : MonoBehaviour
{
private GameManager gameManager = null;
private WalkingSounds walkingSoundsScript = null;
private Animator animator = null;
[Header("Basic Values")]
[SerializeField]
private float _health = 1f;
[SerializeField]
private float _speed = 1f;
private float tempSpeed = 1f;
[SerializeField]
private float _damage = 1f;
[SerializeField]
private float _attackSpeed = 1f;
[SerializeField]
private float _rotationSpeed = 1f;
[SerializeField]
private float animDeathTime = 1f;
[SerializeField]
private bool isBoss = false;
private void Start()
{
gameManager = GameObject.FindGameObjectWithTag("GameManager").GetComponent<GameManager>();
walkingSoundsScript = gameObject.GetComponent<WalkingSounds>();
animator = gameObject.transform.GetChild(0).GetComponent<Animator>();
tempSpeed = _speed;
}
public void ApplyValues()
{
ApplyValuesHelper();
}
private void ApplyValuesHelper()
{
gameObject.GetComponent<NavMeshAgent>().speed = _speed;
if(!isBoss)
{
walkingSoundsScript.StartWalkingLoop();
}
}
public void IncreaseValues(int level)
{
IncreaseValuesHelper(level);
}
private void IncreaseValuesHelper(int level)
{
_speed += level;
_health += level;
_damage += level;
_attackSpeed += level;
_rotationSpeed += level;
ApplyValuesHelper();
}
// Setters
public void ZeroSpeed()
{
ZeroSpeedHelper();
}
private void ZeroSpeedHelper()
{
gameObject.GetComponent<NavMeshAgent>().speed = 0;
}
public void ResetSpeed()
{
ResetSpeedHelper();
}
private void ResetSpeedHelper()
{
gameObject.GetComponent<NavMeshAgent>().speed = _speed;
}
public void SetHealth(float health)
{
SetHealthHelper(health);
}
private void SetHealthHelper(float health)
{
_health = health;
if(_health <= 0)
{
StartCoroutine(Death());
}
}
private IEnumerator Death()
{
animator.SetTrigger("Die");
gameObject.GetComponent<BoxCollider>().enabled = false;
ZeroSpeedHelper();
if(!isBoss)
{
walkingSoundsScript.StopWalkingLoop();
}
yield return new WaitForSeconds(animDeathTime);
if(isBoss)
{
gameManager.Win();
}
gameObject.SetActive(false);
gameObject.GetComponent<NavMeshAgent>().enabled = false;
gameObject.GetComponent<BoxCollider>().enabled = true;
ResetSpeedHelper();
}
public void SetDamage(float damage)
{
SetDamageHelper(damage);
}
private void SetDamageHelper(float damage)
{
_damage = damage;
}
public void SetAttackSpeed(float attackSpeed)
{
SetAttackSpeedHelper(attackSpeed);
}
private void SetAttackSpeedHelper(float attackSpeed)
{
_attackSpeed = attackSpeed;
}
public void SetRotationSpeed(float rotationSpeed)
{
SetRotationSpeedHelper(rotationSpeed);
}
private void SetRotationSpeedHelper(float rotationSpeed)
{
_rotationSpeed = rotationSpeed;
}
// Getters
public float GetHealth()
{
return GetHealthHelper();
}
private float GetHealthHelper()
{
return _health;
}
public float GetDamage()
{
return GetDamageHelper();
}
private float GetDamageHelper()
{
return _damage;
}
public float GetAttackSpeed()
{
return GetAttackSpeedHelper();
}
private float GetAttackSpeedHelper()
{
return _attackSpeed;
}
public float GetRotationSpeed()
{
return GetRotationSpeedHelper();
}
private float GetRotationSpeedHelper()
{
return _rotationSpeed;
}
}
Passive Mob Movement
This script implements movement for passive mobs. The mobs hae two states, random movement and running away. Which is chosen is dependent on the player's distance from the mob. The random movement will move the mob to a spot in a radius around them every certain amount of time set. The running away movement pushes the mob the opposite direction of the player for a time until the player is not within its detection radius.
using UnityEngine;
using UnityEngine.AI;
public class PassiveMobMovement : MonoBehaviour
{
private NavMeshAgent nav = null;
private Animator animator = null;
private GameObject player = null;
[SerializeField]
private float walkRadius = 1f;
[SerializeField]
private float detectRadius = 1f;
[SerializeField]
private float runDistance = 1f;
private float nextTurnTime = 1f;
[SerializeField]
private float nextTurnIncrement = 1f;
[SerializeField]
private float speedThreshold = 0.15f;
private void Start()
{
player = GameObject.FindGameObjectWithTag("Player");
animator = gameObject.transform.GetChild(0).GetComponent<Animator>();
}
private void FixedUpdate()
{
if(CheckPlayerPos())
{
MoveAway();
}
else if(nextTurnTime < Time.time)
{
MoveTo();
}
CheckAnim();
}
private void MoveTo()
{
float ranX = Random.Range(transform.position.x - walkRadius, transform.position.x + walkRadius);
float ranZ = Random.Range(transform.position.z - walkRadius, transform.position.z + walkRadius);
Vector3 moveTo = new Vector3(ranX, 0, ranZ);
NavMeshHit hit;
NavMesh.SamplePosition(moveTo, out hit, 5, 1);
nav.SetDestination(hit.position);
nextTurnTime = Time.time + nextTurnIncrement;
}
private void MoveAway()
{
Transform startTransform = transform;
transform.rotation = Quaternion.LookRotation(transform.position - player.transform.position);
Vector3 runTo = transform.position + transform.forward * runDistance;
NavMeshHit hit;
NavMesh.SamplePosition(runTo, out hit, 5, 1);
transform.position = startTransform.position;
transform.rotation = startTransform.rotation;
nav.SetDestination(hit.position);
}
private bool CheckPlayerPos()
{
if((player.transform.position - transform.position).sqrMagnitude < detectRadius * detectRadius)
{
return true;
} else {
return false;
}
}
private void CheckAnim()
{
if(nav.velocity.magnitude < speedThreshold || nav.isStopped)
{
animator.SetBool("Idling", true);
}
else if(animator.GetBool("Idling"))
{
animator.SetBool("Idling", false);
}
}
public void SetNavAgent()
{
SetNavAgentHelper();
}
private void SetNavAgentHelper()
{
nav = gameObject.GetComponent<NavMeshAgent>();
}
}
Gun System
This script implements a system for using guns and allows different gun types to be made and used with the same code. By doing this new weapons can be implemented in the game quickly.
using UnityEngine;
using TMPro;
public class GunSystem : MonoBehaviour
{
[Header("Gun Stats")]
public int power;
[SerializeField] private int magazineSize;
[SerializeField] private float timeBetweenShooting;
[SerializeField] private float reloadTime;
[SerializeField] private bool allowButtonHold;
[Space]
[Header("Projectile and Spawn Position")]
[SerializeField] private GameObject projectilePrefab;
[SerializeField] private GameObject playerBody;
[SerializeField] private float distanceFromPlayer;
[SerializeField] private float offsetHorizontal;
[SerializeField] private float offsetVertical;
[Space]
[Header("Visuals")]
[SerializeField] private CamShake camShake;
[SerializeField] private float camShakeMagnitude;
[SerializeField] private float camShakeDuration;
[SerializeField] private TextMeshProUGUI bulletCountText;
private Vector3 originalRotation;
private int bulletsPerTap = 1;
private float bulletsLeft;
private float bulletsShot;
private float timeBetweenShots;
private bool shooting;
private bool readyToShoot;
private bool reloading;
private void Awake()
{
bulletsLeft = magazineSize;
readyToShoot = true;
originalRotation = transform.localEulerAngles;
}
private void Update()
{
MyInput();
bulletCountText.SetText(bulletsLeft + "/" + magazineSize);
}
private void MyInput()
{
if(allowButtonHold) shooting = Input.GetKey(KeyCode.Mouse0);
else shooting = Input.GetKeyDown(KeyCode.Mouse0);
if(Input.GetKeyDown(KeyCode.R) && bulletsLeft < magazineSize && !reloading) Reload();
if(readyToShoot && shooting && !reloading && bulletsLeft > 0)
{
bulletsShot = bulletsPerTap;
Shoot();
}
}
private void Shoot()
{
readyToShoot = false;
GameObject projectile = Instantiate(projectilePrefab);
projectile.transform.position = playerBody.transform.position + (playerBody.transform.forward * distanceFromPlayer) + (playerBody.transform.right * offsetHorizontal) + (playerBody.transform.up * offsetVertical);
projectile.transform.forward = playerBody.transform.forward;
StartCoroutine(camShake.Shake(camShakeDuration, camShakeMagnitude));
bulletsLeft--;
bulletsShot--;
Invoke("ResetShot", timeBetweenShooting);
if (bulletsShot > 0 && bulletsLeft > 0)
{
Invoke("Shoot", timeBetweenShots);
}
}
private void ResetShot()
{
readyToShoot = true;
}
private void Reload()
{
reloading = true;
Invoke("ReloadFinished", reloadTime);
}
private void ReloadFinished()
{
bulletsLeft = magazineSize;
reloading = false;
}
}
Walking Sounds
This script implements walking sounds which play from the enemies at their location continuously. These sounds are chosen randomly from a given array of them making the walking varied.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.UI;
public class SettingsMenu : MonoBehaviour
{
public AudioMixer audioMixer;
public TMPro.TMP_Dropdown resolutionDropdown;
Resolution[] resolutions;
void Start()
{
resolutions = Screen.resolutions;
resolutionDropdown.onValueChanged.AddListener(delegate { Screen.SetResolution(resolutions[resolutionDropdown.value].width, resolutions[resolutionDropdown.value].height, false); });
for (int i = 0; i < resolutions.Length; i++)
{
resolutionDropdown.options[i].text = ResToString(resolutions[i]);
resolutionDropdown.value = i;
resolutionDropdown.options.Add(new TMPro.TMP_Dropdown.OptionData(resolutionDropdown.options[i].text));
}
}
string ResToString(Resolution res)
{
return res.width + " x " + res.height;
}
public void SetVolume(float volume)
{
audioMixer.SetFloat("volume", volume);
}
public void SetQuality(int qualityIndex)
{
QualitySettings.SetQualityLevel(qualityIndex);
}
public void SetFullscreen(bool isFullscreen)
{
Screen.fullScreen = isFullscreen;
}
}
Settings Menu
A basic settings menu script for fullscreen, resolution, and volume changes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.UI;
public class SettingsMenu : MonoBehaviour
{
public AudioMixer audioMixer;
public TMPro.TMP_Dropdown resolutionDropdown;
Resolution[] resolutions;
void Start()
{
resolutions = Screen.resolutions;
resolutionDropdown.onValueChanged.AddListener(delegate { Screen.SetResolution(resolutions[resolutionDropdown.value].width, resolutions[resolutionDropdown.value].height, false); });
for (int i = 0; i < resolutions.Length; i++)
{
resolutionDropdown.options[i].text = ResToString(resolutions[i]);
resolutionDropdown.value = i;
resolutionDropdown.options.Add(new TMPro.TMP_Dropdown.OptionData(resolutionDropdown.options[i].text));
}
}
string ResToString(Resolution res)
{
return res.width + " x " + res.height;
}
public void SetVolume(float volume)
{
audioMixer.SetFloat("volume", volume);
}
public void SetQuality(int qualityIndex)
{
QualitySettings.SetQualityLevel(qualityIndex);
}
public void SetFullscreen(bool isFullscreen)
{
Screen.fullScreen = isFullscreen;
}
}
Dodo Follow
This script implements the movement for an enemy in a game I am working on called Dodo. The script changes how the enemy will move depending on their distance from the player. When close enough the enemy will spin around allowing for its shooting animation and attacks to begin.
using UnityEngine;
using UnityEngine.AI;
public class DodoFollow : MonoBehaviour
{
private EnemyValues valuesScript = null;
private DodoCombat combatScript = null;
private NavMeshAgent dodoNav = null;
private Transform player;
private float distance = 1f;
[SerializeField]
private float minDistance = 2f;
private float rotationSpeed = 1f;
private bool attackRunning = false;
private void Start()
{
valuesScript = gameObject.GetComponent<EnemyValues>();
combatScript = gameObject.GetComponent<DodoCombat>();
dodoNav = gameObject.GetComponent<NavMeshAgent>();
player = GameObject.FindGameObjectWithTag("Player").transform;
rotationSpeed = valuesScript.GetRotationSpeed();
}
private void FixedUpdate()
{
FollowRanged();
}
private void FollowRanged()
{
distance = Vector3.Distance(player.position, transform.position);
if(distance > minDistance)
{
if(attackRunning)
{
combatScript.StopRangedAttack();
attackRunning = false;
}
dodoNav.SetDestination(player.position);
} else {
if(!attackRunning)
{
combatScript.StartRangedAttack();
attackRunning = true;
}
dodoNav.SetDestination(transform.position);
Vector3 direction = (player.position - transform.position).normalized;
Quaternion lookRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * rotationSpeed);
}
}
}
using UnityEngine;
using UnityEngine.AI;
public class DodoFollow : MonoBehaviour
{
private EnemyValues valuesScript = null;
private DodoCombat combatScript = null;
private NavMeshAgent dodoNav = null;
private Transform player;
private float distance = 1f;
[SerializeField]
private float minDistance = 2f;
private float rotationSpeed = 1f;
private bool attackRunning = false;
private void Start()
{
valuesScript = gameObject.GetComponent<EnemyValues>();
combatScript = gameObject.GetComponent<DodoCombat>();
dodoNav = gameObject.GetComponent<NavMeshAgent>();
player = GameObject.FindGameObjectWithTag("Player").transform;
rotationSpeed = valuesScript.GetRotationSpeed();
}
private void FixedUpdate()
{
FollowRanged();
}
private void FollowRanged()
{
distance = Vector3.Distance(player.position, transform.position);
if(distance > minDistance)
{
if(attackRunning)
{
combatScript.StopRangedAttack();
attackRunning = false;
}
dodoNav.SetDestination(player.position);
} else {
if(!attackRunning)
{
combatScript.StartRangedAttack();
attackRunning = true;
}
dodoNav.SetDestination(transform.position);
Vector3 direction = (player.position - transform.position).normalized;
Quaternion lookRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * rotationSpeed);
}
}
}
Dodo Combat
This script implements the combat for the before mentioned Dodo. This involves the Dodo's attack animation, instantiating bullets, and their walking sounds.
using System.Collections;
using UnityEngine;
public class DodoCombat : MonoBehaviour
{
private EnemyValues valuesScript = null;
private WalkingSounds walkingSoundsScript = null;
private Animator animator = null;
private Coroutine rangedAttack = null;
[SerializeField]
private GameObject bullet = null;
[SerializeField]
private float jumpTime = 1f;
private void Start()
{
valuesScript = gameObject.GetComponent<EnemyValues>();
walkingSoundsScript = gameObject.GetComponent<WalkingSounds>();
animator = gameObject.transform.GetChild(0).GetComponent<Animator>();
}
private void OnCollisionEnter(Collision other)
{
if(other.gameObject.CompareTag("Bullet"))
{
Destroy(other.gameObject);
}
}
public void StartRangedAttack()
{
StartRangedAttackHelper();
}
private void StartRangedAttackHelper()
{
animator.speed = valuesScript.GetAttackSpeed();
rangedAttack = StartCoroutine(RangedAttack());
walkingSoundsScript.StartWalkingLoop();
}
private IEnumerator RangedAttack()
{
valuesScript.ZeroSpeed();
animator.SetBool("Walk", false);
animator.SetBool("Jump", true);
yield return new WaitForSeconds(jumpTime);
Vector3 targetAngles = transform.GetChild(0).transform.eulerAngles + new Vector3(0, 180f, 0);
transform.GetChild(0).transform.eulerAngles = targetAngles;
animator.SetBool("Jump", false);
while(true)
{
animator.SetBool("Attack", true);
yield return new WaitForSeconds(0.4f);
GameObject bulletInstance = Instantiate(bullet);
Vector3 spawnPos = new Vector3(transform.position.x, transform.position.y, transform.position.z) + transform.forward;
spawnPos.y += 1;
bulletInstance.transform.position = spawnPos;
bulletInstance.transform.eulerAngles = new Vector3(
bulletInstance.transform.eulerAngles.x,
transform.transform.eulerAngles.y,
bulletInstance.transform.eulerAngles.z );
animator.SetBool("Attack", false);
yield return new WaitForSeconds(valuesScript.GetAttackSpeed());
}
}
public void StopRangedAttack()
{
StopRangedAttackHelper();
}
private void StopRangedAttackHelper()
{
animator.SetBool("Jump", false);
animator.SetBool("Attack", false);
animator.SetBool("Walk", true);
Vector3 targetAngles = transform.GetChild(0).transform.eulerAngles + new Vector3(0, 180f, 0);
transform.GetChild(0).transform.eulerAngles = targetAngles;
valuesScript.ResetSpeed();
StopCoroutine(rangedAttack);
walkingSoundsScript.StartWalkingLoop();
}
}
Cutscenes
This script implements a cutscene effect making sure the player is then locked before being unlocked once the cutscene ends.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
// Works on cutscenes
public class Cutscenes : MonoBehaviour
{
[SerializeField] GameObject mainCam;
[SerializeField] GameObject focusCam;
[SerializeField] GameObject speech;
[SerializeField] GameObject boss;
[SerializeField] GameObject player;
[SerializeField] GameObject minions;
[SerializeField] GameObject end;
[SerializeField] GameObject cutStart;
[SerializeField] GameObject cutEnd;
// Update is called once per frame
void Update()
{
Scene currentScene = SceneManager.GetActiveScene();
float sceneNumber = currentScene.buildIndex;
if (sceneNumber == 18)
{
cutStart.SetActive(true);
if (speech == false && boss == false)
{
focusCam.SetActive(true);
cutStart.SetActive(false);
mainCam.SetActive(false);
player.GetComponent<GunnerInput>().enabled = false;
cutEnd.SetActive(true);
Invoke(nameof(End), 7.1f);
}
else if (speech == false)
{
player.GetComponent<GunnerInput>().enabled = true;
mainCam.SetActive(true);
focusCam.SetActive(false);
minions.SetActive(true);
}
}
else if (sceneNumber == 19)
{
if (boss == false)
{
focusCam.SetActive(true);
cutStart.SetActive(false);
mainCam.SetActive(false);
player.GetComponent<GunnerInput>().enabled = false;
cutEnd.SetActive(true);
Invoke(nameof(End), 7.1f);
}
}
}
void End()
{
Scene currentScene = SceneManager.GetActiveScene();
float sceneNumber = currentScene.buildIndex;
if (sceneNumber == 18)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 2);
}
else if (sceneNumber == 19)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
}
