- Home /
Having an issue blocking UI taps from passing through the UI into a gameobject.
Hey guys, I've done a ton of searches on this and I've read all the forums on this, but I'm fairly new to Unity so most of the stuff is a little over my head. Here's my setup: I have a 2D click-to-move adventure game that's being developed for iPad. The player taps the ground and the character moves to that location. I was using the UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject()) to block UI taps from making the player character start wandering, but when the game is actually built to the iPad, it doesn't work at all. Every time the UI is touched, the player's character move, and it's really impacting the game. I'm going to attach my player movement script and maybe you guys could help me figure out how to improve it.
using UnityEngine; using System.Collections;
public class PlayerTest : MonoBehaviour {
public Animator animator;
public Vector2 target = new Vector2();
public Vector2 direction = new Vector2();
public Vector2 position = new Vector2();
public Vector2 movement = new Vector2();
Rigidbody2D rb2D;
public float speed = 2.0f;
void Start()
{
animator = gameObject.GetComponent<Animator>();
rb2D = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
position = gameObject.transform.position;
if (Input.GetMouseButtonDown(0) && !UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
{
target = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Debug.Log("Click");
}
if (target != Vector2.zero && (target - position).magnitude >= 0.1)
{
direction = (target - position).normalized;
movement.x = (direction.x * speed * Time.deltaTime);
movement.y = (direction.y * speed * Time.deltaTime);
rb2D.velocity = movement;
//gameObject.transform.position = new Vector3((direction.x * speed * Time.deltaTime),(direction.y * speed * Time.deltaTime),0);
//gameObject.transform.position.y += (direction.y * speed * Time.deltaTime);
animator.SetBool("isMoving", true);
animator.SetFloat("moveX", direction.x);
animator.SetFloat("moveY", direction.y);
}
else
{
direction = Vector2.zero;
rb2D.velocity = Vector2.zero;
animator.SetBool("isMoving", false);
}
}}
Answer by Vicarian · May 14, 2018 at 04:23 PM
UI elements are GameObjects as well. So IsPointerOverGameObject() will return true even for UI elements. Switch to using a Physics2D.Raycast. The origin point will be your tap location on the screen (Use a ScreenToWorld call), and the direction will be into the scene (most likely forward, not sure with 2D games). Tag the environment as "Ground" and when you Raycast against it, check the tag for Ground, then move if it is. If you need help writing the code, reply back here.
I could use some help writing the code, I've never used a Raycast before.
Answer by Vicarian · May 14, 2018 at 10:17 PM
Sorry, had work come up.
Actually, you don't have to use a direct Raycast call after all. Here's what you do:
Make sure you have an EventSystem somewhere in your scene (search for it)
If you don't, highlight your scene in the Hierarchy, right click, GameObject > UI > EventSystem
Add a Physics2DRaycaster to your scene camera (should be your MainCamera)
Create the GroundMove script below
Highlight all ground objects in your scene
Click the Add Component button in the inspector and search for GroundMove
Double Click the script
Make sure to overwrite your PlayerTest file with the one I provided
You'll need to overwrite your PlayerTest file with the following:
using UnityEngine;
public class PlayerTest : MonoBehaviour {
public Animator animator;
public bool needToMove;
public float timeMoveNeeded;
public Vector2 direction = new Vector2();
public Vector2 position = new Vector2();
public Vector2 movement = new Vector2();
public Vector2 target = new Vector2();
Rigidbody2D rb2D;
public float speed = 2.0f;
void Start() {
animator = gameObject.GetComponent<Animator>();
rb2D = GetComponent<Rigidbody2D>();
foreach (GroundMove gm in FindObjectsOfType<GroundMove>())
gm.RaiseGroundClicked += HandleGroundClicked;
}
void FixedUpdate() {
if (needToMove)
transform.position = Vector2.Lerp(transform.position, target, speed * Time.fixedDeltaTime);
if ((target - (Vector2)transform.position).magnitude <= 0.1f)
needToMove = false;
}
void HandleGroundClicked(object sender, Vector2EventArgs e)
{
target = e.Data;
if ((target - position).magnitude >= 0.1)
{
needToMove = true;
direction = (target - position).normalized;
movement.x = (direction.x * speed * Time.deltaTime);
movement.y = (direction.y * speed * Time.deltaTime);
animator.SetBool("isMoving", true);
animator.SetFloat("moveX", direction.x);
animator.SetFloat("moveY", direction.y);
}
}
}
using UnityEngine;
using UnityEngine.EventSystems;
using System;
public class GroundMove : MonoBehaviour, IPointerDownHandler {
public event EventHandler<Vector2EventArgs> RaiseGroundClicked;
public void OnPointerDown(PointerEventData data)
{
OnRaiseGroundClicked(new Vector2EventArgs(data.pointerCurrentRaycast.worldPosition));
}
protected virtual void OnRaiseGroundClicked(Vector2EventArgs e)
{
EventHandler<Vector2EventArgs> handler = RaiseGroundClicked;
if (handler != null)
handler(this, e);
}
}
public class Vector2EventArgs : EventArgs {
Vector2 m_data;
public Vector2EventArgs(Vector2 data)
{
m_data = data;
}
public Vector2 Data { get { return m_data; } }
}
Ok, what's all that do?
Basically, I don't know how many ground objects you have, so I made an event handler with a custom event argument type. When you click any ground objects, you use the IPointerDown event (provided by the EventSystem in Unity) to raise a custom event (GroundMove.RaiseGroundClicked)
which reports the world location of the point that was clicked. The player object continually listens for any data to come up from these scripts (the event registration in PlayerTest.Start
When data appears (the event is being handled) the player reacts to the click by beginning to move. It obviates the need for FixedUpdate in this case, so rather than checking each frame to see whether the user has interacted with the game, the player object is happy to sit there until the user actually does something. Neat, huh?
EDIT Switched to a Lerp. Setting velocity directly on rigidbodies I think isn't correct practice - need to look into it. I think correct practice is to use the AddForce methods, but I'm not terribly sure. The new code in PlayerTest gets you moving.
So I spent some time this afternoon trying the solution and I can't seem to get it to work. The player character doesn't move at all, even after switching out the deltaTime for the FixedUpdate. Here's the new movement script.
using System.Collections;
using System.Collections.Generic; using UnityEngine;
public class PlayerTest_ : $$anonymous$$onoBehaviour {
public Animator animator;
public Vector2 direction = new Vector2();
public Vector2 position = new Vector2();
public Vector2 movement = new Vector2();
Rigidbody2D rb2D;
Vector2EventArgs e;
public float speed = 2.0f;
bool needTo$$anonymous$$ove = false;
Vector2 target;
void Start()
{
animator = gameObject.GetComponent<Animator>();
rb2D = GetComponent<Rigidbody2D>();
// Subscribe to the Ground$$anonymous$$ove EventHandler
foreach (Ground$$anonymous$$ove gm in FindObjectsOfType<Ground$$anonymous$$ove>())
gm.RaiseGroundClicked += HandleGroundClicked;
}
void OnDestroy()
{
// EventHandlers are memory locations, so we need to return the
// memory back to the system when this is destroyed
foreach (Ground$$anonymous$$ove gm in FindObjectsOfType<Ground$$anonymous$$ove>())
gm.RaiseGroundClicked -= HandleGroundClicked;
}
void HandleGroundClicked(object sender, Vector2EventArgs e)
{
target = e.Data;
needTo$$anonymous$$ove = true;
}
private void FixedUpdate()
{
if (!needTo$$anonymous$$ove)
return;
Vector2 target;
position = gameObject.transform.position;
target = e.Data;
if (target != Vector2.zero && (e.Data - position).magnitude >= 0.1)
{
direction = (target - position).normalized;
movement.x = (direction.x * speed * Time.deltaTime);
movement.y = (direction.y * speed * Time.deltaTime);
rb2D.velocity = movement;
animator.SetBool("is$$anonymous$$oving", true);
animator.SetFloat("moveX", direction.x);
animator.SetFloat("moveY", direction.y);
}
else
{
direction = Vector2.zero;
rb2D.velocity = Vector2.zero;
animator.SetBool("is$$anonymous$$oving", false);
}
needTo$$anonymous$$ove = false;
}
}
The Ground$$anonymous$$ove script is attached to the ground objects. $$anonymous$$y EventSystem has the Event System script and Standalone Input $$anonymous$$odule Script attached. Any advice is welcomed, and thank you for everything so far.
I'll take a look at it when I get home. The small test I did might not have been good enough.
Before I $$anonymous$$r into the code, did you add a collider of any sort to the ground objects? I forgot to mention that.
Answer by unity_BGmJcdED5Dqjfw · May 16, 2018 at 01:22 AM
You could also make a layermask for the UI elements and when using RayCastHit pass in the layermask to check against eg: RaycastHit(start vector*the point of origin for the click*, out ray*the ray that is being cast*, layermask) and it will only return the gameobjects hit on that layer. Then you simply need to set the layer of the UI elements to match that of the layer mask you declared. For more info on this I would recommend watching: this
Also a good approach, if you need to raycast against more than one thing, since the Physics2D raycaster gets everything in its mask.
I think the video link might be broken, I understand what you're saying in concept, but I'm not sure how to set that up tangibly.
This one should work sorry posting from my phone
Your answer
Follow this Question
Related Questions
Move Slider-value to middle value when released 1 Answer
Having trouble with a basic movement script. 2 Answers
Player Movement Problems. 1 Answer
Which UI events propagate? 0 Answers
unity player keeps rotate when using joystick as inbut 0 Answers