Erweiterbares RNG-System

7

Meine Frage ist ziemlich allgemein. Was ist der einfachste Weg, ein RNG-System oder einen Algorithmus zu codieren, der Ergänzungen und Änderungen leicht berücksichtigt? Nehmen wir zum Beispiel an, ich habe ein RNG-System, das einen Gegenstand basierend auf dem zufälligen Wert erzeugt, der gewürfelt wird. Dann könnte eine einfache, zusammengeschlagene Lösung aussehen

if (randomNumber > 0 && randomNumber <= 0.2)
{
    // spawn something
}

if (randomNumber > 0.2 && randomNumber <= 0.4)
{
    // spawn some other thing
}

oder für die Chance, mehrere Gegenstände zu spawnen, vielleicht

if (randomNumber < 0.4)
{
    // spawn something
}

if (randomNumber < 0.2)
{
    // spawn some other thing 
}

Nehmen wir an, ich füge viele, viele mögliche Gegenstände hinzu, die im Spiel erscheinen können. Es wäre mühsam, alle diese Werte zu ändern und dabei ihre relativen Raritäten usw. beizubehalten. Wie mache ich ein flexibleres RNG-System?

Umarme die Zukunft
quelle
4
Schauen Sie sich datengesteuerte Programmiertechniken an. Sie sollten solche fest codierten Werte nicht in Ihrem Code haben.
MichaelHouse
Sie könnten seltene Klammern bauen, die den Gegenständen ein gewisses Maß an Gewicht verleihen. Und dann geben Sie von dort aus alles andere einen Basiswert und lassen Sie es von dort auswählen. Vergrößern Sie bestimmte Klammern je nach Seltenheit, damit sie seltener vorkommen. Sie wollen auch mehr Zufälligkeit als ein einfacher Rand. Etwas mehr datengesteuertes, wie Byte sagte, wäre gut
n_plum
Keine vollständige Antwort, aber Sie möchten wahrscheinlich eine einfache Nachschlagetabelle verwenden. Entscheidend ist, dass Sie dem Vorschlag von @ Byte56 folgen und diese Tabellen aus Textdateien initialisieren , nicht direkt im Code. Fügen Sie noch besser Funktionen hinzu, um die Werte während der Wiedergabe aus den Dateien neu zu laden. Es ist großartig, den Code nicht neu kompilieren zu müssen, um einige Variablen zu ändern.
Miles Rout

Antworten:

7

Sie können einen Algorithmus verwenden, den ich Distributed Probability RNG nenne .

Es sieht aus wie das:

public class Item {}

public class ExampleBucket
{
    private List<Item> bucket = new List<Item>();   

    public void Add (Item item, int count)
    {
        for (int i = 0; i < count; i++)
        {
            bucket.Add(item);
        }
    }

    public Item GetRandom (Random rng)
    {
        var index = rng.Next(0, bucket.Count);

        return bucket[index];
    }
}

Sie können wie folgt initialisieren:

private void InitBucket()
{
    var itemA = new Item();
    var itemB = new Item();
    var itemC = new Item();

    var bucket = new ExampleBucket();

    bucket.Add(itemA, 1);
    bucket.Add(itemB, 2);
    bucket.Add(itemC, 3);
}

Sie können Elemente erhalten, indem Sie GetRandom(Random)Ihre ExampleBucketInstanz aufrufen .


Wie funktioniert es?

Wir haben eine Liste bucketin der ExampleBucketKlasse benannt. Zunächst einmal ist es leer. Durch Aufrufen der Add(Item, int)Methode fügen Sie Itemder bucketListe eine bestimmte Anzahl von s hinzu .

Am Ende unserer InitBucket()Methode bucketsieht die Liste folgendermaßen aus:

itemA // 1 x itemA
itemB // 2 x itemB
itemB
itemC // 3 x itemC
itemC
itemC

Dann generieren wir einfach eine Zufallszahl zwischen 0und bucket.Countund geben das Element zurück, das diesem Index in der bucketListe entspricht.

Die Wahrscheinlichkeit, ein bestimmtes Element aus einer Liste zu erhalten, beträgt Item Count / Total Count. Je mehr Elemente eines bestimmten Typs in der Liste enthalten sind, desto größer ist die Wahrscheinlichkeit, dass dieses Element ausgewählt wird.

Also in diesem Fall;

Probability of getting itemA = 1 / (1+2+3)
Probability of getting itemB = 2 / (1+2+3)
Probability of getting itemC = 3 / (1+2+3)

Wenn Sie GetRandom()100000 Mal laufen (der Test ist hier in Fiddle ), können Sie sehen, dass die Werte den perfekten Ergebnissen ziemlich nahe kommen:

A count = 16690 | Expected = 16666
B count = 33519 | Expected = 33333
C count = 49791 | Expected = 50000

Was mir an diesem Algorithmus am besten gefällt, ist, dass Sie keine Gesamtanzahl von Elementen angeben müssen. Sie können jederzeit einen beliebigen Artikel hinzufügen und müssen sich nicht um den Rest kümmern.

Wenn Sie beispielsweise die Wahrscheinlichkeit erhöhen möchten, itemAvon der Liste zu kommen, können Sie einfach anrufen

bucket.Add (itemA, theAmountYouWant);

und Sie müssen nichts anderes tun.

S. Tarık Çetin
quelle
Vielen Dank für die hilfreiche Antwort. Wie speichere ich eine Instanz von ExampleBucket () in einer Variablen? var Bucket = new ExampleBucket (); scheint bei mir nicht zu funktionieren.
Umarmen Sie die Zukunft
@embracethefuture Was meinst du mit "scheint nicht zu funktionieren", bekommst du einen Fehler?
S. Tarık Çetin
Oh komisch, es funktioniert. ExampleBucket Bucket = new ExampleBucket () Ich meine ... da ich C # verwende. Ich bin mir nicht sicher, warum es früher nicht funktioniert hat. Ich denke, das liegt daran, dass ich versucht habe, es in einem anderen Skript zu tun, ohne eine Referenz einzurichten. Sollte InitBucket () Teil der Item-Klasse sein oder sollte es irgendwo in einem MonoBehaviour-Skript vorhanden sein? Ich versuche nur, meinen Kopf um die Architektur zu wickeln.
Umarmen Sie die Zukunft
1
Die @ embracethefuture- InitBucket()Methode ist eine Beispielverwendung, die nicht Teil des eigentlichen Codes ist. Sie sollten es auf Ihre eigene Codebasis spezialisieren. Aber es sollte nicht in der ItemKlasse sein.
S. Tarık Çetin
Richtig, danke. Das ist was ich dachte. Entschuldigung, ich bin ziemlich neu in der Spielprogrammierung. Aus diesem Grund hatte ich das Problem früher. Ich weiß nicht, wie ich in meinen Monobehaviour-Skripten auf eine Nicht-Monobehaviour-Unterklasse verweisen soll. Ich kann GetComponent nicht verwenden und ExampleBucket-Bucket = new nicht ausführen, da mein Monobehaviour-Skript nicht weiß, dass ein ExampleBucket-Typ vorhanden ist.
Umarmen Sie die Zukunft
9

Gewichtete Zufälle

Anstatt mehr Kopien eines Elements in eine Liste zu schreiben, können wir stattdessen Folgendes tun:

private class WeightedItem {
    public readonly int weight;
    public readonly Item drop;
    //constructor omitted
}

Wir schieben diese dann in eine Liste:

private List<WeightedItem> bucket = new List<WeightedItem>();
private totalWeight = 0;
public void Add (Item item, int count) {
    if(count <= 0) throw new Exception("Invalid random weight");
    bucket.Add(new WeightedItem(item, count));
    totalWeight += count;
}

Und dann bekommen wir unser Ergebnis so:

public Item GetRandom (Random rng) {
    int randomVal = rng.Next(0, totalWeight);
    foreach(WeightedItem item in bucket) {
        randomVal -= item.weight;
        if(randomVal <= 0) {
            return item.drop;
        }
    }
}

Diese Methode ist flexibler als nur das Verschieben von Elementen in eine Liste, da wir die Wrapper-Klasse dafür verantwortlich machen können, dass das Element in einen ItemStack umgewandelt und zusätzliche Daten (wie die Stapelgröße oder NBT-Daten - angewendet werden, wenn wir an Elemente in der Liste denken Minecraft erkennt, wo Itemsich die prototypische Definition befindet und ItemStackwas tatsächlich im Inventar des Spielers angezeigt wird: Größe und NBT-Daten sind nur zusätzliche Datenbits zusätzlich zur prototypischen Definition, die uns speziell über diese Gruppierung informieren, was Ihr Projekt tatsächlich hat und wie es ist vertreten liegt bei Ihnen).

Dies bedeutet, dass wir mehrere zufällige Generatoren mit jeweils einer eigenen Sammlung gewichteter Listen haben können und jeder Eintrag in dieser Liste ( z. B. Äpfel) eine eindeutige Menge generieren kann, je nachdem, in welchem ​​Generator sie sich befinden. eine Hülle wie diese:

public class RandomSizeStackDrop extends WeightedItem {
    public readonly int weight;
    public readonly Item drop;
    public WeightedItem(Item i, int w) {
        drop = i;
        weight = w;
    }

    public ItemStack GetStackFromDrop() {
        //Creates a stack with a size from 1 to 5
        return new ItemStack(drop, Random.Next(1,5));
    }
}

Und anstatt Itemin der GetRandom () -Methode zurückzukehren, kehren wir stattdessen zurückitem.GetStackFromDrop()

Wir könnten also einen Eimer für Gegenstände haben, die in einer Truhe in der Nähe einer Farm erzeugt werden, und Stapel von 1 bis 5 Äpfeln werden angezeigt, aber in einer anderen Truhe, beispielsweise in einem Minenschacht, wird immer nur 1 Apfel erzeugt, wenn ein Apfel auftaucht. Selbst wenn die Wahrscheinlichkeit für beide gleich ist, erzeugt die Truhe in der Nähe der Farm insgesamt mehr Äpfel .

Draco18s vertraut SE nicht mehr
quelle
2

Die meisten Ihrer Bedingungen sind nutzlos, wodurch Ihr Code umsonst länger wird. Dies ist die Art und Weise, wie man Dinge mit Brot und Butter macht.

if (randomNumber <= 0.2)
{
    // 20% chance
}else if (randomNumber <= 0.4)
{
    // 20% chance
}else{
    // 60% chance
}

Wenn Sie es noch bequemer machen möchten, können Sie es mit der Gewichtung versuchen.

Nehmen wir an, Sie haben eine Datenstruktur, die eine nimmt Action. Hier ist ein Pseudocode.

WeightedRandomizer wr = new WeightedRandomizer();

wr.addAction(Action.MOVE, 1); // 1 in 7 chances of happening
wr.addAction(Action.JUMP, 2); // 2 in 7 chances of happening
wr.addAction(Action.FLY, 4); // 4 in 7 chances of happening

Action action = wr.getAction();

Sie haben etwas weniger Kontrolle über den genauen Prozentsatz, aber es ist definitiv ein viel saubererer Ansatz.

Cedric Martens
quelle