I'm working on a brickout game clone, so I'm trying to add a multiply the ball feature to the game. This Video shows the feature.
Here is my code:
//access to all active balls
Ball[] balls = FindObjectsOfType<Ball>();
//loop for the length of the balls
for (int i = 0; i <= balls.Length; i++)
{
if (balls.Length >= 1000)
{
return;
}
else
{
// here I'm trying to fix the issue by checking if the ball exists, so I will call the function if not. return. but console show me same message
if (balls[i] != null)
{
balls[i].CallM3x();
i++;
}
}
}
The unity console message is:
IndexOutOfRangeException: Index was outside the bounds of the array.
My code above is working fine, but the issue is some balls destroy before the for loop end its work by less than a second .
Any idea how to avoid this?
There are two fundamental issues with your loop:
you're looping up to i <= balls.Length which will always throw an error (there is no a[10] in an array of length 10).
you're incrementing i again within the loop, which will skip items
Change the loop condition to i < balls.Length and remove the i++ within the loop.
Also, you don't need to check if (balls.Length >= 1000) within the loop every time - you can check that before the loop (but what's the problem with a collection with more than 10,000 items?)
the issue is some balls destroy before the for loop end its work by less than a second
I don't see how this can be a problem. You're creating an array of references that you're looping over. Even if items are destroyed within the game, the object references still exist within your array. There may be some other flag to tell you that an object is destroyed (I am not a Unity expert), but they would not change to null inside your array.
A simple way to avoid the issue of re-referencing objects you have deleted in a loop is by counting i down from balls.Length to 0 instead of up from 0 to balls.Length.
for (int i = balls.Length - 1; i >= 0; i--)
Also, you might want to look again at your for-loop: you are going up to <= balls.Length, allowing the highest index to be = balls.Length. That index is out of bounds though, because we start counting at 0. I suggest using < instead of <= here.
Ball[] balls = FindObjectsOfType<Ball>();
for (int i = 0; i < balls.Length; i++)
{
if (balls.Length >= 1000)
{
return;
}
else
{
if (balls[i] != null)
{
balls[i].CallM3x();
// i++; <- This is your problem. Remove it.
}
}
}
Related
I 'm not sure how to explain this and I cannot find an answer anywhere. I have a for loop that loops through lines that are represented through strings.
for (int i = 0; i < dataLines.Length; i++)
{
jumpPoint:
Debug.Log("Jumped");
string[] words = dataLines[i].Split();
.
.
. "words[] manipulation and reading"
.
.
}
I have no problems with any of my data processing or things that happen in the loop.
But I have an instance where i need to go to a previous dataLine[] and continue from that point (also re-runs code that has already been run since that point).
What I am doing essentially boils down to
i = ?; //arbitrary number for the situation that is definitely not out of bounds for the loop
goto jumpPoint;
Ive also tried without the jump point also just letting the loop cycle to the next after resetting the for loop index.
I know it's not an issue with the jump point as its used for unrelated things and the jump works fine. Also worth mentioning, that in those instances, I am increasing the i index so the for loop prematurely advances and that works perfect.
So why can I not go backwards in the loop? Is that just something that is not possible?
Not sure if I understand but you mean something like this:
var i = 0;
var startRun = true;
while (true)
{
if (!startRun && i == dataLines.Length - 1)
return;
if (startRun)
startRun = false;
/* do stuff */
if (needToJump)
{
clearLogicOrWhatever();
i = whereYouNeedToJump;
}
else
i++;
}
Of course you will need more validation if going out of index, if step exists or in range and other stuff, this is just like a proof of concept / pseudo.
Nothing is stopping you from going backwards in the loop. You are in control of your code logic, so all you have to do is when your "jumpPoint" condition is met, just change the iterator i back to the previous value that you desire.
For example: Let's say I want to jump back and re-run something because it had the word "jump" in it.
Console.WriteLine("App started.");
List<string> dataLines = new List<string>() { "this is a phrase", "This is another but with jump in it", "this is the last" };
bool alreadyJumped = false;
for (int i = 0; i < dataLines.Count; i++)
{
Console.WriteLine($"Currently Iterating: {i}");
string[] words = dataLines[i].Split();
Console.WriteLine($"Do some with this data: {dataLines[i]}");
if (words.Contains("jump") && !alreadyJumped)
{
alreadyJumped = true;
// Reset the i value so that the next iteration will run again.
i = i - 1;
}
else if (alreadyJumped)
{
// Once
alreadyJumped = false;
}
}
Console.WriteLine("App done.");
This will produce this output:
App started.
Currently Iterating: 0
Do some with this data: this is a phrase
Currently Iterating: 1
Do some with this data: This is another but with jump in it
Currently Iterating: 1
Do some with this data: This is another but with jump in it
Currently Iterating: 2
Do some with this data: this is the last
App done.
I'm aware (from similar posts) that infinite while loops are notorious for causing Unity3d to crash. I'm tring to impliment a while loop within something I'm working on, which I'm fairly sure isn't 'infinite', yet causes the game to crash.
The idea of the logic is to check a list of integers for consecutive numbers and use that as the basis to apply a bonus. The list contains 'effective shots', and has a new int added every time a shot is fired - the more consecutive effective shots, the higher the bonus.
Here's what I have:
int count = 0;
int firstItem = 0;
int multiples = 3;
int currentMultiple = 0;
int bonusX = 0;
foreach (int i in effectiveShots)
{
if (count == 0)
{
firstItem = i;
count = 1;
}
else if (i == firstItem + count)
{
count++;
}
}
while (count >= multiples)
{
currentMultiple = multiples;
if (count == currentMultiple + multiples)
{
bonusX += 1;
}
if (bonusX > 10 || gameOver)
break;
UnityEngine.Debug.Log(bonusX);
}
The logic to check for consective entries in the effectiveShots list was taken from #Jon Skeet's answer here. Though this appears to work, I think that this may be the issue. As soon as a shot is missed, count needs to be reset. Any ideas or suggestions?
The while loop should then be entered once the count of consecutive effective shots has reached the first multiple, i.e. 3 shots. Then, for every set of consequtive effective shots thereafter, increment the bonus, for example:
3 shots: bonusX = 1
6 shots: bonusX = 2
9 shots: bonusX = 3
12 shots: bonusX = 4
and repeat this until `count` is no longer >= 3, i.e. the player missed a shot.
The issue is that as soon as I hit 3 consequtive shots and enter this loop, the game crashes. I dont think I would call this an infinite loop, since missing a shot - setting count == 0 - would mean the while conditions are no longer true, so drop out of the loop (I think?). I also added an additional check to break out of the loop under certain circumstances, but this doesnt make a difference.
If you are able to give a steer as to how to fix this crashing, or have a suggestion on a better approach in general, it would be appreciated!
Nothing in your while loop changes the value of either count or multiples and so the condition will always evaluate to the same value => Infinite loop
I posted yesterday asking for help about the collisions between two instances in game, the enemies and bullets.
The solution was:
foreach (Enemy enemy in enemies)
foreach (Bullet bullet in bullets)
if (bullet.boundingBox.Intersects(enemy.boundingBox))
{
score += 1;
}
And of course this worked score wise, as I now gain a point every time a bullet intersects with an enemy. The problem occurs when I need to destroy both objects (not all, just the ones that have intersected). My first thought was to use the removeAt() function like so:
if (bullet.boundingBox.Intersects(enemy.boundingBox))
{
score += 1;
removeAt();
}
But of course this wouldn't work because generally it's used with an index such as "removeAt(i)". I then tried taking the whole chunk of code that I referenced up the top, and placing it inside both lists like so:
for (int i = bullets.Count - 1; i >= 0; i--)
{
bullets[i].Update(delta);
//Bullets being destroyed upon leaving
if (bullets[i].position.Y < 0)
bullets.RemoveAt(i);
foreach (Enemy enemy in enemies)
foreach (Bullet bullet in bullets)
if (bullet.boundingBox.Intersects(enemy.boundingBox))
{
bullets.RemoveAt(i);
}
}
The game will run at this point, but crash as soon as the bullet collides with the enemy. An error "System.InvalidOperationException" is thrown at the "foreach (Bullet bullet in bullets) line.
I honestly don't know where I should go from here, so any help is appreciated.
The reason for this would be because you are removing elements from bullets while you are enumerating over it. See this:
for (int i = bullets.Count - 1; i >= 0; i--)
You store the count of bullets in i, and decrease it every time. Then you have two places in your code where the logic removes bullets, thus changing the count of bullets. You are never supposed to modify an array you are enumerating over.
As to a possible solution, you could do exactly what Jason in the comments pointed out. Add a bool Delete property to your class, then instead of calling RemoveAt, set the Delete flag to true. The code would look like this:
for (int i = bullets.Count - 1; i >= 0; i--)
{
bullets[i].Update(delta);
//Bullets being destroyed upon leaving
if (bullets[i].position.Y < 0)
{
bullets[i].Delete = true;
}
foreach (Enemy enemy in enemies)
{
if (bullets[i].boundingBox.Intersects(enemy.boundingBox))
{
bullets[i].Delete = true;
}
}
}
bullets.RemoveAll(b => b.Delete);
Often, I prefer to maintain a new collection for objects to be removed, and then remove them after processing is complete. I find it easy to follow and doesn't have the questionable addition of a control property. Similar to this....
List<Bullet> expiredBullets = new List<Bullet>();
for (int i = bullets.Count - 1; i >= 0; i--)
{
bullets[i].Update(delta);
if (bullets[i].position.Y < 0)
{
expiredBullets.Add(bullets[i]);
}
}
foreach(Bullet expiredBullet in expiredBullets)
{
bullets.Remove(expiredBullet);
}
I am trying to make a program which can dispense change for a given amount. Basically I have a method to see if a given value can be met by the currencies in stock.
This is the loop:
private int check(int v)
{
int in = v;
for (int i = valueTable.Length; i >= 0; i--)//from biggest coin to smallest
{
for (int j = 0; j < supplyTable[i-1]; j++)//for the supply of the particular coin
{
if (in >= coinValueTable[i-1] && coinSupplyTable[i+1] > 0)
in -= coinValueTable[i-1];
}
}
return rt;
}
When running debug, it appears that the first iteration of the outer loop only establishes the second loop, and does not execute it. The inner loop is only executed on the outer loop's second iteration, essentially meaning that I miss the last value in the valueTable. I've played around a bit with the for loops, but it always seems to miss then ends of the array.
Any ideas as to why the inner loop doesn't run for the first iteration of the outerloop? Thanks in advance.
You have set coinSupplyTable[7] == 0, so naturally the first iteration, which takes the last element, has condition j < 0, which is immediately false and thus the loop does not run.
Nothing wrong there; no stock means no inner loop.
Also, your if condition inside the inner loop is not correct. You access coinSupplyTable[i+1], which will lead to an Index out of Range exception.
Finally, are you sure you need the inner loop? It seems to me that you only need the outer loop to iterate over each coin type, and then check the supply.
My current code:
Remove()
{
for (int i = 0; i < ConGridView.RowCount; i++)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
ConGridView.Rows.RemoveAt(i);
break;
}
}
}
So what I am trying to call the remove function every time a client disconnect. the function will remove the connection address from the datagridview. It works well when clients are disconnection one by one. However, if 100 connections gets dropped and it tries to remove 100 connections in less than a second, than it errors out saying "Row Index provided is out of range". How should I check for that ?
So far I've tried:
Try, catch.
if (ConGridView.Rows[i] != null), if (i < ConGridView.RowCount)
None of it seem to work so far. I've also got results using (i < ConGridView.RowCount) where i is 26 while RowCount is 24, but the remove at function still activates..
Any idea on this ?
You can't do this. Your code loops through all the rows in ConGridView, but it deletes them as you do. Therefore, at some point you will try to access an item you have deleted, which will cause the error you described.
Probably the best approach it to iterate through the rows in reverse order. This way, deleting a row at the end won't affect when you access rows at the start.
The problem is you initialise your for loop with the current count of rows and then start removing those same rows from the datagridview. At some point your for loop will try to remove a row at an index that is greater than the number of rows left.
Try this instead:
for (int i = ConGridView.RowCount - 1; i >= 0; i--)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
ConGridView.Rows.RemoveAt(i);
break;
}
}
why dont you get the total count to a separate variable and then iterate
Remove()
{
int totalConnections = ConGridView.RowCount;
for (int i = 0; i < totalConnections ; i++)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
ConGridView.Rows.RemoveAt(i);
break;
}
}
}
This issue is becuase you are modifying the collection your are iterating over. It will be better if you use a temporary array and two loops to remove your entries.
Remove()
// You can use an array/list or whatever you want below.
Collection<DataGridViewRow> rowsToDelete = new Collection<DataGridViewRow>();
for (int i = 0; i < ConGridView.RowCount; i++)
{
if (ConGridView.Rows[i].Cells[0].Value.ToString() == Address)
{
rowsToDelete.Add(ConGridView.Rows[i]);
break;
}
}
// now remove the marked entries.
foreach(DataGridViewRow deletedRow in rowsToDelete)
{
ConGridView.Rows.Remove(deletedRow);
}
When you remove an item from an array, it is reconstructed; shifting the remaining elements up by one to remove the gap of the index you have removed.
1. guybrush threepwood
2. murray
3. elaine
4. Jimmy Gibbs Jr.
If you remove 2. item in here; it becomes this:
1. guybrush threepwood
2. elaine
3. Jimmy Gibbs Jr.
When you are iterating, imagine:
for (int i = 0; i < myArray.Count; i++)
{
if (i == 2) myArray.RemoveAt(i);
}
While running this, when i = 3, the element at 3 has changed, you expect it to be 'elaine' but it is 'Jimmy Gibbs Jr.'. One way to fix this is decrease i by one if we delete it, making sure that i refers to correct value.
for (int i = 0; i < myArray.Count; i++)
{
if (i == 2)
{
myArray.RemoveAt(i);
i--;
}
}
I would go for LINQ in this case, though, everything is easier with that.
myArray.RemoveAll(x => x == "murray");
I've tried all the suggestions posted by everyone here, however, the error was still there.
I've solved the problem using a different way... I've switched to TreeNodeView since that's what I was going to use ultimately. Now I can remove as many connection as i want with:
For each(TreeNode TN in ConTreeView)
{
ConTreeView.Nodes.Remove(TN);
}