Dice roller
overview
This Unity-based tool simulates real-world dice rolling using physics-based interactions, providing a visually realistic and tactile experience. After each roll, the system automatically detects the upper-facing side of each die and reads the result, making it perfect for developers, tabletop enthusiasts, or anyone exploring randomization and object detection in Unity. It’s designed as a modular, lightweight utility that can be integrated into larger projects or used independently for testing, demoing, or enhancing digital board game experiences.
Year
2025
Genre
Utility / Simulation
Platform
PC
let's roll!
How to use
- Use the “Wrench” button in the top left to open the selection menu.
- Use “Spacebar” to roll the current selected dice.
C#
using System.Linq;
using UnityEngine;
public class DiceRoller : MonoBehaviour
{
// Rolls the dice using the given force and torque
public void RollDice(Vector3 force, Vector3 torque)
{
Rigidbody rb = GetComponent<Rigidbody>();
// Reset any current motion before applying new forces
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
// Apply force and torque to roll the dice
rb.AddForce(force, ForceMode.Impulse);
rb.AddTorque(torque, ForceMode.Impulse);
}
// Returns the number on the top face of the dice
public int GetTopFace()
{
// Get all DiceFace components on child objects
DiceFace[] faces = GetComponentsInChildren<DiceFace>();
// Pick the one whose 'up' vector is most aligned with world up
DiceFace top = faces.OrderByDescending(f =>
Vector3.Dot(f.transform.up, Vector3.up)
).First();
return top.GetFace(); // Return the face number
}
// Checks if the dice has stopped moving
public bool IsDiceSettled()
{
Rigidbody rb = GetComponent<Rigidbody>();
// Check if both movement and rotation are nearly zero
return rb.velocity.sqrMagnitude < 0.01f && rb.angularVelocity.sqrMagnitude < 0.01f;
}
}
C#
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class DiceManager : MonoBehaviour
{
[Header("Possible dice prefabs")]
[SerializeField] private GameObject[] dicePrefabs; // All available dice prefabs (D6, D20, etc.)
[Header("Spawn Settings")]
[SerializeField] private Transform spawnPoint; // Where dice spawn in the scene
private List<DiceRoller> activeDice = new(); // Currently spawned and rolling dice
private bool canRoll = true; // Prevents re-rolling until all dice settle
private void Update()
{
// Pressing space triggers a roll
if (Input.GetKeyDown(KeyCode.Space))
{
RollAll();
}
}
// Coroutine to spawn dice one by one with force
public IEnumerator SpawnDice(Vector3 spawnDirection)
{
yield return new WaitForSeconds(1f); // Optional delay before spawn begins
int sides = UIManager.Instance.GetCurrentSelectedDice(); // Get selected dice type (e.g. 6)
int amount = UIManager.Instance.GetCurrentSliderValue(); // Get number of dice to spawn
if (spawnPoint == null)
{
Debug.LogError("Spawn point is not set!");
yield break;
}
Debug.Log($"Spawning {amount} D{sides} dice...");
for (int i = 0; i < amount; i++)
{
// Find the correct prefab (e.g., "D6" or "D20")
var prefab = dicePrefabs.FirstOrDefault(d => d.name.Contains($"D{sides}"));
if (prefab == null) yield break;
// Create the dice at the spawn point with a random rotation
GameObject newDice = Instantiate(prefab, spawnPoint.position, Random.rotation);
DiceRoller roller = newDice.GetComponent<DiceRoller>();
// Apply force in spawn direction with random torque for realism
Vector3 randomForce = spawnDirection * Random.Range(6f, 20f);
Vector3 randomTorque = Random.onUnitSphere * Random.Range(3f, 6f);
roller.RollDice(randomForce, randomTorque);
activeDice.Add(roller);
yield return new WaitForSeconds(0.2f); // Slight delay between dice
}
yield return new WaitForSeconds(1.5f); // Let dice bounce before closing gate
FeedbackManager.Instance.CloseGateFeedback().PlayFeedbacks(); // Optional feedback
}
// Triggers dice roll sequence
public void RollAll()
{
if (!canRoll) return; // Don’t roll again if dice are still settling
canRoll = false;
UIManager.Instance.ClearBoard(); // Clear any existing results
Vector3 spawnDirection = spawnPoint.forward; // Dice move forward from spawn point
FeedbackManager.Instance.OpenGateFeedback().PlayFeedbacks();
StartCoroutine(SpawnDice(spawnDirection)); // Begin dice spawning
StartCoroutine(WaitForDiceToSettle()); // Wait for all dice to finish moving
}
// Coroutine to wait until all dice have stopped rolling
private IEnumerator WaitForDiceToSettle()
{
yield return new WaitForSeconds(3f); // Allow initial bounce time
while (activeDice.Any(d => !d.IsDiceSettled()))
{
yield return new WaitForSeconds(0.5f); // Check again later
}
DisplayResults(); // All dice have stopped — show results
}
// Destroys all active dice and clears the list
public void ClearDice()
{
foreach (var die in activeDice)
{
Destroy(die.gameObject);
}
activeDice.Clear();
}
// Collects the result from each die and returns a list of (value, name)
public List<(int result, string diceName)> ReadAllResults()
{
var results = activeDice.Select(d => (d.GetTopFace(), d.gameObject.name)).ToList();
return results;
}
// Sends all dice results to the UI and re-enables rolling
public void DisplayResults()
{
var results = ReadAllResults();
UIManager.Instance.ShowDiceResults(results);
canRoll = true;
}
// Not used currently, but could be used for randomized spawning
Vector3 RandomSpawnPoint() => new Vector3(Random.Range(-2, 2), 2f, Random.Range(-2, 2));
// External getter for canRoll flag
public bool GetCanRoll() => canRoll;
}
Used assets
- Low Poly Fantasy Bundle
Publisher: JustCreate
- Violet Themed UI
Publisher: The GUI Guy
- FEEL
Publisher: MoreMountains
- Medieval Ambient Fantasy
Alakrab