Source file unity-csharp/arcade-classics/robotron-lite/Enemy.cs from the GDnD code examples. Download raw file

// GDnD wiki example
// Demonstrates: Reynolds-style steering — seek the player plus separation from neighbours
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[steering-behaviours]]
 
using UnityEngine;
 
// The enemy hunts the player with two classic steering behaviours added together:
// SEEK (accelerate toward the target) and SEPARATION (push away from nearby
// enemies so the swarm spreads instead of stacking). This is the same vector
// approach as the Asteroids ship, now used for AI — see [[steering-behaviours]].
public class Enemy : MonoBehaviour
{
    [SerializeField] private float maxSpeed = 3.5f;
    [SerializeField] private float maxForce = 8f;
    [SerializeField] private float separationRadius = 0.7f;
    [SerializeField] private float separationWeight = 1.5f;
    [Tooltip("The enemy layer, used to find neighbours for separation.")]
    [SerializeField] private LayerMask enemyMask;
 
    private RobotronGame game;
    private Transform target;
    private Vector2 velocity;
 
    public void Init(RobotronGame owner, Transform player)
    {
        game = owner;
        target = player;
    }
 
    private void Update()
    {
        if (target == null)
        {
            return;
        }
 
        Vector2 steer = Seek((Vector2)target.position) + Separation() * separationWeight;
        velocity = Vector2.ClampMagnitude(velocity + steer * Time.deltaTime, maxSpeed);
        transform.position += (Vector3)(velocity * Time.deltaTime);
    }
 
    // Steer toward a target: desired velocity minus current velocity, force-limited.
    private Vector2 Seek(Vector2 targetPosition)
    {
        Vector2 desired = (targetPosition - (Vector2)transform.position).normalized * maxSpeed;
        return Vector2.ClampMagnitude(desired - velocity, maxForce);
    }
 
    // Steer away from the average direction of nearby enemies.
    private Vector2 Separation()
    {
        Collider2D[] neighbours = Physics2D.OverlapCircleAll(transform.position, separationRadius, enemyMask);
        Vector2 sum = Vector2.zero;
        int count = 0;
 
        foreach (Collider2D neighbour in neighbours)
        {
            if (neighbour.gameObject == gameObject)
            {
                continue;
            }
 
            Vector2 away = (Vector2)(transform.position - neighbour.transform.position);
            float distance = away.magnitude;
            if (distance > 0f)
            {
                sum += away.normalized / distance; // closer neighbours push harder
                count++;
            }
        }
 
        if (count == 0)
        {
            return Vector2.zero;
        }
 
        Vector2 desired = (sum / count).normalized * maxSpeed;
        return Vector2.ClampMagnitude(desired - velocity, maxForce);
    }
 
    public void Hit()
    {
        game?.OnEnemyKilled(this);
    }
}