🐦 Sorti du nid 2026 : un colibri qui apprend à butiner

Série rouge - l'agent entraîné. Le laboratoire Unity ML-Agents Hummingbirds, étape par étape, pour Unity 6000.3 LTS et ML-Agents Release 23.

Guide 2026 - Unity 6 LTS et ML-Agents Release 23

0Ce qu'on va faire, et pourquoi ça compte

Un colibri doit apprendre à voler jusqu'aux fleurs et à y plonger son bec pour boire le nectar. Personne ne lui écrit la trajectoire : on lui donne des observations (où est la fleur la plus proche, comment il est orienté), des actions possibles (pousser dans une direction, tourner) et des récompenses (un peu de nectar, c'est bien ; cogner la limite de l'île, c'est mal). L'algorithme PPO de ML-Agents fait le reste en quelques millions de pas : c'est l'apprentissage par renforcement, et le résultat se voit dans une scène 3D (démo du résultat).

Ce guide suit la formation ML-Agents Hummingbirds d'Unity Learn (2020, Unity 2019.3) : il reprend ses étapes dans l'ordre, avec le code porté sur Unity 6000.3 LTS et ML-Agents Release 23 (paquet 4.0.3, Python 1.1.0). Les endroits qui ont changé depuis la formation portent la pastille 2026. Les vidéos restent une bonne façon d'entendre les explications, mais rien dans ce guide n'exige de les regarder.

Deux défis avant de coder (prenez une feuille)

La formation d'origine commence par deux questions, et c'est la meilleure façon d'entrer dans le sujet. Écrivez vos réponses avant de lire la section 6 ; vous les comparerez ensuite à celles du cours, et rien ne vous empêchera, le labo fini, de tester les vôtres.

  1. Les observations. Sans caméra (trop lourd à entraîner), que donneriez-vous au colibri pour qu'il trouve une fleur et y plonge le bec ? Des positions ? Des rotations ? Des vitesses ? Des rayons (une ligne lancée depuis l'oiseau qui dit ce qu'elle touche) ? Combien de nombres au total ?
  2. Les récompenses. Quand faut-il récompenser l'oiseau, de combien, et quand faut-il le punir ? Une récompense trop généreuse apprend des comportements bêtes ; une punition trop forte apprend à ne plus bouger.

Les trois scripts, dans l'ordre

  1. Flower.cs (une fleur) : sait si elle a du nectar, en donne, se réinitialise.
  2. FlowerArea.cs (l'île) : retrouve toutes ses fleurs, les réinitialise, répond « quelle fleur appartient à ce collider ? ».
  3. HummingbirdAgent.cs (le cerveau et le corps de l'oiseau) : observe, agit, reçoit ses récompenses.

Ce que vous remettez

Les trois fichiers .cs versionnés en plusieurs commits (entre 2 et 5 par script), le cerveau entraîné Hummingbird.onnx, et des captures d'écran de vos courbes TensorBoard : voir la section Remise.

1Installer les outils 2026

Unity 6000.3 LTS

  1. Installer Unity Hub et créer un compte Unity (licence Personal, gratuite).
  2. Dans le Hub, onglet Installs > Install Editor : prendre la ligne marquée LTS, soit Unity 6.3 LTS (6000.3.x), et non la ligne marquée « Recommended » (Unity 6.6, 6000.6.x) : le Hub recommande maintenant une version plus récente, mais ce guide, le code et l'entraînement ont été vérifiés avec 6000.3. Aucun module supplémentaire n'est nécessaire pour ce labo.

Python 3.10.12 et ML-Agents

ML-Agents Release 23 exige Python 3.10 (de 3.10.1 à 3.10.12) : ni 3.11, ni 3.12. Installer cette version depuis python.org (Windows : cocher « Add python.exe to PATH »), puis créer un environnement virtuel dédié et y installer mlagents.

terminal créer l'environnement et installer ML-Agents
# Windows (PowerShell) : remplacer python3.10 par py -3.10
python3.10 -m venv venv-colibri
# Linux / macOS
source venv-colibri/bin/activate
# Windows
venv-colibri\Scripts\activate

# torch en version processeur (aucune carte graphique nécessaire : l'entraînement du colibri tient sur un CPU)
pip install "torch~=2.2.1" --index-url https://download.pytorch.org/whl/cpu
pip install "mlagents==1.1.0" "setuptools<81"

# vérification : l'aide doit s'afficher
mlagents-learn --help

Piège : setuptools 81 et plus a retiré le module pkg_resources dont mlagents 1.1.0 a encore besoin. Sans "setuptools<81", mlagents-learn s'arrête sur ModuleNotFoundError: No module named 'pkg_resources'.

Attendu : mlagents-learn --help affiche usage: mlagents-learn [-h] [--env ENV_PATH] .... Gardez ce terminal : c'est de lui qu'on lancera l'entraînement.

2Créer le projet et importer la scène

  1. Dans le Hub, New project, modèle Universal 3D (URP), nom Hummingbirds. Les matériaux de la scène sont déjà des matériaux URP : aucune conversion.
Le Hub, New project : choisir le modèle Universal 3D (capture prise avec 2022.3 ; la liste est la même avec 6000.3).
Le Hub, New project : choisir le modèle Universal 3D (capture prise avec 2022.3 ; la liste est la même avec 6000.3).
  1. Télécharger HummingbirdScene_1.0.zip (copie de secours : sur ce site), le dézipper : il contient HummingbirdScene_1.0.unitypackage.
  2. Dans Unity : Assets > Import Package > Custom Package..., choisir le .unitypackage, Import (tout coché). Le dossier Assets/Hummingbird apparaît : Materials, Meshes, Prefabs, Scenes, Textures.
La fenêtre Import Unity Package : tout est coché, Import.
La fenêtre Import Unity Package : tout est coché, Import.
  1. Faire le ménage du modèle : supprimer Assets/TutorialInfo, Assets/ExampleAssets, Assets/Scenes et Assets/Readme (clic droit > Delete). Ils ne servent pas au labo.
  2. Ouvrir la scène Assets/Hummingbird/Scenes/Training (double-clic).

Deux réglages du projet

OùQuoiPourquoi
Edit > Project Settings > Physics > Default Contact Offset0.001 L'oiseau est minuscule : avec le décalage par défaut (1 cm), son bec « touche » le nectar bien avant d'y être.
Main Camera (dans la scène) > Clipping Planes > Near0.02 Pour voir les fleurs de près sans qu'elles disparaissent.

La formation faisait aussi générer l'éclairage (Window > Rendering > Lighting > Generate Lighting) avec le matériau Skybox_Mat en ciel : c'est esthétique, pas nécessaire à l'entraînement. Si la scène est sombre, faites-le.

3Installer le paquet ML-Agents dans Unity 2026

  1. Window > Package Manager, menu déroulant en haut à gauche : Unity Registry.
Window > Package Manager.
Window > Package Manager.
  1. Chercher ML Agents, choisir la version 4.0.3 (ou la plus récente 4.x), Install. Le paquet installe lui-même Inference Engine, le moteur qui fera tourner le cerveau entraîné dans Unity.
  2. Si la version 4.x n'apparaît pas : roue dentée du Package Manager > Advanced Project Settings > cocher Enable Pre-release Packages, puis rafraîchir.

Depuis la formation de 2020 : plus de paquet com.unity.ml-agents.extensions (fusionné dans le paquet principal), plus de Barracuda ni de Sentis (remplacés par Inference Engine), et le cerveau est un fichier .onnx, plus .nn.

Créer les trois scripts

Dans Assets/Hummingbird, créer un dossier Scripts, puis trois scripts C# vides (clic droit > Create > C# Script) nommés exactement Flower, FlowerArea et HummingbirdAgent. Les sections 4 à 6 donnent leur contenu, morceau par morceau : lisez chaque morceau avant de le coller, c'est là qu'on comprend l'agent.

Commits : un commit après chaque morceau collé et compilé (entre 2 et 5 par script). C'est ce qui est évalué, pas la vitesse.

4Flower.cs : une fleur qui donne du nectar

Une fleur connaît deux colliders : FlowerNectarCollider (un déclencheur, le bec y entre) et FlowerCollider (solide, l'oiseau rebondit dessus). Elle a une quantité de nectar, une couleur pleine et une couleur vide, et deux services : Feed() et ResetFlower().

Flower.cs les variables, les accesseurs et la position du nectar
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Manages a single flower with nectar
/// </summary>
public class Flower : MonoBehaviour
{
    [Tooltip("The color when the flower is full")]
    public Color fullFlowerColor = new Color(1f, 0f, .3f);

    [Tooltip("The color when the flower is empty")]
    public Color emptyFlowerColor = new Color(.5f, 0f, 1f);

    /// <summary>
    /// The trigger collider representing the nectar
    /// </summary>
    [HideInInspector]
    public Collider nectarCollider;

    // The solid collider representing the flower petals
    private Collider flowerCollider;

    // The flower's material
    private Material flowerMaterial;

    /// <summary>
    /// A vector pointing straight out of the flower
    /// </summary>
    public Vector3 FlowerUpVector
    {
        get
        {
            return nectarCollider.transform.up;
        }
    }

    /// <summary>
    /// The center position of the nectar collider
    /// </summary>
    public Vector3 FlowerCenterPosition
    {
        get
        {
            return nectarCollider.transform.position;
        }
    }

    /// <summary>
    /// The amount of nectar remaining in the flower
    /// </summary>
    public float NectarAmount { get; private set; }

    /// <summary>
    /// Whether the flower has any nectar remaining
    /// </summary>
    public bool HasNectar
    {
        get
        {
            return NectarAmount > 0f;
        }
    }

Le vecteur FlowerUpVector (la direction vers laquelle la fleur pointe) et FlowerCenterPosition serviront à l'agent pour viser.

Flower.cs Feed() : donner du nectar, et changer de couleur quand la fleur est vide
    /// <summary>
    /// Attempts to remove nectar from the flower
    /// </summary>
    /// <param name="amount">The amount of nectar to remove</param>
    /// <returns>The actual amount successfully removed</returns>
    public float Feed(float amount)
    {
        // Track how much nectar was successfully taken (cannot take more than is available)
        float nectarTaken = Mathf.Clamp(amount, 0f, NectarAmount);

        // Subtract the nectar
        NectarAmount -= amount;

        if (NectarAmount <= 0)
        {
            // No nectar remaining
            NectarAmount = 0;

            // Disable the flower and nectar colliders
            flowerCollider.gameObject.SetActive(false);
            nectarCollider.gameObject.SetActive(false);

            // Change the flower color to indicate that it is empty
            flowerMaterial.SetColor("_BaseColor", emptyFlowerColor);
        }

        // Return the amount of nectar that was taken
        return nectarTaken;
    }
Flower.cs ResetFlower() : la fleur se remplit et reprend sa couleur
    /// <summary>
    /// Resets the flower
    /// </summary>
    public void ResetFlower()
    {
        // Refill the nectar
        NectarAmount = 1f;

        // Enable the flower and nectar colliders
        flowerCollider.gameObject.SetActive(true);
        nectarCollider.gameObject.SetActive(true);

        // Change the flower color to indicate that it is full
        flowerMaterial.SetColor("_BaseColor", fullFlowerColor);
    }
Flower.cs Awake() : retrouver le matériau et les deux colliders par leur nom
    /// <summary>
    /// Called when the flower wakes up
    /// </summary>
    private void Awake()
    {
        // Find the flower's mesh renderer and get the main material
        MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
        flowerMaterial = meshRenderer.material;

        // Find flower and nectar colliders
        flowerCollider = transform.Find("FlowerCollider").GetComponent<Collider>();
        nectarCollider = transform.Find("FlowerNectarCollider").GetComponent<Collider>();
    }
}

Attendu : le projet compile (pas de ligne rouge dans la console). Commit.

5FlowerArea.cs : l'île et ses fleurs

Une île contient des plantes, qui contiennent des fleurs. Le script de l'île dresse la liste de toutes les fleurs au démarrage, en descendant récursivement la hiérarchie et en reconnaissant les plantes au tag flower_plant. Il garde aussi un dictionnaire collider de nectar → fleur, pour que l'agent retrouve instantanément la fleur qu'il butine.

FlowerArea.cs les variables : le diamètre de l'île, les plantes, le dictionnaire, la liste des fleurs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Manages a collection of flower plants and attached flowers
/// </summary>
public class FlowerArea : MonoBehaviour
{
    // The diameter of the area where the agent and flowers can be
    // used for observing relative distance from agent to flower
    public const float AreaDiameter = 20f;

    // The list of all flower plants in this flower area (flower plants have multiple flowers)
    private List<GameObject> flowerPlants;

    // A lookup dictionary for looking up a flower from a nectar collider
    private Dictionary<Collider, Flower> nectarFlowerDictionary;

    /// <summary>
    /// The list of all flowers in the flower area
    /// </summary>
    public List<Flower> Flowers { get; private set; }
FlowerArea.cs ResetFlowers() : chaque plante tourne au hasard, chaque fleur se remplit
    /// <summary>
    /// Reset the flowers and flower plants
    /// </summary>
    public void ResetFlowers()
    {
        // Rotate each flower plant around the Y axis and subtly around X and Z
        foreach (GameObject flowerPlant in flowerPlants)
        {
            float xRotation = UnityEngine.Random.Range(-5f, 5f);
            float yRotation = UnityEngine.Random.Range(-180f, 180f);
            float zRotation = UnityEngine.Random.Range(-5f, 5f);
            flowerPlant.transform.localRotation = Quaternion.Euler(xRotation, yRotation, zRotation);
        }

        // Reset each flower
        foreach (Flower flower in Flowers)
        {
            flower.ResetFlower();
        }
    }
FlowerArea.cs GetFlowerFromNectar() : de quel fleur vient ce collider ?
    /// <summary>
    /// Gets the <see cref="Flower"/> that a nectar collider belongs to
    /// </summary>
    /// <param name="collider">The nectar collider</param>
    /// <returns>The matching flower</returns>
    public Flower GetFlowerFromNectar(Collider collider)
    {
        return nectarFlowerDictionary[collider];
    }
FlowerArea.cs Awake(), Start() et FindChildFlowers() : la recherche récursive des fleurs
    /// <summary>
    /// Called when the area wakes up
    /// </summary>
    private void Awake()
    {
        // Initialize variables
        flowerPlants = new List<GameObject>();
        nectarFlowerDictionary = new Dictionary<Collider, Flower>();
        Flowers = new List<Flower>();
    }

    private void Start()
    {
        // Find all flowers that are children of this GameObject/Transform
        FindChildFlowers(transform);
    }

    /// <summary>
    /// Recursively finds all flowers and flower plants that are children of a parent transform
    /// </summary>
    /// <param name="parent">The parent of the children to check</param>
    private void FindChildFlowers(Transform parent)
    {
        for (int i = 0; i < parent.childCount; i++)
        {
            Transform child = parent.GetChild(i);

            if (child.CompareTag("flower_plant"))
            {
                // Found a flower plant, add it to the flowerPlants list
                flowerPlants.Add(child.gameObject);

                // Look for flowers within the flower plant
                FindChildFlowers(child);
            }
            else
            {
                // Not a flower plant, look for a Flower component
                Flower flower = child.GetComponent<Flower>();
                if (flower != null)
                {
                    // Found a flower, add it to the Flowers list
                    Flowers.Add(flower);

                    // Add the nectar collider to the lookup dictionary
                    nectarFlowerDictionary.Add(flower.nectarCollider, flower);

                    // Note: there are no flowers that are children of other flowers
                }
                else
                {
                    // Flower component not found, so check children
                    FindChildFlowers(child);
                }
            }
        }
    }
}

Attendu : compile. Commit.

6HummingbirdAgent.cs : l'agent 2026

La classe hérite d'Agent (ML-Agents). Elle redéfinit cinq méthodes que ML-Agents appelle au bon moment : Initialize(), OnEpisodeBegin(), CollectObservations(), OnActionReceived() et Heuristic(). Tout le reste (position de départ, fleur la plus proche, collisions) est de la mécanique de jeu ordinaire.

Règle d'or en redéfinissant une méthode : ne copiez jamais la signature d'un tutoriel, copiez celle de la classe parente (clic sur Agent dans votre éditeur). En 2020, OnActionReceived recevait un float[] ; depuis la version 2.0 du paquet, elle reçoit un ActionBuffers, et Heuristic aussi. Le code ci-dessous est déjà à jour.

HummingbirdAgent.cs les variables : forces, vitesses, bec, caméra, mode d'entraînement
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
using UnityEngine;

/// <summary>
/// A hummingbird Machine Learning Agent
/// </summary>
public class HummingbirdAgent : Agent
{
    [Tooltip("Force to apply when moving")]
    public float moveForce = 2f;

    [Tooltip("Speed to pitch up or down")]
    public float pitchSpeed = 100f;

    [Tooltip("Speed to rotate around the up axis")]
    public float yawSpeed = 100f;

    [Tooltip("Transform at the tip of the beak")]
    public Transform beakTip;

    [Tooltip("The agent's camera")]
    public Camera agentCamera;

    [Tooltip("Whether this is training mode or gameplay mode")]
    public bool trainingMode;

    // The rigidbody of the agent
    new private Rigidbody rigidbody;

    // The flower area that the agent is in
    private FlowerArea flowerArea;

    // The nearest flower to the agent
    private Flower nearestFlower;

    // Allows for smoother pitch changes
    private float smoothPitchChange = 0f;

    // Allows for smoother yaw changes
    private float smoothYawChange = 0f;

    // Maximum angle that the bird can pitch up or down
    private const float MaxPitchAngle = 80f;

    // Maximum distance from the beak tip to accept nectar collision
    private const float BeakTipRadius = 0.008f;

    // Whether the agent is frozen (intentionally not flying)
    private bool frozen = false;

    /// <summary>
    /// The amount of nectar the agent has obtained this episode
    /// </summary>
    public float NectarObtained { get; private set; }

Naissance et début d'épisode

HummingbirdAgent.cs Initialize() et OnEpisodeBegin() : réinitialiser, puis placer l'oiseau au hasard (une fois sur deux devant une fleur)
    /// <summary>
    /// Initialize the agent
    /// </summary>
    public override void Initialize()
    {
        rigidbody = GetComponent<Rigidbody>();
        flowerArea = GetComponentInParent<FlowerArea>();

        // If not training mode, no max step, play forever
        if (!trainingMode) MaxStep = 0;
    }

    /// <summary>
    /// Reset the agent when an episode begins
    /// </summary>
    public override void OnEpisodeBegin()
    {
        if (trainingMode)
        {
            // Only reset flowers in training when there is one agent per area
            flowerArea.ResetFlowers();
        }

        // Reset nectar obtained
        NectarObtained = 0f;

        // Zero out velocities so that movement stops before a new episode begins
        rigidbody.linearVelocity = Vector3.zero; // Unity 6 : velocity est devenu linearVelocity
        rigidbody.angularVelocity = Vector3.zero;

        // Default to spawning in front of a flower
        bool inFrontOfFlower = true;
        if (trainingMode)
        {
            // Spawn in front of flower 50% of the time during training
            inFrontOfFlower = UnityEngine.Random.value > .5f;
        }

        // Move the agent to a new random position
        MoveToSafeRandomPosition(inFrontOfFlower);

        // Recalculate the nearest flower now that the agent has moved
        UpdateNearestFlower();
    }

2026 Unity 6 : rigidbody.velocity s'appelle maintenant rigidbody.linearVelocity.

Les cinq actions

À chaque décision, le réseau de neurones (ou le clavier) produit cinq nombres entre -1 et 1 : trois composantes de poussée (x, y, z), un tangage (pitch) et un lacet (yaw).

HummingbirdAgent.cs OnActionReceived(ActionBuffers) : pousser, puis tourner en douceur
    /// <summary>
    /// Called when and action is received from either the player input or the neural network
    ///
    /// vectorAction[i] represents:
    /// Index 0: move vector x (+1 = right, -1 = left)
    /// Index 1: move vector y (+1 = up, -1 = down)
    /// Index 2: move vector z (+1 = forward, -1 = backward)
    /// Index 3: pitch angle (+1 = pitch up, -1 = pitch down)
    /// Index 4: yaw angle (+1 = turn right, -1 = turn left)
    /// </summary>
    /// <param name="actions">The actions to take</param>
    public override void OnActionReceived(ActionBuffers actions)
    {
        // Don't take actions if frozen
        if (frozen) return;

        // Extract continuous actions
        var vectorAction = actions.ContinuousActions;

        // Calculate movement vector
        Vector3 move = new Vector3(vectorAction[0], vectorAction[1], vectorAction[2]);

        // Add force in the direction of the move vector
        rigidbody.AddForce(move * moveForce);

        // Get the current rotation
        Vector3 rotationVector = transform.rotation.eulerAngles;

        // Calculate pitch and yaw rotation
        float pitchChange = vectorAction[3];
        float yawChange = vectorAction[4];

        // Calculate smooth rotation changes
        smoothPitchChange = Mathf.MoveTowards(smoothPitchChange, pitchChange, 2f * Time.fixedDeltaTime);
        smoothYawChange = Mathf.MoveTowards(smoothYawChange, yawChange, 2f * Time.fixedDeltaTime);

        // Calculate new pitch and yaw based on smoothed values
        // Clamp  pitch to avoid flipping upside down
        float pitch = rotationVector.x + smoothPitchChange * Time.fixedDeltaTime * pitchSpeed;
        if (pitch > 180f) pitch -= 360f;
        pitch = Mathf.Clamp(pitch, -MaxPitchAngle, MaxPitchAngle);

        float yaw = rotationVector.y + smoothYawChange * Time.fixedDeltaTime * yawSpeed;

        // Apply the new rotation
        transform.rotation = Quaternion.Euler(pitch, yaw, 0f);
    }

Les dix observations

Ce que l'agent « voit » à chaque décision : sa rotation (4 nombres, un quaternion), la direction normalisée de son bec vers la fleur la plus proche (3), deux produits scalaires qui disent s'il est devant la fleur et s'il la vise (1 + 1), et la distance relative (1). Dix nombres, c'est tout : ni image, ni position absolue.

HummingbirdAgent.cs CollectObservations(VectorSensor) : 4 + 3 + 1 + 1 + 1 = 10
    /// <summary>
    /// Collect vector observations from the environment
    /// </summary>
    /// <param name="sensor">The vector sensor</param>
    public override void CollectObservations(VectorSensor sensor)
    {
        // If nearestFlower is null, observe an empty array and return early
        if (nearestFlower == null)
        {
            sensor.AddObservation(new float[10]);
            return;
        }
        
        // Observe the agent's local rotation (4 observations)
        sensor.AddObservation(transform.localRotation.normalized);

        // Get a vector from the beak tip to the nearest flower
        Vector3 toFlower = nearestFlower.FlowerCenterPosition - beakTip.position;

        // Observe a normalized vector pointing to the nearest flower (3 observations)
        sensor.AddObservation(toFlower.normalized);

        // Observe a dot product that indicates whether the beak tip is in front of the flower (1 observation)
        // (+1 means that the beak tip is directly in front of the flower, -1 means directly behind)
        sensor.AddObservation(Vector3.Dot(toFlower.normalized, -nearestFlower.FlowerUpVector.normalized));

        // Observe a dot product that indicates whether the beak is pointing toward the flower (1 observation)
        // (+1 means that the beak is pointing directly at the flower, -1 means directly away)
        sensor.AddObservation(Vector3.Dot(beakTip.forward.normalized, -nearestFlower.FlowerUpVector.normalized));

        // Observe the relative distance from the beak tip to the flower (1 observation)
        sensor.AddObservation(toFlower.magnitude / FlowerArea.AreaDiameter);

        // 10 total observations
    }

Le pilotage au clavier

Quand le Behavior Type est « Heuristic Only », ML-Agents appelle Heuristic() au lieu du réseau : c'est vous qui jouez le cerveau, pour vérifier que les actions font ce qu'on croit.

HummingbirdAgent.cs Heuristic(in ActionBuffers) : les touches deviennent des actions entre -1 et 1
    /// <summary>
    /// When Behavior Type is set to "Heuristic Only" on the agent's Behavior Parameters,
    /// this function will be called. Its return values will be fed into
    /// <see cref="OnActionReceived(ActionBuffers)"/> instead of using the neural network
    /// </summary>
    /// <param name="actionsOut">And output action array</param>
    public override void Heuristic(in ActionBuffers actionsOut)
    {
        // Create placeholders for all movement/turning
        Vector3 forward = Vector3.zero;
        Vector3 left = Vector3.zero;
        Vector3 up = Vector3.zero;
        float pitch = 0f;
        float yaw = 0f;

        // Convert keyboard inputs to movement and turning
        // All values should be between -1 and +1
        // Unity 6 (2026) : le projet peut utiliser le nouveau Input System, l'ancien Input Manager, ou les deux.
        // Les directives #if choisissent la bonne API selon le reglage Active Input Handling du projet.
#if ENABLE_INPUT_SYSTEM
        var clavier = UnityEngine.InputSystem.Keyboard.current;
        if (clavier != null)
        {
            // Forward/backward
            if (clavier.wKey.isPressed) forward = transform.forward;
            else if (clavier.sKey.isPressed) forward = -transform.forward;

            // Left/right
            if (clavier.aKey.isPressed) left = -transform.right;
            else if (clavier.dKey.isPressed) left = transform.right;

            // Up/down
            if (clavier.eKey.isPressed) up = transform.up;
            else if (clavier.cKey.isPressed) up = -transform.up;

            // Pitch up/down
            if (clavier.upArrowKey.isPressed) pitch = 1f;
            else if (clavier.downArrowKey.isPressed) pitch = -1f;

            // Turn left/right
            if (clavier.leftArrowKey.isPressed) yaw = -1f;
            else if (clavier.rightArrowKey.isPressed) yaw = 1f;
        }
#else
        // Forward/backward
        if (Input.GetKey(KeyCode.W)) forward = transform.forward;
        else if (Input.GetKey(KeyCode.S)) forward = -transform.forward;

        // Left/right
        if (Input.GetKey(KeyCode.A)) left = -transform.right;
        else if (Input.GetKey(KeyCode.D)) left = transform.right;

        // Up/down
        if (Input.GetKey(KeyCode.E)) up = transform.up;
        else if (Input.GetKey(KeyCode.C)) up = -transform.up;

        // Pitch up/down
        if (Input.GetKey(KeyCode.UpArrow)) pitch = 1f;
        else if (Input.GetKey(KeyCode.DownArrow)) pitch = -1f;

        // Turn left/right
        if (Input.GetKey(KeyCode.LeftArrow)) yaw = -1f;
        else if (Input.GetKey(KeyCode.RightArrow)) yaw = 1f;
#endif

        // Combine the movement vectors and normalize
        Vector3 combined = (forward + left + up).normalized;

        // Add the 3 movement values, pitch, and yaw to the actionsOut array
        var continuousActions = actionsOut.ContinuousActions;
        continuousActions[0] = combined.x;
        continuousActions[1] = combined.y;
        continuousActions[2] = combined.z;
        continuousActions[3] = pitch;
        continuousActions[4] = yaw;
    }

2026 Unity 6 propose deux systèmes d'entrées. Le bloc #if ENABLE_INPUT_SYSTEM lit le clavier avec le nouveau Input System, le bloc #else avec l'ancien Input Manager : le code compile quel que soit le réglage Active Input Handling du projet.

La mécanique : geler, se placer, trouver la fleur la plus proche

HummingbirdAgent.cs FreezeAgent(), UnfreezeAgent() et MoveToSafeRandomPosition() : jusqu'à 100 essais pour une position libre
    /// <summary>
    /// Prevent the agent from moving and taking actions
    /// </summary>
    public void FreezeAgent()
    {
        Debug.Assert(trainingMode == false, "Freeze/Unfreeze not supported in training");
        frozen = true;
        rigidbody.Sleep();
    }

    /// <summary>
    /// Resume agent movement and actions
    /// </summary>
    public void UnfreezeAgent()
    {
        Debug.Assert(trainingMode == false, "Freeze/Unfreeze not supported in training");
        frozen = false;
        rigidbody.WakeUp();
    }

    /// <summary>
    /// Move the agent to a safe random position (i.e. does not collide with anything)
    /// If in front of flower, also point the beak at the flower
    /// </summary>
    /// <param name="inFrontOfFlower">Whether to choose a spot in front of a flower</param>
    private void MoveToSafeRandomPosition(bool inFrontOfFlower)
    {
        bool safePositionFound = false;
        int attemptsRemaining = 100; // Prevent an infinite loop
        Vector3 potentialPosition = Vector3.zero;
        Quaternion potentialRotation = new Quaternion();

        // Loop until a safe position is found or we run out of attempts
        while (!safePositionFound && attemptsRemaining > 0)
        {
            attemptsRemaining--;
            if (inFrontOfFlower)
            {
                // Pick a random flower
                Flower randomFlower = flowerArea.Flowers[UnityEngine.Random.Range(0, flowerArea.Flowers.Count)];

                // Position 10 to 20 cm in front of the flower
                float distanceFromFlower = UnityEngine.Random.Range(.1f, .2f);
                potentialPosition = randomFlower.transform.position + randomFlower.FlowerUpVector * distanceFromFlower;

                // Point beak at flower (bird's head is center of transform)
                Vector3 toFlower = randomFlower.FlowerCenterPosition - potentialPosition;
                potentialRotation = Quaternion.LookRotation(toFlower, Vector3.up);
            }
            else
            {
                // Pick a random height from the ground
                float height = UnityEngine.Random.Range(1.2f, 2.5f);

                // Pick a random radius from the center of the area
                float radius = UnityEngine.Random.Range(2f, 7f);

                // Pick a random direction rotated around the y axis
                Quaternion direction = Quaternion.Euler(0f, UnityEngine.Random.Range(-180f, 180f), 0f);

                // Combine height, radius, and direction to pick a potential position
                potentialPosition = flowerArea.transform.position + Vector3.up * height + direction * Vector3.forward * radius;

                // Choose and set random starting pitch and yaw
                float pitch = UnityEngine.Random.Range(-60f, 60f);
                float yaw = UnityEngine.Random.Range(-180f, 180f);
                potentialRotation = Quaternion.Euler(pitch, yaw, 0f);
            }

            // Check to see if the agent will collide with anything
            Collider[] colliders = Physics.OverlapSphere(potentialPosition, 0.05f);

            // Safe position has been found if no colliders are overlapped
            safePositionFound = colliders.Length == 0;
        }

        Debug.Assert(safePositionFound, "Could not find a safe position to spawn");

        // Set the position and rotation
        transform.position = potentialPosition;
        transform.rotation = potentialRotation;
    }
HummingbirdAgent.cs UpdateNearestFlower() : la fleur pleine la plus proche du bec
    /// <summary>
    /// Update the nearest flower to the agent
    /// </summary>
    private void UpdateNearestFlower()
    {
        foreach (Flower flower in flowerArea.Flowers)
        {
            if (nearestFlower == null && flower.HasNectar)
            {
                // No current nearest flower and this flower has nectar, so set to this flower
                nearestFlower = flower;
            }
            else if (flower.HasNectar)
            {
                // Calculate distance to this flower and distance to the current nearest flower
                float distanceToFlower = Vector3.Distance(flower.transform.position, beakTip.position);
                float distanceToCurrentNearestFlower = Vector3.Distance(nearestFlower.transform.position, beakTip.position);

                // If current nearest flower is empty OR this flower is closer, update the nearest flower
                if (!nearestFlower.HasNectar || distanceToFlower < distanceToCurrentNearestFlower)
                {
                    nearestFlower = flower;
                }
            }
        }
    }

Les récompenses

Deux sources, et c'est tout : du nectar quand le bout du bec est dans le collider de nectar (+0,01 par pas de physique, plus un bonus jusqu'à +0,02 si l'oiseau fait face à la fleur) ; une pénalité de -0,5 quand il cogne la limite de l'île (tag boundary). Tout ce que l'agent apprendra vient de ces trois nombres.

HummingbirdAgent.cs OnTriggerEnter, OnTriggerStay, TriggerEnterOrStay, OnCollisionEnter : nectar et collisions
    /// <summary>
    /// Called when the agent's collider enters a trigger collider
    /// </summary>
    /// <param name="other">The trigger collider</param>
    private void OnTriggerEnter(Collider other)
    {
        TriggerEnterOrStay(other);
    }

    /// <summary>
    /// Called when the agent's collider stays in a trigger collider
    /// </summary>
    /// <param name="other">The trigger collider</param>
    private void OnTriggerStay(Collider other)
    {
        TriggerEnterOrStay(other);
    }

    /// <summary>
    /// Handles when the agen'ts collider enters or stays in a trigger collider
    /// </summary>
    /// <param name="collider">The trigger collider</param>
    private void TriggerEnterOrStay(Collider collider)
    {
        // Check if agent is colliding with nectar
        if (collider.CompareTag("nectar"))
        {
            Vector3 closestPointToBeakTip = collider.ClosestPoint(beakTip.position);

            // Check if the closest collision point is close to the beak tip
            // Note: a collision with anything but the beak tip should not count
            if (Vector3.Distance(beakTip.position, closestPointToBeakTip) < BeakTipRadius)
            {
                // Look up the flower for this nectar collider
                Flower flower = flowerArea.GetFlowerFromNectar(collider);

                // Attempt to take .01 nectar
                // Note: this is per fixed timestep, meaning it happens every .02 seconds, or 50x per second
                float nectarReceived = flower.Feed(.01f);

                // Keep track of nectar obtained
                NectarObtained += nectarReceived;

                if (trainingMode)
                {
                    // Calculate reward for getting nectar
                    float bonus = .02f * Mathf.Clamp01(Vector3.Dot(transform.forward.normalized, -nearestFlower.FlowerUpVector.normalized));
                    AddReward(.01f + bonus);
                }

                // If flower is empty, update the nearest flower
                if (!flower.HasNectar)
                {
                    UpdateNearestFlower();
                }
            }
        }
    }

    /// <summary>
    /// Called when the agent collides with something solid
    /// </summary>
    /// <param name="collision">The collision info</param>
    private void OnCollisionEnter(Collision collision)
    {
        if (trainingMode && collision.collider.CompareTag("boundary"))
        {
            // Collided with the area boundary, give a negative reward
            AddReward(-.5f);
        }
    }
HummingbirdAgent.cs Update() et FixedUpdate() : la ligne verte vers la fleur, et le cas de la fleur vidée
    /// <summary>
    /// Called every frame
    /// </summary>
    private void Update()
    {
        // Draw a line from the beak tip to the nearest flower
        if (nearestFlower != null)
            Debug.DrawLine(beakTip.position, nearestFlower.FlowerCenterPosition, Color.green);
    }

    /// <summary>
    /// Called every .02 seconds
    /// </summary>
    private void FixedUpdate()
    {
        // Avoids scenario where nearest flower nectar is stolen by opponent and not updated
        if (nearestFlower != null && !nearestFlower.HasNectar)
            UpdateNearestFlower();
    }
}

Attendu : compile. Commits (ce script en mérite 3 à 5).

7Configurer la scène

Le code est écrit ; il faut maintenant le brancher sur les objets. Tout se passe dans les prefabs de Assets/Hummingbird/Prefabs (double-clic pour ouvrir un prefab, flèche < en haut de la hiérarchie pour revenir) et dans la scène Training.

Les trois tags

Edit > Project Settings > Tags and Layers > Tags, bouton + : créer flower_plant, nectar et boundary (en minuscules, exactement).

Project Settings > Tags and Layers : les trois tags, tels quels.
Project Settings > Tags and Layers : les trois tags, tels quels.

Les prefabs

Prefab > objetRéglage
FlowerBud > FlowerAdd Component > Flower (notre script)
FlowerBud > Flower > FlowerNectarColliderTag nectar ; Mesh Collider : Convex coché, Is Trigger coché
FlowerPlantTag flower_plant (sur la racine du prefab)
FloatingIslandAdd Component > FlowerArea
FloatingIsland > IslandBoundariesTag boundary
FlowerNectarCollider dans le prefab FlowerBud : tag nectar, Mesh Collider avec Convex et Is Trigger cochés.
FlowerNectarCollider dans le prefab FlowerBud : tag nectar, Mesh Collider avec Convex et Is Trigger cochés.

Le colibri (prefab Hummingbird)

ComposantValeurs
Rigidbody (déjà présent)Use Gravity décoché, Linear Damping 2
Hummingbird Agent (notre script)Max Step 5000 ; Move Force 2, Pitch Speed 100, Yaw Speed 100 ; Beak Tip = glisser l'enfant BeakTip ; Agent Camera = la caméra ci-dessous ; Training Mode coché
Behavior Parameters (ajouté avec le script)Behavior Name Hummingbird ; Vector Observation > Space Size 10, Stacked Vectors 1 ; Actions > Continuous Actions 5, Discrete Branches 0 ; Behavior Type Default ; Use Child Sensors coché
Decision RequesterAdd Component > Decision Requester : Decision Period 5, Take Actions Between Decisions coché
Camera (nouvel enfant)Clic droit sur Hummingbird > Camera : Position (0, 0.02, -0.03), Clipping Planes Near 0.01, supprimer l'Audio Listener, décocher le composant Camera (elle ne sert qu'au mini-jeu)
Clic droit sur Hummingbird > Camera : la caméra de l'agent devient un enfant de l'oiseau.
Clic droit sur Hummingbird > Camera : la caméra de l'agent devient un enfant de l'oiseau.
Le prefab Hummingbird configuré : Behavior Parameters, Hummingbird Agent, Decision Requester (capture 2022.3 : le nom du comportement y est en minuscules, prendre Hummingbird).
Le prefab Hummingbird configuré : Behavior Parameters, Hummingbird Agent, Decision Requester (capture 2022.3 : le nom du comportement y est en minuscules, prendre Hummingbird).

Les trois capteurs à rayons

Les dix observations ne disent rien des obstacles. Trois objets vides portant un Ray Perception Sensor 3D (Add Component) donnent à l'agent des « moustaches » : vers l'avant, vers le haut, vers le bas. Créer RaysForward (clic droit sur Hummingbird > Create Empty), le configurer, puis le dupliquer (Ctrl+D) deux fois en changeant le nom, la rotation et le nombre de rayons.

ObjetPositionRotationSensor NameDetectable TagsRays Per DirectionMax Ray DegreesRay Length
RaysForward(0, 0.005, 0.02)(0, 0, 0)RaysForwardUntagged, boundary36020
RayUp(0, 0.005, 0.02)(-90, 0, 0)RayUpUntagged, boundary0020
RayDown(0, -0.005, 0.02)(90, 0, 0)RayDownUntagged, boundary0020
Unity 6 : RaysForward sélectionné dans la hiérarchie du colibri, le composant Ray Perception Sensor 3D rempli (3 rayons par direction, 60 degrés, longueur 20). Dupliquer ensuite pour RayUp et RayDown.
Unity 6 : RaysForward sélectionné dans la hiérarchie du colibri, le composant Ray Perception Sensor 3D rempli (3 rayons par direction, 60 degrés, longueur 20). Dupliquer ensuite pour RayUp et RayDown.

« Untagged » détecte tout ce qui n'a pas de tag (rochers, buissons, arbres). Avec 3 rayons par direction et 60°, RaysForward lance 7 rayons en éventail ; avec 0, RayUp et RayDown n'en lancent qu'un seul.

La scène

Dans la scène Training, l'île FloatingIsland contient déjà un Hummingbird (déplier l'île dans la hiérarchie pour le voir) : c'est une instance du prefab, elle reçoit automatiquement tout ce que vous avez configuré dans le prefab. S'il manquait, glisser le prefab depuis le dossier Prefabs sur l'île. Sa position de départ n'a pas d'importance, OnEpisodeBegin() le replace.

Unity 6 : la scène Training du projet de ce guide avec ses huit îles, le colibri de la première sélectionné (ses rayons de perception apparaissent en rouge dans la vue Scene).
Unity 6 : la scène Training du projet de ce guide avec ses huit îles, le colibri de la première sélectionné (ses rayons de perception apparaissent en rouge dans la vue Scene).
La scène Training prête : le Hummingbird est enfant de FloatingIsland, avec sa caméra et ses trois capteurs ; Rigidbody sans gravité, Drag 2. Le message « Couldn't connect to trainer on port 5004 » en bas est normal tant qu'aucun entraîneur ne tourne.
La scène Training prête : le Hummingbird est enfant de FloatingIsland, avec sa caméra et ses trois capteurs ; Rigidbody sans gravité, Drag 2. Le message « Couldn't connect to trainer on port 5004 » en bas est normal tant qu'aucun entraîneur ne tourne.

8Tester au clavier avant d'entraîner

  1. Sur le Hummingbird de la scène : Behavior Parameters > Behavior Type = Heuristic Only.
  2. Play. Dans la vue Scene, une ligne verte relie le bec à la fleur la plus proche.
W S avancer, reculer
A D gauche, droite
E C monter, descendre
↑ ↓ piquer du nez, cabrer
← → tourner

Attendu : l'oiseau répond aux touches ; quand le bec entre dans une fleur, la fleur passe du rose au violet (vidée) et la ligne verte saute à la fleur suivante. Si rien ne bouge, relire la section 7 (Beak Tip ? Decision Requester ?).

Remettre ensuite Behavior Type = Default : c'est le réglage qui laisse ML-Agents brancher l'entraîneur Python, ou le cerveau .onnx quand il y en a un.

9Entraîner 2026

Le fichier de configuration

Créer un dossier config à la racine du projet (à côté de Assets, pas dedans) et y mettre Hummingbird.yaml. Le nom de la section (Hummingbird:) doit être exactement le Behavior Name de l'agent.

config/Hummingbird.yaml format Release 23 (behaviors:, hyperparameters:, network_settings:) ; l'ancien trainer_config.yaml de 2020 ne marche plus
behaviors:
  Hummingbird:
    trainer_type: ppo
    hyperparameters:
      batch_size: 2048
      buffer_size: 20480
      learning_rate: 3.0e-4
      beta: 5.0e-3
      epsilon: 0.2
      lambd: 0.95
      num_epoch: 3
      learning_rate_schedule: linear
    network_settings:
      normalize: false
      hidden_units: 256
      num_layers: 2
      vis_encode_type: simple
    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0
    time_horizon: 128
    max_steps: 5000000
    summary_freq: 10000
    checkpoint_interval: 500000
    threaded: true

Lancer

terminal démarrer l'entraîneur, puis appuyer sur Play dans Unity
# dans le terminal où le venv est activé, à la racine du projet Unity
mlagents-learn config/Hummingbird.yaml --run-id=colibri1

# le terminal affiche le logo ML-Agents puis :
# [INFO] Listening on port 5004. Start training by pressing the Play button in the Unity Editor.

Attendu : dès Play, le terminal écrit une ligne toutes les 10 000 pas : Hummingbird. Step: 10000. Time Elapsed: ... Mean Reward: ... Std of Reward: .... La récompense moyenne commence près de 0 (ou négative : l'oiseau cogne les limites) et monte au fil des centaines de milliers de pas. Si vous cliquez Play plus de 60 secondes après le lancement, l'entraîneur abandonne : relancer la commande.

Deux terminaux : à droite mlagents-learn qui attend le Play (« Listening on port 5004 ») et affiche les hyperparamètres du yaml ; à gauche TensorBoard. Capture de 2025 (versions antérieures), le déroulement est identique.
Deux terminaux : à droite mlagents-learn qui attend le Play (« Listening on port 5004 ») et affiche les hyperparamètres du yaml ; à gauche TensorBoard. Capture de 2025 (versions antérieures), le déroulement est identique.

Aller plus vite

Lire les courbes

terminal TensorBoard lit le dossier results/ que mlagents-learn remplit
# dans un deuxième terminal (venv activé), à la racine du projet
tensorboard --logdir results
# puis ouvrir http://localhost:6006
TensorBoard : une récompense cumulée qui part sous zéro et monte vers 35-40 en 2 millions de pas, puis plafonne. C'est à ça que ressemble un colibri qui a appris.
TensorBoard : une récompense cumulée qui part sous zéro et monte vers 35-40 en 2 millions de pas, puis plafonne. C'est à ça que ressemble un colibri qui a appris.
Notre entraînement de vérification du guide, sur un portable à 4 coeurs sans carte graphique (Unity 6, ML-Agents Release 23, 8 îles, time-scale 20) : 2 millions de pas en 62 minutes. La récompense moyenne reste autour de zéro jusqu'à 750 000 pas (l'oiseau apprend d'abord à ne plus cogner les limites), monte franchement entre 0,75 et 1,25 million (16 à 1 million), puis plafonne vers 30 à 35 à partir de 1,5 million : 34,4 au dernier point. Le plateau ne monte plus beaucoup au-delà, 5 millions de pas donnent le même oiseau en plus régulier.
Notre entraînement de vérification du guide, sur un portable à 4 coeurs sans carte graphique (Unity 6, ML-Agents Release 23, 8 îles, time-scale 20) : 2 millions de pas en 62 minutes. La récompense moyenne reste autour de zéro jusqu'à 750 000 pas (l'oiseau apprend d'abord à ne plus cogner les limites), monte franchement entre 0,75 et 1,25 million (16 à 1 million), puis plafonne vers 30 à 35 à partir de 1,5 million : 34,4 au dernier point. Le plateau ne monte plus beaucoup au-delà, 5 millions de pas donnent le même oiseau en plus régulier.

La courbe à regarder : Environment / Cumulative Reward. Elle doit monter, avec des creux. Prenez une capture au début, une au milieu et une à la fin : ce sont les trois captures de la remise. Repères mesurés avec ce guide sur un portable à 4 coeurs : environ 540 pas par seconde avec 8 îles et time-scale 20, donc 1 million de pas en une demi-heure et le plateau (2 millions) en une heure ; les 5 millions du yaml prennent deux heures et demie, et on peut arrêter avant dès que la courbe plafonne.

Le défi du cours : jouer avec le yaml

Une fois un premier cerveau obtenu, la formation propose d'expérimenter les hyperparamètres (documentation Training Configuration File) : batch_size et buffer_size (combien d'expériences on accumule avant d'apprendre, les plus sensibles), num_layers (3 couches au lieu de 2), beta. Un seul changement à la fois, un nouveau --run-id par essai, et TensorBoard superpose les courbes : vous avez maintenant une base de comparaison.

10Donner le cerveau à l'oiseau 2026

  1. Après Ctrl+C, le cerveau est dans results/colibri1/Hummingbird.onnx.
  2. Le copier dans le projet : Assets/Hummingbird/Cerveaux/Hummingbird.onnx (créer le dossier). Unity l'importe comme un Model Asset d'Inference Engine.
  3. Sur le prefab Hummingbird : Behavior Parameters > Model = glisser ce fichier ; Behavior Type = Inference Only (ou Default, sans entraîneur lancé).
Unity 6 : le fichier Hummingbird.onnx importé comme Model Asset d'Inference Engine (entrées obs_0 à obs_3 avec un lot dynamique, sortie continuous_actions).
Unity 6 : le fichier Hummingbird.onnx importé comme Model Asset d'Inference Engine (entrées obs_0 à obs_3 avec un lot dynamique, sortie continuous_actions).
Unity 6 : le colibri avec son cerveau. Behavior Parameters : Model = Hummingbird (Model Asset), Behavior Type = Inference Only ; dessous, Hummingbird Agent avec Max Step 5000.
Unity 6 : le colibri avec son cerveau. Behavior Parameters : Model = Hummingbird (Model Asset), Behavior Type = Inference Only ; dessous, Hummingbird Agent avec Max Step 5000.
  1. Dans la scène, ne garder qu'une seule île active : sélectionner les sept copies et décocher la case à côté de leur nom dans l'inspecteur (ou les supprimer, l'entraînement est fini). Voir le piège ci-dessous.
  2. Play : l'oiseau vole seul vers les fleurs. Training Mode peut rester coché (les récompenses continuent d'être comptées, c'est sans effet sur le vol) ou être décoché pour un vol sans fin d'épisode.

2026 Piège mesuré avec ML-Agents 4.0.3 : quand huit oiseaux partagent le même cerveau en inférence, un seul vole correctement et les sept autres restent inertes : l'inférence par lot leur envoie des actions toutes nulles (mesuré : 0,00 sur les cinq valeurs, pendant que le premier reçoit ses vraies actions). Le cerveau n'y est pour rien : le même fichier .onnx, seul sur son île, récolte 30 à 45 de récompense par épisode. En inférence, une île suffit ; les huit îles servent à entraîner.

Le cerveau est petit (quelques centaines de ko) : il va dans le dépôt GitHub avec les scripts.

11Remise

Dans votre dépôt GitHub du module Agents (ou un dépôt public à vous, lié dans le README de remise) :

Le projet Unity entier (dossiers Library, Temp, Logs) ne va pas sur GitHub : seulement les fichiers ci-dessus, ou le dossier Assets/Hummingbird/Scripts et config.

Pour aller plus loin (facultatif) : le mini-jeu de la formation (unité 8, scripts GameManager.cs et UIController.cs, paquet HummingbirdUI_1.0.zip), où vous affrontez votre propre cerveau au clavier.

12Dépannage : les erreurs qu'on a vraiment rencontrées

SymptômeCause et remède
No module named 'pkg_resources'pip install "setuptools<81" dans le venv (section 1).
mlagents refuse de s'installer, ou torch introuvablePython n'est pas en 3.10 : python --version. Recréer le venv avec Python 3.10.
error CS0115: no suitable method found to override sur OnActionReceived ou HeuristicSignature de 2020 (float[]). Prendre celle de la classe parente : ActionBuffers (section 6).
InvalidOperationException: You are trying to read Input using the UnityEngine.Input class...Le projet est en Input System seul et le code lit l'ancien Input. Utiliser le bloc #if ENABLE_INPUT_SYSTEM de la section 6, ou Project Settings > Player > Active Input Handling = Both.
Tout est rose (ou hachuré rose et turquoise) dans la scèneDeux causes. La plus fréquente : Unity 6 compile les shaders pour votre carte graphique et affiche un motif hachuré en attendant ; patienter une ou deux minutes (barre de progression en bas à droite), surtout à la première ouverture. Sinon : le projet n'est pas en URP (modèle « 3D » au lieu de « Universal 3D ») ; recréer le projet avec le bon modèle, ou Window > Rendering > Render Pipeline Converter.
The Unity environment took too long to respond ou l'entraîneur s'arrête avant PlayPlay doit être cliqué dans les 60 secondes. Relancer mlagents-learn, puis Play.
Couldn't connect to trainer on port 5004 dans la console UnityNormal quand aucun entraîneur ne tourne : l'agent passe en heuristique ou en inférence. Pour entraîner, lancer mlagents-learn d'abord.
Le bec traverse la fleur sans nectarDefault Contact Offset pas à 0.001, ou FlowerNectarCollider pas Convex + Is Trigger, ou tag nectar manquant.
La récompense reste négative après 500 000 pasLes capteurs à rayons ne détectent pas boundary, ou IslandBoundaries n'a pas le tag. Vérifier la section 7.
Un lecteur construit (File > Build) sous Linux ne se connecte jamais à l'entraîneurLe paquet 4.0.3 range libgrpc_csharp_ext.x64.so dans Plugins/AnyCPU/ de la construction ; copier ce fichier dans Plugins/x86_64/ à côté. Dans l'éditeur (bouton Play), le problème n'existe pas.
En Inference Only, un seul oiseau vole, les autres ne bougent pasInférence par lot de ML-Agents 4.0.3 : un seul agent par cerveau reçoit ses actions. Désactiver les îles supplémentaires (section 10) : le cerveau est bon.
Le .onnx n'apparaît pas dans le champ ModelAttendre la fin de l'import (barre de progression), vérifier que le fichier est bien dans Assets.

Retour à l'énoncé du labo Sorti du Nid · assets : ML-Agents: Hummingbirds (Unity Learn, Adam Kelly, 2020) · doc : ML-Agents.