Checking Number of GameObjects in my array - c#

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

Related

What is the best way to return all objects within a 3D area?

I'm currently working on an FPS and trying to create a "Gamemode" controller that gives the team with the most team members within a defined 3d area a point for their team. My first attempt looks something like:
public class TDM : Gamemode
{
private Collider pointArea;
private Dictionary<Player, int> teamA;
private Dictionary<Player, int> teamB;
private int methodNumberOfPlayersOnPoint(List<Player> players)
{
int count = 0;
foreach (Player player in players)
{
if (pointArea.bounds.Contains(player.transform.position))
{
count++;
}
}
return count;
}
}
is there a better way to do this?
You can call OverlapSphere which gets all the elements within a sphere. Or a box using OverlapBox or capsule using OverlapCapsule.
void GettAll(Vector3 center, float radius)
{
Collider[] objectsHit = Physics.OverlapSphere(center, radius);
foreach (var obj in objectsHit )
{
// do something for each object
}
}
I don't have much experience in Unity yet so I'll let someone else provide code, but I believe what you're looking for are Trigger Colliders. You basically want to let the engine itself do the heavy lifting for checking for intersection of your player with the zone.
Then in the method that gets called when the player enters the area you can add them to the count that is being used by your scorekeeping component for calculating point accrual. When the player leaves you remove them from the count.
If the position alone (ignoring any shape of objects) is enough precise then this is probably already a good approach.
You could/should cache the pointArea.bounds though before the loop so it is fetched only once.
And you could use Linq to simplify the code like
private int methodNumberOfPlayersOnPoint(List<Player> players)
{
var bounds = pointArea.bounds;
return players.Count(player => bounds.Contains(player.transform.position));
}
Note though that the Bounds in Unity are always world axis aligned! That means if your area is rotated in any way the bounds are not accurate.
In such case you might wan to rather use a PhysicsOverlapBox which allows you to check a specific rotation as well. This requires all your objects having colliders of course
private static int methodNumberOfPlayersOnPoint(BoxCollider area)
{
return Physics.OverlapBox(area.transform.TransformPoint(area.center), area.size / 2f, area.transform.rotation).Length;
}
Note for now this returns ALL objects within this area. you might want to filter for a certain LayerMask and include a filter for the Team (e.g. tag or some ID stored in Player)

How to detect which GameObject is being collided with

I'm somewhat new to Unity, and I'm trying to figure out a way to add different point values to these different birds when you hit them with a rock (don't ask). It was working like this:
Destroy(other.gameObject);
player.GetComponent<PlayerController>().score += 1;
It adds 1 point to the player when a bird is hit, but when I try to detect which bird was being hit, I get this error:
"Property or indexer 'Component.gameObject' cannot be assigned to -- it is read only"
Is there a better way to do this? Please explain to me like I'm a child or else I will probably not understand.
GameObject bird1;
GameObject bird2;
GameObject bird3;
void Start()
{
bird1 = GameObject.Find("Bird1");
bird2 = GameObject.Find("Bird2");
bird3 = GameObject.Find("Bird3");
}
void OnTriggerEnter(Collider other)
{
if(other.gameObject = bird1)
{
Destroy(other.gameObject);
player.GetComponent<PlayerController>().score += 1;
}
else if(other.gameObject = bird2)
{
Destroy(other.gameObject);
player.GetComponent<PlayerController>().score += 2;
}
else if(other.gameObject = bird3)
{
Destroy(other.gameObject);
player.GetComponent<PlayerController>().score += 3;
}
}
you can use gameObject.name.Equals() for checking which bird collided.
void OnTriggerEnter(Collider other)
{
if(other.gameObject.name.Equals("Bird1"))
{
Destroy(other.gameObject);
player.GetComponent<PlayerController>().score += 1;
}
else if(other.gameObject.name.Equals("Bird2"))
{
Destroy(other.gameObject);
player.GetComponent<PlayerController>().score += 2;
}
else if(other.gameObject.name.Equals("Bird3"))
{
Destroy(other.gameObject);
player.GetComponent<PlayerController>().score += 3;
}
}
The error you're encountering is fairly straightforward. If we look at one of the if-statements:
if(other.gameObject = bird1)
The problem lies with the equality-sign you're using. A single "=" is an assignment, whereas a comparison (which is what you want here) uses two; "==".
A simple little mnemonic I sometimes use is that "It takes two to compare".
If you want to detect if the GameObject being hit is exactly this specific bird, something along what you have makes sense. But if your intent is more along the line of this kind of object that is a bird, there are a few different ways to go about it.
The first one is to use Tags. You can essentially add a string-label to objects in your game as being "this kind of thing", and run comparisons on the name to verify that what you've hit is what you want to respond to. Unity already have a few predefined tags (Untagged, Respawn, Finish, EditorOnly, MainCamera, Player, GameController), but you can create custom ones. Tags are great for doing a rough sorting.
Say that you defined a custom tag named "Bird". You could then check it like so:
if(other.gameObject.tag == "Bird")
You can read more about tags here, and also see a similar usage on Unity's documentation on collisions. Do note that a Collision and a Collider are not exactly the same.
Using tags may not give you exactly what you want, however. I see that you would like to add different scores depending on what bird you hit. This could be handled by having a separate script on the birds, which contain their score-value. You can then increment the score on your PlayerController based on the value stored in the bird, which also separates the responsibilities of your objects a bit.
EDIT: here's an example on how the above could be accomplished. Say you have a small Bird-script, like so:
public class Bird : MonoBehaviour
{
public int value;
}
Attaching this to your bird object, the value field can now be changed from within the editor. Give the bird a "Bird" tag as well. Then, if your OnTriggerEnter in your existing script is changed to something like this:
void OnTriggerEnter(Collider other)
{
if(other.gameObject.tag == "Bird")
{
Bird bird = other.GetComponent<Bird>();
player.GetComponent<PlayerController>().score += bird.value;
Destroy(other.gameObject);
}
}
Then it should work nicely.
EDIT 2: as #acornTime pointed out, you can also use the CompareTag method, which is slightly faster, and handles some error cases:
other.gameObject.CompareTag("Bird");

Why variable is not increased when collision is detected?

I have a float named kill and I want to increase it every time when a bullet collide with an enemy.
This is my code but it isn't working. It always remains zero.
public float kill = 0;
Text killed;
void OnCollisionEnter2D(Collision2D bullet)
{
if(bullet.collider.tag == "bullet")
{
Destroy(gameObject);
kill++;
}
}
void Update()
{
killed = GameObject.Find("killed").GetComponent<Text>();
killed.text = kill.ToString();
}
First, don't call GameObject.Find or GetComponent frequently, like in Update, as they are expensive operations. Instead, call them as few times as you can (such as calling them once in Start) and cache the result.
Also, your kill count should by all means be an integer, unless you have a very good reason it shouldn't be.
Also, when you call Destroy(gameObject) you're basically getting rid of all of the values in the fields of the component. - So you should be storing your kill count elsewhere. You could use a singleton for this, or a static class member, or even simply just using the text component to keep track of the value - you can use int.TryParse for that.
Finally, to set the inital value of the text, you can check if it's already a number and if it isn't, set it to zero. int.TryParse can also be used for that.
Altogether:
Text killed;
void Start()
{
killed = GameObject.Find("killed").GetComponent<Text>();
int curKilled;
if (!int.TryParse(killed.text, out curKilled))
{
// Does not already contain a number, set it to zero
killed.text = "0";
}
}
void OnCollisionEnter2D(Collision2D bullet)
{
if(bullet.collider.tag == "bullet")
{
int curKilled;
if (int.TryParse(killed.text, out curKilled))
{
killed.text = (curKilled+1).ToString();
}
else
{
// assume it should have been zero
killed.text = "1";
}
Destroy(gameObject);
}
}
The script is attached to enemy?Seems like it should be attached to the player object. Since the kill counter should belong to the player? If it is attached to the enemy object ,you also have to add to kills before destroying the game object

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.

Using foreach loop to find GameObjects that are children of something else (Unity3d)

I am making a video game, and while I have an existing code in generic C#, I now have to move it to Unity. I have some basic knowledge with generic C#, but I just started to learn the Unity way of coding.
For starters, I want to write a code that positions all game areas to correct positions, then turn them invisible. Yes, don't be surprised, they need to be all in same places.
Areas can have three size options, I called them Small, Medium and Large. Small and large areas have to be written manually.
List <GameObject> SmallAreas = new List<GameObject>();
void DefineSmallAreas()
{
SmallAreas.Add(areaConfirmLoad);
SmallAreas.Add(areaConfirmQuit);
SmallAreas.Add(areaConfirmSave);
SmallAreas.Add(areaGameSaved);
SmallAreas.Add(areaSave);
SmallAreas.Add(areaLoad);
}
Same with large areas.
Now, all other areas, are medium, and there is a large number of them.
So, I want to go through all game objects that are children of "areaContainer", check if their names start with "area", and if they do, I want to add them to MediumAreas list.
That's how I tried it:
void DefineMediumAreas()
{
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (GameObject thisObject in areaContainer)
{
char[] a = thisObject.Name.ToCharArray();
if (a.Length >= 4)
{
char[] b = { a[0], a[1], a[2], a[3] };
string thisObjectType = new string(b);
(if (thisObjectType == "area")&&(!(SmallAreas.Contains(thisObject))
&&(!(LargeAreas.Contains(thisObject)))
{
MediumAreas.Add(thisObject);
}
}
}
This however shows an error, that "areaContainer" can't be used that way, I don't have access to Unity now, so can't copy exact message. I think that it's something like "Gameobject doesn't have IEnumerator".
I did try to google for the better approach, and found something called "transform".
foreach(Transform child in transform)
{
Something(child.gameObject);
}
What I don't understand, is how to use this "transform" in my specific situation.
Please don't get angry at me if this question is silly, I am very new to Unity, and have to learn it from scratch.
And a small second question. Will this work of turning object invisible work:
foreach(GameObject thisObject in MediumAreas)
{
thisObject.position = MediumVector;
thisObject.GetComponent<Renderer>().enabled = false;
}
MediumVector is location where the object must be moved to, and it seems to be working.
You can do this: foreach(Transform child in transform)
because the Transform class implements IEnumerable and have some mechanism that enables you to access the child GameObjects with the foreach loop.
Unfortunately, you can't do this: foreach (GameObject thisObject in areaContainer)
because areaContainer is a GameObject and this implementation is not done for the GameObject class. That's why you are getting this error:
foreach statement cannot operate on variables of type
'UnityEngine.GameObject' because 'UnityEngine.GameObject' does not
contain a public definition for 'GetEnumerator'
To fix it, change your loop to use Transform after finding the GameObject:
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (Transform thisObject in areaContainer.transform){}
The complete code:
List<GameObject> MediumAreas = new List<GameObject>();
void DefineMediumAreas()
{
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (Transform thisObject in areaContainer.transform)
{
//Check if it contains area
if (thisObject.name.StartsWith("area"))
{
//Add to MediumAreas List
MediumAreas.Add(thisObject.gameObject);
}
}
}
There is multiple approaches to your problem. One of them is using Tags. Simply mark your MediumArea prefab with some Tag and then you can find all tagged GameObjects with FindGameObjectsWithTag(string) (Unity Docs). Then you can populate your collection like that:
MediumAreas.AddRange(FindGameObjectsWithTag("MediumArea"));
Second approach could be finding all objects with same attached script FindObjectsOfType<T>() (Unity Docs). This is usefull when you are searching for instances of same type, like Medium Area.
Lets say that you have an Area script
public class Area : MonoBehaviour {
public AreaSize Size; // AreaSize is Enum
}
Then you can simply find your areas like:
var allAreas = FindGameObjectsOfType<Area>();
var mediumAreas = allAreas.Where(e => e.Size == AreaSize.Medium); // using System.Linq;
I created a project to answer your question, the final result will be like this :
As you can see I have created a game object called "areaContainer" and added 3 children with respective names : "area01", "area02" and "anotherObject".
The script that manage to get all "areaContainer" children that start with "area" looks like :
public GameObject areaContainer;
public List<GameObject> MediumAreas = new List<GameObject>();
private void Start()
{
DefineMediumAreas();
}
void DefineMediumAreas()
{
for (int i = 0; i < areaContainer.transform.childCount; i++)
{
var childGameObject = areaContainer.transform.GetChild(i).gameObject;
if (childGameObject.name.StartsWith("area"))
MediumAreas.Add(childGameObject);
}
}
1- I ended up referencing the areaContainer object in a script rather than using GameObject.Find because it's more performant.
2- To get a child of a game object you need to access to its transform and call GetChild(index). So by iterating through the parent container which is "areaContainer" we are getting its childCount.
3- To check if the name start with "area", simply use .StartsWith("area") which return true or false.
For your second question, you can hide object disabling the Renderer or by deactivating it (thisObject.SetActive(false);
I hope this help you; Happy coding!
You want to access all game objects that are children of "areaContainer"
In void DefineMediumAreas() function, you need to Transform[] to get an array of childeren. Use this:
Transform[] areaContainer = GameObject.Find("areaContainer").GetComponentsInChildren<Transform>();
foreach(Transform thisTransform in areaContainer)
{
...
}
I hope it helps you

Categories

Resources