OnCollisionEnter for only one object with same script - c#

The problem is that - when two objects with the same scripts collide, I need to destroy them and create a new object. But they collide eachother and instead of one final object,i have 2 of them.
private void OnCollisionEnter2D(Collision2D collision)
{
var drop = collision.gameObject.GetComponent<DropBase>();
if (drop != null)
{
Destroy(collision.gameObject);
Instantiate(_pile, transform.position, Quaternion.identity);
Destroy(gameObject);
}
}
I was trying give every of object lifetime,and the longer living object have rights to create new one,but i have situation where thay created at same time,and this method doesn't work.

You can deactive the two game objects before destroying them, then you can ignore the collision if one of the game object is inactive.
private void OnCollisionEnter2D(Collision2D collision)
{
var incomingGameObject = collision.gameObject;
if(!gameObject.activeSelf || !incomingGameObject.activeSelf)
return;
var drop = incomingGameObject.GetComponent<DropBase>();
if (drop != null)
{
incomingGameObject.SetActive(false);
Destroy(incomingGameObject);
Instantiate(_pile, transform.position, Quaternion.identity);
gameObject.SetActive(false);
Destroy(gameObject);
}
}

You can try to notify another object that the collision is already processed. It can be done in different ways, here is one of them:
public bool IsPileInstantiated { get; private set; }
void Awake()
{
IsPileInstantiated = false;
}
private void OnCollisionEnter2D(Collision2D collision)
{
var drop = collision.gameObject.GetComponent<DropBase>();
if (drop != null)
{
if(drop.IsPileInstantiated == false)
{
Instantiate(_pile, transform.position, Quaternion.identity);
this.IsPileInstantiated = true;
}
Destroy(gameObject);
}
}
Perhaps it even has the sense to move this logic out of your drop objects. For example, notify another object (some GameManager or something like that) about a collision, and that object will have events from both objects, can filter these events, destroy objects if needed and create a needed object if it is needed.

Related

How can I input and destroy the other gameObject on Trigger

I want to destroy a GameObject when it hits the trigger but with an input. No errors, but its not working in unity
using UnityEngine;
public class DestroyTile : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Tile"))
{
if(Input.GetKeyDown(KeyCode.Space))
{
Destroy(other.gameObject);
}
}
}
}
what should I do to make this work??
Very unlikely that you manage to hit the key in exactly the same frame the collision happens ;)
As a quick fix rather use OnTriggerStay
OnTriggerStay is called once per physics update for every Collider other that is touching the trigger.
public class DestroyTile : MonoBehaviour
{
// Called once every FixedUpdate for each trigger you are touching
private void OnTriggerStay(Collider other)
{
if (other.CompareTag("Tile"))
{
if(Input.GetKeyDown(KeyCode.Space))
{
Destroy(other.gameObject);
}
}
}
}
With above way there is only one last issue: OnTriggerStay is not actually called every frame but rather every FixedUpdate so you might miss a key press if it happened in a frame where FixedUpdate isn't called. Therefore in general getting User input within FixedUpdate (or other physics based continuous methods) is unreliable and should always be done in Update.
So instead you could/should do something like e.g.
public class DestroyTile : MonoBehaviour
{
private readonly HashSet<GameObject> _currentlyTouching = new HashSet<GameObject>();
private void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Tile") return;
// For some reason we still have the other object stored
if(_currentlyTouching .Contains(other.gameObject)) return;
// Store the other object
_currentlyTouching.Add(other.gameObject);
}
private void OnTriggerExit(Collider other)
{
// We don't have this other object stored
if(!_currentlyTouching.Contains(other.gameObject) return;
// remove this other object
_currentlyTouching.Remove(other.gameObject);
}
private void Update()
{
// Are we touching anything?
// This check is cheaper than Input.GetKeyDown
if(_currentlyTouching.Count <= 0) return;
if(!Input.GetKeyDown(KeyCode.Space)) return;
// destroy (all) the touched object(s)
foreach(var obj in _currentlyTouching)
{
Destroy(obj);
}
// Don't forget to also clear the HashSet in that case
_currentlyTouching.Clear();
}
}
This way you have the User Input separated in Update (every frame) and only track the entering and exiting colliders via the Physcis based system.

How to check if i`m touching something in Unity

I've got OnCollisionEnter2D function, but it say's only once, when i`m landing on ground, i want function that will return object name(or smthng like this) if i'm touching it, so if i'll stay on it it will say me about it not only once but every frame.
It's as simple as using the correct method OnCollisionStay2D which is called every frame while you are colliding with another object
Sent each frame where a collider on another object is touching this object's collider (2D physics only).
To be fair: Their example in that link is bullshit since it is for OnTriggerStay2D ^^
It could look like
private void OnCollisionStay2D(Collision2D collision)
{
Debug.Log($"I am touching {collision.gameObject.name}", this);
}
If instead you want to keep track of every touching object I would rather use something like
private HashSet<GameObject> _currentlyTouching = new HashSet<GameObject>();
private void OnCollisionEnter2D(Collision2D collision)
{
if(!_currentlyTouching.Contains(collision.gameObject))
{
_currentlyTouching.Add(collision.gameObject);
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if(_currentlyTouching.Contains(collision.gameObject))
{
_currentlyTouching.Remove(collision.gameObject);
}
}
private void Update()
{
var logString = new StringBuilder("I am touching ");
foreach(var touchingObject in _currentlyTouching)
{
logString.Append(touchingObject.name).Append(" ");
}
Debug.Log(logString.ToString(), this);
}

Hierarchy issue Unity

Im making a simple game in Unity 3d, basically you have a backdround object with more gameobjects on top, what you have to do is drag those game objects into some trash cans, as soon as you hit the correct trash can with the correct object said object gets deleted, the idea of the game is that when you put all the objects in the corect cans you win the game
I have the drag and acceptance all made but i cant figure out how to make the script so that when theres no more objects, you win the game.
I've been trying to use Hierarchy on an empty game object thats the parent for the objects that you have to destroy, but i dont know how to actually delete the objects so that the code can register it, any help?
The code for the drag and the code im using for the trash cans:
using UnityEngine;
using UnityEngine.EventSystems;
public class arrastrar : MonoBehaviour, IDragHandler
{
public float z = 0.0f;
public void OnDrag(PointerEventData eventData)
{
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = z;
transform.position = Camera.main.ScreenToWorldPoint(mousePosition);
}
}
And the code inside the trashcans
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
public class botarBasura: MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
if (other.gameObject.name == "cubo")
Destroy(other.gameObject);
} else if (other.gameObject.name == "escombros")
{
other.gameObject.transform.position = new Vector3(-1, 2.5f, -2); //If its not the correct trash can it returns the object to the starting position
}
} else if ( other.gameObject.name == "botellas")
{
other.gameObject.transform.position = new Vector3(0, 2.5f, -2);
}
}
}
You can use a scriptable object to keep track of your object. Objects just need to add and remove themselves to a list in the scriptable object in their OnEnable() and OnDisable() callbacks. When an object removes itself you can have code check to see if there are no more objects left and trigger an event that something in your scene will react to.
(Dragable Object)
public class DragableObject : MonoBehaviour{
//reference to the scriptable object
public DragableObjectSet RuntimeSet;
void OnEnable(){
RuntimeSet.Add(this);
}
void OnDisable(){
RuntimeSet.Remove(this);
//you can put code checking if the runtime set is empty here
//or you can put it in the DragableObjectSet Remove method
}
}
Scriptable object example code
public DragableObjectSet : ScriptableObject
{
public List<DragableObject> RuntimeSet = new List<DragableObject>();
public void Add(DragableObject obj){
RuntimeSet.Add(obj);
}
public void Remove(DragableObject obj){
RuntimeSet.Remove(obj);
if(RuntimeSet.Count() == 0)
//some code here raising an event (if desired)
}
}
This type of object is called a runtime set and is detailed in this talk around the 40 minute mark.
https://www.youtube.com/watch?v=raQ3iHhE_Kk
A solution could possibly be using a singleton:
Create a new empty GameObject and add this code:
using UnityEngine;
public class TrashCounter : MonoBehaviour {
public static TrashCounter instance = null;
public int counter = 0;
void Awake(){ instance = this; }
}
Now, wherever you create an object (or in the Start() method of any object code), you just need to do TrashCounter.instance.counter++ and you will have your trash count.
After that, you just need to update this counter everytime you delete an object by doing TrashCounter.instance.counter-- and after you ask if(TrashCounter.instance.counter == 0) WinGame().
You can get an array of all active & not-yet-destroyed GameObjects with some enabled component with Object.FindObjectsOfType<ComponentType>();. Once you have that, you could then look at the length of that array.
So, you could do something like this in the code that destroys/deactivates the draggable gameobject:
GameObject otherObject = /* get draggable object to possibly destroy */;
if ( /* test to remove otherObject */ )
{
// deactivate the draggable object
otherObject.setActive = false;
// get all other draggable objects
DraggableObject[] remainingDraggableObjects = Object.FindObjectsOfType<DraggableObject>();
if (remainingDraggableObjects.Length == 0)
{
// there are no more active gameobjects with enabled DraggableObject component
// handle win condition here.
}
}
In your specific example, it could look like this:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.name == "cubo")
{
other.gameObject.setActive = false; // better than destroying in most cases
// get all other draggable objects
arrastrar[] remainingObjects = Object.FindObjectsOfType<arrastrar>();
if (remainingObjects.Length == 0)
{
// there are no more active gameobjects with enabled arrastrar component
// handle win condition here.
}
}
// ...

OnCollisionExit is not being called

Why is OnCollisionExit not being called? I am using both OnCollisionEnter and OnCollisionExit but unfortunately only OnCollisionEnter is being called.
public bool HandleCollided = false;
public void OnCollisionEnter(Collision col)
{
if(col.gameObject.name == "RightHandAnchor")
{
HandleCollided = true;
}
}
public void OnCollisionExit(Collision col)
{
if(col.gameObject.name == "RightHandAnchor")
{
HandleCollided = false;
}
}
It's impossible to tell why your code isn't working based the given snippet - this code depends on the configuration of each of the GameObjects' inspector windows in the editor.
Both the GameObject that this script is attached to and the colliding GameObject must have one Collider component attached to each of them (for example, a BoxCollider component or a SphereCollider component). Both Colliders must have their isTrigger checkboxes disabled. The GameObject that this script is attached to must also have a Rigidbody component attached.
In order to debug this situation, add Debug.Log() statements in your functions. This is generally good practice, and it might be that the function is being called but the conditional statement is not true.
Here are some additional ideas on what might be going wrong:
You may be changing the name of the colliding GameObject somewhere else in your code.
You may be destroying the GameObject.
It might be that neither function was being called and HandleCollided was being changed elsewhere in your code.
It might be that the parameter, col, is not what you expect.
public void OnCollisionEnter(Collision col)
{
Debug.Log("Collision Enter!");
Debug.Log(col.gameObject.name);
}
public void OnCollisionExit(Collision col)
{
Debug.Log("Collision Exit!");
Debug.Log(col.gameObject.name);
}
So you said "There are two objects that collides with each other. One has sphere collider attached to it and another has box collider. One of the objects have rigidbody attached as well." On which one is the code? and yes this matters! only 1 of the objects will keep track of the exit meaning that if it's the non-kinematic it will not work.
Unfortunately using OnCollisionExit did not work so instead I used OnTriggerEnter and OnTriggerExit. I activated "isTrigger" for both objects.
public void OnTriggerEnter(Collider col)
{
Debug.Log("entered");
if (col.gameObject.name == "RightHandAnchor")
{
HandleCollided = true;
}
}
public void OnTriggerExit(Collider other)
{
Debug.Log("exit");
if (other.gameObject.name == "RightHandAnchor")
{
print("No longer in contact with " + other.transform.name);
HandleCollided = false;
}
}
You need a non-kinematic rigidbody attached to your object to get the event for OnCollisionExit
Try to use OnCollisionEnter() and OnTriggerExit() !!
Example:
void OnCollisionEnter(Collision collision)
{
if((collision.gameObject.GetComponent<AttributeManager>().attributes & doorType) != 0)
{
this.GetComponent<BoxCollider>().isTrigger = true;
}
}
private void OnTriggerExit(Collider other)
{
this.GetComponent<BoxCollider>().isTrigger = false;
}

Repeat function (unity3d/c#)

First of all, here's the code:
using UnityEngine;
using System.Collections;
namespace UltimateSurvival
{
public class Radiation : MonoBehaviour
{
public GameObject radiationEffect;
public EntityVitals Vitals { get { return m_Vitals; } }
private EntityVitals m_Vitals;
// Use this for initialization
void Start() {
InvokeRepeating ("OnTriggerEnter", 1.5f, 3.5f);
}
// Update is called once per frame
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
radiationEffect.SetActive(true);
//yield return new WaitForSeconds(5);
var entity = other.GetComponent<EntityEventHandler>();
if(entity)
{
var healthEventData = new HealthEventData(-Random.Range(7.0f, 23.0f));
entity.ChangeHealth.Try(healthEventData);
}
//yield return new WaitForSeconds(5);
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
radiationEffect.SetActive(false);
}
}
}
}
What I'm trying to do is that I want this script to execute OnTriggerEnter every 3.5 seconds. As you can see, I'm using InvokeRepeating but it seems like it doesnt work. I've also tried changing void OnTriggerEnter on IENumerator OntriggerEnter and then yield return new WaitForSeconds(5); - It didn't work either. I'm really confused D: Please help!
It seems you're trying to solve the problem of draining HP from the player if player is inside the area of radiation. This is a solution that will use most of your current code, but is not neccesarily the best code. I'd also like to inform you of OnTriggerStay, which
is called once per frame for every Collider other that is touching the trigger.
and can also be used to solve this problem. I'm going to use your already declared OnTriggerEnter and OnTriggerExit to damage every player inside area every 3.5 seconds.
public GameObject radiationEffect;
public EntityVitals Vitals { get { return m_Vitals; } }
private EntityVitals m_Vitals;
// Declare a list that will contain the players.
List<GameObject> playersInArea = new List<GameObject>();
// Use this for initialization
void Start() {
InvokeRepeating ("DamagePlayers", 1.5f, 3.5f);
}
void DamagePlayers() {
foreach (var player in playersInArea) {
var entity = player.GetComponent<EntityEventHandler>();
if(entity)
{
var healthEventData = new HealthEventData(-Random.Range(7.0f, 23.0f));
entity.ChangeHealth.Try(healthEventData);
}
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
playersInArea.Add(other.gameObject);
radiationEffect.SetActive(true);
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
playersInArea.Remove(other.gameObject);
if (playersInArea.Count == 0) {
radiationEffect.SetActive(false);
}
}
}
Something like that should work. If you only have 1 player it should work all the same, but this supports multiple players. Let me know if you have any further issues.
You have problems calling your method using InvokeRepeating because of two reasons:
InvokeRepeating can not be used with method that have parameters: you probably have the Trying to Invoke method: [...] couldn't be called. message in your console.
OnTriggerEnter is a method that is automatically called by Unity when the gameobject's collider is set as Trigger and another collider enters it: as #Hellium said, calling such methods manually is a bad idea (same as calling Start or Update manually: this can happen but really doesn't make sense in most of the scenarios).
Hope this helps,

Categories