I have a game I am developing in Unity where AI is doing large calculations when it is its turn. It searches the position to depth 1, then 2, then 3 etc. Between each depth I want to instantiate a Gameobject with info about the depth to UI. The problem is that nothing happens until the AI is completely finished, then all items are added at once. Here is some code to explain better:
private void AIMakeMove()
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
SearchPosition(currentDepth);
}
}
private void SearchPosition(int _currentDepth)
{
// Search the position to the given depth
score = Negamax(_currentDepth);
// Print things PROBLEM HERE
GameObject printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
I also tried just a simple Debug.Log instead of Instantiate but same thing happens then, all prints to console happens after the AI is done with its thinking process.
Why is my UI not updating with information? I tell it to create some things after it run the first iteration with depth 0 but it skips this step and goes on depth 2 instead. Can someone please let me know how to get information out between each depth?
The problem is that nothing happens until the AI is completely finished
well the UI is only updated if the Unity main-thread is allowed to finish a frame.
You, however, block the main thread until all iterations are finished.
If it is okey for you to block between each instantiation then you could simply use a Coroutine and do something like
private void AIMakeMove()
{
StartCoroutine(AIMakeMoveRoutine());
}
private IEnuemrator AIMakeMoveRoutine()
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
SearchPosition(currentDepth);
// This now tells Unity to "interrupt" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
private void SearchPosition(int _currentDepth)
{
score = Negamax(_currentDepth);
GameObject printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
This will finish a frame and start a new one (thus refresh the UI) after each finished iteration.
However, if this still blocks the rest of your application too much you should additionally actually run the calculation async e.g. using a Task like
private void AIMakeMove()
{
StartCoroutine(AIMakeMoveRoutine());
}
private IEnuemrator AIMakeMoveRoutine()
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
// you can yield another IEnuemrator -> executes this and waits for it to finish
yield return SearchPosition(currentDepth);
// This now tells Unity to "interrupt" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
private IEnumerator SearchPosition(int _currentDepth)
{
// run the NegamaxTask asynchronously in the background
var task = Task.Run(() => Negamax(_currentDepth));
// wait for the task to finish
while(!task.IsCompleted)
{
// do nothing but skip frames to allow the rest of the application to run smoothly
yield return null;
}
// If you do nothing else inside the loop this could also be written as
//yield return new WaitWhile(() => !task.IsComoleted);
// or
//yield return new WaitUntil(() => task.IsCompleted);
// since the task is already finished it is save / non-blocking to access the result now
score = task.Result;
var printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
Now this allows your application to continue with a normal frame-rate while in the background you do the heavy calculations and once in a while get a result back when an iteration is finished.
Try using a thread:
private void AIMakeMove()
{
new System.Threading.Thread(() =>
{
for (int currentDepth = 1; currentDepth < maxDepth + 1; currentDepth++)
{
SearchPosition(currentDepth);
}
}).Start();
}
private void SearchPosition(int _currentDepth)
{
// Search the position to the given depth
score = Negamax(_currentDepth);
// Print things PROBLEM HERE
GameObject printItem = Instantiate(printItemPrefab, printItemParent.transform);
Debug.Log(_currentDepth);
}
Related
I am programming a task in VR where the user hears an audio file with a certain amount of numbers and needs to press a key after that. If he did it right then he should press "r" and an audio file that is one number longer will be played. If he did it wrong he should press "w" and if it was his first wrong answer after a right answer an audio file will be played that has the same amount of numbers than before. If he did it wrong and already did it wrong right before that the amount of numbers is reduced by one.
In total there will be 10 audio files which is why I decided to use a for loop.
But now I don't know how I can make the for loop wait for the user input. I've also read that it is in general not god to use a for loop when you have to wait for a user input but I don't know what else I can do.
Here is my code so far:
IEnumerator playDSBtask()
{
bool secondWrong = false;
fillList();
for (int i = 0; i < 10; i = i + 1)
{
List<AudioSource> x = chooseList(score);
AudioSource myAudio = x[0];
float duration = myAudio.clip.length;
myAudio.GetComponent<AudioSource>();
myAudio.Play();
yield return new WaitForSeconds(duration + 5);
if (Input.GetKeyDown("r"))
{
score++;
secondWrong = false;
}
else if (Input.GetKeyDown("f") && secondWrong == false)
{
secondWrong = true;
}
else if (Input.GetKeyDown("f") && secondWrong == true)
{
score--;
}
x.Remove(myAudio);
}
}
But this won't work since the for loop will just continue if none of the if or else if statements are true and therefor executed.
Using a for loop in your case is totally fine since it is in a Coroutine where you yield inside of it so there will be no endless loop and therefore freeze of the main thread.
You could probably use WaitUntil something like
...
yield return new WaitForSeconds(duration + 5);
// Wait until any o the expected inputs is pressed
yield return WaitUntil(() => Input.GetKeyDown("r") || Input.GetKeyDown("f"));
// then check which one exactly
if (Input.GetKeyDown("r"))
{
...
alternatively this could also be done in a more generic way but also more error prone
...
yield return new WaitForSeconds(duration + 5);
// wait until something breaks out of the loop
while(true)
{
// check if any key was pressed
if(Input.anyKeyDown)
{
// handle r key
if (Input.GetKeyDown("r"))
{
score++;
secondWrong = false;
// important!
break;
}
else if (Input.GetKeyDown("f"))
{
if(!secondWrong)
{
secondWrong = true;
}
else
{
score--;
}
// Important!
break;
}
}
// if nothing called break wait a frame and check again
yield return null;
}
x.Remove(myAudio);
...
In general you might want to use KeyCode.F and KeyCode.R instead of strings.
From a UX perpective though you would either want to reduce the 5 seconds wait or/and show some kind of UI feedback for when user input is handled/awaited.
I am trying to use two yields within a coroutine loop (because I need to iterate out arrays with pauses between each loop).
The first loop works correctly, with all the yields working for the right amount of time. By the second loop, the yield return new WaitForSeconds() begins counting down right away, not waiting for the yield and code before it to complete (it seems). By the time of the third loop, the timing is all off.
I tried using a while loop instead of a for but got the same result.
TLDR: I need to loop out my arrays with pauses between each one. How can I use more than one yield past the first loop through in a coroutine?
public IEnumerator doPathfinding()
{
for (int i = 0; i < waypoint.Length; i++)
{
// get first waypoint from array
var _waypoint = waypoint[i];
// get node on A* of cloest waypoint
closestPointOnNavmesh = AstarPath.active.GetNearest(_waypoint.transform.position, NNConstraint.Default).clampedPosition;
// Move towards destination
ai.destination = closestPointOnNavmesh;
// Wait until within X range of waypoint
yield return new WaitUntil(() => distanceReached == true);
// Agent is now at waypoint and automatically stops. Wait 5 seconds before looping to next waypoint.
yield return new WaitForSeconds(5f);
}
Debug.Log("Loop End");
}
public override void OnUpdate()
{
// Get current distance to the target. If distance is less than offset, then sent event.
currentDistance = Vector3.Distance(go.transform.position, closestPointOnNavmesh);
if(currentDistance <= arrivalOffset.Value)
{
distanceReached = true;
}
else
{
distanceReached = false;
}
}
The code inside the couroutine is fine, it works as intended.
Most probably, based on the issue you reported, you're calling more than once the coroutine at the same time.
Use a bool to check if the coroutine has already started or not, like this:
bool isDoPathFindingRunning = false;
IEnumerator = doPathFinding();
private void Awake() {
pathFinding = doPathfinding();
}
private void WhereeverYouStartCoroutine() {
if (!isDoPathFindingRunning)
StartCoroutine(pathFinding);
}
public IEnumerator doPathfinding() {
isDoPathFindingRunning = true;
// Do your stuff
isDoPathFindingRunning = false;
}
One of the scripts on my scene involves creating a grid of cubes. The process does take a fair amount of time and I've been trying to implement it without causing Unity to become unresponsive.
The code at present - works to effect - you can watch as the grid is generated. However, the other scripts in the scene are doing their tasks at the same time and is causing exceptions (they're accessing tiles on the grid which are not yet created).
The script below is the only script that has an 'Awake' function - the other scripts are executing 'Update' and 'OnGUI' before the first script has finished its awake.
GridGenerator.cs
public GameObject[,] tiles = new GameObject[Constants.BOARD_WIDTH, Constants.BOARD_HEIGHT];
// Run a double loop to create each tile.
void Awake ()
{
StartCoroutine(doSetup());
}
IEnumerator doSetup()
{
yield return StartCoroutine (loadLevel());
DoOtherStuff();
}
IEnumerator loadLevel()
{
for (int i = 0; i < Constants.BOARD_WIDTH; i++)
{
for (int j = 0; j < Constants.BOARD_HEIGHT; j++)
{
tiles[i,j] = newTile();
yield return(0);
}
}
}
How would I make the doOtherStuff() method wait until the co-routine is finished?
I've tried a boolean toggle (which froze unity)
I've tried using lock (which didn't work)
Or is there a more efficient way of doing this?
EDIT: The code successfully completes OtherStuff() after the the grid is generated. Apart of the doOtherStuff() is to pass a reference of the grid to a separate class.
The separate class is throwing exceptions when trying to access the grid in the Update method. Which suggests, the update method is being called before the Awake in the GridGenerator.cs is completed.
The easiest way would be to kick off your coroutine within another coroutine, so that you can yield to it:
void Awake ()
{
StartCoroutine(doSetup());
}
IEnumerator doSetup ()
{
yield return StartCoroutine(createGrid()); // will yield until createGrid is finished
doOtherStuff();
}
IEnumerator createGrid()
{
for (int i = 0; i < Constants.BOARD_WIDTH; i++)
{
for (int j = 0; j < Constants.BOARD_HEIGHT; j++)
{
// Instantiate a new tile
yield return(0);
}
}
}
}
I'm trying to create something like particle system on XNA 4, C#.
I've created a function that makes particles move from each other if they get close enough. It contains cycle in cycle and therefore it is laggy. Without this function, program starts to lag with 400-500 of particles, and with function - nearly 180.
Question 1. Can I improve perfomance by creating background thread to process these particle collisions?
So, created thread with timer working inside. When I launch my game, it starts fine but when number of particles gets more than nearly 130-150, there appears a runtime error Collection has changed. Unable to perform enumeration." (it is a translation from another language, not the exact message).
Collection "neighbours" - local variable in a function Particle.RunAwayFromNeighbours - is being changed during the enumeration process, then, i think, one thread is somehow calling function while previous call is not completely performed. A strange thing.
I tried to use Threading.Monitor class to synchronize calls, but I have very few experience of multi-thread programming so I think i made something wrong. It didn't solve the problem, it's still the same error with the same count of particles.
I also tried to use lock operator but it's still the same situation.
Question 2. How do I synchronize threads, finally?
There is class Player, he "owns" some particles. Other thread works in this class. Code below is written using Monitor class.
Code ( ... is not necessary part):
class Player
{
...
Thread CollisionThread;
static double check_period=100;
System.Timers.Timer checktimer = new System.Timers.Timer(check_period);
public void StartCollisionChecking()
{
checktimer.AutoReset = true;
checktimer.Elapsed += (o, e) => { CheckCollisions(); };
CollisionThread = new Thread(this.CheckCollisionCycle);
CollisionThread.Start();
} //call in LoadContent
void CheckCollisionCycle()
{
checktimer.Start();
} //call 1 time in new thread
void CheckCollisions()
{
for (int i = 0; i < Army.Count - 1; i+=2 )
{
var p = Army[i];
p.RunAwayFromNeighbours();
}
} //called in CheckCollisionCycle
}
class Particle : VO
{
static float neighbour_search_radius = 2;
static float run_speed_q = 0.1f;
IEnumerable<Particle> CheckForNeighbours()
{
return owner.GetArmy().Where(a => Vector2.Distance(a.location.GetXY(), location.GetXY()) < neighbour_search_radius);
}
public void RunAwayFromNeighbours()
{
object x = new object() ;
Monitor.Enter(x);
try
{
var neighbours = CheckForNeighbours();
foreach (Particle p in neighbours)
{
Vector2 where_to_run = location.GetXY() - p.location.GetXY();
speed += where_to_run * run_speed_q;
}
}
finally
{
Monitor.Exit(x);
}
}
Chances are that neighbours is being modified during your foreach, try doing:
foreach (Particle p in neighbours.toList())
And see if that sorts it.
This will have you iterate over a copy of neighbours. It's not the best solution (you should avoid this collision in the first place) but it's a quick workaround to see if that's actually where the fault lies.
I solved the problem by making 2 things:
1. I added bool variable, that shows whether thread has finished its work or not, into class Particle. It looks like this:
public bool thread_completed = true;
public void RunAwayFromNeighbours()
{
thread_completed = false;
object x = new object() ;
Monitor.Enter(x);
try
{
var neighbours = CheckForNeighbours().ToList();
foreach (Particle p in neighbours)
{
Vector2 where_to_run = location.GetXY() - p.location.GetXY();
speed += where_to_run * run_speed_q;
}
}
finally
{
Monitor.Exit(x);
thread_completed = true;
}
2. I changed line
return owner.GetArmy().Where(a => Vector2.Distance(a.location.GetXY(), location.GetXY()) < neighbour_search_radius).ToList();
to line
return owner.GetArmy().ToList().Where(a => Vector2.Distance(a.location.GetXY(), location.GetXY()) < neighbour_search_radius);
I've been messing around trying to create an analog style scoring system for my game that I am building with Unity3D and I've noticed a huge lag when I call AnalogScoreProcessing() from the updateScore() function:
/******************************** Update Score ********************************/
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
AnalogScoreProcessing(oldScore, newScore);
}
// Display GUI score
oldScore = newScore;
guiText.text = "" + oldScore;
}
/*************************** Analog Scoring System ****************************/
void AnalogScoreProcessing(int oldScore, int newScore){
for(int i = oldScore; i <= newScore; i++){
print (i);
}
}
Do I have to create a new thread or a co-routine to complete the looping task while the remaining parts of updateScore() are carried out? I've never done any threading but I have used co-routines. I'm just not sure what I should be using here.
The easiest solution is to use coroutines here.
You basically want the players to see the effect of counting up right?
So say you want to display a count up to the user being +1 to the user ever few frames or so so the count up will be perceivable.
Try...
I would do it like this
int current =0;
int target = 0;
float percentateOfTarget = 0;
float step = 0.1f;
IEnumerable AnalogScoreProcessing()
{
while(current <= target)
{
current = (int)Math.Ceiling(Mathf.Lerp(current, target, percentateOfTarget));
return yield new WaitForSeconds(step);
percentageOfTarget += step;
// Display GUI score
guiText.text = "" + current;
}
}
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
StopCoroutine("AnalogScoreProcessing");
current = oldScore;
target = newScore;
StartCoroutine("AnalogScoreProcessing");
}
}
Please be aware this code is pulled directly out of my head so youll probably have to tweek some things here and there but should give you something close to what you desire.
You could/should even scale the GUIText up while the coroutine running for an even more dramatic effect. Let me know how it goes.
As I side note coroutines are not separate threads in unity they are ran on unity's main thread.
Well i dont understandt the Logic behind the function, but cant you just print the score once it changes?
if (oldScore != newScore)
{
//AnalogScoreProcessing(oldScore, newScore);
Debug.log(newScore);
}
Also you have to set the GUI calls inside the
OnGUI()
function inside your file.
If there is a large difference between oldScore and newScore then the print function in AnalogScoreProcessing will be run many times.
You might want to look at using a tween to change the displayed value, as you cannot display more than one value per frame anyway. Or if you're printing to the console then... why are you doing that?
edit: have moved quick and dirty solution a more appropriate question/answer