
Inom spelutveckling behöver man ofta ha nÄgot kopplat till slumpen: Unity har sin egen Random, men det finns Àven System.Random som alternativ. För lÀnge sedan fick vi intrycket att de tvÄ kan fungera olika (Àven om de borde ha en jÀmn fördelning).
Vi fördjupade oss inte i detaljerna â det rĂ€ckte med att övergĂ„ngen till System.Random löste alla problem. Nu har vi bestĂ€mt oss för att gĂ„ till botten med detta och genomföra en liten studie: hur "partiska" eller förutsĂ€gbara RNG:er Ă€r, och vilken man bör vĂ€lja. Jag har hört motstridiga Ă„sikter om deras "Ă€rlighet" â lĂ„t oss ta reda pĂ„ hur de verkliga resultaten förhĂ„ller sig till de pĂ„stĂ„dda.
En kort introduktion eller vad Àr RNG egentligen?
Om du redan Àr bekant med slumpgeneratorer kan du gÄ direkt till avsnittet "Testning".
Slumptal (ST) Ă€r en sekvens av siffror som genereras genom en viss slumpmĂ€ssig (kaotisk) process, en entropikĂ€lla. Det vill sĂ€ga, detta Ă€r en sekvens vars element inte hĂ€nger ihop med nĂ„got matematiskt lag â de saknar ett orsakssamband.
Det som skapar ST kallas en slumptalsgenerator (RNG). Det verkar enkelt, men övergÄngen frÄn teori till praktik gör det faktiskt inte sÄ lÀtt att implementera en algoritm för att generera en sÄdan sekvens.
Anledningen ligger i avsaknaden av den dÀr kaotiska egenskapen i modern konsumelektronik. Utan den slutar slumptal att vara slumpmÀssiga och deras generator blir en vanlig funktion av kÀnda argument. För en rad specialiteter inom IT-omrÄdet Àr detta ett allvarligt problem (till exempel för kryptografi), medan det för andra finns fullt acceptabla lösningar.
Det behövs en algoritm som skulle returnera, om Ă€n inte verkligt slumpmĂ€ssiga tal, sĂ„ nĂ€ra dem som möjligt â de sĂ„ kallade pseudorandom-talen (PST). Algoritmen kallas i detta fall för en pseudorandom-talsgenerator (PRNG).
Det finns flera alternativ för att skapa en PRNG, men för alla Àr följande relevant:
- Behovet av föregÄende initiering.
En randomiseringskommitté (GPC) saknar en kÀlla till entropi, sÄ innan den anvÀnds mÄste den ange ett initialt tillstÄnd. Detta anges som ett nummer (eller en vektor) och kallas för ett frö (seed, random seed). Ofta anvÀnds en rÀknare för klockcykler eller det numeriska ekvivalenten för systemtiden som seed.
- Ă
terproducerbarheten av sekvensen.
GPC Àr helt deterministisk, sÄ det seed som anges vid initialisering entydigt bestÀmmer hela den framtida sekvensen av tal. Det betyder att en enskild GPC, initialiserad med samma seed (vid olika tidpunkter, i olika program, pÄ olika enheter) kommer att generera samma sekvens.
Det Ă€r ocksĂ„ viktigt att kĂ€nna till den sannolikhetsfördelning som GPC representerar â vilka siffror den kommer att generera och med vilken sannolikhet. Oftast Ă€r det antingen en normalfördelning (normal distribution) eller en uniform fördelning (uniform distribution).

Normalfördelning (till vÀnster) och uniform fördelning (till höger)
Anta att vi har en rÀttvis tÀrning med 24 sidor. Om vi kastar den kommer sannolikheten att ettan kommer att rulla att vara 1/24 (pÄ samma sÀtt Àr sannolikheten för varje annat nummer densamma). Om vi gör mÄnga kast och registrerar resultaten, kan vi se att alla sidor dyker upp med ungefÀr samma frekvens. I grund och botten kan denna tÀrning betraktas som en GPC med uniform fördelning.
Men vad hÀnder om vi kastar 10 sÄdana tÀrningar och rÀknar den totala summan av poÀngen? Kommer den att behÄlla sin uniformitet? Nej. Oftast kommer summan att ligga nÀra 125 poÀng, det vill sÀga nÀra ett visst medelvÀrde. Och som en följd kan vi, Àven innan kastet, ungefÀr uppskatta det framtida resultatet.
Anledningen Ă€r att det finns flest kombinationer för att fĂ„ det genomsnittliga poĂ€ngsumman. Ju lĂ€ngre bort frĂ„n det, desto fĂ€rre kombinationer â och dĂ€rmed mindre sannolikhet för att det intrĂ€ffar. Om dessa data visualiseras kan de pĂ„ avstĂ„nd likna formen av en klocka. DĂ€rför kan man med viss överdrift kalla systemet av 10 tĂ€rningar för en GPC med normal fördelning.
Ett annat exempel, men denna gĂ„ng i plan â skytte mot en mĂ„ltavla. Skyttarens GPC kommer att generera ett par nummer (x, y), som representeras pĂ„ grafen.

HĂ„ller du med om att alternativet till vĂ€nster Ă€r mer nĂ€ra verkligheten â det Ă€r en slumptalsgenerator med normalfördelning. Men om du behöver sprida stjĂ€rnor pĂ„ den mörka himlen, passar det högra alternativet, som erhĂ„llits med hjĂ€lp av en slumptalsgenerator med uniform fördelning, bĂ€ttre. I allmĂ€nhet, vĂ€lj generator beroende pĂ„ den uppgift som ska lösas.
Nu ska vi prata om entropin i sekvensen av slumptal. Till exempel, det finns en sekvens som börjar sÄ hÀr:
89, 93, 33, 32, 82, 21, 4, 42, 11, 8, 60, 95, 53, 30, 42, 19, 34, 35, 62, 23, 44, 38, 74, 36, 52, 18, 58, 79, 65, 45, 99, 90, 82, 20, 41, 13, 88, 76, 82, 24, 5, 54, 72, 19, 80, 2, 74, 36, 71, 9, âŠ
Hur slumpmÀssiga Àr dessa siffror vid första ögonkastet? LÄt oss börja med att kontrollera fördelningen.

Det ser nÀstan ut som en jÀmn fördelning, men om man lÀser sekvensen i par av tvÄ siffror och tolkar dem som koordinater pÄ ett plan, fÄr man detta:

Mönstren blir tydligt synliga. Eftersom datan i sekvensen Ă€r ordnad pĂ„ ett visst sĂ€tt (det vill sĂ€ga har lĂ„g entropi) kan detta skapa den sĂ„ kallade "partiskheten". Ă
tminstone passar en sÄdan slumptalsgenerator inte bra för att generera koordinater pÄ ett plan.
En annan sekvens:
42, 72, 17, 0, 30, 0, 15, 9, 47, 19, 35, 86, 40, 54, 97, 42, 69, 19, 20, 88, 4, 3, 67, 27, 42, 56, 17, 14, 20, 40, 80, 97, 1, 31, 69, 13, 88, 89, 76, 9, 4, 85, 17, 88, 70, 10, 42, 98, 96, 53, âŠ
Det verkar som att allt Àr bra hÀr Àven pÄ planet:

LÄt oss titta pÄ volymen (lÀser tre siffror):

Och Äterigen mönster. Det gÄr inte att skapa en visualisering i fyra dimensioner. Men mönster kan existera Àven i denna dimension och större.
I samma kryptografi, dĂ€r hĂ„rda krav stĂ€lls pĂ„ slumptalsgeneratorer, Ă€r en sĂ„dan situation kategoriskt oacceptabel. DĂ€rför har speciella algoritmer utvecklats för att bedöma deras kvalitet, som vi inte kommer att beröra just nu. Ămnet Ă€r omfattande och skulle kunna fylla en egen artikel.
Testning
Om vi inte vet nÄgot med sÀkerhet, hur ska vi dÄ hantera det? Ska vi korsa vÀgen om vi inte vet vilket trafikljus som tillÄter det? Konsekvenserna kan vara olika.
Samma gÀller den ökÀnda slumpen i Unity. Det Àr bra om dokumentationen avslöjar nödvÀndiga detaljer, men historien som nÀmndes i början av artikeln intrÀffade just pÄ grund av bristen pÄ önskad specifik information.
Utan att veta hur verktyget fungerar kan du inte tillÀmpa det korrekt. Det Àr dags att kontrollera och göra ett experiment för att slutgiltigt försÀkra dig om Ätminstone fördelningen.
Lösningen var enkel och effektiv â samla statistik, fĂ„ objektiva data och titta pĂ„ resultaten.
Forskningens Àmne
I Unity finns det flera sĂ€tt att generera slumpmĂ€ssiga tal â vi testade fem.
- System.Random.Next(). Genererar heltal (integer) inom ett angivet vÀrdeintervall.
- System.Random.NextDouble(). Genererar dubbeld precision (double) inom intervallet [0; 1).
- UnityEngine.Random.Range(). Genererar enkel precision (float) inom ett angivet vÀrdeintervall.
- UnityEngine.Random.value. Genererar enkel precision (float) inom intervallet [0; 1).
- Unity.Mathematics.Random.NextFloat(). Del av det nya biblioteket Unity.Mathematics. Genererar enkel precision (float) inom ett angivet vÀrdeintervall.
NÀstan överallt i dokumentationen angavs en jÀmn fördelning, förutom UnityEngine.Random.value (dÀr distributionen inte angavs, men i enlighet med UnityEngine.Random.Range() förvÀntades en jÀmn fördelning) och Unity.Mathematics.Random.NextFloat() (dÀr grunden Àr xorshift-algoritmen, sÄ vi behöver Äterigen vÀnta pÄ en jÀmn fördelning).
Som förvÀntade resultat togs de som angavs i dokumentationen.
Metodologi
Vi skrev en liten applikation som genererade sekvenser av slumpmÀssiga tal med varje av de presenterade metoderna och sparade resultaten för vidare bearbetning.
LÀngden pÄ varje sekvens Àr 100 000 tal.
VĂ€rdeintervall för de slumpmĂ€ssiga talen â [0, 100).
Data samlades in frÄn flera mÄlplattformar:
- Windows
â Unity v2018.3.14f1, Editor-lĂ€ge, Mono, .NET Standard 2.0 - macOS
â Unity v2018.3.14f1, Editor-lĂ€ge, Mono, .NET Standard 2.0
â Unity v5.6.4p4, Editor-lĂ€ge, Mono, .NET Standard 2.0 - Android
â Unity v2018.3.14f1, byggd för enhet, Mono, .NET Standard 2.0 - iOS
â Unity v2018.3.14f1, byggd för enhet, il2cpp, .NET Standard 2.0
Implementering
Vi har flera olika sÀtt att generera slumpmÀssiga tal. För var och en av dem kommer vi att skriva en separat wrapper-klass som ska tillhandahÄlla:
- Möjlighet att ange vÀrdeintervall [min/max). Det kommer att anges genom konstruktorn.
- Metod som returnerar RNG. Vi vÀljer float som typ, eftersom det Àr mer allmÀnt.
- Genereringsmetoden för att mĂ€rka resultat. För bekvĂ€mlighetens skull kommer vi att returnera det fullstĂ€ndiga klassnamnet + metodenamnet som anvĂ€nds för att generera SÄ.
Vi börjar med att deklarera en abstraktion som representeras av grÀnssnittet IRandomGenerator:
namespace RandomDistribution
{
public interface IRandomGenerator
{
string Name { get; }
float Generate();
}
}Implementering av System.Random.Next()
Denna metod tillĂ„ter att ange ett vĂ€rdeintervall, men den returnerar heltal (integer), medan vi behöver float. Vi kan helt enkelt tolka integer som float, eller sĂ„ kan vi utöka vĂ€rdeintervallet med flera ordningar, vilket kompenserar för dem vid varje SÄ-generering. Det kommer att bli nĂ„got som liknar fixed-point med angiven ordning av noggrannhet. Vi kommer att anvĂ€nda denna variant, eftersom den Ă€r nĂ€rmare det verkliga float-vĂ€rdet.
using System;
namespace RandomDistribution
{
public class SystemIntegerRandomGenerator : IRandomGenerator
{
private const int DefaultFactor = 100000;
private readonly Random _generator = new Random();
private readonly int _min;
private readonly int _max;
private readonly int _factor;
public string Name => "System.Random.Next()";
public SystemIntegerRandomGenerator(float min, float max, int factor = DefaultFactor)
{
_min = (int)min * factor;
_max = (int)max * factor;
_factor = factor;
}
public float Generate() => (float)_generator.Next(_min, _max) / _factor;
}
}Implementering av System.Random.NextDouble()
HĂ€r Ă€r det ett fast vĂ€rdeintervall [0; 1). För att projicera det pĂ„ det angivna i konstruktorn anvĂ€nder vi enkel aritmetik: X * (max â min) + min.
using System;
namespace RandomDistribution
{
public class SystemDoubleRandomGenerator : IRandomGenerator
{
private readonly Random _generator = new Random();
private readonly double _factor;
private readonly float _min;
public string Name => "System.Random.NextDouble()";
public SystemDoubleRandomGenerator(float min, float max)
{
_factor = max - min;
_min = min;
}
public float Generate() => (float)(_generator.NextDouble() * _factor) + _min;
}
}Implementering av UnityEngine.Random.Range()
Denna metod i den statiska klassen UnityEngine.Random tillĂ„ter att ange ett vĂ€rdeintervall och returnerar SÄ av typen float. Inga ytterligare konverteringar behöver göras.
using UnityEngine;
namespace RandomDistribution
{
public class UnityRandomRangeGenerator : IRandomGenerator
{
private readonly float _min;
private readonly float _max;
public string Name => "UnityEngine.Random.Range()";
public UnityRandomRangeGenerator(float min, float max)
{
_min = min;
_max = max;
}
public float Generate() => Random.Range(_min, _max);
}
}Implementering av UnityEngine.Random.value
Egenskapen value av den statiska klassen UnityEngine.Random returnerar ett slumpmÀssigt tal av typen float frÄn ett fast intervall av vÀrden [0; 1). Vi projicerar det pÄ det angivna intervallet pÄ samma sÀtt som vid implementeringen av System.Random.NextDouble().
using UnityEngine;
namespace RandomDistribution
{
public class UnityRandomValueGenerator : IRandomGenerator
{
private readonly float _factor;
private readonly float _min;
public string Name => "UnityEngine.Random.value";
public UnityRandomValueGenerator(float min, float max)
{
_factor = max - min;
_min = min;
}
public float Generate() => (float)(Random.value * _factor) + _min;
}
}Implementation av Unity.Mathematics.Random.NextFloat()
Metoden NextFloat() i klassen Unity.Mathematics.Random returnerar ett slumpmĂ€ssigt tal av typen float och tillĂ„ter att ange ett intervall av vĂ€rden. Det viktiga Ă€r att varje instans av Unity.Mathematics.Random mĂ„ste initieras med ett seed â sĂ„ undviker vi att generera upprepade sekvenser.
using Unity.Mathematics;
namespace RandomDistribution
{
public class UnityMathematicsRandomValueGenerator : IRandomGenerator
{
private Random _generator;
private readonly float _min;
private readonly float _max;
public string Name => "Unity.Mathematics.Random.NextFloat()";
public UnityMathematicsRandomValueGenerator(float min, float max)
{
_min = min;
_max = max;
_generator = new Random();
_generator.InitState(unchecked((uint)System.DateTime.Now.Ticks));
}
public float Generate() => _generator.NextFloat(_min, _max);
}
}Implementation av MainController
Flera implementationer av IRandomGenerator Àr klara. NÀsta steg Àr att generera sekvenser och spara den resulterande datasetet för bearbetning. För detta skapar vi en scen i Unity och ett litet skript MainController som kommer att utföra allt nödvÀndigt arbete och ocksÄ ansvara för interaktionen med UI.
Vi definierar storleken pÄ datasetet och intervallet av slumpmÀssiga tal, samt skaffar en metod som returnerar en array av instÀllda och klara generatorer.
namespace RandomDistribution
{
public class MainController : MonoBehaviour
{
private const int DefaultDatasetSize = 100000;
public float MinValue = 0f;
public float MaxValue = 100f;
...
private IRandomGenerator[] CreateRandomGenerators()
{
return new IRandomGenerator[]
{
new SystemIntegerRandomGenerator(MinValue, MaxValue),
new SystemDoubleRandomGenerator(MinValue, MaxValue),
new UnityRandomRangeGenerator(MinValue, MaxValue),
new UnityRandomValueGenerator(MinValue, MaxValue),
new UnityMathematicsRandomValueGenerator(MinValue, MaxValue)
};
}
...
}
}Nu skapar vi datasetet. I det hÀr fallet kommer datagenereringen att kombineras med att skriva resultaten till en textström (i csv-format). För lagring av vÀrdena för varje IRandomGenerator avsÀtts en egen kolumn, och den första raden innehÄller generatorns namn.
namespace RandomDistribution
{
public class MainController : MonoBehaviour
{
...
private void GenerateCsvDataSet(TextWriter writer, int dataSetSize, params IRandomGenerator[] generators)
{
const char separator = ',';
int lastIdx = generators.Length - 1;
// skriv header
for (int j = 0; j <= lastIdx; j++)
{
writer.Write(generators[j].Name);
if (j != lastIdx)
writer.Write(separator);
}
writer.WriteLine();
// skriv data
for (int i = 0; i <= dataSetSize; i++)
{
for (int j = 0; j <= lastIdx; j++)
{
writer.Write(generators[j].Generate());
if (j != lastIdx)
writer.Write(separator);
}
if (i != dataSetSize)
writer.WriteLine();
}
}
...
}
}Det ÄterstÄr att kalla pÄ metoden GenerateCsvDataSet och spara resultatet i en fil, eller direkt överföra data frÄn den slutliga enheten till mottagaren. server.
namespace RandomDistribution
{
public class MainController : MonoBehaviour
{
...
public void GenerateCsvDataSet(string path, int dataSetSize, params IRandomGenerator[] generators)
{
using (var writer = File.CreateText(path))
{
GenerateCsvDataSet(writer, dataSetSize, generators);
}
}
public string GenerateCsvDataSet(int dataSetSize, params IRandomGenerator[] generators)
{
using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
{
GenerateCsvDataSet(writer, dataSetSize, generators);
return writer.ToString();
}
}
...
}
}Projektets kÀllfiler finns pÄ .
Resultat
Det blev inget mirakel. Vad vi förvĂ€ntade oss, det fick vi â i alla fall en jĂ€mn fördelning utan antydan till sammansvĂ€rjningar. Jag ser ingen mening med att bifoga individuella grafer frĂ„n plattformarna â de visar alla ungefĂ€r samma resultat.
Verkligheten Àr denna:

Visualisering av sekvenser pÄ planet frÄn alla fem metoder för generering:

Och visualisering i 3D. Jag lÀmnar bara resultatet frÄn System.Random.Next(), för att inte skapa en massa identiskt innehÄll.

BerÀttelsen i inledningen om normalfördelningen frÄn UnityEngine.Random upprepades inte: antingen var den ursprungligen felaktig, eller sÄ har nÄgot förÀndrats i motorn sedan dess. Men nu Àr vi helt övertygade.
KĂ€lla: habr.com
