Menu
CoddyTech

Gegner-KI

Teil des Abschnitts Spielarchitektur in Unity der Unity-Journey von Coddy. Lektion 31 von 50.

Das Gehirn eines Gegners ist eine Zustandsmaschine mit einem Zustand pro Verhalten. Dieses Skelett läuft zwischen seinen Posten hin und her, verfolgt einen Spieler, der sich bis auf 6 Einheiten nähert, und schwingt einmal pro Sekunde mit seinem Schwert, wenn es nahe genug ist. Jeder Zustand entscheidet selbst, wie er verlassen wird, und das Skelett behält die Hilfsfunktionen, die sie gemeinsam nutzen:

public class Skeleton : MonoBehaviour
{
    public Transform player;
    public Transform[] posts;
    public float speed = 2f;
    public StateMachine machine = new StateMachine();
    public IState patrol, chase, attack;

    void Start()
    {
        patrol = new PatrolState(this);
        chase = new ChaseState(this);
        attack = new AttackState(this);
        machine.ChangeState(patrol);
    }

    void Update() { machine.Tick(); }

    public float PlayerDistance() { return Vector3.Distance(transform.position, player.position); }

    public void WalkTo(Vector3 target)
    {
        target.y = transform.position.y;
        transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
    }
}

public class PatrolState : IState
{
    private Skeleton skel;
    private int next;
    public PatrolState(Skeleton skel) { this.skel = skel; }
    public void Enter() { }
    public void Exit() { }

    public void Tick()
    {
        if (skel.PlayerDistance() < 6f) { skel.machine.ChangeState(skel.chase); return; }
        Vector3 post = skel.posts[next].position;
        post.y = skel.transform.position.y;
        skel.WalkTo(post);
        if (skel.transform.position == post) next = (next + 1) % skel.posts.Length;
    }
}

public class ChaseState : IState
{
    private Skeleton skel;
    public ChaseState(Skeleton skel) { this.skel = skel; }
    public void Enter() { }
    public void Exit() { }

    public void Tick()
    {
        float d = skel.PlayerDistance();
        if (d > 6f) { skel.machine.ChangeState(skel.patrol); return; }
        if (d < 1.5f) { skel.machine.ChangeState(skel.attack); return; }
        skel.WalkTo(skel.player.position);
    }
}

public class AttackState : IState
{
    private Skeleton skel;
    private float cooldown;
    public AttackState(Skeleton skel) { this.skel = skel; }
    public void Enter() { cooldown = 0f; }
    public void Exit() { }

    public void Tick()
    {
        if (skel.PlayerDistance() > 1.5f) { skel.machine.ChangeState(skel.chase); return; }
        cooldown -= Time.deltaTime;
        if (cooldown <= 0f) { Debug.Log("Swing!"); cooldown = 1f; }
    }
}

Die Zustände werden einmal erstellt und bleiben so lange bestehen wie das Skelett, daher bleiben ihre Felder zwischen den Besuchen erhalten. next von PatrolState ist auch nach einer Verfolgung noch gesetzt, und das Skelett läuft weiter zu dem Posten, auf den es gerade zusteuert. Enter setzt nur das zurück, was neu beginnen soll: AttackState setzt cooldown auf 0, sodass der erste Schwerthieb erfolgt, sobald das Skelett den Spieler erreicht.

Die gesamte Bewegung läuft über WalkTo, hier MoveTowards auf dem Transform, wodurch das Skelett durch Wände läuft. Um das Skelett stattdessen mit einem Rigidbody zu bewegen, setzt WalkTo die Geschwindigkeit des Körpers, und das Skelett aktualisiert die Zustandsmaschine in FixedUpdate, wo Time.deltaTime dem Physikschritt entspricht. Die drei Zustände ändern sich nicht: Sie geben an, wohin es gehen soll, der Besitzer gibt an, wie.

challenge icon

Aufgabe

Mittel

Ein Ritter bewacht den Burghof. Die Pfeile bewegen den Player, dessen Health über TakeDamage(int amount) verfügt (beides ist bereits eingerichtet). Knight.cs enthält die Felder, die im Inspector ausgefüllt werden (waypoints W0 bis W2, player, playerHealth, speed 3 und die unten aufgeführten Reichweiten), sowie Hilfsmethoden, die sein Rigidbody verwenden: DistanceTo(point) und PlayerDistance() messen die Entfernung über den Boden, WalkTo(target) bewegt sich auf einen Punkt zu, Stop() bleibt stehen. Gib ihm ein Gehirn:

  • Erstelle in Knight.cs die Zustandsmaschine und die drei Zustände (Felder patrol, chase und attack), beginne mit der Patrouille und rufe in FixedUpdate bei jedem Tick die Zustandsmaschine auf.
  • Schreibe in KnightStates.cs die Klassen PatrolState, ChaseState und AttackState, die jeweils den Ritter als Argument im Konstruktor entgegennehmen.
  • Patrouille: Gehe zu waypoints[next]; sobald du weniger als 0.3 davon entfernt bist, gehe zum nächsten Punkt und nach dem letzten zurück zum ersten. Ist der Spieler näher als sightRange (5), wird er verfolgt. Nach einer Verfolgung setzt die Patrouille den Weg zum Wegpunkt fort, auf den sie zuging.
  • Verfolgung: Gehe zum Spieler. Bei weniger als attackRange (1.5): angreifen. Bei mehr als loseRange (8): patrouillieren. Zwischen 5 und 8 wird die Verfolgung fortgesetzt, damit der Ritter am Rand seiner Sicht nicht zwischen den beiden Zuständen hin und her wechselt.
  • Angriff: Bleib stehen und füge dem Spieler sofort 1 Schaden zu, dann jede Sekunde, solange er sich innerhalb von attackRange befindet; ist er weiter entfernt, nimm die Verfolgung wieder auf.

Drücke Play, lass den Ritter dich entdecken, einen Treffer landen und renne dann weg. Drücke anschließend Check.

Probier es selbst

using UnityEngine;

// PatrolState, ChaseState und AttackState
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Spielarchitektur in Unity