Before designing the player mechanics for the main character we had to work out how he would move, what kind of person he is, how he interacts with the world around him and the theme of the game.
With this knowledge I could paint a good picture of his abilities, strengths and weaknesses. The main character is a human detective caught up in a web of suspicious cases in a carnival with murderous killer clowns roaming around so I thought that realistic movements, sprinting, jumping and crouching would add to the fear in the game as it makes you believe you are that player with the same physics but in an uncanny world.
He would have to be able to inflict damage and take damage from the killers or else you lose the purpose to run, hide or fight and would make for a pretty boring game. The player can sprint away from the killers easily so to make it more challenging I implemented a stamina bar that depletes when sprinting and when the bar runs out the player has to wait for it to refill while only being able to move at a walking speed as he gets his breath back. If he gets hurt by a killer, trap or puzzle he can pick up a health drop.
As a detective it would make sense that he would wield a side arm for his protection since he is a veteran and gets the most dangerous jobs. Added a gun mechanic increased the playability and gave the player another option than running away. If he had no ammo count on screen it would make the game feel more arcade like with infinite bullets so added ammo pickups to balance this. He can stun the killer which gives him 10 seconds to get away but can never kill them as they are undead. Because the game is set in an old dark carnival he needed a flashlight which would help him see but also be true to the character of a detective.
Player Mechanics Showcase
Movement
In the "Movement" script sprint, crouch and jump are controlled.
Jumping changes the players vertical velocity by the jump height times negative gravity times 2.
"OnJumpPressed()", "OnCrouchPressed()" and "OnSprintPressed()" are all called when their key is pressed with the new input system.
Implemented 2 IEnumerator methods to refill stamina and hide stamina bar after 2 seconds of not sprinting.
I used Unity's 'Slider' component which has some properties like maxValue ready made.
"UseStamina" method decreases the current stamina value and updates the stamina bar value to that until zero value.
The stamina bar will decreases while sprinting and automatically refills until full while you can't sprint until refilled.
The bar disappears after 2 seconds of not sprinting so it doesn't obstruct the players view.
Gun
The Gun Class is a component of the Player object so takes in the gun object reference "gun". From here all actions of the gun are called from game events to sound.
"Equip" is called when the user hits the 'G' key, if it is equipped then it will unequip and vice versa.
When the user left clicks the mouse button the "Shoot" method is called in the "Gun" Class.
If the hit object has a "Damageable" script component then it can be shot and take damage.
One bullet is taken away along with one HP from the killer.
If the killers HP reaches zero then "stunKiller" event is raised, which stuns him for a few seconds.
Then set the killers HP back to max HP so he can be stunned again.
Attached to each killer is an event listener that listens for the stun event to be raised, then calls the "StunKiller" method from the killers "Patrol" script which changes his state to "stunned" and plays the stun animation along with sounds.
Player shooting mechanic in action - shooting a dummy object to test the bullet impact effects.
Flashlight
The flashlight is an important item in the game, allowing the player to see where he is walking and find clues.
The "FlashLight" script is a component of player object so the flashlight object is "lightSource".
When the key "F" is pressed, "Use()" is called and if it is enabled it will be disabled and vice versa.
Scriptable Objects
The player mechanic systems I made rely heavily on Scriptable Objects explained in full on the Unity docs website here. Basically they are an asset and can save duplication of data when a prefab is instantiated because instead of have a variable created on each prefab you can make a SO of the variable and share it among them all also saving memory. It is a data container that can save large amounts of data independent of Class instances. If you change the value of a SO in the editor while the game is running the value will stay the same when the game stops so it makes it easier for testing.
I use Scriptable Objects to store any data that will need to communicate across different systems like player HP. When the players health reaches zero a lot of systems will need to happen like display a cutscene, play a death sound or reset some items so instead of using public methods or lots of events, just drag the SO data into each system once and when a change happens to HP all will be updated automatically.
To be able to create an asset you must include "CreateAssetMenu" and your chosen filename and menu name.
I called the Class "PlayerHPSO" and inherited from "NumberVariableSO" which just holds the generic value of the variable you choose to use, I chose "int" and inherits from "ScriptableObject" instead of "MonoBehaviour" like normal Unity scripts.
There is a max health value that can be seen by any script with the data asset attached.
Then there are two simple methods to add and subtract the HP if the player picks up health or gets damaged.
To make a data asset for player HP, right-click on any folder in the Project window, I chose the Player folder, then click "Create", "Scriptable Objects", "Player" and finally "PlayerHPSO" which is the SO Class above.
I added a "PlayerHPSO" reference into the "Player Pickup Behavior" script.
I added a "PlayerHPSO" reference into the "Inventory Manager" script.
Then I dragged and dropped the "PlayerHPSO" data asset into the "Player Pickup Behavior" script. Now I can use this data to check if the players health is not full so he can pick up health.
I dragged and dropped the "PlayerHPSO" data asset into the "Inventory Manager" script. Now I can add or subtract health depending on a health pickup or a trap and query how much health he has to know which health UI icon to display on screen.
Custom Events and Listeners
To make it easier sending any kind of event with or without parameters, I made a custom event and event listener.
Started off making an interface that can take any data type as a parameter with a method "OnEventRaised" that must be implemented by any Class inheriting from this Class.
The event listener takes in 3 parameters, the first one can be any data type like an "int", the second must be a "BaseGameEventSO<P>", the third must be a "UnityEvent<P>" and all must take the same data type as a parameter.
When the listener is enabled the game event is checked if it is null by the "?" after "GameEvent", then "RegisterListener(this)" is called from it which adds this event listener to it's list of listeners because one game event can have multiple listeners.
When "OnDisable()" is called the game event removes this event listener from it's list of event listeners.
"OnEventRaised(P parameters)" method creates a unity response in the editor that events and unity methods can be dropped into.
The game event must be inherited from with your own concrete data type parameter and is inherited from ScriptableObject.
It has a list of "IGameEventListener<P>" called "listeners" which holds all the event listeners listening to this game event.
When "Raise" is called, the method attached to the listener is called.
Pickups
The player can pick up certain items by colliding with them such as health and ammo.
-
A blood splatter effect shows on screen when damage is taken from any damageable object with the alpha value increasing for each event.
Blood splatter alpha decreases for each damage event and disappears once the heart is full again.