I have an object that moves towards another object and physically collides with it, I want that collision/colliding event to happen only once. I tried using a bool but it didn't work as intended. It seems that I'm doing something wrong.
bool doDamage = true;
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Target" && doDamage)
{
doDamage = false;
// damage code
}
}
void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Target")
{
doDamage = true;
}
}
Edit:
I want the "damage code" to run only once, even if the 2 objects are still in contact. This script is only assigned to 1 object, not both.
I don't know the easiest way to explain why your code is not working but I will try.
You have two GameObjects:
GameObject A with doDamage variable.
GameObject B with doDamage variable.
When GameObject A collides with GameObject B:
A.The OnCollisionEnter2D function is called on GameObject A.
if(other.gameObject.tag == "Target" && doDamage) executes because doDamage is true.
B.The doDamage variable from GameObject A is then set to false.
This does not affect the doDamage variable from GameObject B.
Then
C.The OnCollisionEnter2D function is called on GameObject B.
if(other.gameObject.tag == "Target" && doDamage) executes because doDamage is true.
D.The doDamage variable from GameObject B is then set to false.
Both your damage code will run because doDamage is always true in each OnCollisionEnter2D call. What you are currently doing is only affecting doDamage variable in each individual script.
What you are currently doing:
Setting doDamage in the local/this script to false while also checking if local/this doDamage is set or not.
What you need to do:
Set doDamage in the other script to false but read the local/this doDamage to check if it is set or not.
This is what it should look like:
public class DamageStatus : MonoBehaviour
{
bool detectedBefore = false;
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Target"))
{
//Exit if we have already done some damage
if (detectedBefore)
{
return;
}
//Set the other detectedBefore variable to true
DamageStatus dmStat = other.gameObject.GetComponent<DamageStatus>();
if (dmStat)
{
dmStat.detectedBefore = true;
}
// Put damage/or code to run once below
}
}
void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Target")
{
//Reset on exit?
detectedBefore = false;
}
}
}
If you want to run a snippet of code once, run the code directly in the callback event that you want to trigger your code.
void OnCollisionEnter2D(Collision2D other)
{
DoSomeDamageTo(other.gameObject);
}
This should only trigger once upon collision.
If you want this to only ever happen once (e.g. if the object holding the script were to hit something again), you'll need a flag to say that the damage has already been done:
bool hasHitSomething = false;
void OnCollisionEnter2D(Collision2D other)
{
if (!hasHitSomething)
{
DoSomeDamageTo(other.gameObject);
hasHitSomething = true;
}
}
I suspect given the code you've shared, that either the objects are colliding multiple times in engine (very possible if they're solid and physics-controlled), leading to OnCollisionEnter being called multiple times. OnCollisionEnter should only be called once per collision. If you are observing it being called multiple times, either you are actually observing multiple collisions, or you've found an obscure Unity bug. The former is much, much more likely.
Related
Anyone can help me how to fix this problem i'm having?
*1st script:
public static bool attacking;
public Collider2D attackTrigger;
private void Awake()
{
attackTrigger.enabled = false;
}
private void Update()
{
if (attacking == true)
{
Debug.Log("Box Collider Enabled");
attackTrigger.enabled = true;
StartCoroutine(DisableCollider());
}
}
IEnumerator DisableCollider()
{
yield return new WaitForSeconds(1);
attacking = false;
attackTrigger.enabled = false;
Debug.Log("Box Collider Disabled");
}
*2nd script:
public float damage = .10f;
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.isTrigger!=true && collision.CompareTag("Enemy"))
{
Debug.Log("Enemy is Damaged");
EHealthBar.TakeDamage(damage);
}
}
i'm trying to get my player character to attack an enemy using collision, well it does work and the player does damage the enemy but it will only work if the enemy's box collider enters my attack again.
if the enemy is already in my area of attack which has a box collision 2d attached, the enemy doesn't get damaged and that's not what i was going for.
I can change it to OnTriggerStay2D but the enemy will keep on getting damaged till the collider is disabled again. care to help?
I think the problem is
IEnumerator DisableCollider()
{
yield return new WaitForSeconds(1);
attacking = false;
...
which causes that your IF statement in your update is still occuring for 1 second and starting a lot of coroutines. The easiest solution would be moving attacking = false; before yield, but i guess you want to use that variable to be aware if user is in attack state. So i suggest to use another bool variable.
if (invokeAttack)
{
attacking = true;
invokeAttack = false
Debug.Log("Box Collider Enabled");
attackTrigger.enabled = true;
StartCoroutine(DisableCollider());
}
Anyway, you didn't show us how you invoke attack, but maybe instead of changing variable (attacking/invokeAttack in your case) to true and checking it in Update you just invoke a method which enables collider and starts coroutine?
I'm using the IEnumerator function and have had some issues with my if statement working:
IEnumerator Spawning()
{
Debug.Log("Spawning being called...");
if (GameObject.Find("FPSController").GetComponent<BoxCollide>().hitTrigger == true)
{
Debug.Log("CUSTOMER SHOULD SPAWN!");
while (countStop == false) {
yield return new WaitForSeconds(2);
Debug.Log("Fisher Spawned!");
counter++;
spawnNewCharacter.SpawnCharacter();
if (counter >= 3){
countStop = true;
}
}
}
}
After some debugging, it turns out that my if statement actually works. This issue is actually the IEnumerator function being called as soon as I run my game. I need a way to call this IEnumerator function only when my hitTrigger == true, but I can't seem to get anything to work.
I've tried this on top of the IEnumerator function:
void Update()
{
if (GameObject.Find("FPSController").GetComponent<BoxCollide>().hitTrigger == true)
{
Debug.Log("Spawning now...");
StartCoroutine(Spawning());
}
}
But still can't even get any of the Debug.Log's to come through. Would appreciate some help on this!
Side Information
Find() and GetComponent()
You don't want to use GameObject.Find(...) in the Update method, as it's an expensive call. The Update method is called each frame, so you'd call GameObject.Find(...) 60 times in 1 second at 60fps.
So when you use GetComponent() or Find() you want to save a reference to these objects like shown in the snippets below.
Better locations to use methods like GetComponent() or GameObject.Find() are the Awake() and Start() methods.
Awake
Awake is used to initialize any variables or game state before the
game starts. Awake is called only once during the lifetime of the
script instance. Awake is called after all objects are initialized so
you can safely speak to other objects or query them using eg.
GameObject.FindWithTag. [...]
Explanation is taken from the linked documentation.
Start
Start is called on the frame when a script is enabled just before any
of the Update methods is called the first time. Like the Awake
function, Start is called exactly once in the lifetime of the script.
However, Awake is called when the script object is initialised,
regardless of whether or not the script is enabled. Start may not be
called on the same frame as Awake if the script is not enabled at
initialisation time.
Explanation is also taken from the linked documentation
Possible Solution
Add the first Component (FPSControllerCollission) onto the object that holds your FPSController.
It makes use of unities OnTriggerEnter & OnTriggerExit methods.
This script is gonna set the IsTriggered bool to true, when a trigger entered the space of the box collider.
Note: A collider acts as a trigger, when the "Is Trigger" checkbox on
the component is checked.
You can do similar with OnCollisionEnter/Exit to recognize, when a Collider enters the space of the box collider.
Note that the following is only an example and you'll have to tweak /
integrate it into your code
[RequireComponent(typeof(BoxCollider))]
public class FPSControllerCollission : MonoBehaviour {
public bool IsTriggered;
private void OnTriggerEnter(Collider other) {
this.IsTriggered = true;
}
private void OnTriggerExit(Collider other) {
//Maybe check the tag/type of the other object here
this.IsTriggered = false;
}
}
The following SpawnController class could be integrated in the class you allready have.
public class SpawnController : MonoBehaviour {
private FPSControllerCollission _fpsControllerCollission;
private void Awake() {
this._fpsControllerCollission = FindObjectOfType<FPSControllerCollission>();
}
private void Update() {
if (this._fpsControllerCollission.IsTriggered) {
StartCoroutine(nameof(Spawning));
}
}
IEnumerator Spawning() {
Debug.Log("Spawning being called...");
if (this._fpsControllerCollission == true) {
Debug.Log("CUSTOMER SHOULD SPAWN!");
bool countStop = false;
int counter;
while (countStop == false) {
yield return new WaitForSeconds(2);
Debug.Log("Fisher Spawned!");
counter++;
spawnNewCharacter.SpawnCharacter();
if (counter >= 3) {
countStop = true;
}
}
}
}
}
Rundown
Ok guys and gals need a little help with this. Basically I'm instantiating a healthPack from a prefab in an open position. After a certain time elapses I am trying to destroy the HealthPack if it isn't picked up, set a bool healthPackExist back to false, and instantiate another healthPack at a free position.
Problem:
When I've attempted to access the gameObject being instantiated I end up either destroying the whole Parent Hierarchy or just wiping the script out.
Solutions
I've tried destroying the root object, searching for the name of the created object, adding a tag to the object (Health Pack) and searching for it always getting errors.
Code is below:
public GameObject healthPackPrefab;
public GameObject health;
private float healthTimer;
private bool healthExist;
// Use this for initialization
void Start ()
{
//set healthExist to false to indicate no health packs exist on game start
healthExist = false;
}
// Update is called once per frame
void Update ()
{
//first check to see if a health pack exist, if not call method to spawn a health pack
//otherwise check to see if one exist and if it does is it's timer created with it
//has been passed by Time.time (returns game clock time), if yes destroy the created
//health pack.
if (healthExist == false)
{
spawnUntilFull ();
}
else if (healthExist == true && Time.time > healthTimer)
{
//Error occuring here when trying to destroy
//Destroy(transform.root.gameObject) //destroys script, object scripts on, and prefab
Destroy(this.gameObject); //does same as Destroy(transform.root.gameObject
healthExist = false;
}
}
Transform NextFreePosition()
{
//free space
foreach (Transform childPositionGameObject in transform)
{
//if no health packs located return location of child object to spawn new health pack
if (childPositionGameObject.childCount == 0)
{
return childPositionGameObject;
}
}
return null;
}
void spawnUntilFull()
{
//returns next free position in space
Transform freePosition = NextFreePosition ();
//if free slot is available
if (freePosition && healthExist == false)
{
//instantiates health object and places it in scene at coordinates received from
//freePosition
health = Instantiate (healthPackPrefab, freePosition.position, Quaternion.identity) as GameObject;
//spawns enemy onto a position under the Parent (Enemy Formation)
health.transform.parent = freePosition;
//set bool to true to stop spawning
healthExist = true;
//seat HealthTimer to 5 seconds after creation for use in Update method
healthTimer = Time.time + 5.0f;
}
}
What you are effectively doing when you call Destroy() is destroying the script. To achieve what you want (destroying the health pack) you simply have to call Destroy on it instead:
Destroy(health);
On a side note, to avoid messy code using Time.time, Destroy() has an overload that uses two parameters:
Destroy(GameObject object, float delay);
This should help simplify your code and make it more readable.
You could add a separate script on the prefab with your custom DestroyObject method in it.
Start a timer on that script as soon as the object is created.
Now if it is not collected within a certain time, you could easily destroy the object.
I have craete 2 ball sprite & one hole sprite, when this 2 ball sprite enters into the hole it need to load new scene.
im trying to find multi collision with AND operator but it's not working,if i try with one condition it workes fine, i don't know why.
void OnTriggerEnter2D(Collider2D col)
{
if ((col.gameObject.tag == "ball2") && (col.gameObject.tag == "ball")) {
Application.LoadLevel("Main");
}
}
Logistically, the code you've presented can never equate to true, because you're comparing the same string against two different values. If it returns false from the first test, it returns false. If it returns true from the first test, then it will always return false from the second, because the first evaluated it as being "ball2" - and thus it will return false.
If you're looking for it to return true if either (not both) side is true, use the or operator (condition1 || condition2)
void OnTriggerEnter2D(Collider2D col)
{
if ((col.gameObject.tag == "ball2")
|| (col.gameObject.tag == "ball"))
{
Application.LoadLevel("Main");
}
}
ps, yes, I like the Visual Studio code formatting, particularly for cases where conditional blocks are multiple lines
EDIT: Going by your comments, you're waiting to get two objects into the collision. In such a case, you cannot perform this AND test, because you're testing the same object since you only received the col parameter once. Because you get each collision as a discreet, separate event, you should instead set a flag per occurrence, and then advance when both flags have been set.
private bool ball = false;
private bool ball2 = false;
void OnTriggerEnter2D(Collider2D col)
{
// Set each flag individually to allow for separate events triggering each
if(col.gameObject.tag == "ball") ball = true;
if(col.gameObject.tag == "ball2") ball2 = true;
// Perform operation once both flags have been set
if(ball && ball2) Application.LoadLevel("Main");
}
Add two different collider with two different Gameobject and add two different collider script. Access one variable from other script if that's true then go on...
void OnTriggerEnter2D(Collider2D col) // Collider1 Script
{
if ((col.gameObject.tag == "ball2"))
{
firstcollider = true;
}
}
void OnTriggerEnter2D(Collider2D col) // Collider2 Script
{
if (Collider1.firstcollider == true ) && (col.gameObject.tag == "ball"))
{
Application.LoadLevel("Main");
}
}
Refer Google to access a variable from one script to another script if you dont know. I don't know this is the best way for code but just for crack....
I am trying to play a particle effect when an enemy is killed but it seems to play on a randomly selected one rather than the one that was hit. However the enemy that was hit still disappears and still add points to the score.
At the moment I have three scripts to carry this out (All have been shortened so I'm only showing the relevant code):
One which is attached to boxes that are thrown at enemies that detects if they have collided with an enemy prefab.
void OnCollisionEnter (Collision theCollision) {
if (canProjectileKill == true) {
// If the projectile hits any game object with the tag "Enemy" or "EnemyContainer".
if (theCollision.gameObject.tag == "Enemy") {
GameObject.Find("EnemyExplosion").GetComponent<enemyDeath>().ProjectileHasHitEnemy();
// Destroy the projectile itself.
Destroy (gameObject);
// Destroy the game object that the projectile has collided with (E.g. the enemy).
Destroy (theCollision.gameObject);
GameObject.Find("Manager").GetComponent<projectileSpawner>().deleteProjectile();
}
}
}
Another that is attached to the enemy prefabs which detects if they have been hit by a box.
void OnCollisionEnter (Collision theCollision) {
if(theCollision.gameObject.tag == "Projectile") {
GameObject.Find("EnemyExplosion").GetComponent<enemyDeath>().EnemyHasBeenHit();
}
}
I then run an if statement asking if both the box has hit the enemy prefab AND if the enemy prefab has been hit by the box in an attempt to identify a single prefab rather than all of them. However this still doesn't work.
public bool HasProjectileHitEnemy;
public bool HasEnemyBeenHitByProjectile;
void Start () {
gameObject.particleSystem.Stop();
HasProjectileHitEnemy = false;
HasEnemyBeenHitByProjectile = false;
}
public void ProjectileHasHitEnemy () {
// From projectile.
HasProjectileHitEnemy = true;
}
public void EnemyHasBeenHit () {
// From enemy.
HasEnemyBeenHitByProjectile = true;
PlayParticleSystem();
}
public void PlayParticleSystem () {
if (HasEnemyBeenHitByProjectile == true && HasProjectileHitEnemy == true) {
gameObject.particleSystem.Play();
HasProjectileHitEnemy = false;
HasEnemyBeenHitByProjectile = false;
}
}
}
I am aware this is a long question but I have been stuck on this for over a week, so any help would be much appreciated. Thank you :)
I'm not sure what kind of object EnemyExplosion is, but your problem seems to be the search for this object. During OnCollisionEnter you know exactly between which objects the collision occurred. But now you're starting a search for any object that is called EnemyExplosion. That's the reason your particles appear random.
Update:
Ok, with your structure something like that
EnemyContainer
- EnemyExplosion
- Particle System
- EnemyModel
- Collider
If EnemyModel contains the collider, you can get to EnemyExplosion and finally enemyDeath the following way.
var explosion = theCollision.transform.parent.gameObject.GetComponent<EnemyExplosion>();
explosion.GetComponent<enemyDeath>().ProjectileHasHitEnemy();
Now that you're accessing the correct object, you can remove some of your double checks and rely on one collider event.
I seem to have found a way around this. Instead I've just set it to instantiate the particle system whenever an enemy detects that it has collided with a projectile. I then use a Coroutine to delete the particle system 2 seconds after.