Lews Therin
Lews Therin

Reputation: 3777

Detect when all dice have stopped moving

This seems like such a simple problem but I'm having a hard time with it. I'm rolling some dice (GameObjects) and trying to detect when they all have stopped moving (so I can then calculate the score).

Here's what I've tried:

public class GameManager : MonoBehaviour
{
    public GameObject[] _dice;

    public Vector3 _rollStartPosition;
    public float _rollForce;
    public float _rollTorque;

    bool anyDieIsMoving = false;

    void FixedUpdate()
    {
        if (!anyDieIsMoving && Input.GetMouseButtonDown(0))
            RollDice();
    }

    void RollDice()
    {
        foreach (var die in _dice)
        {
            // Roll() adds force and torque from a given starting position
            die.GetComponent<Die>()
               .Roll(_rollStartPosition, Random.onUnitSphere * _rollForce, Random.onUnitSphere * _rollTorque);
        }

        StartCoroutine(CheckIfDiceAreMoving());

        // Calculate score and do something with it...
    }

    IEnumerator CheckIfDiceAreMoving()
    {
        foreach (var die in _dice)
        {
            var dieRigidbody = die.GetComponent<Rigidbody>();
            if (!dieRigidbody.IsSleeping())
            {
                anyDieIsMoving = true;
                yield return null;
            }
        }
    }
}

The problem with the above code is that it immediately tries to calculate the score before all the dice have stopped moving (I discovered this by adding a bunch of Debug.Log() statements).

How can I wait for all the dice to stop moving before calculating the score?

Upvotes: 1

Views: 287

Answers (3)

In addition to what the other two answers have said, I would change CheckIfDiceAreMoving() in this way (using siusiulala's version):

while(anyDieIsMoving){
    anyDieIsMoving = false;
    foreach (var die in _dice)
    {
        var dieRigidbody = die.GetComponent<Rigidbody>();
        if (!dieRigidbody.IsSleeping())
        {
            anyDieIsMoving = true;
            break;
        }
    }
    yield return null;
}

As soon as one die is not moving, break out of the for loop as it is no longer relevant. The original code puts the for loop on pause and returns to it at the same index later. Afterall, it's the while loop that we want coroutine'd.

Upvotes: 1

siusiulala
siusiulala

Reputation: 1060

In RollDice(), it calculate score before getting status of anyDieIsMoving. I think you show move the calculate score to coroutine.

void RollDice()
{
    foreach (var die in _dice)
    {
        // Roll() adds force and torque from a given starting position
        die.GetComponent<Die>()
           .Roll(_rollStartPosition, Random.onUnitSphere * _rollForce, Random.onUnitSphere * _rollTorque);
    }

    StartCoroutine(CheckIfDiceAreMoving());

}

IEnumerator CheckIfDiceAreMoving()
{
    while(anyDieIsMoving){
        anyDieIsMoving = false;
        foreach (var die in _dice)
        {
            var dieRigidbody = die.GetComponent<Rigidbody>();
            if (!dieRigidbody.IsSleeping())
            {
                anyDieIsMoving = true;
                yield return null;
            }
        }
    }

    // Calculate score and do something with it...
}

Upvotes: 1

Programmer
Programmer

Reputation: 125315

You have to make RollDice a coroutine function then you can yield or wait for the CheckIfDiceAreMoving function to return with yield return. Even better, turn the if (!dieRigidbody.IsSleeping()) into while (!dieRigidbody.IsSleeping()) so that the CheckIfDiceAreMoving function will not exit until all dice stops moving. Also, check input in the Update function instead of FixedUpdate which is used to move Rigidbody.

This is the rafactored code:

public class GameManager : MonoBehaviour
{
    public GameObject[] _dice;

    public Vector3 _rollStartPosition;
    public float _rollForce;
    public float _rollTorque;
    bool doneRolling = true;

    void Update()
    {
        if (doneRolling && Input.GetMouseButtonDown(0))
        {
            StartCoroutine(RollDice());
        }
    }

    IEnumerator RollDice()
    {
        doneRolling = false;

        foreach (var die in _dice)
        {
            // Roll() adds force and torque from a given starting position
            die.GetComponent<Die>()
               .Roll(_rollStartPosition, Random.onUnitSphere * _rollForce, Random.onUnitSphere * _rollTorque);
        }

        //Wait until all dice tops moving
        yield return CheckIfDiceAreMoving();


        // Calculate score and do something with it...


        //Set doneRolling to true so that we call this funtion again
        doneRolling = true;
    }

    IEnumerator CheckIfDiceAreMoving()
    {
        foreach (var die in _dice)
        {
            var dieRigidbody = die.GetComponent<Rigidbody>();
            //Wait until all dice stops moving
            while (!dieRigidbody.IsSleeping())
            {
                yield return null;
            }
        }
    }
}

Upvotes: 2

Related Questions