Section 5: Adding Enemies
In this section, we took the time to add enemies to our level with AI behavior for patrolling and chasing the player.
Section Intro - Adding Enemies
There are no notes for this lecture.
Designing an Enemy
In this lecture, we took the time to prepare an enemy scene with animations. There are no notes for this lecture.
Refactoring the State Machine
In this lecture, we refactored the state machine by taking advantage of inheritance. Now that we're introducing a second character, we want to be able to reuse code. For this reason, we refactored the states and character classes by creating a generic class, which holds code that will be used by multiple classes. Here's the hierarchy of classes.
Character
Player
Enemy
CharacterState
PlayerState
PlayerIdleState
PlayerMoveState
PlayerDashState
EnemyState
EnemyIdleState
Drawing Patrol Paths
In this lecture, we learned how to use the Path3D node to draw paths. There are no notes for this lecture.
Resources
Creating the Enemy Return State
In this lecture, we worked on the enemy return state. First, we grabbed a reference to the node that stores the path.
Next, we were able to grab the path from the Path3D
class with the Curve
property, which is a resource containing the path data. On this property, we accessed a specific point on our path with the GetPointPosition
method.
Points on a path are stored in an array. This method returns a specific point's position, which we can specify by passing in an index. After grabbing this position, we added it to the global position of the PathNode
because the GetPointPosition
method only returns a local position. If we ever want a global position from a local position, we must add the local position with the parent's global position. After grabbing this information, we set the destination
variable to the result.
All nodes have the GlobalPosition
property, which is inherited by the Node3D
type.
Moving the Enemy
In this lecture, we learned how to calculate the destination for an enemy. First, we learned about the GlobalPosition
property, which is available on all Node3D
types. This property always stores the global position of the node in our world.
Afterward, we used a method called DirectionTo
, which is available on Vector3
types. This method calculates a property direction between the current position of an object and another position. It accepts a Vector3
value as an argument like so:
During this process, we had to detect that the enemy reached their destination. However, this process won't go smoothly if the current position of a node and destination aren't exact. That can be hard to check if you have physics enabled, as some areas may be completely unreachable. For this reason, we're going to use Godot's navigation system in the following lecture.
Baking a Navigation Mesh
In this lecture, we learned how to bake a navigation mesh, which is data that tells our game what areas are walkable. While optional, it's recommended to bake a navigation mesh, as generating a mesh can be an expensive task. Baking is the process of storing the results of a complex task before the game has been built.
A navigation mesh can be generated with the NavigationRegion3D
node. This node stores a resource with the navigation mesh and can even help you generate the mesh. As long as you add the obstacles and environments as a child to this node, you should be good to go when generating the mesh.
Navigation Agents
In this lecture, we updated our enemies to be navigation agents. Godot has a script called the navigation server that is responsible for keeping track of the navigation area, obstacles, and agents. Agents are the movable characters in our game that can use the navigation mesh. Before an agent should move, it should check with the navigation server if it can do so.
We can communicate with the navigation server with the NavigationAgent3D
node. By adding this node, we'll be able to use its methods.
Firstly, we notified the navigation server of a new location by updating the TargetPosition
property.
Next, we used the IsNavigationFinished
method to detect if the agent reached their destination.
Unlike before, the agent doesn't need to arrive at their destination exactly. If they get close enough, Godot will let you know that an agent has arrived at their destination.
It's important that you set the destination on the frame before calling the
IsNavigationFinished
method. Otherwise, Godot might complain that a target position hasn't been set.
Enemy Patrol State
In this lecture, we created the enemy patrol state. There are no notes for this lecture.
Patrolling the Path
In this lecture, we got the enemy walking from point to point on the path. Firstly, we waited for the enemy to reach a point by subscribing to the NavigationFinished
signal. This signal gets emitted when an agent reaches their target position.
Afterward, we learned about the Mathf.Wrap
method to make sure that the next point in the path exists. This method will make sure that a value stays within a range. If a value extends beyond the range, the value gets wrapped around.
There are three arguments, which is the value to check, the minimum threshold and maximum threshold. For the maximum threshold, we can grab the number of points in a path via the PointCount
property on the Curve
property.
While moving the enemy, it's important to call the GetNextPathPosition
method so that the navigation server is informed that the agent is moving.
Resources
NavigationAgent3D - https://docs.godotengine.org/en/stable/classes/class_navigationagent3d.html
Pausing on Patrols
In this lecture, we set up a timer to force an enemy to pause when they arrive at a destination before moving on to the next one. We used the Timer
node to accomplish this task. To make the game more random, we configured the WaitTime
property each time to a random time with the RandomNumberGenerator
class.
We must create a new instance of this class to use it. Once we do so, we can call the RandfRange
method to generate a random float value. This method accepts a minimum and maximum range. The value returned by this method is assigned to the WaitTime
property on the Timer
node.
To check the timer stops before moving to a new destination, we're checking the IsStopped
method, which will tell us if a timer has stopped running.
Resources
Godot Documentation - https://docs.godotengine.org
Exiting States
In this lecture, we took the time to set up exit states in our state machine. In some of our states, we're subscribing to signals. However, we should unsubscribe from these signals to prevent our state from remaining active. To do so, we had to set up an exit state. The setup is similar to the enter state.
To unsubscribe from a state, we would use the -=
operator instead of the +=
operator. Here's an example of us unsubscribing from the NavigationFinished
signal.
Detecting the Player
In this lecture, we added a Area3D
node to the enemy to help us detect bodies. This node is designed for detecting nodes with physics on them, such as the player using the CharacterBody3D
node. On this node, we can listen for when a body is detected with the Bodyentered
signal.
One lesson we learned during this process was how to register methods for signals with parameters properly. If a signal has a parameter, our method must include that parameter. Otherwise, it'll be rejected by C#. In the case of the BodyEntered
signal, our method must have a parameter called Node3D
like so:
Resources
Chasing the Player
In this lecture, we learned how to chase the player after being detected by the area node. First, we had to grab the player by using the GetOverlappingBodies
method. This method returns all bodies that have been detected by an Area3D
node.
In addition, we called the First()
method to grab the first result. Since the GetOverlappingBodies
method returns an array and we're only interested in the player, we used this method to grab the first result. However, we have to cast the type into CharacterBody3D
since the return type is a Node3D
.
In addition, we must include the System.Linq
namespace since this method is not available unless we add this namespace.
Other than that, everything else we did were things we did before.
Resources
Godot Documentation - https://docs.godotengine.org/
Attacking the Player
In this lecture, we worked on setting up the attack state. This state only gets transitioned from the chase state. The chase state was also updated to transition to the return state if the player leaves the Area3D
node for chasing the player. For this logic, we subscribed to the BodyExited
signal, which gets emitted when a body leaves an area.
Last updated