Vigash Mini Project
Vigash Mini Project
R.J. VIGASH
The success and final outcome of the project required a lot of guidance and
assistance from many people and I am extremely privileged to have got this all along the
completion of my project. All that I have done is only due to such supervision and assistance
and I would not forget to thank them.
I respect and thank Dr. C. Initha Lebanon Ebency, Principal of Annai Violet
Arts and Science College for providing me an opportunity to do the project and giving me all
support and guidance which made me complete the project duly. I am extremely thankful to her
for providing such a nice support.
Also, I would like to extend my sincere esteems to staff in the laboratory for their
timely support.
CANDIDATE’S DECLARATION
I hereby declare that the work, which is being presented in the Project Report entitled "
GAME DEVELOPMENT USING UNITY AND C# (KLV : THE LONE WOLF)"
in partial fulfilment for the award of Degree of Bachelor Of Computer Applications and
submitted to the Department of Computer Applications, Annai Violet Arts & Science
College, is a record of my investigations carried under the Guidance of Mrs. S.
Mahalakshmi., M.C.A., M.Phil., Head of the Department of Computer
Applications under university of madras, Chennai.
I have not submitted the matter presented in this Project Report anywhere for the award
of any other Degree.
R.J. VIGASH
Register No: 212001196
Annai Violet Arts & Science College
TABLE OF CONTENT
CHAPTER TITLE PAGE
NO:
1. ABSTRACT 01
2. INTRODUCTION 02
3. OBJECTIVE 03
4. SYSTEM ANALYSIS 04
4.1 Purpose 04
4.2 Scope 04
4.3 Constraints 04
4.4 Risks 04
4.5 Functional Requirements 05
5. EXISTING SYSTEM 06
5.1 Operating System 06
5.2 Hardware 06
5.3 Development Tools 06
5.4 Testing Tools 06
6. PROPOSED SYSTEMS 07
6.1 Game Engine - Unity 07
6.2 AI Framework - Unity Machine 07
Learning Agents
6.3 Version Control - Git: 07
6.4 Game Assets and Resources: 07
6.5 Deployment 07
7. SYSTEM REQUIREMENTS 08
7.1 Software Requirements 08
7.2 Hardware requirements 08
8. MODULES 09
8.1 Flow chart 10
9. SYSTEM DESIGN 11
9.1 UseCase diagram 11
9.2 Game flow 11
9.3 Player input and character movement 12
9.4 Enemy AI 14
9.5 Animation layers 14
10. DEVELOPMENT PROCESS 16
11. CODING 17
12. MODELS USED 64
13. SYSTEM TESTING 65
13.1 Test Planning: 65
13.2 Test Execution: 65
13.3 Test Reporting: 65
13.4 Test Closure: 65
13.5 Regression Testing: 65
13.6 Performance Testing: 66
13.7 Security Testing: 66
13.8 Compatibility Testing: 66
13.9 Usability Testing: 66
13.10 Acceptance Testing: 66
14. SCREEN SHOTS 67
15. APPLICATION AND FUTURE 71
ENHANCEMENT
16. CONCLUSION 73
17. REFERENCE 74
1. ABSTRACT
KLV: The lone wolf is a thrilling third-person shooter game set in a spaceship that has
been attacked by a mysterious alien force. The game is developed using the Unity
game engine and C# scripting, with a focus on creating a fast-paced and immersive
experience for players. In the game, the player takes on the role of a skilled space
marine who must fight their way through the ship to find the source of the alien signal
that triggered the attack. The game features a variety of weapons and abilities that the
player can use to battle the alien forces, including an assault rifle, a shotgun, and a
powerful energy blade. The game is set in a detailed and immersive environment,
featuring dark corridors, flickering lights, and mysterious alien technology. The game
also features challenging puzzles that the player must solve to progress through the
game and uncover the truth behind the alien attack. The development of the game
involved the use of Unity's powerful game engine, including its physics engine, audio
system, and advanced animation tools. C# scripting was used extensively to create
custom game mechanics, including the enemy AI and weapon systems Overall, KLV:
The lone wolf is an exciting and action-packed game that showcases the capabilities
of the Unity game engine and C# scripting. The game provides a thrilling experience
for players who enjoy third-person shooter games and sci-fi themes. The project aims
to showcase the team's proficiency in game development and design principles while
demonstrating their capability in building AI technology for enemy characters in the
game. The game should provide an immersive and challenging gameplay experience,
where the player must strategically eliminate all the aliens and reach the designated
destination. The project also seeks to provide an opportunity for the team to apply
theoretical concepts to practical game development while demonstrating their ability
to use advanced game development technology.
1
2. INTRODUCTION
KLV: The Lone Wolf is an exciting new game developed using Unity and C#. In this
epic space adventure, you take on the role of a lone astronaut who must fight against
hordes of alien invaders to save your spacecraft from destruction. Armed only with
your trusty gun and your wits, you must battle your way through a dangerous and
treacherous environment to rescue your vessel and ensure your survival. With
stunning graphics, fast-paced gameplay, and an immersive storyline, KLV: The Lone
Wolf is a thrilling and action-packed experience that will keep you on the edge of
your seat. Get ready to face the challenge and save the day in this exciting new game!
The game should provide challenging gameplay mechanics that require the player to
strategically eliminate all the aliens and reach the designated destination. The project
aims to showcase the team's ability to create a visually stunning and interactive game
that delivers an exciting and engaging experience to players. we used Blender to make
the model which is open-source 3d modeling software that is good for making any
kind of 3d model. We named KLV in the KLV:The Lone wolf stands for Killer lunar
vangurd . this game is made to entertain the guys who play the game gets satisfied
with the gameplay and the stunning visual provided by our graphical design team m
2
3. OBJECTIVE
The objective of the KLV: The Lone Wolf project is to develop an engaging 3D game
using Unity and C# that immerses players in the role of a gay astronaut fighting
against alien invaders in a spaceship. The game should provide challenging gameplay
mechanics that require the player to strategically eliminate all the aliens and reach the
designated destination. The project aims to showcase the team's ability to create a
visually stunning and interactive game that delivers an exciting and engaging
experience to players. The project also seeks to demonstrate the team's proficiency in
game development and design principles. The project aims to showcase the team's
proficiency in game development and design principles while demonstrating their
capability in building AI technology for enemy characters in the game. The game
should provide an immersive and challenging gameplay experience, where the player
must strategically eliminate all the aliens and reach the designated destination. The
project also seeks to provide an opportunity for the team to apply theoretical concepts
to practical game development while demonstrating their ability to use advanced
game development technology.
3
4. SYSTEM ANALYSIS
4.1 Purpose
The purpose of the KLV: The Lone Wolf project is to create an engaging 3D game
that provides an immersive and challenging gameplay experience. The game should
feature astronaut fighting against alien invaders in a spaceship, where the player must
strategically eliminate all the aliens and reach the designated destination. The project
aims to showcase the team's proficiency in game development and design principles
while demonstrating their capability in building AI technology for enemy characters
in the game.
4.2 Scope
The scope of the project is to develop a 3D game using Unity and C# that features
multiple levels, enemy AI, engaging gameplay mechanics, and challenging obstacles.
The game should be designed to provide an immersive and exciting experience to
players while showcasing the team's proficiency in game development technology.
4.3 Constraints
The project is constrained by the time and resources available, as it is a part of the
3rd-year mini project for the college under Madras University. The project team
consists of Karan Singh, Logesh, and Vigash, and they have limited experience in
game development. The team will need to work collaboratively and efficiently to
complete the project within the given time frame and budget.
4.4 Risks
Some potential risks associated with the project include delays in completing certain
aspects of the game development process, such as enemy AI or level design. The team
may also encounter technical issues that impact the quality or functionality of the
game. Finally, there may be unexpected changes in project requirements or scope,
which could impact the project timeline or budget. To mitigate these risks, the team
should communicate effectively, plan strategically, and remain flexible throughout the
project development process.
4
4.5 Functional Requirements
The game should allow players to control the main character using standard keyboard
controls, with the ability to move, jump, shoot, and perform other actions as required.
which requires the player to use strategic thinking to defeat them. The game should
include power-ups, collectibles, and other features to enhance the gameplay
experience.
5
5. EXISTING SYSTEMS
5.2 Hardware
The hardware requirements for the KLV: The Lone Wolf game will depend on the
specific device on which the game will be played. The game should be optimized to
run smoothly on a wide range of hardware, including desktops and laptops.
6
6. PROPOSED SYSTEMS
6.5 Deployment
The proposed system for deployment of the KLV: The Lone Wolf game includes
packaging the game executable files and distributing them to the end-users via app
stores or digital distribution platforms such as Steam or itch.io. The game should be
optimized for each platform to ensure smooth performance and compatibility with
different hardware configurations.
7
7. SYSTEM REQUIREMENTS
8
8. MODULES
● Animation
AnimatorManager.cs
● Camera
cinemachineController.cs
postProcessing.cs
● Cutscene
CutsceneButton.cs
● Enemy
BossControler.cs
EnemyController.cs
HitPoint.cs
HitPointBoss.cs
● Managers
EntityManagement.cs
TrackedEntity.cs
● Menu
MainMenu.cs
MouseUseMainMenu.cs
pauseMenu.cs
SettingsMenu.cs
● Player
Combatchecker.cs
PlayerAiming.cs
PlayerShooting.cs
inputManager.cs
PlayerJournal.cs
PlayerLocomotion.cs
PlayerManager.cs
● SaveLoad
PlayerData.cs
SaveSystem.cs
● Scenes
DoorConroller.cs
DoorconrollerAlt.cs
ExtinguishFire.cs
SprinklerFix.cs
LevelTrigger.cs
● Sound effects
DoorSoundController.cs
FireSoundController.cs
SprinklerSoundController.cs
9
PlayerSteps.cs
WeaponSoundController.cs
SceneSoundController.cs
● Weapons
Projectile.cs
ProjectileWeapon.cs
10
9. SYSTEM DESIGN
11
game flow of the KLV:the lone wolf
12
The input structure of the KLV:the lone wolf
13
9.4 Enemy AI
14
Aiming animation
15
10. DEVELOPMENT PROCESS
● Planning and Requirement Gathering: This phase is crucial as it sets the foundation
for the project. During this phase, the team identifies and gathers requirements for the
game. This includes creating the game design document that outlines the game's
mechanics, storyline, art direction, user interface, and sound design. The team also
defines the project scope, timelines, milestones, and deliverables.
● Prototyping: Once the requirements are gathered, the team creates a basic prototype of
the game. The prototype is a rough and unfinished version of the game that allows the
team to test the game mechanics, gameplay, and basic functionality. The prototype
helps identify any problems and provides an opportunity to make changes before
moving forward.
● Design: During the design phase, the team finalizes the game design. The team
creates the game's look and feel, including the user interface, graphics, and sound
effects. The team also develops the game assets such as characters, environment, and
other game elements. This phase is critical as the design elements help set the tone of
the game.
● Development: Once the game design is finalized, the development phase begins. This
phase involves writing code and creating the game mechanics. The team uses the
game engine and programming languages to build the game, incorporating the design
elements and mechanics that were created in the previous phases.
● Testing: After the game is developed, the team tests the game for functionality,
performance, and user experience. This includes identifying and fixing bugs and
ensuring that the game meets the design requirements. The team also checks if the
game is compatible with different platforms and devices.
● Deployment: Once the game is fully tested and approved, it's time to deploy it. The
team packages the game, creates installation files, and prepares the game for
distribution. The team also creates promotional material to market the game.
● Maintenance: After the game is deployed, the team continues to provide support and
maintenance. This includes fixing bugs, addressing user feedback, and updating the
game with new features and content. The team also monitors the game's performance
and analyzes user data to improve the game.
16
11. CODING
● AnimatorManager.cs
using System;
using TheSignal.Camera;
using UnityEngine;
namespace TheSignal.Animation
{
public class AnimatorManager : MonoBehaviour
{
[HideInInspector] public Animator animator;
private AnimatorStateInfo stateInfo;
private CinemachineController cinemachineController;
// Animator parameter IDs for more effective fetching
private static readonly int horizontalInput = Animator.StringToHash("hInput");
private static readonly int verticalInput = Animator.StringToHash("vInput");
private static readonly int strafingHorizontal = Animator.StringToHash("hStrafing");
private static readonly int strafingVertical = Animator.StringToHash("vStrafing");
private static readonly int Running = Animator.StringToHash("Running");
public static readonly int Interacting = Animator.StringToHash("isInteracting");
public static readonly int Jumping = Animator.StringToHash("isJumping");
public static readonly int Grounded = Animator.StringToHash("isGrounded");
private void Awake()
{
animator = GetComponent<Animator>();
cinemachineController =
UnityEngine.Camera.main.GetComponent<CinemachineController>();
}
private void Update()
{
if (!cinemachineController.Paused())
return;
animator.SetFloat(horizontalInput, 0.0f, 0.1f, Time.deltaTime);
animator.SetFloat(verticalInput, 0.0f, 0.1f, Time.deltaTime);
}
17
public void UpdateMovementValues(float horizontalMovement, float
verticalMovement, float moveAmount, bool isRunning)
{
// Animation snapping
float snappedHorizontal = SnappedMovement(horizontalMovement);
float snappedVertical = SnappedMovement(verticalMovement);
if (isAiming)
{
targetWeight = 1.0f;
float strafingHorizontalSnapped = SnappedMovement(horizontal);
float strafingVerticalSnapped = SnappedMovement(vertical);
18
stateInfo = animator.GetCurrentAnimatorStateInfo(2);
}}}
● cinemachineController.cs
using System;
using Cinemachine;
using UnityEngine;
namespace TheSignal.Camera
{
public class CinemachineController : MonoBehaviour
{
private CinemachineBrain brain;
private void Start()
{brain = GetComponent<CinemachineBrain>();}
public void TogglePause(bool isAiming)
{ brain.enabled = !brain.enabled;
Cursor.visible = !Cursor.visible;
Cursor.lockState = Cursor.lockState == CursorLockMode.None ?
CursorLockMode.Locked : CursorLockMode.None;
}
public bool Paused()
{ return !brain.enabled;
}}}
● postProcessing.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace TheSignal.Camera
{
public static class PostProcessingSettings
{
public static bool bloom = true;
public static bool vignette = true;
public static bool dof = true;
}
public class PostProcessing : MonoBehaviour
{
private Volume volume;
19
private DepthOfField dof;
private void Awake()
{
volume = GetComponent<Volume>();
}
private void Start()
{
volume.profile.TryGet(out bloom);
volume.profile.TryGet(out vignette);
volume.profile.TryGet(out dof);
bloom.active = PostProcessingSettings.bloom;
vignette.active = PostProcessingSettings.vignette;
dof.active = PostProcessingSettings.dof;
}
public void SetBloom(bool value)
{
bloom.active = value;
PostProcessingSettings.bloom = value;
}
public void SetVignette(bool value)
{
vignette.active = value;
PostProcessingSettings.vignette = value;
}
public void SetDOF(bool value)
{
dof.active = value;
PostProcessingSettings.dof = value;
}}}
● CutsceneButton.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine;
namespace TheSignal.Cutscene
{
public class CutsceneButtons : MonoBehaviour
{
[SerializeField] private GameObject panel1;
[SerializeField] private GameObject panel2;
[SerializeField] private GameObject button1;
20
[SerializeField] private GameObject button2;
[SerializeField] private GameObject loading;
public void ContinueButton()
{
panel1.SetActive(false);
panel2.SetActive(true);
button1.SetActive(false);
button2.SetActive(true);
}
public void PlayButton()
{
button2.SetActive(false);
panel2.SetActive(false);
loading.SetActive(true);
SceneManager.LoadScene("First Level");
}}}
● BossControler.cs
using System;
using System.Collections;
using TheSignal.Managers;
using UnityEngine;
using UnityEngine.AI;
namespace TheSignal.Enemy
{
public class BossControler : TrackedEntity
{
[SerializeField] private int maxHealth;
[SerializeField] private Transform target;
[SerializeField] private float enemyDistance;
[SerializeField] private GameObject pauseUI;
[SerializeField] private GameObject SettingsUI;
[SerializeField] private GameObject HelpUI;
[SerializeField] private GameObject MainMenuUI;
[SerializeField] private float viewingDistance;
private bool animationChange;
private NavMeshAgent agent;
private Animator anim;
private int currentHealth;
private void Start()
{
21
GetReferences();
}
private void Update()
{
agent.velocity = Vector3.zero;
if (pauseUI.activeInHierarchy || SettingsUI.activeInHierarchy ||
HelpUI.activeInHierarchy || MainMenuUI.activeInHierarchy)
{
anim.gameObject.SetActive(false);
anim.gameObject.SetActive(true);
transform.rotation.Set(transform.rotation.x, transform.rotation.y,
transform.rotation.z, transform.rotation.w);
anim.SetFloat("Movement", 0.0f, 0.3f, Time.deltaTime);
return; }
var targetDistance = Vector3.Distance(transform.position, target.position);
if (targetDistance < enemyDistance && currentHealth > 0)
{
if (animationChange)
{
anim.Play("Combo2");
animationChange = false;
}
else
{
anim.Play("Combo1");
animationChange = true;
}}
else if (viewingDistance >= targetDistance)
{ MoveToPlayer();
RotateToTarget();}
else
{ anim.SetFloat("Movement", 0.0f, 0.3f, Time.deltaTime); }}
private void GetReferences()
{
currentHealth = maxHealth;
agent = GetComponent<NavMeshAgent>();
anim = GetComponent<Animator>();
}
public void TakeDamage(int damageBonus, int maxDamage)
{
// Damage bonus is the bonus applied if the enemy was hit in the head, torso etc.
// Max damage is the maximum damage you can apply based on the projectile type
(fetched from the Projectile script)
22
int damage = CalculateDamage(maxDamage);
damage += damageBonus;
currentHealth -= damage;
Debug.Log($"Dealt {damage} damage!");
if (currentHealth <= 0)
{
Die();
}}
private void Die()
{
//disables capsule collider for the arm and with that it disables damage after dying
var arm = gameObject.GetComponentsInChildren<CapsuleCollider>();
foreach (var item in arm)
{
item.enabled = false;
}
anim.Play("Dying");
Destroy(this.gameObject, 3.7f);
}
private void MoveToPlayer()
{
agent.SetDestination(target.position);
anim.SetFloat("Movement", 1f, 0.3f, Time.deltaTime);
}
private void RotateToTarget()
{
Vector3 direction = target.position - transform.position;
Quaternion rotation = Quaternion.LookRotation(direction, Vector3.up);
transform.rotation = rotation;
}
private int CalculateDamage(int maxDamage)
{
var rnd = new System.Random(Guid.NewGuid().GetHashCode());
var damage = rnd.Next(1, maxDamage + 1);
// If the damage is at least 75% of the maxDamage, apply a critical bonus
if (damage >= maxDamage * 0.75f)
damage += rnd.Next(1, 6);
return damage;
}}}
● EnemyController.cs
using System;
23
using System.Collections;
using TheSignal.Managers;
using UnityEngine;
using UnityEngine.AI;
namespace TheSignal.Enemy
{
public class EnemyController : TrackedEntity
{
[SerializeField] private int maxHealth;
[SerializeField] private Transform target;
[SerializeField] private float enemyDistance;
[SerializeField] private GameObject pauseUI;
[SerializeField] private GameObject SettingsUI;
[SerializeField] private GameObject HelpUI;
[SerializeField] private GameObject MainMenuUI;
[SerializeField] private float viewingDistance;
private NavMeshAgent agent;
private Animator anim;
private int currentHealth;
private void Start()
{
GetReferences();
}
private void Update()
{
agent.velocity = Vector3.zero;
if (pauseUI.activeInHierarchy || SettingsUI.activeInHierarchy ||
HelpUI.activeInHierarchy || MainMenuUI.activeInHierarchy)
{
anim.gameObject.SetActive(false);
anim.gameObject.SetActive(true);
transform.rotation.Set(transform.rotation.x, transform.rotation.y,
transform.rotation.z,transform.rotation.w);
anim.SetFloat("Movement", 0.0f, 0.3f, Time.deltaTime);
return;}
var targetDistance = Vector3.Distance(transform.position, target.position);
if (targetDistance < enemyDistance && currentHealth>0)
anim.Play("Soft Attack");
else if(viewingDistance>=targetDistance)
{
MoveToPlayer();
RotateToTarget();
}
24
else
{
anim.SetFloat("Movement", 0.0f, 0.3f, Time.deltaTime);
}}
private void GetReferences()
{
currentHealth = maxHealth;
agent = GetComponent<NavMeshAgent>();
anim = GetComponent<Animator>();
}
public void TakeDamage(int damageBonus, int maxDamage)
{
// Damage bonus is the bonus applied if the enemy was hit in the head, torso
etc.
// Max damage is the maximum damage you can apply based on the projectile
type (fetched from the Projectile script)
int damage = CalculateDamage(maxDamage);
damage += damageBonus;
currentHealth -= damage;
if (currentHealth <= 0)
{
Die();
}
}
private void Die()
{
//disables capsule collider for the arm and with that it disables damage after
dying
var arm =gameObject.GetComponentsInChildren<CapsuleCollider>();
foreach (var item in arm)
{
item.enabled = false;
}
anim.Play("Dying");
Destroy(this.gameObject, 2.17f);
}
private void MoveToPlayer()
{
agent.SetDestination(target.position);
anim.SetFloat("Movement", 1f, 0.3f, Time.deltaTime);
}
private void RotateToTarget(){
Vector3 direction = target.position - transform.position;
25
Quaternion rotation = Quaternion.LookRotation(direction, Vector3.up);
transform.rotation = rotation;}
private int CalculateDamage(int maxDamage)
{
var rnd = new System.Random(Guid.NewGuid().GetHashCode());
var damage = rnd.Next(1, maxDamage + 1);
// If the damage is at least 75% of the maxDamage, apply a critical bonus
if (damage >= maxDamage * 0.75f)
damage += rnd.Next(1, 6);
return damage;
}}}
● HitPoint.cs
using TheSignal.Weapons;
using UnityEngine;
namespace TheSignal.Enemy
{
public class HitPoint : MonoBehaviour{
private EnemyController enemyController;
[SerializeField] private int damageBonus;
private void Awake()
{ enemyController = GetComponentInParent<EnemyController>();
}
private void OnTriggerEnter(Collider other)
{if (other.CompareTag("Projectile"))
{
int maxDamage =
other.gameObject.GetComponent<Projectile>().maxDamage;
enemyController.TakeDamage(damageBonus, maxDamage);
}}}}
● HitPointBoss.cs
using TheSignal.Weapons;
using UnityEngine;
namespace TheSignal.Enemy
{
public class HitPointBoss : MonoBehaviour
{
private BossControler bossControler;
[SerializeField] private int damageBonus;
private void Awake()
{
bossControler = GetComponentInParent<BossControler>();
26
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Projectile"))
{
int maxDamage =
other.gameObject.GetComponent<Projectile>().maxDamage;
bossControler.TakeDamage(damageBonus, maxDamage);
}}}}
● EntityManagement.cs
using System.Collections.Generic;
using TheSignal.Player.Input;
using UnityEngine;
namespace TheSignal.Managers
{
public class EntityManagement : MonoBehaviour
{
public List<TrackedEntity> trackedEntities;
private InputManager inputManager;
private void Awake()
{
inputManager = trackedEntities[0].GetComponent<InputManager>();
}
public void LateUpdate()
{
if (!inputManager.isExiting)
returnforeach (var t in trackedEntities)
{
t.isRunning = !t.isRunning;
inputManager.isExiting = false;
}}}}
● TrackedEntity.cs
using UnityEngine;
using UnityEngineInternal;
namespace TheSignal.Managers{
public class TrackedEntity : MonoBehaviour{
public bool isRunning { get; set; } = true;
}}
27
● MainMenu.cs
using TheSignal.SaveLoad;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace TheSignal.Menu
{
public class MainMenu : MonoBehaviour
{
public GameObject newGamePanel;
public GameObject mainPanel;
public GameObject quitGamePanel;
public GameObject creditsPanel;
public GameObject loadingPanel;
public GameObject settingsPanel;
public GameObject helpPanel;
#region MainPanel
public void NewGameButton()
{
mainPanel.SetActive(false);
newGamePanel.SetActive(true);
}
public void QuitGameButton()
{
mainPanel.SetActive(false);
quitGamePanel.SetActive(true);
}
public void CreditsButton()
{
mainPanel.SetActive(false);
creditsPanel.SetActive(true);
}
public void LoadGameButton()
{
loadingPanel.SetActive(true);
PlayerData data = SaveSystem.LoadPlayer();
if (data!=null)
{
SceneManager.LoadScene(data.level);
}
else
SceneManager.LoadScene("Cutscene Level");
}
28
public void SettingsButton()
{
mainPanel.SetActive(false);
settingsPanel.SetActive(true);
}
#endregion
#region Newgame
public void CancelNewGame()
{
newGamePanel.SetActive(false);
mainPanel.SetActive(true);
}
public void YesNewGame(string lvlName)
{
SceneManager.LoadScene("Cutscene Level");
}
#endregion
#region Settings
public void BackSettingsButton()
{
mainPanel.SetActive(true);
settingsPanel.SetActive(false);
}#endregion
#region Credits
public void BackButtonCredits()
{
creditsPanel.SetActive(false);
mainPanel.SetActive(true);
}
public void LoadMainMenu()
{
SceneManager.LoadScene("MainMenu");
}
#endregion
#region Help
public void HelpButton()
{
mainPanel.SetActive(false);
helpPanel.SetActive(true);
}
public void HelpBackButtonCredits()
{
helpPanel.SetActive(false);
29
mainPanel.SetActive(true);
}
#endregion
#region Quit
public void YesQuit()
{
Application.Quit();
}
public void NoQuit()
{
quitGamePanel.SetActive(false);
mainPanel.SetActive(true);
}
#endregion
}}
● MouseUseMainMenu.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseUseMainMenu : MonoBehaviour
{
void Start(){
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;}}
● pauseMenu.cs
using System.Linq;
using TheSignal.Camera;
using TheSignal.Player.Input;
using TheSignal.Player.Journal;
using UnityEngine;
using UnityEngine.SceneManagement;
using TheSignal.SaveLoad;
using UnityEngine.UI;
namespace TheSignal.Menu
{
public class PauseMenu : MonoBehaviour
{
private static bool GameIsPaused = false;
30
private InputManager inputManager;
private CinemachineController cinemachineController;
private PlayerJournal playerJournal;
31
{
PauseDead();
}
}
#region ObjectiveTab
void ToggleObjectives()
{
objectiveActive.SetActive(!objectiveActive.gameObject.activeInHierarchy);
objectiveInactive.SetActive(!objectiveActive.gameObject.activeInHierarchy);
inputManager.isPressingTab = false;
}
#endregion
#region MainPanel
public void Resume()
{
mainMenuUI.SetActive(false);
pauseMenuUI.SetActive(false);
settingsUI.SetActive(false);
GameIsPaused = false;
cinemachineController.TogglePause(inputManager.isAiming);
healthAndMissionUI.SetActive(true);
}
public void MainMenu()
{
pauseMenuUI.SetActive(false);
mainMenuUI.SetActive(true);
}
public void Help()
{
pauseMenuUI.SetActive(false);
HelpUI.SetActive(true);
}
public void HelpBackButton()
{
HelpUI.SetActive(false);
pauseMenuUI.SetActive(true);
}
public void Load()
{
PlayerData data = SaveSystem.LoadPlayer();
if (data != null)
{
32
Resume();
playerJournal.LoadEntries(data.addedEntries);
SceneManager.LoadScene(data.level);
}
}
public void Save()
{
SaveSystem.SavePlayer(SceneManager.GetActiveScene().buildIndex,
AddedEntries.entries.Keys.ToList());
}
void Pause()
{
pauseMenuUI.SetActive(true);
GameIsPaused = true;
cinemachineController.TogglePause(inputManager.isAiming);
healthAndMissionUI.SetActive(false);
}
void PauseDead()
{
GameIsPaused = true;
cinemachineController.TogglePause(inputManager.isAiming);
healthAndMissionUI.SetActive(false);
}
#endregion
#region Main Menu Button
public void MainMenuButtonYes(string MainMenuName)
{
GameIsPaused = false;
Time.timeScale = 1f;
SceneManager.LoadScene(MainMenuName);
}
public void MainMenuButtonNo()
{
mainMenuUI.SetActive(false);
pauseMenuUI.SetActive(true);
}
#endregion
#region DeadScreen
public void DeadScreenButtonYes()
{
Scene current = SceneManager.GetActiveScene();
GameIsPaused = false;
Time.timeScale = 1f;
33
SceneManager.LoadScene(current.name);
mainMenuUI.SetActive(false);
pauseMenuUI.SetActive(false);
deathScreenUI.SetActive(false);
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
Resume();
}
public void DeadScreenButtonNo(string MainMenuName)
{
GameIsPaused = false;
Time.timeScale = 1f;
SceneManager.LoadScene(MainMenuName);
}
#endregion
public void SettingsButton()
{
settingsUI.SetActive(true);
pauseMenuUI.SetActive(false);
}
public void SettingsBackButton()
{
settingsUI.SetActive(false);
pauseMenuUI.SetActive(true);
}}}
● SettingsMenu.cs
using System;
using System.Collections.Generic;
using TheSignal.Camera;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Rendering.Universal;
using UnityEngine.UI;
namespace TheSignal.Menu
{
public class SettingsMenu : MonoBehaviour
{
public AudioMixer audioMixer;
[SerializeField] private PostProcessing postProcessing;
[SerializeField] private ScriptableRendererFeature ssao;
34
[Header("UI")]
[SerializeField] private Dropdown resolutionDropdown;
[SerializeField] private Toggle fullscreenToggle;
[SerializeField] private Slider masterVolumeSlider;
[SerializeField] private Slider musicVolumeSlider;
[SerializeField] private Slider sfxVolumeSlider;
[SerializeField] private Dropdown graphicsDropdown;
[SerializeField] private Toggle bloomToggle;
[SerializeField] private Toggle vignetteToggle;
[SerializeField] private Toggle dofToggle;
[SerializeField] private Toggle ssaoToggle;
private Resolution[] resolutions;
private void Awake()
{
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = -1;
}
private void Start()
{
resolutions = Screen.resolutions;
resolutionDropdown.ClearOptions();
PopulateOptions();
SetControls();
}
private void PopulateOptions()
{
var resOptions = new List<string>();
var currentRes = 0;
for (var i = 0; i < resolutions.Length; i++)
{
var resOption = $"{resolutions[i].width} x {resolutions[i].height}";
resOptions.Add(resOption);
if (resolutions[i].width == Screen.currentResolution.width &&
resolutions[i].height == Screen.currentResolution.height)
currentRes = i;
}
resolutionDropdown.AddOptions(resOptions);
resolutionDropdown.value = currentRes;
resolutionDropdown.RefreshShownValue();
graphicsDropdown.value = QualitySettings.GetQualityLevel();
}
public void SetMasterVolume(float volume)
{
35
audioMixer.SetFloat("masterVolume", volume);
}
public void SetMusicVolume(float volume)
{
audioMixer.SetFloat("musicVolume", volume);
}
public void SetSFXVolume(float volume)
{
audioMixer.SetFloat("sfxVolume", volume);
}
public void SetQualityPreset(int qualityIndex)
{
QualitySettings.SetQualityLevel(qualityIndex);
}
public void SetFullscreen(bool isFullscreen)
{
Screen.fullScreen = isFullscreen;
}
public void SetResolution(int resIndex)
{
var res = resolutions[resIndex];
Screen.SetResolution(res.width, res.height, Screen.fullScreen);
}
public void SetBloom(bool bloom)
{
postProcessing.SetBloom(bloom);
}
public void SetVignette(bool vignette)
{
postProcessing.SetVignette(vignette);
}
public void SetDOF(bool dof)
{
postProcessing.SetDOF(dof);
}
public void SetSSAO(bool ssaoToggle)
{
ssao.SetActive(ssaoToggle);
}
private void SetControls()
{
fullscreenToggle.isOn = Screen.fullScreen;
audioMixer.GetFloat("masterVolume", out var masterVolume);
36
audioMixer.GetFloat("musicVolume", out var musicVolume);
audioMixer.GetFloat("sfxVolume", out var sfxVolume);
masterVolumeSlider.value = masterVolume;
musicVolumeSlider.value = musicVolume;
sfxVolumeSlider.value = sfxVolume;
bloomToggle.isOn = PostProcessingSettings.bloom;
vignetteToggle.isOn = PostProcessingSettings.vignette;
dofToggle.isOn = PostProcessingSettings.dof;
ssaoToggle.isOn = ssao.isActive;
}}}
● Combatchecker.cs
using System;
using System.Collections;
using System.Collections.Generic;
using TheSignal.Camera;
using TheSignal.SFX;
using UnityEngine;
namespace TheSignal.Player.Combat
{
public class CombatChecker : MonoBehaviour
{
[SerializeField] private GameObject player;
[SerializeField] private SceneSoundController sceneSoundController;
[SerializeField] private float disengageTime;
private bool enemyPresent;
private bool bossPresent;
private bool enemyMusicTriggered;
private bool bossMusicTriggered;
private List<Collider> tempOthers;
private CinemachineController cinemachineController;
private GameObject boss;
private void Awake()
{
tempOthers = new List<Collider>();
cinemachineController =
UnityEngine.Camera.main.GetComponent<CinemachineController>();
boss = GameObject.FindWithTag("Boss");
}
private void Update()
37
{
if (cinemachineController.Paused())
return;
transform.position = player.transform.position;
CheckEnemies();
CheckToDisengage();
CheckToPlay();
}
private void OnTriggerEnter(Collider other)
{
if (cinemachineController.Paused())
return;
if (!other.CompareTag("Enemy") && !other.CompareTag("Boss"))
return;
if (other.CompareTag("Boss"))
{
bossPresent = true;
if (!tempOthers.Contains(other))
tempOthers.Add(other);
return;
}
enemyPresent = true;
tempOthers.Add(other);
}
private void OnTriggerExit(Collider other)
{
if (cinemachineController.Paused())
return;
if (other.CompareTag("Enemy"))
tempOthers.Remove(other);
}
private IEnumerator Disengage()
{
var temp = disengageTime;
while (disengageTime > 0.0f)
{
disengageTime -= Time.deltaTime / disengageTime;
yield return null;
}
disengageTime = temp;
enemyMusicTriggered = false;
bossMusicTriggered = false;
38
sceneSoundController.PlayTrack(enemyPresent);
}
private void CheckEnemies()
{
for (var i = 0; i < tempOthers.Count; i++)
{
if (!tempOthers[i])
tempOthers.Remove(tempOthers[i]);
}
if (!boss)
bossPresent = false;
}
private void CheckToDisengage()
{
if (enemyPresent && tempOthers.Count == 0)
{
enemyPresent = false;
StartCoroutine(Disengage());
}
}
private void CheckToPlay()
{
if (!enemyMusicTriggered && tempOthers.Count != 0)
{
enemyMusicTriggered = true;
sceneSoundController.PlayTrack(enemyPresent, bossPresent);
}
if (!bossMusicTriggered && bossPresent)
{
bossMusicTriggered = true;
sceneSoundController.PlayTrack(enemyPresent, bossPresent);
}}}}
● PlayerAiming.cs
using Cinemachine;
using UnityEngine;
using TheSignal.Player.Input;
namespace TheSignal.Player.Combat
{
public class PlayerAiming : MonoBehaviour
{
39
[SerializeField] private Canvas crosshairCanvas;
[SerializeField] private Transform aimTarget;
[SerializeField] private Transform distanceCheckOrigin;
[Header("Cinemachine cameras")]
[SerializeField] private CinemachineVirtualCamera normalCamera;
[SerializeField] private CinemachineVirtualCamera aimCamera;
[HideInInspector] public bool allowedFire;
[HideInInspector] public bool enteredAim;
private InputManager inputManager;
private Rigidbody playerRB;
private void Awake()
{
inputManager = GetComponent<InputManager>();
playerRB = GetComponent<Rigidbody>();
allowedFire = true;
}
private void Start()
{
normalCamera.ForceCameraPosition(transform.position - Vector3.back,
transform.rotation);
aimCamera.ForceCameraPosition(transform.position - Vector3.back,
transform.rotation);
}
public void HandleAiming()
{
aimCamera.enabled = inputManager.isAiming;
crosshairCanvas.enabled = inputManager.isAiming;
if (inputManager.isAiming)
{
CheckDistance();
RotateWithLook();}}
private void RotateWithLook()
{
Vector3 worldAimTarget = aimTarget.position;
worldAimTarget.y = transform.position.y;
Vector3 aimDirection = (worldAimTarget - transform.position).normalized;
Quaternion targetRotation = Quaternion.LookRotation(aimDirection);
targetRotation = Quaternion.Slerp(transform.rotation, targetRotation, 15.0f *
Time.fixedDeltaTime);
playerRB.MoveRotation(targetRotation);
}
private void CheckDistance()
{
40
var ray = new Ray
{
origin = distanceCheckOrigin.position,
direction = distanceCheckOrigin.forward
};
var hit = new RaycastHit();
● PlayerShooting.cs
using UnityEngine;
using TheSignal.Player.Input;
using TheSignal.Weapons;
namespace TheSignal.Player.Combat
{
public class PlayerShooting : MonoBehaviour
{
private InputManager inputManager;
private PlayerAiming playerAiming;
private ProjectileWeapon projectileWeapon;
private float lastShot = 0.0f;
private void Awake()
{
inputManager = GetComponent<InputManager>();
playerAiming = GetComponent<PlayerAiming>();
projectileWeapon = GetComponentInChildren<ProjectileWeapon>();
}
public void HandleShooting()
{
lastShot += Time.deltaTime;
if (inputManager.isFiring && inputManager.isAiming && playerAiming.allowedFire
&& playerAiming.enteredAim)
{
projectileWeapon.StartFiring(ref lastShot);
}
}}}
41
● inputManager.cs
using UnityEngine;
using TheSignal.Animation;
using TheSignal.Camera;
namespace TheSignal.Player.Input
{
[RequireComponent(typeof(AnimatorManager))]
public class InputManager : MonoBehaviour
{
private PlayerControls playerControls;
private AnimatorManager animatorManager;
private CinemachineController cinemachineController;
[HideInInspector] public Vector2 movementInput;
[HideInInspector] public float moveAmount;
[HideInInspector] public float verticalInput;
[HideInInspector] public float horizontalInput;
[HideInInspector] public float strafingHorizontal;
[HideInInspector] public float strafingVertical;
[HideInInspector] public bool isRunning;
[HideInInspector] public bool isJumping;
[HideInInspector] public bool isAiming;
[HideInInspector] public bool isFiring;
[HideInInspector] public bool isInteracting;
[HideInInspector] public bool isPressingK;
[HideInInspector] public bool isSelecting;
[HideInInspector] public bool isExiting;
[HideInInspector] public bool isPressingTab;
[HideInInspector] public bool isOpeningJournal;
private void Awake()
{
animatorManager = GetComponent<AnimatorManager>();
cinemachineController =
UnityEngine.Camera.main.GetComponent<CinemachineController>();
}
private void OnEnable()
{
if (playerControls == null)
{
playerControls = new PlayerControls();
// Simply storing the result of each key event
playerControls.Player.Move.performed += i => movementInput =
i.ReadValue<Vector2>();
42
playerControls.Player.Run.performed += i => isRunning = i.ReadValueAsButton();
playerControls.Player.Run.canceled += i => isRunning = i.ReadValueAsButton();
playerControls.Player.Jump.performed += i => isJumping =
i.ReadValueAsButton();
playerControls.Player.Aim.performed += i => isAiming = i.ReadValueAsButton();
playerControls.Player.Aim.canceled += i => isAiming = i.ReadValueAsButton();
playerControls.Player.Fire1.performed += i => isFiring = i.ReadValueAsButton();
playerControls.Player.Fire1.canceled += i => isFiring = i.ReadValueAsButton();
playerControls.Player.Interact.performed += i => isInteracting =
i.ReadValueAsButton();
playerControls.Player.SlowMo.performed += i => isPressingK =
i.ReadValueAsButton();
playerControls.Player.SlowMo.canceled += i => isPressingK =
i.ReadValueAsButton();
playerControls.UI.Select.performed += i => isSelecting = i.ReadValueAsButton();
playerControls.UI.Select.canceled += i => isSelecting = i.ReadValueAsButton();
playerControls.UI.Exit.performed += i => isExiting = i.ReadValueAsButton();
playerControls.UI.Exit.canceled += i => isExiting = i.ReadValueAsButton();
playerControls.UI.Objectives.performed += i => isPressingTab =
i.ReadValueAsButton();
playerControls.UI.Objectives.canceled += i => isPressingTab =
i.ReadValueAsButton();
playerControls.UI.Journal.performed += i => isOpeningJournal =
i.ReadValueAsButton();
playerControls.UI.Journal.canceled += i => isOpeningJournal =
i.ReadValueAsButton();
}
playerControls.Enable();
}
private void OnDisable()
{
playerControls.Disable();
}
private void Update()
{
bool ignoreInput = cinemachineController.Paused();
if (ignoreInput && playerControls.Player.enabled)
{
movementInput = Vector2.zero;
playerControls.Player.Disable();
}
else if (!ignoreInput && !playerControls.Player.enabled)
playerControls.Player.Enable();
43
}
public void HandleAllInputs()
{
HandleMovementInput();
HandleAimingInput();
}
private void HandleMovementInput()
{
verticalInput = movementInput.y;
horizontalInput = movementInput.x;
moveAmount = Mathf.Clamp01(Mathf.Abs(horizontalInput) +
Mathf.Abs(verticalInput));
animatorManager.UpdateMovementValues(moveAmount, moveAmount,
moveAmount, isRunning);
}
private void HandleAimingInput()
{
strafingHorizontal = movementInput.x;
strafingVertical = movementInput.y;
animatorManager.UpdateAimingValues(isAiming, strafingHorizontal,
strafingVertical);
}}}
● PlayerJournal.cs
using System.Collections.Generic;
using TheSignal.Camera;
using TheSignal.Player.Input;
using UnityEngine;
using UnityEngine.UI;
namespace TheSignal.Player.Journal
{
public static class AddedEntries
{
public static Dictionary<int, TextAsset> entries = new Dictionary<int, TextAsset>();
public static bool IsPresent(TextAsset entry)
{
return entries.ContainsValue(entry);
}
}
public class PlayerJournal : MonoBehaviour
{
44
[SerializeField] private GameObject journal;
[SerializeField] private Text headerText;
[SerializeField] private GameObject entryContainer;
[SerializeField] private GameObject buttonContainer;
[SerializeField] private GameObject buttonPrefab;
[SerializeField] private List<TextAsset> textAssets;
private InputManager inputManager;
private CinemachineController cinemachineController;
public bool journalOpened;
private void Awake()
{
inputManager = GetComponent<InputManager>();
cinemachineController =
UnityEngine.Camera.main.GetComponent<CinemachineController>();
}
private void Start()
{
foreach (var entry in AddedEntries.entries)
{
AddEntry(entry.Value);
}}
private void Update(){
if (inputManager.isOpeningJournal)
ToggleJournal();
switch (inputManager.isExiting)
{
case true when journal.activeInHierarchy && !entryContainer.activeInHierarchy:
ToggleJournal();
break;
case true when journal.activeInHierarchy && entryContainer.activeInHierarchy:
CloseEntry();
break;
}}
private void ToggleJournal()
{
cinemachineController.TogglePause(inputManager.isAiming);
journal.SetActive(!journal.activeInHierarchy);
inputManager.isOpeningJournal = false;
inputManager.isExiting = false;
journalOpened = !journalOpened;
}
public void AddEntry(TextAsset entry)
{
45
if (!AddedEntries.IsPresent(entry))
AddedEntries.entries.Add(textAssets.IndexOf(entry), entry);
var button = Instantiate(buttonPrefab, buttonContainer.transform, true);
button.GetComponent<Text>().text = entry.name;
button.GetComponent<Button>().onClick.AddListener(delegate {
OpenEntry(entry.name); });
}
private void OpenEntry(string entryName)
{
headerText.text = entryName;
entryContainer.SetActive(true);
buttonContainer.SetActive(false);
entryContainer.GetComponentInChildren<Text>().text = textAssets.Find(entry =>
entry.name == entryName).text;
}
private void CloseEntry()
{
headerText.text = "Journal";
entryContainer.SetActive(false);
buttonContainer.SetActive(true);
inputManager.isExiting = false;
}
public void LoadEntries(List<int> keys)
{
AddedEntries.entries = new Dictionary<int, TextAsset>();
for (var i = 0; i < keys.Count; i++)
{
AddedEntries.entries.Add(keys[i], textAssets[keys[i]]);
}}}}
● PlayerLocomotion.cs
using UnityEngine;
using TheSignal.Animation;
using TheSignal.Player.Input;
namespace TheSignal.Player.Movement
{
public class PlayerLocomotion : MonoBehaviour
{
[Header("Falling")]
public float leapVelocity;
[HideInInspector] public float inAirTimer;
46
public float fallVelocity;
public LayerMask groundLayer;
public float raycastHeightOffset;
[HideInInspector] public bool isGrounded;
[Header("Movement Speeds")]
public float walkSpeed;
public float runSpeed;
public float rotationSpeed;
[HideInInspector] public bool isJumping;
[Header("Jump Speeds")]
public float jumpHeight;
public float gravityAccel;
private AnimatorManager animatorManager;
private InputManager inputManager;
private Vector3 moveDirection;
private Transform cameraTransform;
private Rigidbody playerRB;
private bool isInteracting;
private void Awake()
{
animatorManager = GetComponent<AnimatorManager>();
inputManager = GetComponent<InputManager>();
playerRB = GetComponent<Rigidbody>();
cameraTransform = Camera.main.transform;
}
public void HandleAllMovement()
{
HandleFalling();
// if (isInteracting) // return;
if (inputManager.isJumping)
{
inputManager.isJumping = false;
HandleJump();
}
HandleMovement();
HandleRotation();
}
private void HandleMovement()
{
moveDirection = cameraTransform.forward * inputManager.verticalInput;
moveDirection += cameraTransform.right * inputManager.horizontalInput;
moveDirection.Normalize();
moveDirection.y = 0.0f;
47
if (isJumping)
return;
if (inputManager.isRunning && inputManager.moveAmount > 0.5f &&
!inputManager.isAiming)
moveDirection *= runSpeed;
else
moveDirection *= walkSpeed;
playerRB.velocity = moveDirection;}
48
private void HandleJump()
{
if (isGrounded)
{
animatorManager.animator.SetBool(AnimatorManager.Jumping, true);
animatorManager.PlayAnimation("JumpUp", false);
var jumpVelocity = Mathf.Sqrt(-2.0f * gravityAccel * jumpHeight);
var newVelocity = moveDirection;
newVelocity.y = jumpVelocity;
playerRB.AddForce(newVelocity, ForceMode.Impulse);
}
}
private void CheckGrounded(ref RaycastHit hit)
{
Vector3 raycastStart = transform.position;
raycastStart.y += raycastHeightOffset;
if (Physics.SphereCast(raycastStart, 0.2f, Vector3.down, out hit, 1.0f, groundLayer)){
if (!isGrounded && isInteracting)
animatorManager.PlayAnimation("Land", true);
inAirTimer = 0;
isGrounded = true;
}
else
{
isGrounded = false;
}}
private void LateUpdate()
{
isInteracting = animatorManager.animator.GetBool(AnimatorManager.Interacting);
}}}
● PlayerManager.cs
using TheSignal.Animation;
using TheSignal.Managers;
using TheSignal.Player.Combat;
using UnityEngine;
using TheSignal.Player.Input;
using TheSignal.Player.Movement;
namespace TheSignal.Player
{
49
[RequireComponent(typeof(InputManager), typeof(PlayerLocomotion),
typeof(Animator))]
public class PlayerManager : TrackedEntity{
private InputManager inputManager;
private PlayerLocomotion playerLocomotion;
private Animator animator;
private PlayerAiming playerAiming;
private PlayerShooting playerShooting;
private void Awake(){
inputManager = GetComponent<InputManager>();
playerLocomotion = GetComponent<PlayerLocomotion>();
animator = GetComponent<Animator>();
playerAiming = GetComponent<PlayerAiming>();
playerShooting = GetComponent<PlayerShooting>();
}
void Start(){
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
private void Update(){
if (!this.isRunning)
return;
inputManager.HandleAllInputs();
}
private void FixedUpdate(){
if (!this.isRunning)
return;
playerAiming.HandleAiming();
playerShooting.HandleShooting();
playerLocomotion.HandleAllMovement();
}
private void LateUpdate(){
if (!this.isRunning)
return;
playerLocomotion.isJumping = animator.GetBool(AnimatorManager.Jumping);
animator.SetBool(AnimatorManager.Grounded, playerLocomotion.isGrounded);
}}}
● PlayerData.cs
using System.Collections.Generic;
using UnityEngine;
50
namespace TheSignal.SaveLoad
{
//makes the class able to be saved in a file
[System.Serializable]
public class PlayerData
{
public int level;
public List<int> addedEntries;
public PlayerData(int CurrentLevel, List<int> addedEntries)
{
level = CurrentLevel;
this.addedEntries = addedEntries;
}}}
● SaveSystem.cs
//used for working with files
using System.Collections.Generic;
using System.IO;
//allows accessing the binary formatter
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
namespace TheSignal.SaveLoad
{
public static class SaveSystem
{
public static void SavePlayer(int CurrentLevel, List<int> addedEntries){
BinaryFormatter formatter = new BinaryFormatter();
//the path were we will save our file
//Application.persistentDataPath is a unity function that generates a safe place for
where we can save our game data
string path = Application.persistentDataPath+"/player.txt";
//cerated the file on the system
FileStream stream = new FileStream(path,FileMode.Create);
PlayerData data = new PlayerData(CurrentLevel, addedEntries);
formatter.Serialize(stream, data);
stream.Close();
}
public static PlayerData LoadPlayer(){
string path = Application.persistentDataPath + "/player.txt";
if (File.Exists(path)){
51
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(path, FileMode.Open);
● DoorConroller.cs
using UnityEngine;
using TheSignal.Player.Input;
using TheSignal.SFX.Environement;
namespace TheSignal.Scenes.Behaviours
{
public class DoorController : MonoBehaviour
{
[SerializeField] private GameObject player;
[SerializeField] private GameObject door;
[SerializeField] private MeshRenderer[] terminalRenderers;
[SerializeField] private GameObject[] terminalPopups;
private InputManager inputManager;
private Animator doorAnimator;
private DoorSoundController soundController;
private bool playerPresent;
private void Awake()
{
inputManager = player.GetComponent<InputManager>();
doorAnimator = door.GetComponent<Animator>();
soundController = door.GetComponent<DoorSoundController>();
// Since we use one material for all terminal renderers in the scene, we must clone the
material at runtime in order to change
// the emission color for just this door
foreach (var terminalRenderer in terminalRenderers)
{
terminalRenderer.material = new Material(terminalRenderer.material);
}}
void LateUpdate()
{
52
foreach (var popup in terminalPopups){
popup.SetActive(playerPresent);
}
if (playerPresent){
if (inputManager.isInteracting){
foreach (var terminalRenderer in terminalRenderers)
{
terminalRenderer.material.SetColor("_EmissionColor", Color.green);
}
doorAnimator.SetBool("isOpening", true);
soundController.PlayConfirmation();
}}
else{
if (doorAnimator.GetBool("isOpening"))
{
foreach (var terminalRenderer in terminalRenderers) {
terminalRenderer.material.SetColor("_EmissionColor", Color.yellow);
}
doorAnimator.SetBool("isOpening", false);
}}}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
playerPresent = true;
}}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
playerPresent = false;
}}}}
● DoorconrollerAlt.cs
using TheSignal.Player.Input;
using UnityEngine;
namespace TheSignal.Scenes.Behaviours
{
public class DoorControllerAlt : MonoBehaviour{
[SerializeField] private GameObject player;
[SerializeField] private GameObject door;
53
[SerializeField] private MeshRenderer[] popupRenderers;
private InputManager inputManager;
private Animator animator;
private bool playerPresent = false;
private void Awake(){
inputManager = player.GetComponent<InputManager>();
animator = door.GetComponent<Animator>();
}
private void LateUpdate(){
if (playerPresent){
foreach (var popup in popupRenderers){
popup.enabled = true;
}
if (inputManager.isInteracting){
animator.SetBool("isOpening", true);
}}
else{
foreach (var popup in popupRenderers){
popup.enabled = false;
}
animator.SetBool("isOpening", false);
}}
private void OnTriggerEnter(Collider other){
if (other.CompareTag("Player"))
playerPresent = true;
}
private void OnTriggerExit(Collider other){
if (other.CompareTag("Player"))
playerPresent = false;
}}}
● ExtinguishFire.cs
using UnityEngine;
namespace TheSignal.Scenes.Behaviours
{
public class ExtinguishFire : MonoBehaviour
{
[SerializeField] private GameObject sprinklerFusebox;
[SerializeField] private float healthLoss;
[SerializeField] private ParticleSystem smoke;
[SerializeField] private ParticleSystem steam;
54
private ParticleSystem fire;
private SprinklerFix sprinklerFix;
private float fireHealth = 100.0f;
private bool fireExtinguished = false;
private BoxCollider colliderForFire;
● SprinklerFix.cs
using TheSignal.Player.Input;
using UnityEngine;
namespace TheSignal.Scenes.Behaviours
{
public class SprinklerFix : MonoBehaviour
{
[SerializeField] private GameObject player;
[SerializeField] private GameObject fixPopup;
[SerializeField] private GameObject popupText;
[SerializeField] private GameObject door;
[SerializeField] private ParticleSystem fuxeboxSparks;
[SerializeField] private ParticleSystem[] sprinklerShowers;
55
[HideInInspector] public bool sprinklersStarted = false;
private InputManager inputManager;
private bool playerPresent = false;
● LevelTrigger.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine;
namespace TheSignal
{
public class LevelTrigger : MonoBehaviour
{
56
[SerializeField] private GameObject HealthBar;
[SerializeField] private GameObject TipsUI;
[SerializeField] private GameObject SlowMoBar;
[SerializeField] private GameObject laodingScreen;
private void OnTriggerEnter(Collider other){
if (other.CompareTag("Player")){
HealthBar.SetActive(false);
TipsUI.SetActive(false);
SlowMoBar.SetActive(false);
laodingScreen.SetActive(true);
//string name
=SceneManager.GetSceneAt(SceneManager.GetActiveScene().buildIndex + 1).name;
//SceneManager.LoadScene(name);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}}}}
● DoorSoundController.cs
using UnityEngine;
namespace TheSignal.SFX.Environement
{
public class DoorSoundController : MonoBehaviour
{
private AudioSource audioSource;
[SerializeField] private float volume;
[SerializeField] private AudioClip doorSlide;
[SerializeField] private AudioClip terminalConfirmation;
private void Awake(){
audioSource = GetComponent<AudioSource>();
audioSource.volume = volume;
}
public void PlaySlide(){
audioSource.PlayOneShot(doorSlide);
}
public void PlayConfirmation(){
audioSource.PlayOneShot(terminalConfirmation);
}}
● FireSoundController.cs
using UnityEngine;
namespace TheSignal.SFX.Environement
57
{
public class FireSoundController : MonoBehaviour
{
private AudioSource audioSource;
private ParticleSystem fireParticles;
[SerializeField] private AudioClip fireSound;
private bool played = false;
private void Awake(){
audioSource = GetComponent<AudioSource>();
fireParticles = GetComponent<ParticleSystem>();
}
private void Update(){
if (fireParticles.isPlaying && !played){
audioSource.PlayOneShot(fireSound);
played = true;
}}
private void OnDisable(){
audioSource.Stop();
}}}
● SprinklerSoundController.cs
using UnityEngine;
namespace TheSignal.SFX.Environement
{
public class SprinklerSoundController : MonoBehaviour
{
private AudioSource audioSource;
[SerializeField] private AudioClip sprinklerSound;
[SerializeField] private ParticleSystem showerParticles;
private bool played = false;
private void Awake(){
audioSource = GetComponent<AudioSource>();
}
private void Update(){
if (showerParticles.isPlaying && !played){
audioSource.PlayOneShot(sprinklerSound);
played = true;
}
if (!showerParticles.gameObject.activeInHierarchy){
audioSource.Stop();
this.gameObject.SetActive(false);
58
}}}}
● PlayerSteps.cs
using System;
using UnityEngine;
namespace TheSignal.SFX.Player
{
[RequireComponent(typeof(AudioSource))]
public class PlayerStep : MonoBehaviour
{
[SerializeField] private AudioSource audioSource;
[SerializeField] private AudioClip[] stepWalkClips;
[SerializeField] private AudioClip[] stepRunClips;
private void Awake()
{
audioSource = GetComponent<AudioSource>();
}
// StepWalk and StepRun are called with animation events (Walk and Run)
private void StepWalk(){
var clip = GetRandomClip(ref stepWalkClips);
audioSource.PlayOneShot(clip);
}
private void StepRun(){
var clip = GetRandomClip(ref stepRunClips);
audioSource.PlayOneShot(clip);
}
private AudioClip GetRandomClip(ref AudioClip[] clips){
var rnd = new System.Random(Guid.NewGuid().GetHashCode());
● WeaponSoundController.cs
using UnityEngine;
namespace TheSignal.SFX.Player
{
public class WeaponSoundController : MonoBehaviour
{
[SerializeField] private AudioSource weaponAudio;
[SerializeField] private float volume;
59
[SerializeField] private AudioClip shotSound;
[SerializeField] private AudioClip chargeSound;
private void Start(){
weaponAudio.volume = volume;
}
public void PlayShot(){
weaponAudio.PlayOneShot(shotSound);
}}}
● SceneSoundController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TheSignal.SFX
{
60
audioSource.volume = startVolume;
audioSource.Stop();
fadingOut = false;
}
private IEnumerator SwitchTrack(bool enemyPresent, bool bossPresent){
while (fadingOut)
yield return new WaitForSeconds(0.1f);
audioSource.clip = enemyPresent ? enemyFightTrack : ambienceTrack;
audioSource.clip = bossPresent ? bossFightTrack : audioSource.clip;
audioSource.Play();
}
public bool IsPlaying(){
return audioSource.isPlaying;
}}}
● Projectile.cs
using UnityEngine;
namespace TheSignal.Weapons
{
[RequireComponent(typeof(Rigidbody))]
public class Projectile : MonoBehaviour
{
private Rigidbody projectileRB;
private Vector3 shootDirection;
private float projectileSpeed;
[SerializeField] private ParticleSystem metalHitEffect;
[SerializeField] private ParticleSystem fleshHitEffect;
public int maxDamage;
private void Awake(){
projectileRB = GetComponent<Rigidbody>();
}
public void Init(Vector3 direction, float speed){
shootDirection = direction;
projectileSpeed = speed;
projectileRB.MoveRotation(Quaternion.LookRotation(shootDirection));
projectileRB.AddRelativeForce(Vector3.forward * projectileSpeed,
ForceMode.VelocityChange);
Destroy(gameObject, 2);
}
private void OnCollisionEnter(Collision collision)
{
61
var contact = collision.GetContact(0);
if (collision.gameObject.layer == 0){
Instantiate(metalHitEffect,contact.point,
Quaternion.LookRotation(contact.normal));
Destroy(gameObject);
}
else if (collision.gameObject.layer == 8){
Instantiate(fleshHitEffect, contact.point,
Quaternion.LookRotation(contact.normal));
Destroy(gameObject);
}}}
● ProjectileWeapon.cs
using TheSignal.SFX.Player;
using UnityEngine;
namespace TheSignal.Weapons
{
public class ProjectileWeapon : MonoBehaviour
{
[SerializeField] private GameObject projectilePrefab;
[SerializeField] private Transform startPoint;
[SerializeField] private Transform target;
[SerializeField] private float projectileSpeed;
[SerializeField] private float fireDelay;
[SerializeField] private ParticleSystem muzzleFlash;
private WeaponSoundController weaponSFX;
private void Awake()
{
weaponSFX = GetComponent<WeaponSoundController>();
}
// lastShot is continuosly updated in the PlayerShooting and passed here
// Whenever sustained fire is made, last shot is reset and allowed to tick again in
order to achieve a delay in fire
public void StartFiring(ref float lastShot)
{
if (lastShot >= fireDelay)
{
var projectile = Instantiate(projectilePrefab, startPoint.position,
Quaternion.identity);
var shootDirection = (target.position - startPoint.position).normalized;
62
projectile.GetComponent<Projectile>().Init(shootDirection,
projectileSpeed);
muzzleFlash.Emit(1);
weaponSFX.PlayShot();
lastShot = 0.0f;
}
}
}
}
63
13. SYSTEM TESTING
65
13.6 Performance Testing:
Performance testing will be conducted to ensure that the game meets the expected
performance criteria and is able to handle the expected user load. This testing will
evaluate the game's responsiveness, stability, scalability, and resource usage.
66
15. APPLICATION AND FUTURE ENHANCEMENT
Multiplayer functionality: Adding a multiplayer mode to the game can significantly increase
its replay value and make it more enjoyable for players who prefer to play with others. This
can be accomplished by allowing players to team up with each other and work together to
fight against the alien invasion.
Virtual reality support: With virtual reality (VR) becoming increasingly popular, adding VR
support to the game can help attract new players who are interested in immersive gaming
experiences. This would require additional development work to ensure that the game is
compatible with popular VR headsets.
Expansion packs: Creating additional levels, enemies, and weapons for the game can help
keep players engaged and interested in the game for a longer period of time. This can be
accomplished by releasing expansion packs periodically, each of which would add new
content to the game.
Mobile version: Developing a mobile version of the game can help attract a wider audience,
as many people prefer to play games on their smartphones and tablets. This would require
optimizing the game for mobile devices, which can be a significant development challenge.
Console version: Porting the game to popular gaming consoles such as PlayStation and Xbox
can help attract a new audience of gamers who prefer to play games on these devices. This
would require additional development work to ensure that the game is compatible with each
platform's hardware and software requirements.
Cross-platform support: Making the game compatible with multiple platforms can help
players continue their progress on different devices, which can be especially appealing to
players who own multiple gaming devices. This would require developing the game in a way
that is compatible with each platform's hardware and software requirements.
DLC (Downloadable Content): Adding downloadable content to the game can help keep
players engaged and interested in the game over time. This can be accomplished by releasing
new levels, enemies, weapons, or other content that can be purchased by players.
71
AI improvements: Improving the artificial intelligence (AI) of the enemies can help make the
game more challenging and unpredictable. This would require additional development work
to create more complex enemy behaviors and actions.
Customization options: Allowing players to customize their character and weapons can help
make the game more personal and engaging. This can be accomplished by adding features
that allow players to customize their character's appearance, choose different weapons, and
modify their attributes.
72
16. CONCLUSION
In conclusion, the development of "KLV: The Lone Wolf" was a challenging yet rewarding
experience for the team. The game features a unique storyline and engaging gameplay, where
the player takes on the role of a lone survivor in a spaceship invaded by aliens. The game was
developed using Unity and C# programming language and utilizes enemy AI to add to the
challenge.
Throughout the development process, we followed a systematic approach that included
requirement gathering, designing, implementation, and testing. We also made use of various
development tools and techniques, including version control systems, task management
software, and debugging tools.
We believe that the game meets the set objectives and requirements and has the potential to
offer an enjoyable gaming experience to its players. In the future, we plan to explore porting
the game to other platforms such as Mac, Linux, and Android. We also plan to enhance the
game's graphics, add more levels and challenges, and integrate more features and
functionalities to make the game even more immersive.
Overall, we are proud of the work we have accomplished and look forward to continuing to
refine and improve the game in the future.
73
17. REFERENCE
74