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