Module 3 - Full Notes PPT
Module 3 - Full Notes PPT
In game design, almost every scenario can be simplified down into a core
choice that the user must make. In chess, it’s what piece to move and where to
move it. In a side scrolling video game, it’s timing when to make that jump.
All these choices require the player to balance the Risks and Rewards of a
situation.
When designing a game, much of the player’s enjoyment comes from weighing
these Risk/Reward situations and acting on them intelligently.
When your hearts get too low, health bar depletes, or HP gets in the single digits
- it raises the stakes for the player.
Subsystems
Whenever you have a large, important gameplay system like Combat, it’s
bound to require the implementation of various subsystems that will work
together in harmony.
Our initial implementation of a combat system will be built on the following
subsystems
_healthMax
This is the maximum health of the object we’re managing. we’ll set the default
health of any object to 10.
_healthCur
This is the current health of the object whose health we’re managing. By default, it
starts at the maximum health level. When this reaches zero, the object will be set to
‘Is Dead’.
_invincibilityFramesMax
Invincibility frames represent an amount of time, in seconds, that an object is
invincible after taking damage.
_invincibilityFramesCur
This is the current invincibility frame countdown. If this is anything other than zero,
incoming damage will be ignored.
_isDead
Whether or not this object’s health has depleted. Because there may be some data
cleanup, or perhaps an animation to play after dying, we will store the object death
as a Boolean value.
Almost as important as the onDeath function is a public IsDead() function,
which will allow other scripts to know whether or not this object should be
active.
Another function we’ll eventually need is Reset(), which will set all the
data of this player back to its default state.
The final bit of logic we’ll be adding is in the Update() function, where we’ll be
adjusting the invincibility frame counter if necessary. We’ll also put in some
temporary code to simply destroy the object when it’s been set to _isDead.
With the HealthManager component made, let’s return to Unity and add
it to our PlayerObj_Sphere object.
Spikes: Your First Hazard
Let’s start building our first hazard by creating three new objects in the Hierarchy
window: one empty GameObject (created using the “Create Empty” option) and two 3D
Cubes (created using the “3D Object > Cube” option).
Rename the empty GameObject to EnemyObj_Spikes.
Select the first Cube, then rotate and flatten it in the transform panel. Set the rotation to
45 on the X-Axis, and set the scale to 0.2, also on the X - Axis.
Next, select the second Cube - likely named Cube (1) - and flatten it with a scale of
0.05 on the X-Axis. This should result in a thin Cube sittinginside the thicker rotated
Cube.
If you haven’t already made a red enemy material, let’s make one now by creating a new
Materials folder in our Assets window. Open this folder and right-click to select the New >
Material option.
With your new material ready, drag it from your assets folder to the larger rotated Cube. This
fresh coat of paint should instantly make it look more dangerous.
Start by renaming the larger Red Cube to “RedCube” and the smaller White Cube to
“WhiteSpikes”. Staying in the hierarchy window, you’ll want to drag the white spikes
entry onto the Red Cube entry.
In addition to the WhiteSpikes, you’ll also want to drag the RedCube entry on to the
EnemyObj_Spikes entry. The Spikes should now be a parent object with two nested sub
objects.
Select the Red Cube, and Duplicate it using the hotkey CTRL + D. Do it a second time to
ensure you have the original red tube and two copies. Note the (1) and (2) now appended
to the names of these duplicated objects.
Select the first duplicate, named “RedCube (1)”. In its Transform panel, set its rotation
values to 45, 90, 0. Then select the second duplicate, “RedCube (2)’, and set it’s rotation
values to 0, 45, 90.
The last step is to add a ‘Sphere Collider’ component to the
EnemyObj_Spikes parent object. Select EnemyObj_Spikes in the
Hierarchy window, then add a Sphere Collider using the Inspector.
Select the “Is Trigger” option, then reduce the radius of the sphere to
0.75. With the Spike object made, it’s time to make a component that
can deal some damage!
The Health Modifier Component
The HealthModifier component will be a script added to objects that can alter the
health of objects they collide with. Our spikes, for instance, will have a health
modifier that decreases the health of the Player object.
Bullets spawned by the player, on the other hand, will have health modifiers that
lower the health of enemies. And power-up items will have health modifiers that
increase the player’s health.
Let’s make the script by going to your Projects > Assets window, opening your
Scripts folder, and adding a new script named “HealthModifier”.
Open the new script in Visual Studio and give it the following members.
float _healthChange = 0;
DamageTarget _applyToTarget = DamageTarget.Player;
public enum DamageTarget
{
Player,
Enemies,
All,
None
}
bool _destroyOnCollision = false;
_healthChange
This is the amount of damage that will be applied when a valid target is collided
with.
_applyToTarget
The _applyToTarget member, along with the DamageTarget, will be used to
determine the validity of the collided object.
_destroyOnCollision
this object will destroy itself when a valid object is collided with. This will be
TRUE for bullets, but FALSE for Spikes.
void onCollisionEnter(Collision collision)
{
GameObject hitObj = collision.gameObject;
HealthManager healthManager = hitObj.GetComponent<HealthManager>();
if ( healthManager && IsValidTarget(hitObj))
{
healthManager.AdjustCurHealth(_healthChange);
if ( _destroyOnCollision )
GameObject.Destroy(gameObject);
}
}
bool IsValidTarget( GameObject possibleTarget )
{
if (_applyToTarget == DamageTarget.All)
return true;
else if (_applyToTarget == DamageTarget.None)
return false;
else if (_applyToTarget == DamageTarget.Player && possibleTarget.GetComponent<PlayerController>())
return true;
else if (_applyToTarget == DamageTarget.Enemies && possibleTarget.GetComponent<AIBrain>())
return true;
return false;
}
Let’s take Unity, make sure the Spike object is selected. In the Inspector window, scroll
down and press Add Component > New Script to add a component named AIBrain. Our
Spike now has an empty component that we can check against, and we can deal with the
actual implementation of AIBrain in a later chapter.
Now that the compiler error is fixed, we can now add our finished HealthModifier
component to the Spike hazard. Make sure the object EnemyObj_Spikes is still selected,
and again press Add Component to add the HealthModifier script to the Spike.
Now make the prefab by dragging the spike from the Hierarchy panel into the prefabs folder
of our projects asset directory.
We now have a dangerous Spike prefab that can be placed throughout the level. The player
will have to time jumps and movement to ensure they don’t take too much damage.
With everything in place, it’s time to press Play and put our new systems to the test. If we did
everything correctly, colliding with a spike twice will result in your player object being
destroyed. Press the Play button again to stop the game, reset the scene, and re-spawn our
player.
Principles of Animation
There are 10 key Principles of Animation that can be applied across both
2D and 3D mediums. By making use of these rules, you can ensure your
heroes, enemies, NPCs, and even environments exude their intended
personality
1. Squash and Stretch
When an object collides with something, or quickly accelerates in a given direction, Its shape will
change in a concept known as Squash and Stretch. It has to do with the inherent elasticity, or
malleability, of most objects.
2. Anticipation
The time spent ‘building up’ to a given action is known as Anticipation. It could be a weightlifter,
struggling with a dumbbell before eventually lifting it. This could be a snake coiling up before
striking its prey.
3. Staging
Making sure the viewer (or player) understands what’s important in a scene is the primary goal of
Staging. This can be achieved through camera positioning, scene lighting, or even character
posing.
4. Exaggeration
Always push your animation to be as readable as possible. This often means you’ll need to
Exaggerate shapes and sizes in a way that isn’t realistic.
6. Arc
Similar to the concept of Slow In / Slow Out, Arcs in animation mimic the smooth movement
curves caused by inertia.
7. Timing
The number of frames between two poses, or Timing, has a massive effect on how an animation
feels.
The Unity editor makes use of a Timeline to allow us to easily manage the number of frames
spent in a given pose.
8. Secondary Action
You can add extra appeal to an animation by introducing a Secondary Action, an extra bit of
movement that infuses your main action with extra personality.
10. Appeal
While all the above concepts are concerned with movement in animation, Appeal focuses on a
character’s design. It’s important that the personality of a character is bold: readable at a glance.
Making A Basic Animation
1. In the Unity Editor, open the scene and create a new folder Assets ->
Prefabs.
2. In the Prefabs folder Right click and create empty 3D object and renamed
it as PlayerObj_Sphere. Drag the PlayerObj_Sphere into scene window.
3. place a new 3D Object > Plane at Position 0,0,0
4. Remove the Mesh Renderer component and set the SphereCollider’s
Center and Radius values.
In the hierarchy window, right-click on the PlayerObj_Sphere. Select
3D Object > Sphere to add the body sphere.
2. Name this new sphere Hero1_Body.
3. Disable the Sphere Collider component on the body.
4. Again, right-click the PlayerObj_Sphere and use 3D Object > Sphere
twice to add two new feet spheres.
5. Name these two new spheres Foot_L and Foot_R.
6. Disable the Sphere Collider components on the feet
In the hierarchy window, right-click on Hero1_Body. Select 3D Object
> Sphere twice to add two Arm spheres.
2. Name one sphere Arm_L and the other Arm_R.
3. Remove the Sphere Collider script from both of these new objects.
In the hierarchy window, right-click on Hero1_Body. Select 3D Object
> Sphere twice to add two spheres for our Eyes.
2. Name one sphere Eye_R, and the other Eye_L.
3. Remove the Sphere Collider script from both of these.
4. Right-Click on each of these eye objects and add a sphere child to
each.
5. Name both child spheres WhiteGlare.
In the hierarchy window, right-click on Hero1_Body. Select 3D Object
> Sphere to create our first Hair object.
2. Name this object Hair_1 and remove the Sphere Collider component.
3. Right-click on Hair_1 and add a child sphere named Hair_2.
4. Right-click on Hair_2 and add a child sphere named Hair_3.
5. Right-click on Hair_3 and add a child sphere named Hair_4.
6. Make sure all these hair objects have their Sphere Collider scripts
disabled or removed.
The Animation View
1. Animation tools are hidden by default. Let’s unhide the Animation View
now: go to the top menu bar to select Window > Animation > Animation
2. Once open, drag this new window down into the bottom panel, where it
will become an additional tab next to the ‘Project’ and ‘Console’ options.
3. The next step is to select the root object that you’ll be animating. In this
case, that would be PlayerObj_Sphere. Select that object in the Hierarchy
window, then select the Create Button In the middle of the Animation View
window.
Go to the left side of this window and press the Add Property button. Select
Hero1_Body > Transform > Rotation and press the + button, now add the
Hero1_Body > Transform > Position parameter.
Keyframes and Inbetweening
1. We have specified the objects and properties we will be animating, a
diamond shaped dot to the right of each parameter. These are called
Keyframes a specified value at a specified time.
2. A Keyframe stores the position, rotation, scale, (or any other parameter) at a
given frame.
3. When there are two keyframes present, the computer performs a process
known as Inbetweening or Tweening for short.
4. Tweening is the interpolation of values between one keyframe and the next.
5. All of this is managed on the Dopesheet - the grid on the right side of the
Animation View window.
6. Using both Keyframes and Tweening, the animation system allows us to
create complex, multi-frame animations by simply setting poses that get
interpolated at runtime.
Let’s start our idle animation by defining its Length, which we’ll make 6
seconds long. Position your cursor over the Dopesheet, and use the Mouse
Wheel to expand the timeline.
Keep scrolling the mouse-wheel until you see 6:00 appear at the top, then
do the following
1. Click and drag to select all keyframes at the 0:00 mark and press ctrl+c
2. Go to the 6:00 mark and click on it and press ctrl+v
Note: When making a looping animation, you’ll always want the first frame and last
frame to have the same keyframes.
Since keyframes are necessary when making an animation, we should cover
the various ways you can add them to your timeline. Using the Animation
View, there are three preferred ways to add keyframes to your animation.
Copy and Paste (which we performed above)
2. Right Click on the Dopesheet, then select “Add Keyframe”
3. Automatically adding keyframes with Recording Mode
To start recording, press the small Red Circle button in the upper left corner
of the Animation View window.
Our Idle animation will simply be our hero looking around. This should only
require us to add two keyframes.
Select the Hero1_Body object, either from the Animation View or the
Hierarchy Window.
2. Switch to the Rotation Tool by pressing the [E] hotkey.
3. Go to the 2:00 mark on the timeline. Rotate the head to look towards the
right, or use the Transform panel to set the rotation to (30, -40, -5).
5. Go to the 4:00 mark on the timeline. Rotate the head to look towards the left
or use the Transform panel to set the rotation to (25, 20, 3).
6. Click anywhere on the Dopesheet, then press [Spacebar] to Play our
animation. You should see the hero look slowly to the right, then to the left.
7. Since we copied the starting keyframes to match the last keyframes, this will
continue in a seamless loop. Press [Spacebar] again to Stop the animation.
Since our hero is bipedal (ie. only has two feet) the cycle is broken up
where 15 frames are spent on the Right foot, and 15 frames spent on the
Left foot.
In the upper left corner of the Animation View, you will see a dropdown menu
with the name of the current Animation clip (look for “SphereHero_Idle”).
Click this area and select Create New Clip from the drop down menu. Name
the new animation SphereHero_Run.
Since the Body object is first on our list, let’s start by adding parameters for
the Position, Rotation, and Scale of Hero1_Body
Now we need to do some keyframe management. Use the following steps to
set up our initial Running keyframes…
1. Move the keyframes for each of the properties from the 1:00 mark to the
0:30 mark. Because Unity determines animation length by the timestamp of the
last keyframe, we’re telling Unity that our run cycle is 30 seconds long.
2. Enable Keyframe Recording Mode (the little red dot next to the rewind
button).
3. Scrub to frame 8 (timestamp 0:8) and move the body upwards (Y position
should be around 0.9).
4. Scrub to frame 15 and move the body back down (Y position around 0.6).
5. Scrub to frame 23 and move the body upwards (Y position around 0.9).
Press the play button (or use the [Spacebar] hotkey). The body of our hero
should now be bobbing up and down, with the eyes, hands, and hair
following it.
At this point the body should be bouncing nicely. Play your animation, or
scrub the timeline, to see how it looks. You should notice some appealing
squash-and-stretch now that we’ve added the Scale and Rotation
keyframes, with frames 8 and 23 stretching the hero vertically, and frame 15
squishing the hero horizontally.
It’s a subtle effect, but important when making a satisfying movement.
Now we’ll move onto the feet. Use the Add Property button to add Position
and Rotation parameters for Foot_R and Foot_L.
Notice there is no data listed for Frame 30. This is because, whenever you
have a looping animation, the last frames should always match the first
frames.
Let’s polish our Run by adding some keyframes to the arms. Use Add Property
to add the Position of Arm_L and Arm_R.
Animations in Action
Press the Play button to start the game and use the Arrow Keys to move your
updated hero around the scene.
when we want to freeze the rotation of a Rigid Body, all we have to do is toggle
the appropriate boxes.
Open the PlayerController script and add the following code at the bottom of the
Update() function.
transform.LookAt(transform.position + new
Vector3(_curFacing.x, 0f, _curFacing.z));
Next, we need to add some code to make our player swap between the Idle
and Run animation states. Select PlayerObj_Sphere, locate the Animator
component’s Controller parameter.
Now you can return to the PlayerController in Visual Studio. Add two new
members at the top of the script.
UpdateAnimation();
void UpdateAnimation()
{
if (_myAnimator == null)
return;
if (_moveInput)
_myAnimator.Play(“Run”);
else
_myAnimator.Play(“Idle”);
}
This function first checks to see if we have a valid Animator component. If
so, it will set either the Run or Idle animation based on whether movement
keys are being pressed by the player.
Our final step is to update the player prefab. We’ve made several changes to
this object, and we want those changes to be applied to other instances of
the prefab.
Programmatically speaking, Enemy objects aren’t that much different than the Player object.
They both have health associated with them. They both components that dictate how fast they
can run, how high they can jump, and what weapons and items they can use.
For the player object, all decisions are made by the user - the gamer pressing the buttons on the
keyboard or controller. Enemy objects, on the other hand, are driven by Artificial Intelligence
(AI) - code that tells them what to do and how to do it.
We’ll be creating a component called AI Brain which will observe the game world (location of
the player, obstacles in the area, etc.) and make decisions based on that data.
Our Basic Enemy
Apply the matColorRed material to CubeHead and matBlack to EyePupil, Eyebrow_L, and
Eyebrow_R.
Then make a new matYellow material and apply it to the YellowEye object.
Our Basic Enemy
This script will manage what the enemy wants to do, also known as the AI State.
Each possible State will have a series of actions that can be assigned to it.
Select the BasicFoe object and select Add Component > New Script to add a component called AIBrain.
Open this Script in Visual Studio and add the following members.
#region ** members **
UnityEvent _curAIDirective;
UnityEvent _defaultActions;
UnityEvent _alertedActions;
UnityEvent _huntActions;
public UnityEvent _miscPattern1Actions;
public UnityEvent _miscPattern2Actions;
public UnityEvent _miscPattern3Actions;
float _pauseTimer = 0;
PlayerController _playerObject = null;
#endregion
First off, you probably noticed the use of a new set of tags: #region and #endregion. These lines of
code specify a block of data that can be collapsed within Visual Studio.
Second, you probably noticed several UnityEvent members being added. These will hold the
Actions associated with the various AI states, and we’ll be talking about them in the next section.
Thirdly, the last few members dealt with the pausing of AI and the storing of a player object. The
member _pauseTimer can be set to pause the movements of any enemy, in an important way to
give the player time to react to a situation.
The _playerObject allows us to cache the player’s information for quick retrieval. This is because
almost every AI decision will be based on the location and status of the player. Which direction to
face, whether to patrol or chase, and when to fire a weapon are all based on the player’s location in
relation to the enemy’s.
private void Start()
{
_playerObject = GameObject.FindObjectOfType<PlayerController>
_curAIDirective = _defaultActions;
}
void Update()
{
if (UpdatePausedAI())
return;
_curAIDirective.Invoke();
}
bool UpdatePausedAI()
{
if (_pauseTimer > 0)
{
_pauseTimer -= Time.deltaTime;
_pauseTimer = Mathf.Max(_pauseTimer, 0f);
}
return (bool)(_pauseTimer > 0f);
}
The Start() functions find the player object and initialize the current AI directive to use the default actions.
The UpdatePausedAI() function determines if the AI should be running this frame. And, if it SHOULD be
run, the Update() function will Invoke the current UnityEvents (call all the functions associated with the
current State of the AI).
Since so much of our AIBrain relies on these UnityEvent objects, let’s take a moment to learn more about
these powerful data types.
Each event in this list is composed of three parts, each of which we’ll need to set.
First, we need to specify the game object that this event will be acting upon.
Second, we’ll need to set the Component and Function being called by this event.
And third, we’ll need to specify any parameters required by the selected function.
Setting the object associated with this event is as simple as dragging that object from the Hierarchy
window to where it says None (Object) in the event panel.
Once an object has been assigned, Unity will search through all the possible components and
functions we can call upon that subject. Click on the drop down menu that currently says ‘No
Function’ to see all the possible functions the AI can trigger.
Setting up the AI Component
Setting up the AI Component
Now back to our Basic Foe. The default action of this enemy will be to sit and wait until the player gets too
close. To accomplish this, we’ll use the function AlertIfPlayerNearby() that we added to AIBrain. All we
have to do now is assign that function in our default action list.
The alert function we’re calling takes a distance value, which serves as our Alert radius. Once the player gets
this close the enemy, the AIBrain will enter its Alerted state. In this case, we’ve set this Distance to 5, meaning
the player needs to keep their distance if they don’t want to alert the enemy.
Once alerted, however, our Basic Enemy should perform the following functions:
Luckily for us, we’ve written corresponding functions for all these actions. Add four new events to the alerted
actions panel, and set the functions to be triggered.
Setting up the AI Component
The last function that we trigger that’s the AI state to be in ‘Hunt the player’ mode. This logic has only one
required action: keep moving towards the player.
Our Basic Foe will sit there until the player gets close. Once they’re alerted, the enemy will jump, turn to the
player, then start chasing them after a brief pause.
There will be situations, however, where complex level design will require a more sophisticated AI. When you
need more intelligence in your AI, Unity has the perfect tool for the job: the Navigation Mesh.
NavMesh
In most cases, our simple implementation of ‘Hunt the Player’ will be good enough. has
adding the complexity for other reasons, this basic movement logic will not be enough.
This is where Unity’s Navigation Mesh becomes necessary. The Navigation Mesh (or
NavMesh) Is a generated piece of geometry that can tell your enemy where it can and
cannot move.
Couple this with integrated Pathfinding - code that figures out how to get from point A to
point B - And suddenly your enemy’s hunting skills will receive a considerable upgrade.
Bring up the navigation window by selecting Window > AI > Navigation.
It should automatically dock itself as a tab within the Inspector window. The navigation
window has 4 categories to work with as mentioned below:
1. Agents
This tab lets you specify various character sizes that will be used your game. By default there’s only
one - Humanoid. Even if you don’t specify a new character category, all the settings available here can
also be tweaked later, directly in the NavMesh Agent component.
2. Areas
This tab presents a list of the various movement areas cut your levels will consist of. By default there
are only three: Walkable, Blocked, and Jump. In most cases, these three will be enough, though adding
more is as easy as entering a new name into the list.
3. Bake
This is the most important tab in the navigation window. With all your level geometry selected,
generating a NavMesh is as easy as pressing the Bake button in the lower right corner. Forgetting this
step will result in broken AI Behavior, so always remember to bake your NavMesh after editing a level.
4. Object
The Object tab gives you NavMesh related information about the selected piece of geometry. Use this
to specify what Area category is assigned to each part of your level.
NavMesh Agents
Before an object will make use of our generated NavMesh, it’ll first need to
be assigned as a NavMesh Agent.
Like most systems in Unity, this is done by assigning a component to the object that
needs to perform the AI Pathfinding.
Select the EnemyObj_BasicFoe object and press Add Component > Navigation >
NavMesh Agent to apply the necessary component.
while using NavMesh is mostly automated, we do need to add a special AIBrain function to hunt the
player using the navigation system. Open the AIBrain script in Visual Studio, and add this line to the top
of the file.
using UnityEngine.AI;
Then, at the bottom of the file, add a new #region for NavMesh specific logic, as well as this new
function.
Once the function is ready, go into the AI component and assign Hunt Actions to use our new
Nav Mesh logic.
As always, our last step is to press the PLAY button to see how it all works. The enemy should
sit there patiently, then become startled when the player gets too close, then start chasing the
player as before.