The first level (out of eleven) from a AAA-like sci-fi FPS. Made in Unity 5.6 using the C# language. The other
levels were made by other teams of comparable sizes in the same working environment.
Notes
We spent about 24 hours designing/planning and 48 hours producing.
I worked with an existing codebase which took care of most of the game’s core cross-level functionality
(like player controls, UI, and enemy AI) to code the functionality specifics for our level. Naturally, a big part of this was front-end.
Scripts
Below you can look at some of the scripts I wrote for this project.
I used my own preference for conventions here, but I’d have no problem using others’.
A quick sum-up:
Interaction system
Objective system
Spawners
Light flickering
Pickup shine
Interaction Handler
This script is responsible for the actual interacting of the player. It does this by shooting raycasts and calling events.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace level_01.Interaction { public class InteractionHandler : MonoBehaviour { #region Members private bool m_IsEnabled = true; private event ChangeInteractionDelegate m_ChangeInteractionEvent; #endregion Members #region Serialized Members [SerializeField] private Player m_Player; [SerializeField] private float m_Range; [SerializeField] private LayerMask m_LayerMask; #endregion Serialized Members #region Properties public bool IsEnabled { get { return m_IsEnabled; } set { m_IsEnabled = value; } } public ChangeInteractionDelegate ChangeInteractionEvent { get { return m_ChangeInteractionEvent; } set { m_ChangeInteractionEvent = value; } } #endregion Properties private void Start() { SubscribeListeners(); } private void OnDestroy() { UnsubscribeListeners(); } private void SubscribeListeners() { if (m_Player != null) { m_Player.DeathEvent += OnPlayerDeath; m_Player.RespawnEvent += OnPlayerRespawn; } } private void UnsubscribeListeners() { if (m_Player != null) { m_Player.DeathEvent -= OnPlayerDeath; m_Player.RespawnEvent -= OnPlayerRespawn; } } private void Update() { if (m_IsEnabled) { /* * Fire a single ray. (get only the first target) */ Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0.0f)); RaycastHit hitInfo; bool hitSuccess = Physics.Raycast(ray, out hitInfo, m_Range, m_LayerMask, QueryTriggerInteraction.Collide); if (!hitSuccess) { CallChangeInteractableEvent(null); return; } GameObject go = hitInfo.collider.gameObject; /* * Did we hit an interactible? */ IInteractable interactable = go.GetComponent<IInteractable>(); InteractableReferention referention = go.GetComponent<InteractableReferention>(); if (interactable == null) { if (referention == null) { CallChangeInteractableEvent(null); return; } else { if (referention.IInteractable == null) { CallChangeInteractableEvent(null); return; } else { interactable = referention.IInteractable; } } } /* * At this point, we should be sure we're looking at an interactable. */ if (!interactable.IsEnabled) { CallChangeInteractableEvent(null); return; } if (Input.GetKeyDown(KeyCode.E)) { interactable.Interact(m_Player); } CallChangeInteractableEvent(interactable); } } private void CallChangeInteractableEvent(IInteractable interactable) { ChangeInteractionDelegate evt = ChangeInteractionEvent; if (evt != null) { evt(interactable); } } private void OnPlayerDeath() { m_IsEnabled = false; CallChangeInteractableEvent(null); } private void OnPlayerRespawn() { m_IsEnabled = true; } } } |
Spawner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace level_01.Spawning { using level_01.Util; public delegate void AutoSpawnEventHandler(GameObject go, SpawnPlan plan); public class Spawner : Singleton<Spawner> { private event AutoSpawnEventHandler m_AutoSpawnEvent; public AutoSpawnEventHandler AutoSpawnEvent { get { return m_AutoSpawnEvent; } set { m_AutoSpawnEvent = value; } } #region Serialized Members [SerializeField] private Transform m_Container; [Tooltip("Add any spawn plans found in direct children to the list.")] [SerializeField] private bool m_AreChildrenSpawnPlans; [SerializeField] private List<SpawnPlan> m_SpawnPlans; [Header("Auto-Executing Plans")] [Tooltip("Start executing the spawn plans automatically according to these settings if they allow it.")] [SerializeField] private bool m_AutoExecute = true; [Tooltip("Execute spawn plans in a random order?")] [SerializeField] private bool m_ExecuteRandomly = false; #endregion Serialized Members #region Properties public List<SpawnPlan> SpawnPlans { get { return m_SpawnPlans; } } #endregion Properties private void Start() { if (m_AreChildrenSpawnPlans) { for (int i = 0; i < transform.childCount; i++) { SpawnPlan plan = transform.GetChild(i).GetComponent<SpawnPlan>(); if (plan != null) { m_SpawnPlans.Add(plan); } } } if (m_AutoExecute) { StartCoroutine(ExecuteSpawnPlansRoutine(m_SpawnPlans, true)); } } private void CallAutoSpawnEvent(GameObject go, SpawnPlan plan) { if (m_AutoSpawnEvent != null) { m_AutoSpawnEvent(go, plan); } } private IEnumerator ExecuteSpawnPlansRoutine(List<SpawnPlan> plans, bool isAutoExec) { List<SpawnPlan> _plans = plans; if (!m_ExecuteRandomly) { _plans.Shuffle(); } /* * Go through the plans and start spawning. */ for (int i = 0; i < _plans.Count; i++) { SpawnPlan plan = _plans[i]; /* * If this is an automatically started routine, check if the plan allows to be auto-executed before executing. */ if (!isAutoExec || plan.IsAutoExecAllowed) { yield return StartCoroutine(ExecuteSpawnPlanRoutine(plan, isAutoExec)); } } } private IEnumerator ExecuteSpawnPlanRoutine(SpawnPlan plan, bool isAutoExec) { yield return new WaitForSeconds(plan.InitialDelay); for (int j = 0; j < plan.Amount || plan.Amount <= 0; j++) { GameObject go = SingleSpawn(plan.transform.position, plan.Prefabs, plan.Radius, plan.IsFlat); if (isAutoExec) { CallAutoSpawnEvent(go, plan); } yield return new WaitForSeconds(plan.Interval); } } /// <summary> /// Instantiate a GameObject instance within a random sphere around origin and parent it to the container. (Or if there is none, the spawner itself) /// </summary> /// <param name="prefabs">The list from which a random prefab will be chosen to spawn.</param> /// <param name="radius">Maximum possible distance.</param> /// <param name="isFlat">Keep the Y spawn pos at origin, instead of fluctuating.</param> /// <returns>The spawned GameObject.</returns> public GameObject SingleSpawn(Vector3 origin, List<GameObject> prefabs, float radius = .0f, bool isFlat = true) { Vector3 pos = origin + Random.insideUnitSphere * radius; if (isFlat) { pos.y = origin.y; } GameObject go = Instantiate(prefabs[Random.Range(0, prefabs.Count)], pos, Quaternion.identity); go.transform.SetParent((m_Container != null) ? m_Container.transform : transform, true); return go; } public GameObject SimpleSpawn(Vector3 origin, GameObject prefab, float radius = .0f, bool isFlat = true) { return SingleSpawn(origin, new List<GameObject>() { prefab }, radius, isFlat); } public void ExecutePlan(SpawnPlan plan) { StartCoroutine(ExecuteSpawnPlanRoutine(plan, false)); } public void ExecutePlans(List<SpawnPlan> plans) { StartCoroutine(ExecuteSpawnPlansRoutine(plans, false)); } public void ExecutePlans() { ExecutePlans(m_SpawnPlans); } public void StopAllSpawnPlanExecutions() { StopAllCoroutines(); } } } |
Light Flickering
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace level_01.FX { using level_01.Util; public class LightFlicker : MonoBehaviour { #region Members private bool m_IsEnabled = true; #endregion Members #region Serialized Members [SerializeField] private List<Renderer> m_Renderers; [SerializeField] private Color m_EmissionColor; [SerializeField] private Light m_Light; [SerializeField] private float m_IntensityMultiplier = 1.0f; [Tooltip("Axis X is amount of delay. Axis Y is chance.")] [SerializeField] private AnimationCurve m_DelayCurve = AnimationCurve.Linear(.0f, .01f, 1.0f, 1.0f); [SerializeField] private float m_DelayMultiplier = 1.0f; #endregion Serialized Members #region Properties public bool IsEnabled { get { return m_IsEnabled; } set { m_IsEnabled = value; } } public Color EmissionColor { get { return m_EmissionColor; } set { m_EmissionColor = value; } } public float IntensityMultiplier { get { return m_IntensityMultiplier; } set { m_IntensityMultiplier = value; } } public float DelayMultiplier { get { return m_DelayMultiplier; } set { m_DelayMultiplier = value; } } #endregion Properties private void Awake() { if (m_Light == null) { m_Light = GetComponent<Light>(); } } private void Start() { StartCoroutine(FlickerRoutine()); } private IEnumerator FlickerRoutine() { while (m_IsEnabled) { yield return new WaitForSeconds(Util.GetCurveValueAtRandomTime(m_DelayCurve) * m_DelayMultiplier); TurnLightOff(); yield return new WaitForSeconds(Util.GetCurveValueAtRandomTime(m_DelayCurve) * m_DelayMultiplier); TurnLightOn(); } } private void TurnLightOn() { m_Light.intensity = m_IntensityMultiplier; SetRendererEmissions(m_EmissionColor); } private void TurnLightOff() { m_Light.intensity = .0f; SetRendererEmissions(Color.black); } private void SetRendererEmissions(Color value) { foreach (Renderer r in m_Renderers) { r.material.SetColor("_EmissionColor", value); } } } } |
Pickup Shine
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace level_01.FX { public class PickupShine : MonoBehaviour { #region Members private Coroutine m_CurrentShineRoutine; #endregion Members #region Serialized Members [SerializeField] private bool m_IsEnabled; [SerializeField] private bool m_AutoStart = true; [SerializeField] private List<Material> m_SharedMaterials; [SerializeField] private float m_Interval = 2.0f; [SerializeField] private float m_SpeedMultiplier = 3.0f; [Range(.0f, 1.0f)] [SerializeField] private float m_IntensityMultiplier = .75f; #endregion Serialized Members #region Properties public bool IsEnabled { get { return m_IsEnabled; } set { m_IsEnabled = value; } } public float Interval { get { return m_Interval; } set { m_Interval = value; } } public float SpeedMultiplier { get { return m_SpeedMultiplier; } set { m_SpeedMultiplier = value; } } public float IntensityMultiplier { get { return m_IntensityMultiplier; } set { m_IntensityMultiplier = value; } } #endregion Properties private void Start() { if (m_AutoStart && m_SharedMaterials.Count > 0) { StartShine(); } } public void StartShine() { if (m_CurrentShineRoutine == null) { m_CurrentShineRoutine = StartCoroutine(ShineRoutine()); } } public void StopShine() { if (m_CurrentShineRoutine != null) { StopCoroutine(m_CurrentShineRoutine); } } private IEnumerator ShineRoutine() { SetMaterialEmissions(Color.black); while (m_IsEnabled) { Color emissionColor = new Color(); float emission = Mathf.PingPong(Time.time * m_SpeedMultiplier, m_IntensityMultiplier); emissionColor.r = emission; emissionColor.g = emission; emissionColor.b = emission; SetMaterialEmissions(emissionColor); if (emission < Time.maximumDeltaTime) { emission = .0f; SetMaterialEmissions(Color.black); yield return new WaitForSeconds(m_Interval); } yield return null; } } private void SetMaterialEmissions(Color value) { foreach (Material mat in m_SharedMaterials) { mat.SetColor("_EmissionColor", value); } } } } |