I figured out the issue with a previous question on here, but now it's still not working.
Luckily I think I have figured out the issue. It's not detecting that an enemy has been killed/destroyed.
The way I have my code set up is that when an enemy is out of health the object is deactivated. But I assumed it would detect when an instance of the object was deactivated. But apparently not (at least from what I have seen).
I think that the best course of action is to make my enemy's "death script" connected to my win condition in some way (unless that isn't the proper way to go about it, in which case let me know what that proper action is).
I assume it would be to make the enemies variable global, but I am uncertain of how to do that or if it would even work for certain.
To reiterate, I am completely new to C# and unity, so if its solution is obvious or if the question is just stupid, remember that I have no clue where to go from this point (which is why this question is being created in the first place). If there is an existing question that can address this please link it, otherwise, anything helps.
here is the code for the win condition
public class GameWinner : MonoBehaviour
{
public GameObject[] enemies;
public Font font;
void OnTriggerEnter(Collider other)
{
enemies = GameObject.FindGameObjectsWithTag("Enemy"); // Checks if enemies are available with tag "Enemy". Note that you should set this to your enemies in the inspector.
if (enemies == null)
{
SceneManager.LoadScene(2);
}
}
}
here is the code for enemy destruction (note, this was ripped from a tutorial, specifically "let's try shooter", let me know if it would be beneficial to rework it completely)
public class ShootableBox : MonoBehaviour
{
//The box's current health point total
public int currentHealth = 3;
public void Damage(int damageAmount)
{
//subtract damage amount when Damage function is called
currentHealth -= damageAmount;
//Check if health has fallen below zero
if (currentHealth <= 0)
{
//if health has fallen below zero, deactivate it
gameObject.SetActive (false);
}
}
}
There's a difference between an empty array, and a null reference.
In your case, you should check to see if the array is empty:
enemies = GameObject.FindGameObjectsWithTag("Enemy"); // Checks if enemies are available with tag "Enemy". Note that you should set this to your enemies in the inspector.
if (enemies.Length == 0)
SceneManager.LoadScene(2);
I have perform a c# script to add mana to the slider ontriggerEnter and Subtract mana on trigguerExit wen touch on Enemy object, but it seems to have some thing wrong, the script have no errors but wen its touch the Enemy Object It Take all the mana, and not the value i set.
I am new on c# scripting, tank you in advance.
This is my Script
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class mana : MonoBehaviour {
public void addmana()
{
manaBar.value += 300;
}
public void Takemana()
{
manaBar.value -= 30;
}
public Slider manaBar;
// Use this for initialization
void Start ()
{
manaBar.value = 300;
if (manaBar != null)
{
manaBar.IsActive();
}
}
void OnTriggerEnter(Collider other)
{
// The switch statement checks what tag the other gameobject is, and reacts accordingly.
switch (other.gameObject.tag)
{
case "manapickup":
Debug.Log(other.gameObject.tag);
Invoke("addmana", 0f);
Destroy (other.gameObject);
break;
}
// Finally, this line destroys the gameObject the player collided with.
//Destroy(other.gameObject);
}
void OnTriggerExit(Collider other)
{
// The switch statement checks what tag the other gameobject is, and reacts accordingly.
switch (other.gameObject.tag)
{
case "Enemy":
Debug.Log(other.gameObject.tag);
Invoke("Takemana", 0f);
break;
}
// Finally, this line destroys the gameObject the player collided with.
//Destroy(other.gameObject);
}
Looking at your code it seems like the problem may come from your Slider component : are you sure its maxValue property is set to the right value ?
(by default this value is set to 1 : you can change it in the Inspector or programmatically calling manaBar.maxValue = 1000.0f;)
Also I'd recommend moving the case "Enemy": [...] part of your script inside the switch of your OnTriggerEnter method : I see no benefit from calling it inside the OnTriggerExit method (but I may be wrong depending on your game's logic).
Some side notes to conclude :
Try to keep your code organized as much as possible : one common layout is _Attributes (Properties) / Monobehaviour methods (Start, Update, OnTriggerEnter, ...) / Custom methods. This helps other when trying to solve your problems and will ease later maintenance of your code.
Try to respect some coding standards (same as code being organized, this will vary a lot from one person/compagny to another) : in C# method names usually start with an uppercase while property names start with a lowercase.
Finally try to stick to the "rules" you've set for yourself : here I see one of your tags is capitilized (Enemy) while the other isn't (manapickup).
As a gamer I'd phrase it like this: An AOE-Stun that stuns everyone it hits and then disappears.
I have enemy objects with a class "EnemyMovement" attached to it.
This class contains a function "Slow". I have a circle that has the class "StunSpell" attached to it. Now I want to call "Slow" once for every enemy object that collides with it.
void OnTriggerStay2D(Collider2D other){
if (other.gameObject.tag == "Enemy") { //Here i want to find every gameobject (by tag "Enemy")
other.GetComponent<EnemyMovement> ().Slow (3, 0f); //Call this function once per gameobject
//And as soon as every object that was found executed "Slow" once:
// -> Destroy(gameObject); to destroy the AOE-Object
}
}
This QA is a bit messy, but it's totally normal and commonplace in video games to simply ...
"...check the distance to each character..."
private void GrenadeTimer()
{
rb.isKinematic = true;
// here is our small explosion...
Gp.explosions.MakeExplosion("explosionA",transform.position);
float radius = splashMeasuredInEnemyHeight *
Gp.markers.GeneralExampleEnemyWidth();
List<Enemy> hits = new List<Enemy>();
foreach(Enemy e in Gp.enemies.all)
{
if (e.gameObject.layer != Grid.layerEnemies) continue;
if ( transform.DistanceTo(e) < radius ) hits.Add(e);
}
hits.SortByDistanceFrom( this.transform );
boss.SequentialHits(hits,damage);
boss.Done(this);
}
It's hard to imagine anything being simpler than that.
Note that we decide on a
radius
in meters, let's say "4.2 meters", inside which we want to do damage to the enemies. (Or, buff them, or whatever the case may be.)
This thing
Gp.enemies.all
is a List<Enemy> ... it holds all the enemies in the game at the moment. Simple right?
If you do not actually have a List<> of all the enemies (or players, NPCs - whatever is relevant) - you are ###ed. Start over on your learning project. Once you have a live List which is unit-tested, come back to this.
This line of code
Grid.layerEnemies
relates to the layer system in Unity. This often causes new hobbyists a problem ...
In reality, you can do absolutely nothing in Unity without using the layers system for every single thing.
It is beyond the scope of this article to get you started on using Layers, so we'll leave that aside. If you prefer, just leave out the line of code in your learning project.
Next. So - we run through and find all the enemies we want to affect. Let us say there are fifteen of them.
Notice that ...
The code gathers them in the loop. They end up in the "hits" list.
By all means, when you're just learning, you can simply apply the buff/damage/etc insinde the loop:
foreach(Enemy e in Gp.enemies.all)
{
if (e.gameObject.layer != Grid.layerEnemies) continue;
if ( transform.DistanceTo(e) < radius )
e.Slow(3f, 0f);
}
However, in any real game, you have to first make a list of the items, and then most typically have a manager (let's say, your "explosions manager!" - whatever) process those hits/ buffs/ damages/ whatever.
The reason is that you can rarely just throw in happenings all in the same frame. Imagine the sound/ visual effects when I quickly explode say fifteen enemies. Almost certainly your creative director / whoever will want them to happen "rat-a-tat-tat" you know? One way or another it will be far more complex than just "triggering them all". (Also, performance-wise you may well have to stagger them - obviously this can be a huge issue involving massive code bases; don't eve mention if the game is networked.) Note that in the actual example given, they end up being staggered, and indeed by distance outwards from the grenade, which looks great.
(As a curiosity, that particular code has been used to explode on the order of one billion grenades!)
Next issue:
looking at your code, you just "GetComponent". The other objects are "dumb". In reality you never do this. Note that in the example code here, there is an actual c# class Enemy
I will paste in some of Enemy at the bottom to give a flavour.
In fact, you almost always keep a List of "the primary c# Class attached to the players/enemies/etc". You generally don't bother much with the GameObject as such.
(If you do need to get to the GameObject, say to Destroy it, you just enemy.gameObject.)
So here, since we're simply checking the distance, you immediately have the Enemy class. (If you're using physics, you have to "GetComponent" to get to the Enemy class; of course you often do that, also.)
That being said - keep in mind the component-behavior nature of Unity:
That being said. My discussion is a bit slippery, there's an "Enemy" class (and indeed there's specific classes for enemies, such as "Dinosaur", "KillerRobot", "AttackParrot" and so on).
Try to bear in mind though, you really need to thing "behaviour-wise" in Unity.
There really shouldn't be an "AttackParrot" class. Really, there should just be components - behaviors - such as
Flies
ThrowsRocks
HasBrightColors
TakesDamage
LaserEyeballs
LandOnTrees
Conceptually an "AttackParrot" would just be a game object, which, happens to have, all of those six behaviors. In contrast, it would not have say "BreathesFire" and "CanHyperjump".
This is all discussed at length here:
https://stackoverflow.com/a/37243035/294884
It's a bit "purist" to say "Oh, there shouldn't be an 'Enemy' class, only behaviors" - but something to bear in mind.
Next up,
You have to have "general" components in a Unity game, which are accessible everywhere. Things like sound effects, scoring and so on.
Unity simply forgot to do this (they'll add it in the future).
Fortunately it's incredibly easy to do. Notice in the above there's a "boss" general component and a "soundEffects" general component which are called to.
In any script in your project that needs to use the general "boss" component or the general "sound" component, it's just...
Boss boss = Object.FindObjectOfType<Boss>();
Sound sound = Object.FindObjectOfType<Sound>();
That's all there is to it...
Boss boss = Object.FindObjectOfType();
This has been explained at vast length so many times, we need only link to it:
https://stackoverflow.com/a/35891919/294884
Note that, if you prefer, the alternate way to do this using PhysX is:
If you'd like to use the built-in physics: Physics2D.CircleCastNonAlloc
If you want, take a couple of days out to master that.
Note that the examples here are for a 2D game, it's identical in 3D.
(When you measure "distance" in 3D, if you game happens only on a flat surface, you may want to bother only measuring the distance on those two axis - but honestly it's irrelevant.)
You may ask, what is SortByDistanceFrom?
SortByDistanceFrom in fact ........ Sorts By Distance From
To save you typing, here is that extension:
public static void SortByDistanceFrom<T>(
this List<T> things, Transform t) where T:Component
{
Vector3 p = t.position;
things.Sort(delegate(T a, T b)
{
return Vector2.Distance(p, a.transform.position)
.CompareTo(Vector2.Distance(p, b.transform.position));
});
}
This raises another issue for new hobbyists.
Example Enemy class
Example - the Enemy class mentioned above ... included to add background.
So, all the actual enemy components (Dinosaurs, Wombats, XFighters, whatever) would derive from this one, overriding (concepts like motion, etc) as appropriate.
using UnityEngine;
using System.Collections;
public class Enemy:BaseFrite
{
public tk2dSpriteAnimator animMain;
public string usualAnimName;
[System.NonSerialized] public Enemies boss;
[Header("For this particular enemy class...")]
public float typeSpeedFactor;
public int typeStrength;
public int value;
// could be changed at any time during existence of an item
[System.NonSerialized] public FourLimits offscreen; // must be set by our boss
[System.NonSerialized] public int hitCount; // that's ATOMIC through all integers
[System.NonSerialized] public int strength; // just as atomic!
[System.NonSerialized] public float beginsOnRight;
private bool inPlay; // ie, not still in runup
void Awake()
{
boss = Gp.enemies;
}
void Start()
{
}
public void ChangeClipTo(string clipName)
{
if (animMain == null)
{
return;
}
animMain.StopAndResetFrame();
animMain.Play(clipName);
}
public virtual void ResetAndBegin() // call from the boss, to kick-off sprite
{
hitCount = 0;
strength = typeStrength;
beginsOnRight = Gp.markers.HitsBeginOnRight();
Prepare();
Gp.run.runLevel.EnemyAvailable();
}
protected virtual void Prepare() // write it for this type of sprite
{
ChangeClipTo(bn);
// so, for the most basic enemy, you just do that
// for other enemy, that will be custom (example, swap damage sprites, etc)
}
void OnTriggerEnter2D(Collider2D c)
{
GameObject cgo = c.gameObject;
// huge amount of code like this .......
if (cgo.layer == Grid.layerPeeps) // we ran in to a "Peep"
{
Projectile p = c.GetComponent<Projectile>();
if (p == null)
{
Debug.Log("WOE!!! " +cgo.name);
return;
}
int damageNow = p.damage;
Hit(damageNow);
return;
}
}
public void _stepHit()
{
if ( transform.position.x > beginsOnRight ) return;
++hitCount;
--strength;
ChangeAnimationsBasedOnHitCountIncrease();
// derived classes write that one.
// todo, actually should the next passage only be after all the steps?
// is after all value is deducted? (just as with the _bashSound)...
if (strength==0) // enemy done for!
{
Gp.coins.CreateCoinBunch(value, transform.position);
FinalEffect();
if ( Gp.skillsTest.on )
{
Gp.skillsTest.EnemyGottedInSkillsTest(gameObject);
boss.Done(this);
return;
}
Grid.pops.GotEnemy(Gp.run.RunDistance); // basically re meters/achvmts
EnemyDestroyedTypeSpecificStatsEtc(); // basically re achvments
Gp.run.runLevel.EnemyGotted(); // basically run/level stats
boss.Done(this); // basically removes it
}
}
protected virtual void EnemyDestroyedTypeSpecificStatsEtc()
{
// you would use this in derives, to mark/etc class specifics
// most typically to alert achievements system if the enemy type needs to.
}
private void _bashSound()
{
if (Gp.bil.ExplodishWeapon)
Grid.sfx.Play("Hit_Enemy_Explosive_A", "Hit_Enemy_Explosive_B");
else
Grid.sfx.Play("Hit_Enemy_Non_Explosive_A", "Hit_Enemy_Non_Explosive_B");
}
public void Hit(int n) // note that hitCount is atomic - hence strength, too
{
for (int i=1; i<=n; ++i) _stepHit();
if (strength > 0) // bil hit the enemy, but enemy is still going.
_bashSound();
}
protected virtual void ChangeAnimationsBasedOnHitCountIncrease()
{
// you may prefer to look at either "strength" or "hitCount"
}
protected virtual void FinalEffect()
{
// so, for most derived it is this standard explosion...
Gp.explosions.MakeExplosion("explosionC", transform.position);
}
public void Update()
{
if (!holdMovement) Movement();
// note don't forget Translate is in Space.Self,
// so you are already heading transform.right - cool.
if (offscreen.Outside(transform))
{
if (inPlay)
{
boss.Done(this);
return;
}
}
else
{
inPlay = true;
}
}
protected virtual void Movement() // override for parabolas, etc etc
{
transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
}
}
So that's a general enemy class. Then you have derives of that, such as Ufo, Dinosaur, Tank, XWingFighter, etc etc. Here's Ufo ...
Note that it overrides many things. It seems to override "Prepare" (the comments suggest it "starts higher", and you can see it overrides other things.
using UnityEngine;
using System.Collections;
public class Ufo:Enemy
{
public Transform projectilePosition;
protected override void Prepare()
{
// ufo always start up high (and then zip up and down)
transform.ForceY(Gp.markers.StartHeightHighArea());
animMain.StopAndResetFrame();
animMain.Play(bn + "A");
animMain.StopAndResetFrame();
Invoke("ZipDown", Random.Range(0.6f,0.8f));
}
protected override void OnGamePause()
{
CancelInvoke();
StopAllCoroutines();
}
protected override void OnGameUnpause()
{
Attack();
if(transform.position.y<0f)
ZipUp();
else
ZipDown();
}
private float fZip = 3.3f;
private void ZipDown() { StartCoroutine(_zipdown()); }
private void ZipUp() { StartCoroutine(_zipup()); }
private IEnumerator _zipdown()
{
Grid.sfx.Play("Enemy_UFO_Move_Down");
float tLow = Gp.markers.StartHeightLowArea();
while (transform.position.y > tLow)
{
transform.Translate(0f,
fZip * -Time.deltaTime * mpsNow, 0f,Space.Self );
yield return null;
}
Attack();
Invoke("ZipUp", Random.Range(0.7f,1.4f));
}
private IEnumerator _zipup()
{
Grid.sfx.Play("Enemy_UFO_Move_Up");
float tHigh = Gp.markers.StartHeightHighArea();
while (transform.position.y < tHigh)
{
transform.Translate(0f,
fZip * Time.deltaTime * mpsNow, 0f,Space.Self );
yield return null;
}
Attack();
Invoke("ZipDown", Random.Range(0.7f,1.4f));
}
private void Attack()
{
Grid.sfx.Play("Enemy_UFO_Shoot");
animMain.Play();
Invoke("_syncShoot", .1f);
}
private void _syncShoot()
{
Gp.eeps.MakeEepUfo(projectilePosition.position);
}
protected override void ChangeAnimationsBasedOnHitCountIncrease()
{
// ufo just goes 4,2,out
if (strength == 2)
{
// if any attack, cancel it
CancelInvoke("ShootGreenPea");
CancelInvoke("Attack");
// on the ufo, anim only plays with attack
animMain.StopAndResetFrame();
animMain.Play(bn + "B");
animMain.StopAndResetFrame();
Invoke("Attack", 1.5f.Jiggle());
}
}
protected override void EnemyDestroyedTypeSpecificStatsEtc()
{
Grid.pops.AddToEnemyCount("ufo");
}
}
Let's think about the idea of "overrides in the Enemy class".
Overrides in an example Enemy class......
The many enemies have different types of movement, right? The general paradigm in the game is things moving in 2D, kinematically (ie, we "move them a certain amount of distance each frame" - not using PhysX here). So the different enemies move in radically different ways.
Here's one that moves in a certain way ... (the comments explain it)
protected override void Movement()
{
// it enters at about 2x normal speed
// the slow crossing of the stage is then about 1/2 normal speed
float mpsUse = transform.position.x < changeoverX ? mpsNow*.5f : mpsNow * 2.5f;
transform.Translate( -Time.deltaTime * mpsUse * typeSpeedFactor, 0f, 0f, Space.Self );
// recall that mpsNow was set by enemies when this was created, indeed
// nu.mpsNow = ordinaryMps * widthSpeedFactor;
}
Here's one that goes along, but sometimes "drifts downwards..."
protected override void Movement()
{
float mm = mpsNow * typeSpeedFactor;
if ( fallingMotion )
transform.Translate(
-Time.deltaTime * mm,
-Time.deltaTime * mm * fallingness, 0f,
Space.Self );
else
transform.Translate(
-Time.deltaTime * mm, 0f, 0f,
Space.Self );
}
Here's one that seems to follow a sinus ...
protected override void Movement()
{
transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
float y = Mathf.Sin( basis-transform.position.x * (2f/length) );
y *= height;
transform.transform.ForceY( y );
}
Here's one that does complex speed changes, zooming around
protected override void Movement()
{
// it enters at about 2x normal speed
// it appears to then slow crossing of the stage about 1/2 normal speed
// however it then zooms to about 3x normal speed
float mpsUse = mpsNow;
float angled = 0f;
if ( transform.position.x > changeoverX) //enter quickly
mpsUse = mpsNow * 3f;
if ( transform.position.x < thenAngled) // for bead, angled section
{
mpsUse = mpsNow * 1.5f;
angled = leanVariation;
}
transform.Translate(
-Time.deltaTime * mpsUse * typeSpeedFactor,
-Time.deltaTime * mpsUse * typeSpeedFactor * angled,
0f, Space.Self );
}
You could make the movement anything - flying, running, bouncing, whatever.
It's all handled in c# by the protected override concept.
Static "globals" class ... simpler than a general component, for simply "holding" certain variables
Here's a trivial example of a static class which holds what you might as well call "globals", In a game engineering milieu, it's sensible to have certain things as "globals".
using UnityEngine;
using Shex;
using System.Collections;
using System.Collections.Generic;
static class Gp
{
public static Enemies enemies;
public static Pickups pickups;
public static Coins coins;
public static Peeps peeps;
public static Eeps eeps;
}
So TBC complicated "general" systems like SoundEffects, Boss, Scoring, AI, Networking, Social, InAppPurchase etc etc as described above, would indeed by the "preload" type objects as explained. (ie, you're using Boss boss = Object.FindObjectOfType(); at the top of any script, in any scene etc, that needs them.)
But for simply variables, things, that just need to access everywhere, you can use a trivial static class like that. Often just the one static class (called something like "Gameplay" or "Gp") does the job for the whole project.
{By all means, some teams would say "screw that, don't use a static class, put it in a "general" ("preload-style") component like Boss...."}
Note that of course a static class is NOT a real MonoBehavior - you "can NOT actually "DO" anything inside it in Unity". It's only for "holding variables" (most often, Lists) you want to access easily everywhere.
Again, be sure to remember that a static class is quite simply NOT a Unity game object or component - hence it very literally is not part of your game; you literally can not "do" anything whatsoever in a static class. To "do" anything, at all, in Unity it must be an actual Component, literally on a specific GameObject, at some position.
Thus for example it's completely useless trying to keep your "score" in a simple static class. Inevitably in relation to the "score" you will want to do all sorts of things (change the display on screen, award points, save encrypted preferences, trigger levels ... whatever, there is a lot of stuff to do). You absolutely cannot do that in a static - you can not "do" anything, at all, in a static - it must be an actual Unity game object. (ie, using the "preload system".) Once again statics are just for literally keeping track of some basically "global" variables, usually lists of things. (Things like "screen markers" are the perfect example.)
Just BTW in game development an "eep" is an enemy projectile and a "peep" is a player projectile, heh!
(Posted on behalf of the OP).
I solved it using a List to check which enemies are colliding with the Circle:
void CallStun(Collider2D coll){
if (coll.gameObject.tag == "Enemy") {
coll.GetComponent<EnemyHealth> ().TakeDamage (damage);
coll.GetComponent<EnemyMovement> ().Slow (3, 0f);
}
}
void OnTriggerStay2D(Collider2D other){
if (stun == false) {
return;
}
if (!collList.Contains (other)) { //if the object is not already in the list
collList.Add (other); //add the object to the list
CallStun (other); //call the functions on the specific object
} else {
Destroy (gameObject); //if no(more) collisions occur -> destroy the circle object
}
}
In the EnemyMovement class have an OnTriggerEnter method. Tag the spell and put a 2dCollider on it. When the OntriggerEnter is hit with tag == "slow spell" call the slow.
Basically I would do it the reverse way you are currently doing it. Doing it the other way will easily alow you to interact with anything that hits the enemy (since that's the intended behavior)
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 ;)
In my game there is a score. When an object collides, a set increment is added to the score. There is one scoreboard. What is happening in my game is that when an object collides it hijacks the scoreboard to show only its own person history of scores. When the next object collides, it takes over the scoreboard and shows its own personal history. I am trying to show the amalgamate score of all of the objects put together, each contributing their part to a grand total.
void OnCollisionEnter (Collision col)
{
if(col.gameObject.name == "Plane")
{
score += 50;
textMeshComponent = GameObject.Find ("Score").GetComponent<TextMesh> ();
textMeshComponent.text = score.ToString ();
}
}
There are 10 of (col.gameObject.name) and there is one "Plane" that they all interact with.
How can I make the score a conglomerate like I described? I am really at a loss for how to manipulate the (col.gameObject.name) side of that equation.
Thanks for your advice.
Make score a member of Plane, instead of this, and make it public so all the other gameObjects can get it.
You can also create score as its own gameObject, or create a gameObject that contains all the globals.
Let me suggest another approach although not recommended textMeshComponent.text = (score+(int.Parse(textMeshComponent.text))).toString();
now this will add points as a whole regardless each gameobject adding its own to the main value
Consider having an object in your scene whose sole concern is counting (and maybe displaying) the score. Lets name it ScoreController.
Whenever an event occurs for which you want to award score to the player, the responsible code just calls your ScoreController and tells them how much score to award.
Your ScoreController GameObject might have a script like this:
public class ScoreController : MonoBehaviour
{
public TextMesh scoreDisplay;
public int Score { get; private set; }
void Start()
{
UpdateView();
}
public void AwardScore(int amount)
{
Score += amount;
UpdateView();
}
private void UpdateView()
{
scoreDisplay.text = Score.ToString();
}
}
Now, other code can award score by calling
GameObject.FindObjectOfType<ScoreController>().AwardScore(123);
If you want to read up on related topics, consider reading about the Model-View-Control design pattern, and about SoC (seperation of concerns).