Rolling a 3D Dice: Detect which number faces up?
I trying to get something to work in unity 3D. I have modelled a 20 sided dice and 3 want a Cheep, Stable and Reliable mechanic to detect which way up the dice is facing. I think I will detect when the objects velocity reaches zero to call the number (Unless you'd suggest a better way). But I'm honestly not even sure where to start with determining the number. I'm quite new to unity and have only completed 1 2D game.
I code in C# too.
I have many different sided dice to apply this too, so a versatile method would be very desirable.
Thank you.
Answer by ekcoh · Jul 13, 2016 at 11:55 AM
If you have modeled the dice and imported it I guess you know which directions each side (number) is facing relative to the object space coordinate space. This could be solved in numerous ways. It could be done (as it sounds you like to do):
to find the number based on the orientation of the model
or, to animate the model to face a direction based on a random number draw
However, I assume based on your phrasing, that you want to do 1)
I would recommend setting up a pre-defined number of direction vectors in object space that maps to numbers in a script attached to the dice. This could be done via Inspector or hard-coded in script. For example you could use a Direction associative map for the lookup. What you can do then (as soon as you consider that the dice is no longer rolling), is to calculate which one of the direction vectors that is closest to a reference vector, e.g. world y-axis up. This would also work for non-flat surfaces as long as epsilon is set accordingly to a minimum angle. For example, let's say this is a script attached to your dice (Psuedo right out of my head into the forum so not checked):
// Setup in Awake() or via inspector....
Dictionary<Vector3, int> lookup = new Dictionary<Vector3, int>();
lookup[Vector3.up] = 1;
lookup[Vector3.right] = 4;
// etc, will of course be a bit more complicated for a non-traditional dice
....
public int getNumber(Vector3 referenceVectorUp = Vector3.up, float epsilonDeg = 5f) {
// here I would assert lookup is not empty, epsilon is positive and larger than smallest possible float etc
// Transform reference up to object space
Vector3 referenceObjectSpace = transform.InverseTransformDirection(referenceVectorUp);
// Find smallest difference to object space direction
float min = float.MaxValue;
Vector3 minKey;
foreach (Vector3 key in lookup.keySet()) {
float a = Vector3.Angle(referenceObjectSpace, key);
if (a <= epsilonDeg && a < min) {
min = a;
minKey = key;
}
}
return (min < epsilonDeg) ? lookup[minKey] : -1; // -1 as error code for not within bounds
}
If you want it to be versatile I would recommend calculating the lookup table based on the mathematics behind the dice layout. Or setup the script direction vectors and epsilon differently based on each dice model.
Hope it provides some sort of help @Raxs...
I really appreciate the depth of your explanation, it is well and truly the most comprehensive and well laid out answer I've ever had.
However, (I can safely assure it is a result of my own inexperience) I just don't understand this code, even with your notes I am having trouble breaking it down.
I can guess that the incomplete list you proposed are Vector 3s for the perpendicular angles that protrude for each face of the dice. So, realistically, the only thing I need to adjust in the code by adding in the data working out the direction of every individual face (depending on which die is cast). If that is true then this method is certainly as versatile as it gets.
I know it is bad practise to acquire code I don't fully understand, but if you could confirm that for me it would be fantastic help.
Happy to help out when there is some interesting problem to solve :) I made the effort to sort my sloppy example out. I will now choose to use two Lists ins$$anonymous$$d of a Dictionary (Since the solution to serializing dictionaries is out of topic) and maybe it is simpler to understand? I added a debug statement just for the sake of the example.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Dice : $$anonymous$$onoBehaviour {
public List<Vector3> directions;
public List<int> sideValues;
void Awake() {
// For the sake of this example we assume a regular cube dice if
// directions haven't been specified in the editor. Sum of opposite
// sides is 7, haven't consider exact real layout though.
if (directions.Count == 0) {
// Object space directions
directions.Add(Vector3.up);
sideValues.Add(2); // up
directions.Add(Vector3.down);
sideValues.Add(6); // down
directions.Add(Vector3.left);
sideValues.Add(2); // left
directions.Add(Vector3.right);
sideValues.Add(4); // right
directions.Add(Vector3.forward);
sideValues.Add(6); // fw
directions.Add(Vector3.back);
sideValues.Add(1); // back
}
// Assert equal side of lists
if (directions.Count != sideValues.Count) {
Debug.LogError("Not consistent list sizes");
}
}
void Start() {
// For sake of example, get number based on current orientation
// This makes it possible to test by just rotating it in the editor and hitting play
// Allowing 30 degrees error so will give (the side that is mostly upwards)
// but will give -1 on "tie"
Debug.Log("The side world up has value: " + GetNumber(Vector3.up, 30f));
}
// Gets the number of the side pointing in the same direction as the reference vector,
// allowing epsilon degrees error.
public int GetNumber(Vector3 referenceVectorUp, float epsilonDeg = 5f) {
// here I would assert lookup is not empty, epsilon is positive and larger than smallest possible float etc
// Transform reference up to object space
Vector3 referenceObjectSpace = transform.InverseTransformDirection(referenceVectorUp);
// Find smallest difference to object space direction
float $$anonymous$$ = float.$$anonymous$$axValue;
int mostSimilarDirectionIndex = -1;
for (int i=0; i < directions.Count; ++i) {
float a = Vector3.Angle(referenceObjectSpace, directions[i]);
if (a <= epsilonDeg && a < $$anonymous$$) {
$$anonymous$$ = a;
mostSimilarDirectionIndex = i;
}
}
// -1 as error code for not within bounds
return (mostSimilarDirectionIndex >= 0) ? sideValues[mostSimilarDirectionIndex] : -1;
}
}
Thank you for your dedication and enthusiasm (: I loaded the script onto a cube and it seems to work perfectly. I will mark your answer as accepted.
I'm guessing that when I use the inspector that the Vector 3 is the perpendicular angle, whilst the Integer is the number of the face? If so I'll get on with the maths! Haha.
Thank you for all your help!
Happy to hear that it worked.
Yes you are correct, the vectors are direction vectors representing the surface normal of each side.
Your code is incredible man, thank you so much! It's exactly what I needed!
Answer by matherkevin067 · Jan 31, 2018 at 01:20 PM
I think there is a much simpler way, with no complicated math.
Attach an Empty to each face of the cube/die.
In a script, read the Y-locations of each Empty.
The Empty with the maximum Y-location is on the top face.
Of course you still need to filter to determine when the bouncing stops.
Old thread I know but im trying this method and im not sure, how would I get the highest Y level in an array of my empty game objects?
var Sidesofdices = DiceIWillUse.GetComponent().SidesOfTheDice;
thats what I got so far. EDIT: for this solution I Used this
//get the array of all the sides empty game objects
var Sidesofdices = DiceIWillUse.GetComponent<DiceStats>().SidesOfTheDice;
//check which one is the highest on the Y access
var gameObject = Sidesofdices.OrderBy(go => go.transform.position.y);
//a var set to the object with the highest Y value in the array
var highest = Sidesofdices.Last();
Answer by NatCou · Oct 23, 2019 at 08:36 PM
Hi currently working on this - so it might help somebody else :) Add box colliders to each face of your cube, a trigger to your floor and read which collider hits...
void OnTriggerStay(Collider col) { if (diceVelocity.x == 0f && diceVelocity.y == 0f && diceVelocity.z == 0f) { switch (col.gameObject.name) { case "Side1": Debug.Log("Hit side 1 " + diceNumber + "is showing"); diceNumber = 20; break; case "Side2": Debug.Log("2 " + diceNumber + "is showing"); diceNumber = 19; break; case "Side3": Debug.Log("3 " + diceNumber + "is showing"); diceNumber = 18; etc }
..Of course this is what you do when your not a math genius ;P Best!
Answer by gnurk · Apr 01, 2020 at 10:22 PM
if ( Vector3.Dot ( Vector3.up, transform.up ) > 0.9f )
The Dotproduct gives 1 if the transform aligns perfectly upwards and is 0 if the "up" direction of the dice points sideways. "transform.up" corresponds to the green arrow in local coordinates. If the dot product is bigger than 0.9 we can say that the two Vectors point pretty much in the same direction.
Your answer
Follow this Question
Related Questions
Adding Gravity to a game object to make a black hole sucking effect. 1 Answer
Is it possible to put collision detection in an if statement? 0 Answers
Camera always facing players direction 1 Answer
How can we control two player objects at one time with RayCasting? 1 Answer
How to set ArticulationBody motion to "limited" in C# 0 Answers