Collection was modified enumeration operation may not execute. Unity [duplicate] - c#

This question already has answers here:
C# Collection was modified; enumeration operation may not execute [duplicate]
(5 answers)
Closed 5 years ago.
I am currently making an RTS style game. I made this fog of war script for making enimies invisible:
void Update () {
foreach (GameObject enemy in enemies) {
if (enemy == null) {
enemies.Remove (enemy);
continue;
} else {
Visible = false;
foreach (GameObject vision in visions) {
if (vision == null) {
visions.Remove (vision);
continue;
} else {
Vector3 point = enemy.GetComponent<Collider> ().ClosestPoint (vision.transform.position);
float range = vision.GetComponent<FieldOfView> ().viewRadius;
float distance = Vector3.Distance (point, vision.transform.position);
if (distance <= range) {
Visible = true;
break;
}
}
}
MeshRenderer render = enemy.GetComponent<MeshRenderer> ();
if (Visible) {
if (!render.enabled) {
render.enabled = true;
}
} else if (!Visible) {
if (render.enabled) {
render.enabled = false;
}
}
}
}
}
}
Whenever an enemy gamobject gets destroyed, I get the error in the title. I'm still pretty new at programming and dont see what could be causing the problem.
I looked around myself for a bit on other threads with the same problem, but I dont really get how to fix it.
Any help is appreciated. If any more information is needed, just ask and I will try and provide it.

Look at this bit of your code:
foreach (GameObject vision in visions) {
if (vision == null) {
visions.Remove (vision);
Now, look back at the error you're getting:
Collection was modified enumeration operation may not execute.
What it is telling you is that you cannot modify a collection while iterating over it with foreach. That includes adding and deleting elements, as well as modifying existing ones.
There are lots of ways to deal with this issue. In general, the ideal solution would be to refactor your code so that modifying it during iteration isn't necessary. If you don't want to do that however, this dirty fix will also work most of the time:
foreach (GameObject vision in visions.ToList())
In your specific case (removing nulls) however, I would do the following:
visions = visions.Where(i => i != null).ToList();
This will remove all null elements. And then you iterate over it afterwards.

Related

Unity script when my character hits the trashbin the item i picked up must be deleted

So i am making a level of unity: In this level you have to sort the garbage.
The player can move with the arrow keys, and can pick up trash with the E key. Then take it to the right trash can. I made a script that should make the character pickup the item and then he can go to the right trashbin and if he hits the trashbin the item will be destroyed, but it does not work and I have no idea what is wrong.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class PickupItem : MonoBehaviour
{
public float pickupRange = 2f;
public LayerMask pickupLayer;
public AudioClip pickupSound;
public string[] pickupTags;
public AudioClip wrongBinSound;
public string[] trashBinTags;
public TextMeshProUGUI itemNameText;
private AudioSource audioSource;
private GameObject currentObject;
private bool holdingItem = false;
private void Start()
{
audioSource = GetComponent<AudioSource>();
}
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, pickupRange, pickupLayer))
{
foreach (string tag in pickupTags)
{
if (hit.collider.tag == tag && !holdingItem)
{
currentObject = hit.collider.gameObject;
if (Input.GetKeyDown(KeyCode.E))
{
StartCoroutine(Pickup());
}
break;
}
}
}
else
{
currentObject = null;
}
}
private void OnCollisionEnter(Collision collision)
{
foreach (string trashBinTag in trashBinTags)
{
if (collision.gameObject.tag == trashBinTag)
{
switch (currentObject.tag)
{
case "paper":
if (trashBinTag == "TrashbinPa")
{
Debug.Log("paper in vuilbak");
audioSource.PlayOneShot(pickupSound);
itemNameText.text = "";
holdingItem = false;
}
break;
case "glass":
if (trashBinTag == "TrashbinG")
{
Debug.Log("glass in vuilbak");
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
itemNameText.text = "";
holdingItem = false;
}
break;
case "metal":
if (trashBinTag == "TrashbinM")
{
Debug.Log("metal in vuilbak");
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
itemNameText.text = "";
holdingItem = false;
}
break;
case "plastic":
if (trashBinTag == "TrashbinP")
{
Debug.Log("plastic in vuilbak");
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
itemNameText.text = "";
holdingItem = false;
}
break;
default:
audioSource.PlayOneShot(wrongBinSound);
break;
}
break;
}
}
}
IEnumerator Pickup()
{
if (currentObject != null)
{
Debug.Log("Object picked up");
yield return new WaitForSeconds(1);
audioSource.PlayOneShot(pickupSound);
currentObject.SetActive(false);
itemNameText.text = "Inventory: " + currentObject.name;
holdingItem = true;
}
}
}
My character settings:
enter image description here
One of mine Trashbin(Glass):
enter image description here
One of mine Trash items(Wine bottle):
enter image description here
I don't know what i'm doing wrong can someone help me?
I tried to debug but no outcome. I can pickup the item but i cannot hit the trashbin so it can delete the item. I use tags for the right trashbin which also use tags.
Alright, so let's take it one step at a the time. First of all, that code needs a lot of optimization as it's a FPS-drop nightmare.
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, pickupRange, pickupLayer))
{
foreach (string tag in pickupTags)
{
if (hit.collider.tag == tag && !holdingItem)
{
currentObject = hit.collider.gameObject;
if (Input.GetKeyDown(KeyCode.E))
{
StartCoroutine(Pickup());
}
break;
}
}
}
else
{
currentObject = null;
}
}
Here you are raycasting every frame regardless of the player wanting to pick up the trash or not (i.e. hitting the E key). This doesn't make much sense. As I said in my comment, this check if (Input.GetKeyDown(KeyCode.E)) should be a lot earlier.
Also, if the player has holdingItem == true, raycasting to search for additional trash doesn't really make sense if the player can only carry one trash at a time. So you can optimize that code out this way. I would even argue that holdingItem will cause conflicts since the same effect can be achieved by using currentObject != null, therefore, I'll recycle holdingItem.
Next, iterating through lists each frame, also adds overhead. If you see the break, you actually call it only if this condition if (hit.collider.tag == tag && !holdingItem) is fulfilled. That means that regardless of holding an item or pressing E, as long as the player stares at some trash, you will Raycast each frame, iterate through the list each frame and compare the tag.
I mentioned tag compare. Comparing tags as string also adds some overhead. Please see GameObject.CompareTag() as that is the proper way to efficiently compare tags. Also learn how conditions work, both for or as well as for and. In your case you chose the most inefficient way to compare:
if (hit.collider.tag == tag && !holdingItem)
Even if you would choose NOT to use CompareTag() (although you should use it), this will always be more efficient:
if (!holdingItem && hit.collider.tag == tag)
This is because if the player holds an item, the next condition is skipped. When using or and and clauses in conditions always use the least expensive and most common one first.
The else is also dodgy there. That means that if the player holds an object, that object will be set to null if a Raycast doesn't hit anything in range. This might be the reason why your trash destruction fails.
This should be better, I think:
private void Update()
{
if (currentObject == null && Input.GetKeyDown(KeyCode.E))
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, pickupRange, pickupLayer))
{
holdingItem = false; // you have a 1 second delay in Pickup()
// no delay here
foreach (string tag in pickupTags)
{
if (!holdingItem && hit.collider.gameObject.CompareTag(tag))
{
holdingItem = true; // stops overlaps
StartCoroutine(Pickup(hit.collider.gameObject));
break;
}
}
}
}
}
IEnumerator Pickup(GameObject trash)
{
if (holdingItem && trash != null && currentObject == null)
{
#if UNITY_EDITOR
// don't include this outside tests
Debug.Log(trash.tag + " picked up");
#endif
yield return new WaitForSeconds(1);
audioSource.PlayOneShot(pickupSound);
currentObject = trash;
currentObject.SetActive(false);
itemNameText.text = "Inventory: " + currentObject.name;
}
}
Next, let's look at the trash bin.
There is a ton of repeated code that can just be simplified away. Easier for maintenance too, not to mention bug fixes.
This is how I'd write OnCollisionEnter():
private void ThrowTrashAway()
{
#if UNITY_EDITOR
// don't include this outside tests
Debug.Log(currentObject.tag + " in vuilbak");
#endif
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
currentObject = null;
itemNameText.text = "";
holdingItem = false;
}
private void OnCollisionEnter(Collision collision)
{
if (currentObject == null)
return; // don't execute anything
if ((collision.gameObject.CompareTag("TrashbinPa") && currentObject.CompareTag("paper"))
|| (collision.gameObject.CompareTag("TrashbinG") && currentObject.CompareTag("glass"))
|| (collision.gameObject.CompareTag("TrashbinM") && currentObject.CompareTag("metal"))
|| (collision.gameObject.CompareTag("TrashbinP") && currentObject.CompareTag("plastic")))
ThrowTrashAway();
else
audioSource.PlayOneShot(wrongBinSound);
}
Also keep in mind you don't destroy paper trash in your original code, like you do with other types of trash. So if you tested with paper, it wouldn't have worked anyway.
At first glance I'd say it's because of how you originally wrote the code in Update(). Of course this is also blind guessing since you haven't provided any debugging info regarding what steps the code executed in each branch on the stack nor did you tell us how each log call was or wasn't called. Regardless of whether you apply the suggestions, at least keep the concept in mind for further projects. Just because checking something in Update() is easier doesn't mean you have to do it there.
Still, as I said in my comment, the best way to really know why your code did not execute is a debugging session (not Debug.Log() calls, although they can help as well but they should never replace real debugging).
Let us know how it turned out.
I'm not entirely sure what your switch statement is doing, since currentObject gets disabled when it gets picked up (currentObject.SetActive(false);). By disabling it, the raycast in your Update() method will not find anything, and currentObject will be set to null every frame.
This will prevent any of your destruction code from running inside the OnCollisionEnter() method.
I would suggest either not disabling the currentObject, or pausing the raycast in Update() when an object is being held. This will allow the switch statement to run inside the collision method, and should destroy your object.

How can I rename my creature's model's hierarchy without breaking animations?

I am a beginner at Unity in terms of skill so please explain as if you were talking to a child if you can!
PROBLEM
I would like to change these names here:
I would like to rename them for two reasons:
so they are more intelligible
because I am using many different assets from the store and each has a different hierarchy with different names and I want to standardize the names so that I can use the below code to determine which part of the creature's body was shot so that it works for every creature
public void CreatureHit(string bodyPart, GunInfo usedWeapon, float intensity) // for guns
{
usedWeapon.PlayHit(creatureSounds);
if (creatureInfo.healthPoints > 0) // to prevent dead creatures from being shot
{
if ("Torso" == bodyPart || "LeftUpperArm" == bodyPart // if the part that was hit was the arms or torso
|| "RightUpperArm" == bodyPart || "LeftLowerArm" == bodyPart // if the part that was hit was the arms or torso
|| "RightLowerArm" == bodyPart)
{
creatureInfo.healthPoints -= usedWeapon.damage * intensity; // deal standard dmg
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.BODYSHOT;
}
else if ("Head" == bodyPart) // if the part that was hit was the head
{
creatureInfo.healthPoints -= usedWeapon.damage * 10 * intensity; // deal 10x dmg
audioSource.PlayOneShot(creatureSounds.hitHead, 1);
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.HEADSHOT;
}
else if ("RightUpperLeg" == bodyPart || "LeftUpperLeg" == bodyPart
|| "RightLowerLeg" == bodyPart || "LeftLowerLeg" == bodyPart)
{
creatureInfo.healthPoints -= usedWeapon.damage / 2 * intensity; // deal half dmg
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.BODYSHOT;
}
}
}
WHAT I TRIED
I renamed them in the hierarchy but then the animations stopped working. I found an old thread from the Unity forum asking if this was possible in 2015 and the OP was told that it wasn't. There were some later technical replies and I felt overwhelmed so I thought I should just create my own thread.
NOTE: there are multiple dozens of characters each with 10+ animations so ideally I need a very efficient solution.
In general you still can't unfortunately. (At least not that simple see below).
The AnimationClips are based on strings storing the relative path from the Animator to the according GameObject the type of the according component and finally the name of the animated serialized fields and properties.
If any of those change e.g. because you renamed the object or change the hierarchy in general the connection is lost and the animation breaks.
You could implement an editor script method that
goes through the affected Animator (GetComponentInParent) of the object
iterates through all used AnimationClips
iterates through each clips property bindings
redirects the property path accordingly to your renaming
This could look somewhat like this
private static void RenameObject(GameObject gameObject, Animator animator, string newName)
{
if (!gameObject)
{
throw new ArgumentException("No object provided", nameof(gameObject));
}
if (string.IsNullOrWhiteSpace(newName))
{
throw new ArgumentException("Object name may not be empty!", nameof(newName));
}
if (!animator)
{
throw new ArgumentException($"Selected object {gameObject} is not a child of an {nameof(Animator)}!", nameof(gameObject));
}
if (gameObject.transform == animator.transform)
{
return;
}
// get the relative path from the animator root to this object's parent
var path = AnimationUtility.CalculateTransformPath(gameObject.transform.parent, animator.transform);
if (gameObject.transform.parent != animator.transform)
{
path += "/";
}
// then append the old and new names
var oldPath = path + gameObject.name;
var newPath = path + newName;
// get the runtime Animation controller
var controller = animator.runtimeAnimatorController;
// get all clips used by this controller
var clips = controller.animationClips;
var changeableObjects = new List<Object>(clips.Length + 1) { gameObject };
changeableObjects.AddRange(clips);
Undo.RecordObjects(changeableObjects.ToArray(), "Change animated object name");
// Go through all clips
foreach (var clip in clips)
{
var floatBindingInfo = new List<AnimationFloatBindingInfo>();
// Get and store all FLOAT keyframe bindings
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
var curveInfo = new AnimationFloatBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
floatBindingInfo.Add(curveInfo);
}
var objectBindingInfos = new List<AnimationObjectBindingInfo>();
// also do the same for all reference keyframe bindings
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
{
var curve = AnimationUtility.GetObjectReferenceCurve(clip, binding);
var curveInfo = new AnimationObjectBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
objectBindingInfos.Add(curveInfo);
}
// a little check to avoid unnecessary work -> are there any affected property curves at all?
if (floatBindingInfo.Count + objectBindingInfos.Count > 0)
{
// Now erase all curves
clip.ClearCurves();
// and assign back the stored ones
AnimationUtility.SetEditorCurves(clip, floatBindingInfo.Select(info => info.Binding).ToArray(), floatBindingInfo.Select(info => info.Curve).ToArray());
AnimationUtility.SetObjectReferenceCurves(clip, objectBindingInfos.Select(info => info.Binding).ToArray(), objectBindingInfos.Select(info => info.Curve).ToArray());
EditorUtility.SetDirty(clip);
}
}
// finally rename the object
gameObject.name = newName;
EditorUtility.SetDirty(gameObject);
}
Since this use case is quite common I took some time to implement an EditorWindow for this. It is still a bit raw but works and supports also undo redo ;) You can find it here
-> Select the object in the Hierarchy -> right click -> "Rename safe for Animator"
You could of course add some shortcut to it etc. that's up to you ;)
Here a little demo of the dialog in action, renaming some nested objects and also performing some undo/redo
However, an alternative in your use case to simply get your code to work with the names as they are might be using tags instead.
As I see it your code is based on three different cases so you could simply have a tag for each like e.g. Head, Arms, Legs and assign and check those accordingly (GameObject.CompareTag) and not touch the names and animations at all.
I have 2 plans.
Create an empty GameObject under the node you want to rename, and attach the collider compoent on it.
CATRigHub001Bone004
└ CATRigHub001Bone004Bone001
└ Rig <-------- Collider
Rename the bone in editor and create a script to automatically rename it to its original name while playing.
public class Rename : MonoBehaviour
{
public string boneName;
[NonSerialized] public string partName;
void Awake()
{
partName = name;
name = boneName;
}
}

Snake Program stops running without exception

I am trying to program snake in console. After a random amount of steps the snake takes, a new GameObject spawns (e.g. an apple that makes the snake grow or a mushroom which makes the snake faster).
I create a random gameObject (on random (left, top) coordinates and then I need to check if that gameObject spawns on top of a snakeBodyPart or on top of another gameObject. If that is the case, I create a new gameObject and try the same again. Only if it doesn´t collide with another gameObject or the Snake, it will spawn.
snakeElemnts = a list, which has all the snake- body-parts;
gameObjects = a list with all existing gameObjects;
If the noCollisionAmount == the amount of snakeElements and GameObjects, there should be no collision and the new GameObject should spawn.
Unfortunately at one point (early on), the snake just stops moving and nothing happens (no exception or anything).
I can´t debug because I have a KeyboardWatcher running, which checks if a key gets pressed. Therefore when I press break, I can only examine the keyboardwatcher.
Setting a breakpoint is not useful, because I never break when the problem arises.
if (this.randomStepNumber == 0)
{
int noCollision = 0; // this variable counts the amount of times there was no collision
GameObject validGameObject;
while (true)
{
validGameObject = this.snakeGameObjectFactory.Generate();
foreach (SnakeElements element in snakeElements)
{
if (validGameObject.XPosition != element.XPosition || validGameObject.YPosition != element.YPosition)
{
noCollision++;
}
else
{
break;
}
}
foreach (GameObject gameObject in gameObjects)
{
if (noCollision == snakeElements.Count) // if there was no collision of the new gameobject with an element of the snake, the gameobjects will get checked
{
if (validGameObject.XPosition != gameObject.XPosition || validGameObject.YPosition != gameObject.YPosition)
{
noCollision++;
}
else
{
break;
}
}
else
{
break;
}
}
if (noCollision == snakeElements.Count + gameObjects.Count) // if there was no collision at all, the check is ended
{
break;
}
else
{
noCollision = 0;
}
}
this.gameObjects.Add(validGameObject);
this.randomStepNumber = this.random.Next(10, 30);
}
Based on your comments that removing this block of code causes the problem to cease, I thought maybe it would be good to clean up the code a little and remove that potential infinite loop with the continue statement. Making use of a little LINQ Enumerable.Any and a do-while loop, we can simplify the logic of the above code. There isn't really a need to be counting collisions, since the purpose of the code is to create a new object iff a collision is detected. That means if we detect a collision with the snake, then we want to generate a new object, or if we detect a collision with another existing object, then we want to generate a new object. So instead of counting, we use the Any statement to check if there is any collision with the snake or an existing object. If there is, then generate a new object. If not, then use that object and continue. Note: In order to use the Enumerable.Any you will need to add a using statement for the namespace System.Linq.
if (randomStepNumber == 0)
{
GameObject validGameObject;
do
{
validGameObject = snakeGameObjectFactory.Generate();
}
while(snakeElements.Any(s => validGameObject.XPosition == s.XPosition && validGameObject.YPosition == s.YPosition) ||
gameObjects.Any(o => validGameObject.XPosition == o.XPosition && validGameObject.YPosition == o.YPosition));
gameObjects.Add(validGameObject);
randomStepNumber = random.Next(10, 30);
}
As a side note. I removed the this keyword as it appeared you were not using it in all cases in your code, and if you do not have to use it, it makes the code a little more readable because it is less wordy. If it needs to be added back because of variable collisions, then I would suggest renaming the variables as having member and local variables with the same name can also be confusing when trying to debug something.
Secondary note - here is a version of the code not using LINQ, in case that is not allowed for your homework.
if (randomStepNumber == 0)
{
GameObject validGameObject = null;
while(validGameObject == null)
{
validGameObject = snakeGameObjectFactory.Generate();
foreach(var snake in snakeElements)
{
if (validGameObject.XPosition == snake.XPosition &&
validGameObject.YPosition == snake.YPosition)
{
validGameObject = null;
break;
}
}
if (validGameObject != null)
{
foreach(var gameObject in gameObjects)
{
if (validGameObject.XPosition == gameObject.XPosition &&
validGameObject.YPosition == gameObject.YPosition)
{
validGameObject = null;
break;
}
}
}
}
gameObjects.Add(validGameObject);
randomStepNumber = random.Next(10, 30);
}
I think you may not understand how "continue" works.
if (noCollision > snakeElements.Count + gameObjects.Count)
{
continue;
}
you have that at the beginning of a while loop. If it's found true you will be stuck in an infinite loop.
secondly, you have a While(true) in there that will never let you save a valid result since it's outside the loop. You should replace the while with a do while and check for a valid result at the end of it, then loop if there was a collision.

Checking Number of GameObjects in my array

I need to know the number of enemyCans left, so when there are none remaining I can trigger my win condition. Right now, I have a script on my explosion component that functions properly meaning it removes any Enemy Cannon that comes within a certain distance of an explosion.
enemyCans is declared and assigned the following value in the start() method, accordingly:
GameObject[] enemyCans; //Before start
void Start() {
enemyCans = GameObject.FindGameObjectsWithTag("EnemyCannon");
}
Then, I use that value in a method called CannonKiller() which iterates through the Enemy Cannon's transforms to check and see if the explosion comes near them. I'm sure this isn't the most elegant way of doing that, but the aforemention method is listed below:
void CannonKiller()
{
foreach(var cannon in GameObject.FindGameObjectsWithTag("EnemyCannon").Select(enemyCans => enemyCans.transform).ToArray())
{
foreach (var aCan in enemyCans)
{
float enemyDis = Vector3.Distance(cannon.position, transform.position);
if (enemyDis <= 4)
{
Destroy(aCan);
}
}
}
}
I would like to be able to have the ability to check and see in the nested foreach loops to see if the number of enemy is zero so I can call my finish method. I assumed something like this would work:
if (enemyCans == 0) //placed inside the foreach
{
finish("enemy");
}
but I was incorrect. How can i check to see if their are no remaining enemy cannons in order to call my finish method.
If possible, I'd suggest avoiding having too many nested foreach loops like that - although Jerry's answer does work, in the worst case you would basically have an O(n3) complexity algorithm, and it's a bit tough to read.
If you have colliders on all your turrets, then you should leverage the physics engine instead. The intent of your code will at least be much clearer if you use a method like Physics.OverlapSphere to identify the turrets hit by the explosion.
So adjusting CannonKiller() to destroy hit turrets and determine whether they all have been destroyed (but in an arguably neater way), your method might look like:
void CannonKiller()
{
// Grab colliders in vicinity of explosion
Collider[] hitColliders = Physics.OverlapSphere(transform.position, 4);
foreach (Collider hitCollider in hitColliders){
// Only act if collider belongs to an enemy cannon
if (hitCollider.gameObject.tag == "EnemyCannon"){
Destroy(hitCollider.gameObject);
// If there are no non-null references to cannon objects, they're all destroyed
if (enemyCans.FirstOrDefault(cannon => cannon != null) == null){
// Execute finishing code, then probably break
}
}
}
}
Since I saw you were already familiar with LINQ, I used it for the "all destroyed" check.
This may not be the best approach, but I think it's as good as it will get without heavily changing your implementation. (Having a manager class as Joe suggested is a good way to split up responsibilities between classes, and make your code more testable/maintainable - so definitely look into that, since it will scale much better as your project grows.)
Well, this code will work:
void CannonKiller()
{
foreach(var cannon in GameObject.FindGameObjectsWithTag("EnemyCannon").Select(enemyCans => enemyCans.transform).ToArray())
{
foreach (var aCan in enemyCans)
{
float enemyDis = Vector3.Distance(cannon.position, transform.position);
if (enemyDis <= 4)
{
Destroy(aCan);
bool allDestoyed = true;
foreach (GameObject o in enemyCans)
{
if (o != null && o != aCan)
{
allDestoyed = false;
break;
}
}
if (allDestoyed)
{
// Here you know all are destroyed
}
}
}
}
}
But I must say it is very ugly way of programing ;)

How can I avoid poor program performance from a TRY statement?

I am currently developing a basic voxel (cuboid) game engine, and I have encountered a performance issue. Every frame the game:
Finds player's viewing direction → Checks every co-ordinate in the player's direct view path → Finds closest occupied space → Draws a wireframe around this block.
I am using OpenTK, a binding of OpenGL for .NET/Mono (specifically C#). My code OnRenderFrame contains this:
try
{
for (int i = 0; i < 8; i++)
{
Vector3 block = InterfaceUtils.FindBlockLoc(Utilities.RoundToInt(cam.Position + (cam.lookat * i)));
if (block != Vector3.Zero)
{
// Draw the wireframe
}
}
}
catch
{
}
The purpose of this code is to check up to eight blocks from the player until it finds a block. The only problem with this is that the try statement most often throws a (handled) exception: but the lag caused by this is massive. When I look into space, I only get 11FPS, but I get 50 if the code is successful.
If the code is successful (it can draw the wireframe), I get this:
The block-finding works by checking the objects list for a block in that location.
public static Vector3 FindBlockLoc(Vector3 location)
{
Vector3 result = new Vector3(Vector3.Zero);
result = Deltashot.Game.objects.Find(v => v.Position == location).Position;
if (result != null)
{
return result;
}
else
{
return Vector3.Zero;
}
}
However, this returns a NullReferenceException and I'm forced to use a TRY statement to get it to run. Which is back to square one again...
Throwing and catching Exceptions is a costly operation in C#, and it seems you are using exceptions as flow control.
Your should avoid the NullReferenceException instead of catching it whenever it happens.
I would guess the exception happens here:
result = Deltashot.Game.objects.Find(v => v.Position == location).Position;
I'd suggest something like this:
public static Vector3 FindBlockLoc(Vector3 location)
{
var result = Deltashot.Game.objects.Find(v => v.Position == location);
if (result != null && result.Position != null)
{
return result.Position;
}
else
{
return Vector3.Zero;
}
}
If you change this line
result = Deltashot.Game.objects.Find(v => v.Position == location).Position;
to this:
result = Deltashot.Game.objects.Find(v => v.Position == location);
if (result != null)
{
return result.Position;
}
...
You might be able to prevent the exception, if the Find method returns null, and you try to access any members from a null result, you get a null reference exception - You should rather than wrap something in a try/catch, spend a little time trying to figure out why you are getting the exception.
An Easy way to do this is to break up all code at dots with checks (until you figure out where the error is happening) As it is also possible that Deltashot.Game is null or Deltashot.Game.objects is null.
I fixed this issue by changing my function to:
public static Volume FindBlock(Vector3 location)
{
return Deltashot.Game.objects.Find(v => v.Position == location);
}
So that I returned the whole Volume object (not just the position). I then changed the code that called it to this:
for (int i = 0; i < 8; i++)
{
var block = InterfaceUtils.FindBlock(Utilities.RoundToInt(cam.Position + (cam.lookat * i)));
if (block != null)
{
// Draw the wireframe
}
}
So that block was initially typeless, and compared it to var's definition of null (which doesn't throw the exception!)
Thank you to Juliano for the (similar) solution and to tophallen for his diagnostic advice.

Categories

Resources