Reputation: 3777
This seems like such a simple problem but I'm having a hard time with it. I'm rolling some dice (GameObject
s) 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
Reputation: 15941
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
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
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