I have a plane that contains around 200 tiles, and I'd like to be able to detect which tile has been clicked by the player. Each tile has a box collider attached. I've also created an empty game object in the scene to which I attached EventSystem and the below script:
public class PlaneBehaviour : MonoBehaviour, IPointerDownHandler {
public GameObject ClickSymbol;
public void Start() {
var physicsRaycaster = FindObjectOfType<PhysicsRaycaster>();
if (physicsRaycaster == null) {
Camera.main.gameObject.AddComponent<PhysicsRaycaster>();
}
}
public void OnPointerDown(PointerEventData eventData) {
var o = eventData.pointerCurrentRaycast.gameObject;
}
}
Now, when user clicks anywhere, nothing happens - I've put a breakpoint inside the OnPointerDown method, but it's not getting hit (and the code inside the Start method is run - I've also verified that by putting a breakpoint in there).
There are many things that could cause the callback function not being called. Before trying these, put Debug.Log inside the OnPointerDown function to make sure that it's being called:
1.Go to GameObject-->UI--->EventSystem.
A GameObject named "EventSystem" will be created and will proper scripts such as EventSystem, StandAloneInputModule attached to it.
2.The PlaneBehaviour script must be attached to the GameObject with the Collider not to an empty GameObject. It detects click on the Objects it is attached to.
3.If you are using a collider that ends with "2D" in its name then Physics2DRaycaster must be used instead of PhysicsRaycaster.
4.Are you using more than two cameras? If so manually attach PhysicsRaycaster to the correct Camera that is currently showing that Object.
5.Re-size the GameObjects you want to detect clicks on to be bigger. I've seen doing this solve the problems sometimes. You then have to move the camera back if this works.
Related
So I'm attempting to create a 2D game in Unity where the gameObjects would be destroyed upon hitting the walls. So i decided to use the isTrigger function and add the script to each of the walls. However, I do not know why the gameObjects don't get destroyed. I have tried to set Collision type as both discrete and continuous for the walls and gameobjects and I've also added static rigidbodys to the walls to see if that would help and even tried changing the size of the collisionbodys of the walls.
Here is the code for the wallscript
public class wallScript : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public void OnTrigger2D(Collider2D other)
{
if (other.tag == "player01")
{
Destroy(other.gameObject);
}
}}
p.s. Even if I remove the conditional statement Destroy() still does not work
You should use void OnCollisionEnter2D(Collision2D other) {...} instead of void OnTrigger2D(Collider2D other).
And uncheck Is Trigger checkbox on your object's Collider.
To Fix The Problem You Are Facing, First Select The Wall Sprite In The Scene, Scroll Down To The Collider And Make Sure Is Trigger Is Checked.
Other Than That Just Check If The Tag You Typed Into The Code Matches The Player You Are Trying To Destroy. Remember, It's Case Sensitive!
After That The Code Should Run Just Fine.
So i have this code to enter a new scene:
using System.Collections;
using UnityEngine;
// add this line to use the SceneManagment library
using UnityEngine.SceneManagement;
public class LoadScenes : MonoBehaviour {
[SerializeField] private string loadLevel;
void onTriggerEnter(Collider other) {
if (other.CompareTag ("Player")) {
SceneManager.LoadScene (loadLevel);
}
}
}
I then add this script to the cube and select it a trigger. I then type in the scene that I want it to send me too, but when i walk into it nothing happens at all. I have tried different variations but it just doesnt seem to work.
My character that I am using is a unity asset called man in suit but I have selected its tag as "Player". Any suggestions would be great!
The Handler for your trigger won't be invoked
As Sunimal allready noted you need to fix the typo.
void OnTriggerEnter(Collider other) {
if (other.CompareTag ("Player")) {
SceneManager.LoadScene (loadLevel);
}
}
Ensure your Scene is included and checked in the Build Settings
As you can see in the Screenshot below i have added a SampleScene to my build settings. There are 2 ways of adding scenes into the build
By clicking "Add Open Scenes" you can add the scene which is
currently open to that list.
Drag & Drop the scene from your ProjectView into the List
Ensure your SceneName is set correctly
Your loadLevel field would in my case need to have the value "Scenes/SampleScene".
[SerializeField] private string loadLevel;
The player needs a collider
As you use the OnTriggerEnter method, your Player object needs to have some sort of Collider attached to it. This can be a BoxCollider, SphereCollider or some other Collider. Note that the "Is Trigger" checkbox needs to be checked. Else it won't act as trigger.
Edit: Thanks Eddge for correcting me. See this answer for a deeper explanation about Triggers.
Programatically ensure you have a BoxCollider component beside your LoadScenes component
You can add the RequireComponent Attribute at your class. It basically ensures you have the given type added as a component. This will also automatically add a box collider to an object, when you add this script.
[RequireComponent(typeof(BoxCollider))]
public class LoadScenes : MonoBehaviour {
/// your other code is here
}
Thanks to Sunimal for this hint!
What if that did not solve the problem?
If all this does not help, please provide an screenshot of the inspector of your Playerobject. That way we can see what components are attached to that object and how they are "configured"
SceneManagement
To use the SceneManager to load a scene you must ensure that your scene is in the build settings, per Tobias's answer.
Triggers
In all software development case does matter and it is incredibly important. OnTriggerEnter is not the same as onTriggerEnter, also note OnTriggerEnter(Collider col) is not the same as OnTriggerEnter(Collision col)
In order to use any of the trigger methods there are 3 things that are a must:
Both Objects have to have colliders.
One of the colliders have to be marked as a trigger.
One of the objects have to have a rigidbody.
The trigger event is sent to the object with the rigidbody and whatever object is the trigger, in the circumstance that both objects are triggers both will receive it.
Here's the code I'm having problems with:
public class PlaneBehaviour : MonoBehaviour {
public GameObject ClickSymbol;
public void Update() {
if (Input.GetMouseButtonDown(0)) {
Instantiate(ClickSymbol, Input.mousePosition, Quaternion.identity);
}
}
}
When user clicks the left mouse button, then multiple ClickSymbol's will be instantiated. How to prevent that behavior (create the object only once) ?
As for the PlaneBehaviour script, it's attached to multiple (not
intersecting, not overlapping) objects on a plane
That's what I thought you are doing and wanted to verify this before adding an answer. Joe's answer should work but this is a bad design.
You get the multiple Objects instead of one because the script is attached to multiple Objects.
The proper solution here is to have one script that reads from the Input then call a function or trigger an event in another script if required. Create a new script called InputReader or with similar name then move your if (Input.GetMouseButtonDown(0))... Make that that this new script is only attached to one GameObject. Just attach it to an empty GameObject. That's it.
It will prevent problems such as executing code twice when there is an input event.
Now, let;s say that you want to notify another script attached to another GameObject when key is pressed, you can find that GameObject, get that script that is attached to it then call its function.
GameObject obj = GameObject.Find("YourOtherObjectName");
Script script = obj.GetComponent<Script >();
script.notifyKeyPress();
I attached it to multiple game objects to be able to detect which game
object was clicked
That's not how to detect which GameObject is clicked. Since this is a plane, I assume it is a 3D Object with a 3D collider. See 6.For 3D Object (Mesh Renderer/any 3D Collider) from my other post on how to do this.
I am trying to create a Minesweeper + clicker/idle game for practice. For now, I can't get the clicking to work properly. I spawned in all objects through code meaning they are all the same prefabs but at different locations. When I click them the code activates for all of them and all of the tiles change to being open. I am not sure if I'm using the wrong click function here, as I know there is also an OnMouseDown() function instead of Input.GetMouseButtonDown(0) but the only one where the code activates is in the second one.
public class TileClick : MonoBehaviour {
public GameObject openTile;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown(0)) {
Instantiate(openTile, transform.position, Quaternion.identity);
Object.Destroy(this.gameObject);
}
}
}
That's my clicking detection. While it does detect clicks, as I said before, it detects them for ALL tiles and activates them all. I only want it to activate on one of them. I've seen RayCasting stuff but I really need more explanation on how that thing works (if that's the solution).
If the TileClick script component is attached to your prefab, then for every tile you instantiate, you have a corresponding TileClick component attached.
When you click, the input is detected by all of these blocks simultaneously, and each of them calls Instantiate but the transform.position is the one corresponding to the tile that makes the call. This is why it seems like all the tiles open up simultaneously, because you are, in fact, instantiating an "open" tile on each of them every time you click.
You should keep all your user input code in a separate MonoBehaviour, and attach that to a single empty GameObject in your scene. For detecting the hit, you should attach a 2DCollider component to your tile prefab (you can decide the type according to the shape of your tiles), and then just use a simple RayCast in your user input code to instantiate an "open" tile at the position where the hit occurs:
void Update () {
if (Input.GetMouseButtonDown(0)) {
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition),
Vector2.zero);
if(hit.collider != null)
{
Instantiate(openTile,
hit.collider.gameObject.transform.position,
Quaternion.identity);
}
}
I highly recommend understanding how RayCast works since it is a very useful tool. Here's a good place to start: https://docs.unity3d.com/ScriptReference/Physics2D.Raycast.html
Let me explain the question first :)
I have a hero and an enemy. It is a fighting game. Both hero and enemy have idle, block, punch and getHit animations and states in the Animation Controller. The hero and enemy have scripts attached to them. The hero is controlled by player and then enemy is controller by AI.
Now I attach the script to hero first and then the enemy. Now when the enemy punches and if hero is not defending the hero takes a hit. But if the enemy is not blocking and the hero hits the enemy doesnt take the hit. This is because the script was attached to hero first.
Now if I remove the script from both and attach the enemy script first to enemy and then attach the hero script to hero. The wiseversa is true. In the enemy the hits will be registered and on the hero the hits wont be registered.
I am not sure why this is happening and I have tried different things and still the problem persists.
I also tried looking everywhere online for solution but none of them addressed my concern.
Thanks :)
below is the enemy script that checks is enemy can take hit
void Update () {
myTick++;
currentTick = myTick;
GameObject player = GameObject.Find("theDude");
Animator pAnim = player.GetComponent<Animator>();
//Getting Hit
if (pAnim.GetBool("tIsPunching"))
{
if (anim.GetBool("bEnemyIsDefending") == false)
{
Debug.Log("enemy got hit");
anim.SetTrigger("tEnemyGotHit");
anim.SetBool("bEnemyIsDefending", true);
}
}
}
And here is the hero script that checks if hero can take hit.
void Update () {
totalTime += Time.deltaTime;
GameObject enemy = GameObject.Find("Enemy");
Animator eAnim = enemy.GetComponent<Animator>();
//Getting Hit
if (eAnim.GetBool("tEnemyIsPunching"))
{
if (anim.GetBool("bIsDefending") == false)
{
Debug.Log("player got hit");
anim.SetTrigger("tGotHit");
}
}
}
Instead of get object I used to have a public GameObject and attached the hero and enemy in the respective classes. But It doesnt make any difference.
I think your problem lies in your use of the triggers tEnemyIsPunching and tIsPunching. Triggers get reset whenever they cause a transition to occur
(see: https://docs.unity3d.com/Manual/AnimationParameters.html).
In your case tIsEnemyPunching or (tIsPunching) is getting reset in the same frame as it gets set. Here is an example of what one update loop may look like in your game if the hero script is added first:
Hero Update()
Check if enemy is punching
He is not, so don't do anything
Enemy Update()
Punch! Set 'bIsEnemyPunching' = true
Animation Update()
bIsEnemyPunching is true so transition to the punching animation
reset bIsEnemyPunching = false
On the next update let's look at what happens in the hero update:
Hero Update()
Check bIsEnemyPunching
bIsEnemyPunching was reset in the previous frame, so it is false
Since the enemy isn't punching don't do anything
So Hero never sees the punch because bIsEnemyPunching got reset before Hero had a change to check it.
This is why the order of adding the scripts matters. Whichever script updates first is able to punch because the second script will see the trigger before it gets reset. However, the second script to update will never be able to punch because the trigger gets reset before the other scripts gets a chance to update.
One solution is to check the name of the animation state instead of the trigger value.
static int punchStateHash = Animator.StringToHash("Punch");
AnimatorStateInfo enemyState = eAnim.GetCurrentAnimatorStateInfo(0);
if (enemyState.nameHash == punchStateHash){
//The enemy is punching!
}
Alternatively, whenever a punch is triggered just call a function on whichever character is getting punched. So when the enemy punches the hero the Punch(...) function on the enemy should call TakePunch(...) on hero (or whatever you want to call those functions). The hero then checks his own state to determine if he is blocking.
Additional Note
You should avoid using the following code in an update function:
GameObject player = GameObject.Find("theDude");
Animator pAnim = player.GetComponent<Animator>();
These functions are very slow because Unity must search all objects for the one called theDude and then search all it's components to find the animator. Do this once and save off the result.