How to make a trigger only for one of objects? - c#

I have a car in my game with 4 wheels(Unity3D):
Also i have a trigger of EndLevel:
But after when im going throght the trigger its trying to work 4th times
How can i change it?
I tried to add my "Player(car)" inside EndGame object but its didnt fix my problem.
using UnityEngine;
public class EndTrigger : MonoBehaviour
{
public GameManager gameManager;
void OnTriggerEnter()
{
gameManager.CompleteLevel();
}
}

First of all note that OnTriggerEnter(Collider other) requires a parameter of type Collider otherwise it wouldn't get called at all.
Flag
The simplest solution might be adding a bool flag as already mentioned by Eric Warburton's answer.
Layers
I would prefer to rather tackle the origin of the issue and suggest using different Layers and then configure the Layer-based collision detection via the Edit→ProjectSettings→Physics→ Layer Collision Matrix.
Create a Layer e.g. END and assign it to your goal collider object. Make this object not Is Trigger and rather attach your script checking for OnTriggerEnter here.
Create a Layer e.g. Player create a new dedicated invisible object with a collider and enable Is Trigger here. This object has the only purpose of colliding with the goal collider nothing else. Assign the Player layer here.
Configure the collision matrix thus that END only collides with Player and nothing else. And Player only collides with END and nothing else - or maybe later another effect layer like e.g. PowerUps ;)
You can create up to 24 custom layers and make use of the already existing ones so this should hold up a while
Tags
Another alternative to the Layers is using Tags
As previously I would make the END object not a trigger but rather use one on the Player.
Then you can simply compare the tag using CompareTag
void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Player")) return;
gameManager.CompleteLevel();
}
in very very complex games this might be sometimes better since you can create a lot more Tags than Layers.

Well there are a few things that I can think of trying.
You can make sure that only one of your colliders is a trigger. There should be a bool check in the properties to uncheck for your wheels.
You can also do something like creating a counter or a bool that prevents the OnTriggerEnter() from firing multiple times if you only want it to fire once. You can reset it at the start of levels if needs be.
Something like
void OnTriggerEnter()
{
if (!gameManager.IsLevelComplete)
gameManager.CompleteLevel();
}
Inside the gameManager script
public bool IsLevelComplete { get; set; }
public void CompleteLevel()
{
IsLevelComplete = true;
//Do stuff
}

Related

Unity scripting Ambient light turns off but I can't turn it back on with the same button

I've tried a few ways of doing it. I've asked a few friends but, nothing seems to work so far. I have changed the script several times so I don't have the other ways that I have tried anymore. However, this is the one that gave me the least errors. Others I had to continuously change it to a recommended way by Unity but ended at a dead end and nothing worked.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LightActivator : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
gameObject.SetActive(false);
}
else if (Input.GetKeyDown(KeyCode.Space))
{
gameObject.SetActive(true);
}
}
}
I want to be able to turn on and off the ambient/directional light with just 1 button. Is that not possible?
two little flaws here
after you do
gameObject.SetActive(false);
this very same object is now inactive -> this component as well -> Update is no longer being called at all ;)
You always will ever only treat the first case .. the second one is never called at all since the condition will always match already with the first block!
Instead separate the logic from the target object and do e.g.
// This class should go to a different object that is always active in your scene
public class LightActivator : MonoBehaviour
{
// Here you reference the object you want to (de)activate via drag&drop in the Inspector
public GameObject theLight;
void Update()
{
// You only need one condition check
if (Input.GetKeyDown(KeyCode.Space))
{
// INVERT the active state of the light object whatever it currently is
theLight.SetActive(!theLight.activeSelf);
}
}
}
In order to keep things together you could e.g. simply make the according light object a child of the LightActivator object ;)

Collision triggers without direct attachment to object

This is a very amateur question and I'm not entirely sure how to title it. Essentially I want to use the OnCollisionEnter2D method for multiple (while separate) game objects in the same script, rather than a script for each separate game object.
I've tried the following with no success:
void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.name == "Ballone")
{
charge += 1;
}
}
I believe this isn't functioning properly because it's attached to a script for my event system, but I'm not sure how to change the syntax.
OnCollisionEnter2D is an event handler.
During FixedUpdate when Unity performs collision calculations, it notifies colliders about whether they have collided and passes them the relevant Collision2D object. If that collider is attached to an object with a script component that has a OnCollisionEnter2D method, then the Collision2D object is passed into that method and the collision is handled.
In that context, it doesn't make sense for an unrelated object to handle OnCollisionEnter2D events that belong to other objects. However, you can capture the events on each object and then pass those events (or data from those events) to another more "general purpose" method in another script if you want.
E.g
Create a class and method to handle your collisions then attach it to a GameObject in the scene such as an empty GameObject named CollisionManager
class CollisionManagerScr : MonoBehaviour {
public void HandleCollision(GameObject collidingObject, Collision2D col) {
Debug.Log("I've managed this collision with " + collidingObject.name);
Debug.Log(col) // Some info about the Collision2D object
}
}
Then, attach a script to the objects that you want to check for collision on:
public class MyCollisionScript : MonoBehaviour {
private CollisionManagerScr colManager;
void Start() {
colManager = GameObject.Find("CollisionManager").GetComponent<CollisionManagerScr>();
}
void OnCollisionEnter2D(Collision2D col) {
if (col.gameObject.name.equals("Ballone")) {
if (colManager != null) {
colManager.HandleCollision(this, col); // Passes in the object that detected the collision
colManager.HandleCollision(col.gameObject, col) // Passes in the Ballone object
}
}
}
}
Now, whenever OnCollisionEnter2D is triggered for those objects, it calls HandleCollision. HandleCollision can then do whatever you want it to do. There undoubtedly is a use case for something like this but it smells a lot like unnecessary abstraction to me.
Please bear in mind that I wrote this on the fly so there could be problems/typos etc. Also, this is just one potential approach. You could also implement an approach that is effectively the same using EventHandlers etc. but that is just adding an additional event handling layer to the existing event handling layer.

Why are transforms typically passed when working with in-game objects rather than game objects?

I am well along in learning Unity basics but would like to nail down my understanding of the relation between components and the objects that own them. In the tutorials I've been watching typically use or pass the Transform component when working with objects pulled in code. For example:
void Explode ()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius);
foreach (Collider collider in colliders)
{
if (collider.tag == "Enemy")
{
Damage(collider.transform);
}
}
}
which calls "Damage" with the transform on the colliders it finds:
void Damage (Transform enemy)
{
Enemy e = enemy.GetComponent<Enemy>();
if (e != null)
{
e.TakeDamage(damage);
}
}
Looking at this, it is clear that it is pulling a Component found on all game objects and then using "GetComponent" to pull another component instance by name since the "Enemy" component isn't going to have its own method. Why not just pass collider.gameObject though? I tried this (after changing the Damage to expect a GameObject) and it worked fine.
This seems more intuitive to me but all of the tutorials I've seen use the transform component instead. Does anyone have any insight into why this is so? It would help me deepen my understanding of how Unity structures its code.
I'm not sure what tutorials you are following, but the ones I followed when I first started with Unity used GameObject instead of Transform.
I agree that it is more intuitive to use GameObject. Transform is a part or Component of a GameObject, but since it's mandatory it will always be there, hence the public field .transform. But since a GameObject is technically the holder of all its Componenents, it would be most logical, architecture wise, to pass that as a parameter instead.
At the end of the day, it makes little difference in your examples since you can call a lot of the same methods on both Transform as GameObject.
TLDR:
Use whatever you feel makes most sense.
Short answer
GameObject is more intuitive as you say.
Long answer
I think (my opinion) it's best to pass the most specific and relevant component.
For example, assume you have a function for a character to pick up a crate:
public void PickUp(Transform crate)
{
crate.SetParent(this.transform);
crate.localPosition = Vector3.zero; // or whatever your attachment point is
}
Here it makes sense in my mind that the Transform is passed, because it's the most specific component that will be needed.By passing GameObject here you are only delaying the inevitable GetComponent<Transform> or go.transform.
On the other hand, if you have a function to hide a create then passing the game object would be the bare minimum you need to achieve this:
public void Hide(GameObject crate)
{
crate.SetActive(false);
}
Passing anything else just delays the inevitable x.gameObject
In the explosion example I don't think that I would pass either to be honest. I would probably do this:
void Explode ()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius);
var enemies = colliders
.Select(x => x.GetComponent<Enemy>())
.OfType<Enemy>(); // Or IHealth, IDamageable, ITarget, etc if you have other things that can take damage.
foreach (var enemy in enemies) // If empty, nothing will happen by default
{
enemy.TakeDamage(damage);
}
}
With this approach you can see that there is no need to check tags or nulls. The enemies enumerable is guaranteed to contain either enemies or nothing at all.
By always passing gameObject/transforms you will always have to worry about what it is that you are really receiving at the destination component. You will also open yourself to situations where you are not sure anymore where certain changes to you gameObjects are being made because it can be anything in the system that's making those changes. Your ColorChangerComponent could actually also be moving the object around, destroying some other components, etc. By giving the component a Renderer, it more naturally limits the component to changes on the Renderer only (although you could obviously violate this limitation unless you perform actions against appropriate interfaces).
The only time it really makes sense to pass a generic component is when you are broadcasting this 'event' to a bunch of possible receivers that will each to a different thing with the object. I.e. you can't be certain at compile time what the gameobject will be used for, or you know that it will be used for many different things.
The fact that Unity itself returns Collider[] for the collision check sort of supports this line of thinking, otherwise it would've just returns GameObject[]. The same goes for OnTriggerEnter(Collider collider) or OnCollisionEnter(Collision collision) etc.

Unity spawning repeatedly, unused spawn positions, problems thereof

I want to enable and disable instead of instantiating and destroying everytime. It's pooling system that i seek.
instead of destroying it i want to disable it and instead of instantiating it again i want to enable it in a random position and type
You can't do that.
But the solution is incredibly simple. When you destroy it, call to the manager for a new one.
Exactly as you do here:
public void OnMouseDown()
{
manager.SpawnNewObstacle(transform.position);
Destroy(gameObject);
}
You're done!
Let's say you want (for example) the same type to spawn. Or, one green produces three gold, for example. Just do this
public void OnMouseDown()
{
if (myType == .Green)
manager.SpawnThreeGold(transform.position);
if (myType == .Gold)
manager.SpawnOneBlack(transform.position);
Destroy(gameObject);
}
.. or whatever the case may be. It's that easy.

Best way to tally objects in an area with collision? (Unity3D - C#)

What would be the best way to tally objects that are tagged under a specific name? What am i doing wrong? My current goal is to use a box collider to identify and tally up specific objects in a room. Any response to a solution or an alternative way of achieving this goal will be appreciated.
My attempt:
using UnityEngine;
using System.Collections;
public class roomColliders : MonoBehavior {
public int numberOfTargets;
void Start (){
numberOfTargets = 0;
}
void OnCollisionEnter(Collision col){
if(col.gameObject.tag == "Target"){
numberOfTargets += 1;
}
}
}
Additionally, I tried assigning with Box Colliders and Rigidbody in a myriad of ways with the objects and I had no success. I have three objects with a tag of "Target" assigned to them but in my inspector, the numberOfTargets tallies up only one object. I have come under the conclusion, that maybe I need to use a statement such as this "foreach(ContactPoint contact in col.contacts)". I could be wrong, tell me if so. If this is near the answer. Is it anyway I can assign 'col.contacts.tag = "Target"'? I get an error if I do so if I used it with 'foreach'.
If only you haven't do it intentionally, using OnCollisionEnter is wrong. You must use OnTriggerEnter instead. And do not forget to toggle on Is Trigger property on box collider attached to the object with roomColliders script.
And note that your script does not actually calculate number of objects currently intersected the trigger. It calculates number of times when objects of interest were start to intersect the trigger. But since the trigger may be moved to and fro, the same object of interest may intersect it more than once.

Categories