Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

Unity random seed

Unity Random Seed – Gérer l’aléatoire

Aujourd’hui, nous explorons la classe Random d’Unity, en mettant l’accent sur l’utilisation d’une seed.

Présenter une fonctionnalité est plus engageant avec un exemple concret.

C’est pourquoi nous allons implémenter un système de type salle rogue-like (comme Hades) avec un système de récompense aléatoire basé sur une seed.

Enfin, nous explorerons l’aléatoire dans l’un des rogue-likes les plus appréciés.

Table des matières

Mais avant d’aller plus loin, revenons aux bases et explorons comment implémenter l’aléatoire.

Remarque : Mes exemples sont assez simples, donc je vais utiliser la classe Random d’Unity.
Si vous avez besoin d’accomplir des tâches plus complexes avec plusieurs seeds, je recommande d’utiliser « System.Random » directement ou même d’utiliser une autre bibliothèque qui convient à vos besoins.

Unity : Comment utiliser Random ?

Random est une classe statique qui fournit plusieurs fonctions pour obtenir des nombres aléatoires.

Obtenir un nombre aléatoire entre 0 et 1

Pour obtenir un nombre aléatoire entre 0 et 1, utilisez la propriété statique « value » de « Random« .

using UnityEngine;

var value = Random.value;
      

Obtenir un nombre aléatoire dans une plage

Pour obtenir un nombre aléatoire entre deux valeurs, utilisez la fonction statique « Range » de « Random ».

using UnityEngine;

var value = Random.Range(min, max);
      

Lors de l’utilisation d’entiers, la valeur de départ est inclusive, et la valeur de fin est exclusive. Lors de l’utilisation de nombres float, les deux valeurs sont incluses.

Modifier la seed

Pour modifier la seed, utilisez la fonction « InitState » de « Random ».
Faites attention de ne pas utiliser la propriété seed, car elle est obsolète.

using UnityEngine;

Random.InitState(seed);
      

Quel est notre objectif ?

Notre objectif est d’implémenter l’aléatoire derrière un système de salles.
L’objectif de notre jeu est de traverser différentes salles, à la fin de chaque salle, le joueur reçoit une récompense aléatoire (pouvoir, or, événements, etc.).

De plus, le joueur a le contrôle sur son destin, choisissant quelle salle entrer, le jeu ressemblera à un labyrinthe.

Choix de salle à HadesHades – Choix entre 2 portes – Image de IGN

Indépendamment des choix de navigation de l’utilisateur, nous voulons une équité dans le jeu.
Deux joueurs avec la même seed dans la même salle devraient recevoir les mêmes récompenses.

Comment fonctionnent « Random » et « seed » ?

L’aléatoire, du moins dans Unity, est géré en utilisant des nombres pseudo-aléatoires.

Ce processus commence avec une seed initiale, qui est utilisée pour générer le premier nombre, qui génère une autre seed utilisée pour générer le nombre suivant, et ainsi de suite.

Explication de la seed aléatoire
Illustration de la seed aléatoire avec des valeurs fictives

Ainsi, en utilisant la même seed, on obtiendra toujours les mêmes nombres dans nos fonctions aléatoires.

Dans notre scénario, cela pose un problème car l’utilisateur détermine le chemin :

Mauvaise récompense avec la seed aléatoire

En utilisant Random directement, nous observons que les récompenses ne sont plus liées aux salles mais au nombre de salles traversées.
De plus, dans un jeu, d’autres aléatoires peuvent survenir entre différentes salles en raison des actions de l’utilisateur ou des monstres, fournissant une expérience différente avec la même seed.

Il n’y a rien de mal avec cette fonctionnalité, mais dans notre cas, nous voulons des récompenses identiques.

Pourquoi faire cela ?

Vous pourriez vous demander pourquoi s’embêter avec cela – quel est l’intérêt d’avoir une seed qui reproduit parfaitement le niveau ?

  • Partage de niveau : Pour partager le gameplay vécu avec d’autres.
  • Équité dans un jeu compétitif : L’aléatoire peut perturber l’équité entre deux joueurs. Leur fournir la même seed dans un affrontement aide à régler le score.
  • Aide au débogage : Pendant les phases de test, si un utilisateur rencontre un bug, partager la seed facilite sa reproduction par les développeurs.
  • Assistance à la communauté des speedrun : Pour les speedrunners, ce système pourrait aider à l’entraînement en répétant des segments avec une seed spécifique.

Attention : L’objectif n’est pas de supprimer entièrement l’aléatoire, mais de le contrôler pour maintenir une forme d’équité.

Le plan

Le plan pour résoudre ce problème est assez simple.
Lors du lancement du jeu, nous précalculerons les récompenses pour chaque salle en utilisant une liste ordonnée.
Ensuite, nous régénérerons une seed pour gérer des actions indépendantes de notre seed dans un jeu.

Plan aléatoire

Nous nous en tiendrons à une architecture relativement simple.
Un GameRoomManager stockera une liste de RoomConfigurations (un ScriptableObject stockant la configuration de chaque salle).
Le GameRoomManager sera responsable d’attribuer chaque récompense aléatoire à la RoomConfiguration correcte.

Codons, Random et seed dans Unity

Pour chaque salle, nous commencerons avec quatre récompenses possibles, chacune ayant la même probabilité.
Une énumération Reward représentera ces différentes options :

public enum Reward
{
    POWER,
    LEVELUP,
    GOLD,
    EVENT
}
      

Ensuite, le ScriptableObject RoomConfiguration :

using UnityEngine;

[CreateAssetMenu()]
public class RoomConfiguration : ScriptableObject
{
    public int id;

    // Configurations exemple
    public AudioClip backgroundMusic;
    public Sprite sprite;
    public Vector2 minMaxNumberEnemy;

    // Autres champs de configuration ici...

    // Cette valeur sera définie par notre GameRoomManager
    public Reward? Reward { get; set; }
}
      

Et enfin, le cœur du programme, le GameRoomManager :

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System;
using Random = UnityEngine.Random;

public class GameRoomManager : MonoBehaviour
{
    public List<RoomConfiguration> roomConfigurations = new List<RoomConfiguration >();

    public void InitRooms(int seed)
    {
        Random.InitState(seed);

        // En option, triez par id
        roomConfigurations.Sort((a, b) => a.id.CompareTo(b.id));

        foreach (var room in roomConfigurations)
        {
            room.Reward = GetRandomReward();
        }

        EndInit();
    }

    private Reward GetRandomReward()
    {
        // A mettre en cache, ce sera mieux !
        var rewards = Enum.GetValues(typeof(Reward)).Cast().ToList();

        // Aléatoire de base entre nos récompenses
        return rewards[Random.Range(0, rewards.Count)];
    }

    public void EndInit()
    {
        // Obtenez une nouvelle seed pour tout autre aléatoire
        Random.InitState((int)DateTime.Now.Ticks);
    }
}
      

Pour initialiser nos salles, il suffit d’appeler la méthode « InitRooms« , en passant une seed en paramètre.
Chacune de nos RoomConfigurations sera parcourue pour associer une récompense à celle-ci.

Ensuite, nous pouvons récupérer une salle par son identifiant en utilisant la fonction suivante :

public RoomConfiguration GetRoomConfigurationById(int id)
{
    return roomConfigurations.Find(x => x.id == id);
}
      

Maintenant, voici ce que nous avons :

Récompense correcte avec la seed aléatoireRemarque : J’ai utilisé un code différent pour générer cette image.

Parfait, maintenant nos récompenses restent les mêmes par salle.

Peut-être vous demandez-vous si ce code est toujours viable avec des milliers de salles ?
La classe « Random » est si rapide qu’elle peut générer un million de nombres aléatoires en une centaine de millisecondes (chiffre à prendre avec des pincettes).

Remarque : Dans ce code, les ScriptableObjects sont stockés directement en mémoire. En fonction du nombre de salles stockées et de leurs données, l’utilisation de Addressable Assets peut être plus efficace.

Étude de cas : Comment Hades gère son aléatoire

Il est maintenant temps de voir comment l’un des leaders du marché des rogue-lites a implémenté son système d’aléatoire.

Juste un rappel : Hades est un rogue-lite où vous incarnez le fils du dieu de la mort essayant de s’échapper du monde souterrain.

Pour obtenir ces informations, je me suis intéressé aux speedruns.
Honnêtement, les speedrunners sont ceux qui connaissent le mieux un jeu après les développeurs.

Sur speedrun.com, j’ai trouvé ces informations (information traduit) :

« Une nouvelle seed est générée en fonction d’une combinaison de votre seed actuelle et du nombre de fois où RNG a été utilisé, et cette nouvelle seed remplace votre seed actuelle. »

Ici, nous pouvons voir le fonctionnement de base d’un Random.
Initialement, il y a une seed qui crée notre premier état, évoluant à chaque utilisation de Random.

Cependant, le seul point qui change est qu’une nouvelle seed est générée après avoir quitté une salle.

« Cependant, lorsque les portes se déverrouillent, votre position est réinitialisée au début. »

Dans Hades, les portes s’ouvrent après avoir terminé la salle (en obtenant la récompense…).
Lorsqu’ils mentionnent la réinitialisation de la position, cela signifie commencer avec une seed sans aucun aléatoire appliqué.

En réalité, la seed est réinitialisée une fois lorsque la récompense apparaît et une fois lorsque les portes s’ouvrent.

Hades salle aléatoire réinitialisationCapture d’écran de la vidéo Youtube : Hades – Routed Speedrunning Basics – How to Run a Route

Pour une meilleure visualisation, vous pouvez regarder cette vidéo :


Hades – Routed Speedrunning Basics – How to Run a Route | cgull

Si je devais traduire cette fonctionnalité sur Unity, cela ressemblerait à quelque chose comme ceci :


    using UnityEngine;

    public class RoomHades : MonoBehaviour
    {
        private Random.State _roomState;

        // Cette fonction est appelée lorsque le joueur entre dans la salle.
        public void AfterChangeRoom()
        {
            GenerateNewSeed();

            // Sauvegarder l'état actuel
            _roomState = Random.state;
        }

        private void GenerateNewSeed()
        {
            // Quelques calculs pour générer une nouvelle seed
        }

        // Le joueur a vidé la salle et reçoit la récompense.
        public void UnlockDoor()
        {
            // Réinitialiser la seed
            Random.state = _roomState;
        }
    }

Explications, Théories

C’est là que cela devient intéressant – pourquoi utiliser ce processus et ne pas simplement conserver la seed de base ?

Je n’ai pas la réponse exacte, mais je vais proposer deux théories.
N’hésitez pas à me corriger dans les commentaires =)

1. Renforcement des variations de gameplay

Générer une nouvelle seed après chaque salle contribue à diversifier le gameplay.
Avec une seed qui change fréquemment, les joueurs peuvent vivre une plus grande variété de configurations de salles, de rencontres et de récompenses, rendant chaque partie plus unique.

2. Amélioration des mesures anti-triche et complexification du routage

Changer la seed à chaque porte contribue à rendre le routage (choix stratégiques de chemin) plus complexe.
Comme on peut le voir avec nos amis speedrunners, manipuler l’aléatoire reste un défi.

De plus, si la seed restait constante, les joueurs pourraient potentiellement mémoriser les configurations de salle et optimiser leur progression, introduisant un problème potentiel de triche.

J’espère que cet article a été agréable et vous a aidé à comprendre un peu mieux comment fonctionnent Random et leurs seeds.

Illustration de Bud par OpenClipart-Vectors

Illustration de fond par Peace,love,happiness

En savoir plus

Consultez nos autres articles sur Unity :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *