We are building an example that is similar to the "Kitten - Placing Virtual objects in AR" as shown here:
https://developers.google.com/tango/apis/unity/unity-howto-placing-objects.
Basically when you touch the screen, a kitten appears on the real world plane (floor).
In our app we have a side menu, with a few buttons and each shows a different game object. We want to detect touch anywhere on the screen except where there is UI. We want the UI to block touches in Tango, and only allow touches to instantiate the related game objects on areas of the screen without UI elements.
The touch specific code is here:
void Update() {
if (Input.touchCount == 1) {
// Trigger placepictureframe function when single touch ended.
Touch t = Input.GetTouch(0);
if (t.phase == TouchPhase.Ended) {
PlacePictureFrame(t.position);
}
}
}
(The PlacePictureFrame() places a picture frame object at the touch position.)
I can't find any Tango examples which has touch and UI combined. I've tried an asset called LeanTouch to block touches behind UI elements but it doesn't seem to work with Tango specifically. Please help!
I have tried using method 5 from this:
How to detect events on UI and GameObjects with the new EventSystem API
and while it does add a PhysicsRaycaster to the TangoARCamera (which is tagged as MainCamera), the OnPointerDown method produces no debug logs no matter where you touch the screen. Tango is a special case so this is not a duplicate question. See below:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class PictureFrameUIController : MonoBehaviour, IPointerClickHandler {
public GameObject m_pictureFrame;
private TangoPointCloud m_pointCloud;
void Start() {
m_pointCloud = FindObjectOfType<TangoPointCloud>();
addPhysicsRaycaster();
}
void addPhysicsRaycaster() {
PhysicsRaycaster physicsRaycaster = GameObject.FindObjectOfType<PhysicsRaycaster>();
if (physicsRaycaster == null) {
Camera.main.gameObject.AddComponent<PhysicsRaycaster>();
}
}
public void OnPointerClick(PointerEventData eventData) {
Debug.Log("Clicked: " + eventData.pointerCurrentRaycast.gameObject.name);
PlacePictureFrame(eventData.pointerCurrentRaycast.screenPosition);
}
//void Update() {
// if (Input.touchCount == 1) {
// // Trigger placepictureframe function when single touch ended.
// Touch t = Input.GetTouch(0);
// if (t.phase == TouchPhase.Ended) {
// PlacePictureFrame(t.position);
// }
// }
//}
void PlacePictureFrame(Vector2 touchPosition) {
// Find the plane.
Camera cam = Camera.main;
Vector3 planeCenter;
Plane plane;
if (!m_pointCloud.FindPlane(cam, touchPosition, out planeCenter, out plane)) {
Debug.Log("cannot find plane.");
return;
}
// Place picture frame on the surface, and make it always face the camera.
if (Vector3.Angle(plane.normal, Vector3.up) > 60.0f && Vector3.Angle(plane.normal, Vector3.up) < 140.0f) {
Vector3 forward = plane.normal;
// Vector3 right = Vector3.Cross(plane.normal, cam.transform.forward).normalized;
// Vector3 forward = Vector3.Cross(right, plane.normal).normalized;
Instantiate(m_pictureFrame, planeCenter, Quaternion.LookRotation(forward, Vector3.up));
} else {
Debug.Log("surface is not steep enough for picture frame to be placed on.");
}
}
public void DeleteAllFrames() {
GameObject[] frames = GameObject.FindGameObjectsWithTag("Frame");
if (frames == null) {
return;
}
foreach (GameObject frame in frames) {
Destroy(frame);
}
}
}
If you want to detect a click anywhere on the screen except for where there is a UI control/component, you have to check if the pointer is over the UI with EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId).
If on desktop, use EventSystem.current.IsPointerOverGameObject(). You are using Tango so EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId). should be used.
void Update()
{
if (Input.touchCount == 1)
{
//Trigger placepictureframe function when single touch ended.
Touch t = Input.GetTouch(0);
if (t.phase == TouchPhase.Ended)
{
//Make sure that pointer is not over UI before calling PlacePictureFrame
if (!EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))
{
PlacePictureFrame(t.position);
}
}
}
}
Edit:
It seems like this works with TouchPhase.Began only.
Change t.phase == TouchPhase.Ended to t.phase == TouchPhase.Began and this should work as expected. Make sure to test with a mobile device/tango instead of your mouse.
Related
I'm working on a simple android game in Unity 2D, I have to make our player jump by adding force or changing the velocity of its Rigidbody2D (I know how). The problem is I don't know how to tell unity that add force on single screen tap (I'm not familiar with touch inputs) so any help would be appreciated. Please keep it clean as I'm just a beginner.
Here's the script I made so far.
{
public Rigidbody2D _playerRB;
public bool _canMove;
public float _speed; //keep this above cam speed (relative)
public float _jumpForce;
void Start()
{
_playerRB = GetComponent<Rigidbody2D>();
}
void Update()
{
_playerRB.velocity = new Vector2(_speed, _playerRB.velocity.y);
if (Input.touchCount > 0)
{
_playerRB.velocity = new Vector2(_playerRB.velocity.x, _jumpForce);
}
}
}
You can achieve this by handling the touch input in your Update function:
void Update()
{
_playerRB.velocity = new Vector2(_speed, _playerRB.velocity.y);
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
_playerRB.velocity = new Vector2(_playerRB.velocity.x, _jumpForce);
}
}
}
Also there are different TouchPhases such as TouchPhase.Moved which indicates that the touch input has moved and TouchPhase.Ended which indicates that the finger has stopped touching the screen. Using these in your Update you can achieve a lot with touch controls.
I am developing a game where objects fall from the top of the screen, and you try to and tap on them before they reach the bottom of the screen. The code works fine when I played the game in the editor (with the touch controls swapped to mouse controls), except when I run the game on a phone, the game only seems to register a successful hit if you tap slightly in front of the object in the direction that it is traveling, and does not register a hit if you tap towards the back end or center of the object. I have built and ran the game over 10 times now, each time trying to fix this issue but nothing seems to help. My theory at the moment is that my code for the touch controls have too much going on and/ or have redundancies and by the time it checks whether or not an object is at the position of the touch, the object has moved to a different location. Any thoughts on why the hit boxes are off, and is there a better way to do hit detection with touch screen?
void FixedUpdate()
{
if (IsTouch())
{
CheckTouch(GetTouchPosition());
}
}
// Returns true if the screen is touched
public static bool IsTouch()
{
if (Input.touchCount > 0)
{
if (Input.GetTouch(0).phase == TouchPhase.Began)
{
return true;
}
}
return false;
}
// Gets the position the touch
private Vector2 GetTouchPosition()
{
Vector2 touchPos = new Vector2(0f, 0f);
Touch touch = Input.GetTouch(0);
if (Input.GetTouch(0).phase == TouchPhase.Began)
{
touchPos = touch.position;
}
return touchPos;
}
// Checks the position of the touch and destroys the ball if the
ball is touched
private void CheckTouch(Vector2 touchPos)
{
RaycastHit2D hit =
Physics2D.Raycast(Camera.main.ScreenToWorldPoint(
(Input.GetTouch(0).position)), Vector2.zero);
if (hit.collider != null)
{
destroy(hit.collider.gameObject);
}
}
I am making a game where an object follows my finger. But I do not want the object to jump to my finger if I tap anywhere on the screen. I am using a raycast to do this. It works perfectly except if I move my finger too fast the object freezes.
My theory is that because of it being in Update() that once I move my finger off the object it detects that I can no longer drag it. I have no idea how to fix this.
public GameObject Bigball;
public GameObject Smallball;
// Update is called once per frame
private void Update ()
{
// lets ball follow finger
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Moved || Input.GetTouch(0).phase == TouchPhase.Stationary)
{
var touch = Input.GetTouch(0);
Vector3 fingerPos = touch.position;
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(fingerPos);
if (Physics.Raycast(ray, out hit) && (hit.collider.tag == "ball"))
{
Smallball.SetActive(false);
Bigball.SetActive(true);
Vector3 pos = new Vector3((Camera.main.ScreenToWorldPoint(Input.GetTouch(0).position)).x,
(Camera.main.ScreenToWorldPoint(Input.GetTouch(0).position)).y, 0);
Bigball.transform.position = pos;
}
}
else
{
Bigball.SetActive(false);
Smallball.SetActive(true);
Smallball.transform.position = new Vector3(Bigball.transform.position.x, Bigball.transform.position.y, 0);
}
}
It might make more sense to only Raycast once when the finger touches the screen. If that Raycast finds an object to drag, simply store that object and move it to follow the finger every Update(), as long as the finger continues to touch the screen.
Once the finger is released, just forget about the object until the next touch finds a new object to drag.
An alternative would be to use no Raycasts at all but build on IBeginDragHandler, IDragHandler and IEndDragHandler, as Bijan already mentioned.
This question already has answers here:
How to detect click/touch events on UI and GameObjects
(4 answers)
Closed 4 years ago.
i'm currently making a soccer game.
In this mini-game, when the player touch the ball, it adds force, and the goal is to make the higher score.
So i wrote:
void Update()
{
if(Input.touchCount == 1 && Input.GetTouch(0).phase== TouchPhase.Began)
{
AddForce, and playsound ect...
}
}
With this, when i touch anywhere on the screen, it adds force, but i just want to add force when i touch my gameobject (the ball).
How can i do that?
Thanks! :)
With this, when i touch anywhere on the screen, it adds force, but i
just want to add force when i touch my gameobject (the ball).
To detect tap on a particular GameObject, you have to use Raycast to detect click on that soccer ball. Just make sure that a collider(eg Sphere Collider) is attached to it.
void Update()
{
if ((Input.touchCount > 0) && (Input.GetTouch(0).phase == TouchPhase.Began))
{
Ray raycast = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
RaycastHit raycastHit;
if (Physics.Raycast(raycast, out raycastHit))
{
Debug.Log("Something Hit");
if (raycastHit.collider.name == "Soccer")
{
Debug.Log("Soccer Ball clicked");
}
//OR with Tag
if (raycastHit.collider.CompareTag("SoccerTag"))
{
Debug.Log("Soccer Ball clicked");
}
}
}
}
Use OnMouseDown() in your script. Here is an example:
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour {
void OnMouseDown() {
Application.LoadLevel("SomeLevel");
}
}
P.S : remove that previous code from Update() method.
EDIT : Alternative code for mobile devices
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class OnTouchDown : MonoBehaviour
{
void Update () {
// Code for OnMouseDown in the iPhone. Unquote to test.
RaycastHit hit = new RaycastHit();
for (int i = 0; i < Input.touchCount; ++i) {
if (Input.GetTouch(i).phase.Equals(TouchPhase.Began)) {
// Construct a ray from the current touch coordinates
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(i).position);
if (Physics.Raycast(ray, out hit)) {
hit.transform.gameObject.SendMessage("OnMouseDown");
}
}
}
}
}
Attach the above script to any active gameObject in the scene and OnMouseDown() will work for mobile devices as well.
You need to do several items here
you need to raycast the touch to determine if the ball is touched
you need to increment the power on the duration of the touch
you need to fire the ball and reset the power on release of the touch
The code example here shows how to handle the rayCast
the code example here should help you with part 2
3 is nice and simple you just need to trigger your launch code an reset on Ended
put them together and you will get something like this
public class ExampleClass : MonoBehaviour {
void FixedUpdate() {
for (int i = 0; i < Input.touchCount; ++i) {
Ray camRay = Camera.main.ScreenPointToRay (Input.GetTouch(i).Position);
RaycastHit ballHit;
if(Physics.Raycast (camRay, out ballHit, camRayLength, ballMask))
{
if (Input.GetTouch(i).phase == TouchPhase.Stationary) {
power += speed + Time.deltaTime;
}
else if (Input.GetTouch(i).phase == TouchPhase.Ended) {
LauchBall(power);
power = 0;
}
}
}
}
}
NOTE:code is for example only it has not been tested and is not complete
this uses the physics update as the trigger you can instead link directly to an objects triggers as suggested by some of the other answers
Take Touch Event using Co-Rouitne will be good for you. If you will not use
coroutine, then it constant check for touch event in OnUpdate. Its better to take
touch event in co-routine.
This is my link in which you can check how to use it.
http://unitycodestuff.blogspot.in/2017/11/how-to-use-co-routine-for-taking-touch.html
I have this script where it would add the SelectMove script at runtime when a character was touched by the user.
public GameObject target;
void Start () {
}
// Update is called once per frame
void Update () {
if(Input.touchCount > 0 || Input.GetTouch(0).phase == TouchPhase.Began){
ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
Debug.DrawRay(ray.origin,ray.direction * 20,Color.red);
if(Physics.Raycast(ray, out hit,Mathf.Infinity)){
Debug.Log(hit.transform.gameObject.name);
target = hit.transform.gameObject;
//Destroy(hit.transform.gameObject);
selectedPlayer();
}
}
}
void selectedPlayer(){
target.AddComponent(Type.GetType("SelectMove"));
}
In the code above, if the user will click Player A, Player A will move using accelerometer. What I need to do is if I click another character, say Player B, I need Player A to stop moving and it is now Player B's time to move. But I seemed not to get what I want. I tried destroying the script by using Destroy(this) or by this code:
if (target != null)
{
var sphereMesh = target.GetComponent<SelectMove>();
Destroy(sphereMesh);
}
But it is still not working.
If I don't destroy the previous added script, if the user clicks for another character, the previous selected player still moves along with the new one.
Is there another way that I could achieve what I needed to do?
public GameObject target = null;
void Update ()
{
if(Input.touchCount > 0 || Input.GetTouch(0).phase == TouchPhase.Began)
{
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
Debug.DrawRay(ray.origin,ray.direction * 20,Color.red);
RaycastHit hit;
if(Physics.Raycast(ray, out hit,Mathf.Infinity))
{
Debug.Log(hit.transform.gameObject.name);
if(this.target != null){
SelectMove sm = this.target.GetComponent<SelectMove>()
if(sm != null){ sm.enabled = false; }
}
target = hit.transform.gameObject;
//Destroy(hit.transform.gameObject);
selectedPlayer();
}
}
}
void selectedPlayer(){
SelectMove sm = this.target.GetComponent<SelectMove>();
if(sm == null){
target.AddComponent<SelectMove>();
}
sm.enabled = true;
}
The code reuses your base, the new part is that you use the previous selected object to disable the SelectMove script (I assume it is the one moving things). Instead of destroying it, it enables/disables so you save that memory consumption.
I do not think Adding and destroying scripts is the convenient method here.
Try this:
1. Add the SelectMove script to all movable players.
2. Add a public property bool IsMoving = false; to the SelectMove script and make an if statement to move if IsMoving is true.
3. Change selectedPlayer() method to instead change the target player IsMoving to true, and set the previous one (or simply all other players) to false.