4
\$\begingroup\$

I'm working on a Dice Application in Unity that involves randomly rotating a dice model on instantiation and then dropping it. But in order for this application to be useful I need to guarantee that the Dice Roll is close enough to random to be used for a live tabletop game. The problem I'm getting is that after some testing the dice rolls are not random at all.

Here's some pictures of a a D20 I'm working with as it looks at instantiation.

Bottom view:

Dice From the Bottom

Top view:

Dice From the Top

Side view 1:

Dice From the Side 1

Side view 2:

Dice From the Side 2

Side view 3:

Dice From the Side 3

My first attempt at randomizing the dice rolls was to use this code:

m_Instance.transform.Rotate(UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f));

But after 250,000 test rolls, the data shows that it's almost twice as likely to land on a side face than it is the top or bottom faces

enter image description here

After doing some digging, I found a recommendation to use this code instead:

m_Instance.transform.rotation = Quaternion.Euler(UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f));

Which gave me the exact opposite results, where faces on the top and bottom of the dice were suddenly twice as likely as those on the sides.

enter image description here

So my mad-scientist approach to this problem currently is to randomly pick between the two rotation methods. So that way it's randomly decided if one face will be twice as likely as another.

int randomRange = UnityEngine.Random.Range(0, 2);
    if (randomRange == 1)
    {
        m_Instance.transform.Rotate(UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f));
    }
    else
    {
        m_Instance.transform.rotation = Quaternion.Euler(UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f), UnityEngine.Random.Range(0.0f, 360.0f));
    }

Which is getting me close, but not quite perfect.

enter image description here

But it's not quite close enough for my liking as some faces like 10 seem favored by both methods. So my question is: is there something wrong with my code that's making the rolls not as random as I'm imagining? And if not, is there a way of rotating a dice in Unity such that the result of the rotation is suitably random enough to be used in a potentially professional setting?

EDIT: Thanks for the response DMGregory! That's definitely looking better, but I'm still seeing that 10 and 11 are favored sides after a million test rolls. with both getting a > '5.8' percent chance of landing there. Perhaps there's something wrong with my implementation of your code?

public void RandomizeDice()
{
    m_Instance.transform.rotation = UniformRandomRotation();
}

Quaternion UniformRandomRotation()
{
    float x0 = UnityEngine.Random.value;
    float theta1 = UnityEngine.Random.Range(0f, Mathf.PI * 2f);
    float theta2 = UnityEngine.Random.Range(0f, Mathf.PI * 2f);
    // Make this theta2 in the range (0, PI * 1) if you want w > 0

    float r1 = Mathf.Sqrt(1f - x0);
    float r2 = Mathf.Sqrt(x0);

    return new Quaternion(
        Mathf.Cos(theta2) * r2,
        Mathf.Sin(theta1) * r1,
        Mathf.Cos(theta1) * r1,
        Mathf.Sin(theta2) * r2);
}

public void UnRandomizeDice()
{
    m_Instance.transform.rotation = Quaternion.Euler(270, 270, 270);
}   
\$\endgroup\$
4
\$\begingroup\$

I recommend developing a healthy distrust of Euler angles. ;)

They're great for printing a particular rotation in a reasonably human-readable way. They're terrible for computing arbitrary rotations in a controlled way. Their parameters are not globally orthogonal, and as you've discovered, they don't uniformly cover the space of all rotations.

For any kind of computed rotation, you'll almost always have an easier time working in quaternions (though an argument an be made for yaw-pitch for camera rotatations where you only want a small,controlled slice of the space of all rotations)

Unfortunately Unity's built-in quaternion method Random.rotation is not uniform (something I'd never spotted before trying to roll dice with it, so thank you for this illuminating test case!), but we can make our own with its helper methods, or a little math.

For something quick and dirty, we can use:

 transform.rotation = Quaternion.LookRotation(Random.onUnitSphere, Random.onUnitSphere);

This forms a rotation that points the local z axis somewhere randomly in 3 dimensional space, and twists the local y axis randomly about that axis.

If you want to do the math yourself, we can sample a uniform random quaternion using the technique presented by Ken Shoemake in the Graphical Gems III chapter Uniform Random Rotations:

Quaternion UniformRandomRotation() {
    float x0 = Random.value;
    float theta1 = Random.Range(0f, Mathf.PI * 2f);
    float theta2 = Random.Range(0f, Mathf.PI * 2f);
    // Make this theta2 in the range (0, PI * 1) if you want w > 0

    float r1 = Mathf.Sqrt(1f - x0);
    float r2 = Mathf.Sqrt(x0);

    return new Quaternion(
        Mathf.Cos(theta2) * r2,
        Mathf.Sin(theta1) * r1,
        Mathf.Cos(theta1) * r1,
        Mathf.Sin(theta2) * r2);
}

I get the expected 5% probability for all 20 sides, plus or minus about 0.015% and falling as the trial count climbs.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the response! That's definitely looking better, but I'm still seeing that 10 and 11 are favored sides after a million test rolls. with both getting a > '5.8' percent chance of landing there. Perhaps there's something wrong with my inplemenation of your code? \$\endgroup\$ – Nick Burlingame Apr 21 '18 at 13:19
  • \$\begingroup\$ Does your die bounce using physics? It could be that the center of mass is a little off, or that the mesh is slightly asymmetric. I definitely didn't see consistent variances as much as 0.8%. \$\endgroup\$ – DMGregory Apr 21 '18 at 13:21
  • \$\begingroup\$ I edited my post above to show you what my code is doing. I know that my UnRandomize method returns the dice to it's original rotation but maybe it's doing something else while it does that My dice do have some bounciness, but during my testing I"m not even letting my dice hit the ground. I'm rotating them, reading them, and then resetting them before they have a chance to move much. \$\endgroup\$ – Nick Burlingame Apr 21 '18 at 13:25
  • \$\begingroup\$ How do you perform the reading? Are you checking which face normal is most vertical / which face center is closest to the top? \$\endgroup\$ – DMGregory Apr 21 '18 at 13:27
  • \$\begingroup\$ I think youre on to something. Each dice contains an array of empty game objects with their sole value being the face they represent. The gameobject is then placed in the center of its corresponding face. I loop through the array of gameobjects and find the one that has the highest y value and use that as the value of the roll. But to be honest when setting those gameobjects I just eyeballed it, so I may need to let the dice hit the ground to get the most accurate reading. \$\endgroup\$ – Nick Burlingame Apr 21 '18 at 13:32
1
\$\begingroup\$

Short answer

As of Unity version 2017.1 (maybe earlier), use Random.rotationUniform instead, as it is designed to provide a more uniform distribution.

Use-case answer

You don't have to perfectly simulate real, physical dice, so you are free to separate the dramatic player-feel from the mundane random-calculation.

I'd suggest running a dice-rolling animation, maybe with randomized parameters for spin time, travel distance, and number of bounces. Store the correct rotations to display each outcome/face, then calculate the result based on plain-old Random.Range. Set the final rotation to display the appropriate face, possibly using an Animation Event as a trigger.

Juice it up with some cool sound effects (different sounds for different surface & dice-material combos?), and maybe some subtle camera shake, and there you go.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.