Snake Program stops running without exception - c#

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.

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 do I access specific raycast hits in a 'for' loop?

I have a loop that draws 4 raycasts from the bottom of my character, which are used to detect collision with objects. My issue is that if raycast #1 and raycast #4 are colliding with different objects they return the values from both objects.
I'd like to make it so that I look for a hit on raycast #4 only, and if that doesn't return a hit then I check raycast #3, etc. Once I have a hit I will check for the value of the object with which the raycast is colliding. I tried using RaycastHits[], but I believe this is intended to be used when you want to analyze multiple hits within all raycasts, not just a single raycast.
for (int i = 0; i < VerticalRayCount; i++)
{
Vector2 rayOrigin = (directionY == -1) ? RaycastOrigin.bottomLeft : RaycastOrigin.topLeft;
rayOrigin += Vector2.right * (VerticalRaySpacing * i + deltaMovement.x);
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, collisionMask);
Debug.DrawRay(rayOrigin, Vector2.up * directionY * rayLength, Color.red);
if (directionY == -1)
{
if (directionX == 1)
{
if (i == VerticalRayCount -1)
{
if (hit)
{
if (currentPlatform != hit.collider.gameObject)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
}
}
}
else if (i == 0)
{
if (hit)
{
if (currentPlatform != hit.collider.gameObject)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
}
}
}
}
}
}
I see two problems in your code:
In your for-loop currentPlatform gets allways overwritten by the last hit.
But since you check if(currentPlatform != hit.collider.gameObject) it will allways switch between the last two hits if there is more than one. This happens because it hits the first one and sets it. Than the second hit is not equal to the first one so it gets overwritten. So the next time the currentPlatform is still the second hit and gets again overwritten with the first hit of the new loop and always goes on like this.
To avoid the second remove those if(currentPlatform != hit.collidergameObject).
And you should add break; after a hit so the for loop doesn't continue with the other raycasts but rather exits the for loop immediately. So you get only one hit and don't overwrite them.
...
if(hit)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
// here add break so the for loop is not continued
break;
}
...
On this way currentPlattform allways has the value of the first hit.

If-else statement for changing levels

I have a got a function for changing levels in my game. In the game, the first level changes to the second level correctly. But the second level changes to the first level too, then count = 4. How can I fix this? Here is my code:
public void CheckLevelCompletion() {
if ((count == 4) && (levelOneCompleted == false)) {
img.sprite = levelCompletedImage;
count = 0;
levelOneCompleted = true;
SceneManager.LoadScene("FifthGameSecondLevel");
}
else if ((count == 6) && (levelOneCompleted == true))
{
img.sprite = levelCompletedImage;
count = 0;
levelTwoCompleted = true;
SceneManager.LoadScene("FifthGameThirdLevel");
}
}
I don't know your code steps, but maybe it is this situation, I think.
If scene reloaded, your game object in previous scene is destroyed.
So levelOneCompleted is always false.
It is why SceneManager.LoadScene("FifthGameThirdLevel"); does not run.
If it is right, I recommend levelOneCompleted make public and set true in second level game object.

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

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.

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 ;)

Categories

Resources