Transforms
Rotation and Orientation in Unity

Agregando Aleatoriamente Elementos de Juego

Los elementos o valores aleatoriamente escogidos son importantes en muchos juegos. Esta sección le muestra a usted cómo usar las funciones aleatorias integradas en Unity para implementar algunas mecánicas comunes de juego.

Escogiendo un elemento aleatorio de una matriz

Escoger un elemento de matriz al azar se reduce a la elección de un número entero aleatorio entre cero y el valor máximo de un valor de índice de una matriz (que es igual al tamaño de la matriz menos uno). Esto es fácilmente hecho usando la función integrada Random.Range:-

 var element = myArray[Random.Range(0, myArray.Length)];

Escogiendo Elementos con Diferentes Probabilidades

Algunas veces, usted necesita escoger elementos aleatoriamente pero con algunos elementos con mayor probabilidad de ser escogidos que otros. Por ejemplo, un NPC puede reaccionar de maneras distintas cuando se encuentra un jugador.

  • 50% de probabilidad de un saludo amistoso
  • 25% de probabilidad de huir
  • 20% de probabilidad de un ataque inmediato
  • 5% de probabilidad de ofrecer dinero como un regalo

Puede visualizar estos diferentes resultados como una tira de papel dividido en secciones, cada una de las cuales ocupa una fracción de la longitud total de la tira. La fracción ocupada es igual a la probabilidad que tiene eso de ser escogido. Hacer la elección es equivalente a elegir un punto al azar a lo largo de la longitud de la tira (digamos lanzando un dardo) y luego ver en qué sección está.

En el script, la tira de papel es en realidad una matriz de floats que contienen las diferentes probabilidades de los artículos en orden. El punto aleatorio se obtiene multiplicando Random.value por el total de todos los floats en la matriz (no tienen por qué sumar 1; la cosa importante es el tamaño relativo de los diferentes valores). Para encontrar en cuál elemento de la matriz “está” el punto, en primer lugar, revisar para ver si es menor que el valor en el primer elemento. Si es así, entonces el primer elemento es el seleccionado. De lo contrario, resta el valor del primer elemento del valor del punto, y lo compara con el segundo elemento y así sucesivamente hasta que se encuentre el elemento correcto. En código, esto se vería algo como lo siguiente:-


//JS 

function Choose(probs: float[]) {
    var total = 0;
    
    for (elem in probs) {
        total += elem;
    }
    
    var randomPoint = Random.value * total;
    
    for (i = 0; i < probs.Length; i++) {
        if (randomPoint < probs[i])
            return i;
        else
            randomPoint -= probs[i];
    }
    
    return probs.Length - 1;
}

//C#

    float Choose (float[] probs) {

        float total = 0;

        foreach (float elem in probs) {
            total += elem;
        }

        float randomPoint = Random.value * total;

        for (int i= 0; i < probs.Length; i++) {
            if (randomPoint < probs[i]) {
                return i;
            }
            else {
                randomPoint -= probs[i];
            }
        }
        return probs.Length - 1;
    }

Tenga en cuenta que la última instrucción de return es necesaria porque Random.value puede devolver un resultado de 1. En este caso, la búsqueda no encontrará el valor aleatorio del punto en ninguna parte. Cambiando la linea

 if (randomPoint &lt; probs[i])

Ponderando valores continuos aleatorios

El método de arreglo de floats funciona bien si usted tiene resultados discretos, pero también hay situaciones dónde usted quiere producir un resultado más continuo - digamos, usted quiere volver aleatorio la cantidad de piezas de oro que se pueden encontrar en un cofre, y usted lo quiere hacer que sea un número entre 1 y 100, pero quisiera hacer que sea más probable obtener un número menor. Utilizando el método de arreglo de floats esto requiere que usted configure un arreglo de 100 floats (i.e. secciones en una tira de un papel) lo cual es difícil de manejar; y si usted no está limitado a números enteros pero cualquier cualquier número entre ese rango, es imposible utilizar ese acercamiento.

Un mejor acercamiento para resultados continuos es utilizar AnimationCurve para transformar un valor aleatorio ‘raw’ a uno ‘ponderado’; al dibujar diferentes formas curvas, usted puede producir diferentes ponderaciones. El código también es más simple de escribir:

//JS

function CurveWeightedRandom(curve: AnimationCurve) {
    return curve.Evaluate(Random.value);
}

//C#

float CurveWeightedRandom(AnimationCurve curve) {
    return curve.Evaluate(Random.value);
}

Un valor aleatorio ‘raw’ entre 0 y 1 es escogido al leer de Random.value. Luego es pasado a curve.Evaluate(), que trata esto como si fuera una coordenada horizontal, y devuelve la coordenada vertical correspondiente de la curva en esa posición horizontal. Las partes con poca profundidad de la curva tienen mayor posibilidad de ser escogidos, mientras que partes más pendientes tienen una probabilidad menor de ser escogidas.

Una curva lineal no pondera valores en absoluto; la coordenada horizontal es igual a la coordenada vertical para cada punto en la curva.
Una curva lineal no pondera valores en absoluto; la coordenada horizontal es igual a la coordenada vertical para cada punto en la curva.
Esta curva es menos profunda al principio, y luego más empinado al final, por lo que tiene una mayor probabilidad de valores más bajos y una probabilidad reducida de valores mayores. Usted puede ver que la altura de la curva en la linea dónde x=0.5 es acerca de 0.25, lo cual significa que tiene una probabilidad mayor a la mitad de obtener un valor entre 0 y 0.25.
Esta curva es menos profunda al principio, y luego más empinado al final, por lo que tiene una mayor probabilidad de valores más bajos y una probabilidad reducida de valores mayores. Usted puede ver que la altura de la curva en la linea dónde x=0.5 es acerca de 0.25, lo cual significa que tiene una probabilidad mayor a la mitad de obtener un valor entre 0 y 0.25.
Esta curva es menos profunda al principio y al final, haciendo que los valores cercanos a los extremos sea más común, y en la mitad pendiente lo cual haría esos valores muy raros. Tenga en cuenta que con esta curva, los valores de altura han aumentado para arriba: el final de la curva está en 1, y la parte superior de la curva está en 10, lo cual significa que los valores producidos por la curva estarán en el rango de 1-10, en vez de 0-1 como las curvas anteriores.
Esta curva es menos profunda al principio y al final, haciendo que los valores cercanos a los extremos sea más común, y en la mitad pendiente lo cual haría esos valores muy raros. Tenga en cuenta que con esta curva, los valores de altura han aumentado para arriba: el final de la curva está en 1, y la parte superior de la curva está en 10, lo cual significa que los valores producidos por la curva estarán en el rango de 1–10, en vez de 0–1 como las curvas anteriores.

Tenga en cuenta que estas curvas no son curvas distribuidas con probabilidad como las que usted encontraría en una guía de teoría de probabilidad, pero son más como curvas de probabilidad inversamente acumulativas.

Al definir una variable AnimationCurve pública en uno de sus scripts, usted será capaz de ver y editar la curva a través de la ventana del Inspector visualmente, en vez de necesitar calcular valores.

Esta técnica produce números floating-point. Si usted quiere calcular un resultado entero - por ejemplo, usted quisiera 82 piezas de oro en vez de 82.1214 piezas de oro - usted puede simplemente calcular el valor a una función como Mathf.RoundToInt().

Barajar una Lista

Una mecánica de juego común es elegir un conjunto conocido de artículos, pero que ellos lleguen en un orden aleatorio. Por ejemplo, una baraja de cartas se barajan normalmente para que no salgan en una secuencia predecible. Usted puede barajar los elementos de una matriz, visitando cada elemento e intercambiándolos con otro elemento situado en un índice al azar de la matriz:-

//JS

function Shuffle(deck: int[]) {
    for (i = 0; i < deck.Length; i++) {
        var temp = deck[i];
        var randomIndex = Random.Range(0, deck.Length);
        deck[i] = deck[randomIndex];
        deck[randomIndex] = temp;
    }
}

//C#

    void Shuffle (int[] deck) {
        for (int i = 0; i < deck.Length; i++) {
            int temp = deck[i];
            int randomIndex = Random.Range(0, deck.Length);
            deck[i] = deck[randomIndex];
            deck[randomIndex] = temp;
        }
    }

Escogiendo de un conjunto de elementos sin repetición

Una tarea común es elegir un número de elementos al azar de un conjunto sin coger el mismo más de una vez. Por ejemplo, usted podrá querer generar un número de NPCs en puntos de regeneración al azar, pero asegurarse de que sólo un NPC se genera en cada punto. Esto se puede hacer mediante la iteración a través de los elementos en la secuencia, tomando una decisión al azar para cada uno sobre si es o no, agregado al conjunto elegido. A medida que cada elemento es visitado, la probabilidad de ser elegido es igual al número de elementos que se necesita todavía, dividido por el número que aún queda para ser escogidos.

Como un ejemplo, supongamos que hay diez puntos de regeneración disponibles, pero sólo cinco deben ser usados. La probabilidad de que el primer punto sea elegido es de 5 / 10 o 0.5. Si es elegido, entonces la probabilidad para el segundo elemento será de 4 / 9 o 0.44 (ie, cuatro elementos todavía faltan, nueve restantes para elegir). Esto continúa hasta que el conjunto contenga los cinco elementos requeridos. Usted puede logar esto en código de la siguiente manera:-


//JS


var spawnPoints: Transform[];

function ChooseSet(numRequired: int) {
    var result = new Transform[numRequired];
    
    var numToChoose = numRequired;
    
    for (numLeft = spawnPoints.Length; numLeft > 0; numLeft--) {
        // Adding 0.0 is simply to cast the integers to float for the division.
        var prob = (numToChoose + 0.0) / (numLeft + 0.0);
        
        if (Random.value <= prob) {
            numToChoose--;
            result[numToChoose] = spawnPoints[numLeft - 1];
            
            if (numToChoose == 0)
                break;
        }
    }
    
    return result;
}


//C#

    Transform[] spawnPoints;

    Transform[] ChooseSet (int numRequired) {
        Transform[] result = new Transform[numRequired];

        int numToChoose = numRequired;

        for (int numLeft = spawnPoints.Length; numLeft > 0; numLeft--) {

            float prob = (float)numToChoose/(float)numLeft;

            if (Random.value <= prob) {
                numToChoose--;
                result[numToChoose] = spawnPoints[numLeft - 1];

                if (numToChoose == 0) {
                    break;
                }
            }
        }
        return result;
    }

Tenga en cuenta que aunque la selección es al azar, los elementos en el conjunto elegido estarán en el mismo orden que tenían en la matriz original. Si los elementos se van a utilizar de uno en uno en secuencia entonces el orden puede hacer que en parte sean predecibles, por lo que puede ser necesario barajar la matriz antes de su uso.

Puntos Aleatorios en el Espacio

Un punto aleatorio en un volumen cúbico puede ser elegido ajustando cada component de un Vector3 a un valor devuelto por un Random.value:-

 var randVec = Vector3(Random.value, Random.value, Random.value);

Cuando el volumen es una esfera (ie, cuando usted quiere un punto aleatorio dentro un radio dado de un punto de origen), usted puede utilizar Random.insideUnitSphere multiplicado por el radio deseado:-

 var randWithinRadius = Random.insideUnitSphere * radius;



 var randWithinCircle = Random.insideUnitCircle * radius;

… a una prueba de menos que-o-igual que, evitaría la instrucción return extra, pero también permitiría un elemento ser elegido ocasionalmente, incluso cuando su probabilidad es cero. Esto le da un punto dentro el cubo con lados de una unidad. El cubo puede ser escalado simplemente multiplicando los components X, Y y Z del vector por el tamaño del lado deseado. Si alguno de estos ejes es establecido a cero, el punto siempre estará dentro un plano único. Por ejemplo, escogiendo un punto aleatorio en el “piso” es usualmente una cuestión de ajustar los components X y Z aleatoriamente y ajustar el component Y a cero. Tenga en cuenta que si establece uno de los components del vector resultante a cero, usted no tendrá un punto correcto aleatorio dentro del círculo. Aunque el punto está indexado al azar y se encuentra dentro del radio adecuado, la probabilidad es muy sesgada hacia el borde del círculo y lo puntos se distribuirán de forma muy desigual. Usted, en lugar de, debería utilizar Random.insideUnitCircle para esta tarea. Tenga en cuenta que Random.Range devuelve el valor de un rango que incluye el primer parámetro pero excluye el segundo, entonces usando myArray.Length aquí le da el resultado correcto.

Transforms
Rotation and Orientation in Unity