Day 113 — Setting up Enemy AI in a 2D Platformer
Hey and welcome!
Now that we’ve got the design for our enemies planned out and ready it’s time to create some AI for them so that they’ll move back and forth between a fixed point and pose a threat. Let’s start off with the Moss Giant enemy.
We’re going to be setting up the Moss Giant pretty similar to how we set up the player, first start off by creating an empty object and call it Moss_Giant. Next slice the images for the walk sprite sheet and the idle sprite sheet for the moss giant which you’ll find under Sprites > Characters. The slice will need to be 512 x 512.
With that done grab the first image of the idle animation and drag it into your Moss_Giant object and rename this Sprite.
Set up the Idle and Walk animations in the animation window and create an Idle trigger parameter in the Animator Controller that gets created from this. Add two transitions going back and forth between your state and make sure that the transition from Idle to Walk has the exit time option active. On the other transition you’ll not want exit time but you do want to make sure that your Idle trigger is in the conditions here.
If you test that out now you should get something like the above. The idea is that we want the moss giant to pause and do the idle animation any time they reach one of the waypoints we set and then will turn around once the animation finishes.
Let’s start working on the code now! Go ahead and add in this property to your Enemy script:
[SerializeField]
protected Transform pointA, pointB;
These are going to be the waypoints that all our enemies will move between so it makes sense to add this part here so that the other enemy types can inherit this. Once you’re happy with that it’s time to update your MossGiant script with this code:
private Vector3 _currentTarget;
private Animator _anim;
private SpriteRenderer _mossGiantSprite;void Start()
{
_anim = GetComponentInChildren<Animator>();
_mossGiantSprite = GetComponentInChildren<SpriteRenderer>();
}public override void Update()
{
if (_anim.GetCurrentAnimatorStateInfo(0).IsName("Idle"))
{
return;
} Movement();
}private void Movement()
{
if (_currentTarget == pointA.position)
{
_mossGiantSprite.flipX = true;
}
else
{
_mossGiantSprite.flipX = false;
} if (transform.position == pointA.position)
{
_currentTarget = pointB.position;
_anim.SetTrigger("Idle");
}
else if (transform.position == pointB.position)
{
_currentTarget = pointA.position;
_anim.SetTrigger("Idle");
} transform.position = Vector3.MoveTowards(transform.position, _currentTarget, speed * Time.deltaTime);
}
If you take a look at the code in the Movement() method you should be able to recognise most of it as it works nearly identical to the code we’ve used before for moving platforms. All we do here is move the moss giant between two points by using MoveTowards and then assign the pointA or pointB position to our _currentTarget variable depending on which point the moss giant has reached.
Also whenever the _currentTarget has been assigned the value of pointA that’s when we flip the sprite since they’ll be turning around to head that way.
One interesting thing we’ve got going on is GetCurrentAnimatorStateInfo. As the name implies this let’s us get information on the current state that the animator controller is on. If it’s on our Idle state we simply use the return keyword which stops the code from executing further and will keep doing this if statement until the condition is false which will cause the moss giant to start moving.
Last part to do now is to map out the points, you should be pretty familiar with this as it’s just the case of duplicating the Moss_Giant object twice and then remove all the components from them when you’re happy with their positions. Then it’s just the case of dragging and dropping these into your pointA and pointB properties in the MossGiant component in the inspector.
With all that put together you should have a pretty successful and menacing looking moss giant roaming about your game now!