Unity ML-Agents: KI-Training in einer "Cat & Mouse" Simulation


In diesem Projekt habe ich mich in die Welt des Reinforcement Learning (bestärkendes Lernen) gewagt. Das Ziel war es, einen KI-Agenten zu trainieren, der in einer geschlossenen Arena überlebt, Ressourcen sammelt und dabei aktiv einem Verfolger ausweicht.

Anstatt dem Agenten explizit zu programmieren, wie er sich bewegen soll (z.B. “wenn Zombie nah, dann lauf weg”), habe ich ihm nur Ziele und Bestrafungen gegeben. Durch Trial-and-Error über 2 Millionen Simulationsschritte hat der Agent selbstständig komplexe Überlebensstrategien entwickelt.

Die Trainingsumgebung in Unity

Die Trainingsumgebung in Unity 2

Das Szenario: Überleben um jeden Preis

Die Simulation besteht aus drei Hauptakteuren in einer geschlossenen Arena:

  1. Der Agent (Pink): Unser KI-Protagonist. Er kann sich bewegen und hat eine spezielle “Dash”-Fähigkeit (schneller Sprint), die jedoch einen Cooldown hat.
  2. Der Zombie (Grün): Ein einfacher Bot, der den Agenten ständig verfolgt. Eine Berührung bedeutet das sofortige Ende der Episode (-10 Punkte).
  3. Das Pellet (Blau): Das Zielobjekt. Das Einsammeln bringt Punkte (+5) und lässt das Pellet an einer neuen zufälligen Position respawnen.

Zusätzlich sind die Wände tödlich. Der Agent musste also lernen, nicht nur dem Zombie auszuweichen, sondern sich auch nicht in die Enge treiben zu lassen.

Der technische Ansatz

Das Projekt basiert auf Unity und dem ML-Agents Toolkit. Als Trainingsalgorithmus kam PPO (Proximal Policy Optimization) zum Einsatz, ein Standardverfahren für solche kontinuierlichen Kontrollprobleme.

1. Die Wahrnehmung (Sensoren)

Damit der Agent seine Umwelt “sehen” kann, habe ich ihn mit Ray Perception Sensors 3D ausgestattet. Man kann sich das wie ein LIDAR-System vorstellen: Der Agent sendet unsichtbare Strahlen in einem 360-Grad-Radius aus. Diese Strahlen liefern ihm Informationen darüber, ob sich in einer bestimmten Richtung eine Wand, ein Pellet oder der Zombie befindet und wie weit diese entfernt sind.

Zusätzlich erhält das neuronale Netz numerische Daten (Vektor-Beobachtungen):

  • Die eigene Position und Geschwindigkeit.
  • Die relative Position zum Ziel.
  • Der Status des “Dash”-Cooldowns (Bereit oder nicht).

2. Das Belohnungssystem (Reward Function)

Der kritischste Teil beim Reinforcement Learning ist das Design der Belohnungen. Mein finales System sah so aus:

  • Existenz: +0.001 pro Schritt (Belohnt reines Überleben).
  • Pellet: +5.0 (Das Hauptziel).
  • Tod (Wand/Zombie): -10.0 (Katastrophales Versagen).
  • Notfall-Dash: +0.2 (Wenn der Zombie sehr nah ist und der Agent dasht, wird dies bestärkt).

Anfangs war die Bestrafung für Wände zu niedrig (-1). Das führte dazu, dass der Agent lieber gegen die Wand rannte (schneller Tod), als sich der Stresssituation mit dem Zombie zu stellen. Nach der Erhöhung auf -10 lernte er, Wände genauso zu fürchten wie den Zombie.

3. Das Training

Das Training lief über 2 Millionen Schritte (Steps).

  • Phase 1 (0 - 200k Schritte): Der Agent bewegt sich kaum oder rennt zufällig gegen Wände. Der durchschnittliche Reward war negativ (-9.0).
  • Phase 2 (500k Schritte): Der Agent versteht, dass Pellets gut sind, wird aber oft noch gefangen. Reward wird positiv (+17.0).
  • Phase 3 (1.5M+ Schritte): “Superhuman” Performance. Der Agent nutzt den Dash taktisch, um dem Zombie im letzten Moment auszuweichen, und “kited” ihn effizient, um an Pellets zu kommen. Der durchschnittliche Reward stieg auf über 70.0 (entspricht ca. 14 eingesammelten Pellets pro Leben ohne zu sterben).

Das Ergebnis

Das folgende Video zeigt den fertig trainierten Agenten (mit dem ONNX-Modell) in Aktion. Man beachte, wie er wartet, bis der Zombie nahe ist, um dann den Dash zu nutzen und durch die Lücke zu entkommen.

Verwendete Technologien

  • Engine: Unity 2022 LTS
  • ML-Framework: Unity ML-Agents (PyTorch Backend)
  • Algorithmus: PPO (Proximal Policy Optimization)
  • Sprache: C# (für die Spiel-Logik und Agenten-Steuerung)
  • Inferenz: Barracuda / ONNX Runtime

Code-Einblick

Hier ein Auszug aus der AgentController.cs, der zeigt, wie der Agent Entscheidungen (Aktionen) in Bewegung umsetzt und Belohnungen verteilt:

public override void OnActionReceived(ActionBuffers actions)
{
    // 1. Dash-Logik (Diskrete Aktion)
    int dashAction = actions.DiscreteActions[0];
    if (dashAction == 1 && m_DashCooldownTimer <= 0)
    {
        m_IsDashing = true;
        // Belohnung für taktischen Dash in Gefahrensituationen
        if (zombie != null && Vector3.Distance(transform.localPosition, zombie.localPosition) < 2.5f)
        {
            AddReward(0.2f);
        }
    }

    // 2. Bewegung (Kontinuierliche Aktionen)
    float moveX = actions.ContinuousActions[0];
    float moveZ = actions.ContinuousActions[1];
    
    // Physik-basierte Bewegung
    if (m_Rb != null)
    {
        Vector3 velocity = new Vector3(moveX * currentSpeed, 0, moveZ * currentSpeed);
        m_Rb.linearVelocity = velocity;
    }

    // 3. Kollisions-Handling (Bestrafung)
    // Wird in OnCollisionEnter verarbeitet:
    // Wand -> AddReward(-10f); EndEpisode();
    // Zombie -> AddReward(-10f); EndEpisode();
}