Enemies can come in all sorts of different shapes and sizes. As such, I personally prefer to all base them of one universal enemy Controller, so all enemies have the same default values that can be called.
Here I've created a basic version of said controller. It contains code for moving, damaging, getting damaged and death. A lot of references will be made to the Player tutorial, so if you haven't read that one and as such don't understand some of the things written here, I do recommend taking a look at it.
Right, here's the basic controller script:
Controller.cs:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (Rigidbody2D))]
public class Controller : MonoBehaviour
{
[System.NonSerialized] public bool attacking = false; // Is the thing currently attacking?
[System.NonSerialized] public bool hitInvul = false; // Is the thing currently hit?
public float moveForce = 5f; // Amount of force added to move the thing.
public float rotForce = 50f; // Amount of force added to rotate the thing.
public int health = 5; // The amount of life a thing has.
public bool alive = true; // Is the thing alive?
[SerializeField] private LayerMask attackLayerMask; // The layermask for attacking.
[SerializeField] private float attackCooldown = 1f; // The cooldown after attacking before the thing can attack again.
[SerializeField] private float knockbackForce = 5f; // The force at which a thing is knocked back when hit.
private float attackCooldownCurrent = 0f; // The cooldown currently in action.
private int damage = 1; // The amount of damage a thing does.
private Rigidbody2D myRigidbody; // Reference to the thing's rigidbody component.
private float lastAttackID; // The attackID of the last attack the thing was hit by.
void Awake ()
{
// Setting up references.
myRigidbody = GetComponent();
if (myRigidbody == null)
{
gameObject.AddComponent(typeof (Rigidbody2D));
}
}
void Update ()
{
if (alive)
{
if (health <= 0)
{
Death();
}
}
if (attackCooldownCurrent > 0)
{
attackCooldownCurrent -= Time.deltaTime;
}
}
void Death ()
{
alive = false;
Debug.Log("I'm " + gameObject.name + " and I'm dead");
}
void OnCollisionEnter2D (Collision2D collision) // This is how the enemy attacks the player.
{
if (alive)
{
Collider2D collider = collision.collider; // By touching him!
if (collider.tag != null)
{
if (collider.tag == "Player")
{
collider.GetComponent().Damage(damage,transform.position); // Get hit!
}
}
}
}
public void Rotate (Vector2 direction, float to)
{
// ... add a force to the thing.
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(0, 0, to), Time.deltaTime * rotForce);
}
public void Move (Vector2 targetPos, bool aggroed)
{
Vector2 direction = new Vector2(targetPos.x - transform.position.x, targetPos.y - transform.position.y);
// ... add a force to the thing.
myRigidbody.AddForce(transform.right * moveForce);
float to = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
float angle = Mathf.LerpAngle(transform.rotation.z, to, Time.deltaTime);
Rotate(targetPos, to);
}
public void Damage (int damage, Vector3 damager, float attackID)
{
if (attackID != lastAttackID && alive) // Ow I'm hit.
{
lastAttackID = attackID; // Or wait, was that the same attack I was already hit by?
Knockback(damager); // Activate knockback...
health -= damage; // and lose health.
}
}
private void Knockback(Vector3 damager)
{
// I'm pushed back.
Vector2 direction = new Vector2 (damager.x - transform.position.x,damager.y - transform.position.y);
myRigidbody.AddForce(direction * -knockbackForce,ForceMode2D.Impulse);
}
}
As you may see, this one already has the knockback function that was mentioned in the Player tutorial integrated in it's code. This is because the enemies attack by touching the player, as such being knocked back is a logical action. Note that as always, you can change anything you'd like, I even encourage it! Explore, learn, create!
Continuing on to the AI part of the enemies. The enemies' behaviors can range from simple aggressive chargers to highly intelligent patrolling beasts. Though for now I'll show you a simple aggressive AI and a simple patrolling AI that switches to aggressive when the player gets near.
AggressiveAI.cs:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (Controller))] // Here we force the enemy to get a Controller.cs component (if it doesn't already have one) so you can just drag the AI script onto your enemy and it'll automatically create everything else you need.
public class AggressiveAI : MonoBehaviour
{
public float aggroRange = 4f, attackRange = 1f; // This is the range at which the enemy gets aggroed towards the player and starts following it.
private Controller controller; // Just setting up references so it will be easier to access them from the rest of the code.
private GameObject playerObject;
private Character playerController;
// Use this for initialization
void Awake ()
{
// Making sure all references are set correctly.
controller = GetComponent();
playerObject = GameObject.FindWithTag("Player");
playerController = playerObject.GetComponent();
}
// Update is called once per frame
void FixedUpdate ()
{
if (controller.alive) // If I'm alive...
{
float distance = Vector2.Distance(playerObject.transform.position, transform.position); // What's my distance to the player?
if (distance <= aggroRange) // I see you bastard!
{
controller.Move(playerObject.transform.position, true); // GO FOR IT! KILL KILL KILL!
}
}
}
}
As you can see, by writing one basic script all others are based off, the rest can be short simple scripts where you can easily and clearly write what you want to happen.
Next up, patrolling AI.
PatrollingAI.cs:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (Controller))]
public class PatrollingAI : MonoBehaviour
{
public float aggroRange = 4f; // This is the range at which the enemy gets aggroed towards the player and starts following it.
private Controller controller; // Just setting up references so it will be easier to access them from the rest of the code.
private GameObject playerObject;
private Character playerController;
[SerializeField] private Transform[] patrolMarkerObjects;
private Vector3[] patrolMarkers;
private bool patrolling = true;
private int currentMarker = 0;
// Use this for initialization
void Awake ()
{
// Making sure all references are set correctly.
controller = GetComponent();
playerObject = GameObject.FindWithTag("Player");
playerController = playerObject.GetComponent();
patrolMarkers = new Vector3[patrolMarkerObjects.Length];
for (int i = 0; i < patrolMarkers.Length; i++)
{
patrolMarkers[i] = patrolMarkerObjects[i].position;
}
}
// Update is called once per frame
void Update ()
{
if (controller.alive)
{
float distance = Vector2.Distance(playerObject.transform.position, transform.position);
if (distance <= aggroRange || patrolMarkerObjects.Length == 0) // If you're aggroed, stop patrolling! Go get him!
{
patrolling = false;
controller.Move(playerObject.transform.position, true);
}
else if (patrolling) // Continue patrolling please.
{
distance = Vector2.Distance(patrolMarkers[currentMarker], transform.position);
Debug.LogFormat("Distance {0}, Current Marker {1}",distance,currentMarker);
if (distance < 0.4f) // Hit your marker!
{
if (currentMarker < patrolMarkers.Length-1) // Is there a next one?
{
currentMarker++; // Go to the next one!
}
else
{
currentMarker = 0; // The first one is next!
}
}
else
{
controller.Move(patrolMarkers[currentMarker], false);
}
}
else if (!patrolling) // Return to the nearest patrol marker.
{
float small = Vector2.Distance(patrolMarkers[0], transform.position);
for (int i = 1; i < patrolMarkers.Length; i++)
{
distance = Vector2.Distance(patrolMarkers[i], transform.position);
if (distance < small)
{
small = distance;
currentMarker = i;
}
}
patrolling = true;
}
}
}
}
The patrolling system works by placing empty game objects as markers to indicate the path the enemy has to patrol along. This system works with looping the path, so whenever the enemy arrives at the last marker, it just continues to the first. Though you can also change that to mirroring the path when the last one is reached. This can be done quite simply by using the following code:
private bool goingBack = false;
if (distance < 0.4f) // Hit your marker!
{
if (!goingBack)
{
if (currentMarker < patrolMarkers.Length-1) // Is there a next one?
{
currentMarker++; // Go to the next one!
}
else
{
currentMarker--; // The last one you went to is next!
goingBack = true;
}
}
else
{
if (currentMarker > 0) // Is there a next one?
{
currentMarker--; // Go to the next one!
}
else
{
currentMarker++;
goingBack = false;
}
}
}
Go ahead and explore all cool possibilities! Good luck!
Geen opmerkingen:
Een reactie posten