How to detect touches and long touches on buttons in unity for android?
I have already tried this function but it returns true if i touch any place on screen:
bool checkTouch()
{
for(int i = 0; i < Input.touchCount; i++)
{
TouchPhase tp = Input.GetTouch(i).phase;
if(tp == TouchPhase.Began || tp == TouchPhase.Ended || tp == TouchPhase.Stationary)
return true;
}
return false;
}
One way to achieve Buttons that allow that, is to create your own button, implementing the necessary interfaces like IPointerDownHandler, IPointerUpHandler.
That way you could manage how will the button act, here is an example:
using UnityEngine;
using UnityEngine.EventSystems;
public class LongClickButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
private bool pointerDown;
private float pointerDownTimer;
[SerializeField]
private float requiredHoldTime;
public void OnPointerDown(PointerEventData eventData)
{
pointerDown = true;
Debug.Log("OnPointerDown");
}
public void OnPointerUp(PointerEventData eventData)
{
Reset();
Debug.Log("OnPointerUp");
}
private void Update()
{
if (pointerDown)
{
pointerDownTimer += Time.deltaTime;
if (pointerDownTimer >= requiredHoldTime)
{
//do your LongClick stuff
Debug.Log("LongClick");
Reset();
}
}
}
private void Reset()
{
pointerDown = false;
pointerDownTimer = 0;
}
}
Remember to attach the script to a GameObject that can be interactable, like an Image.
Related
I'm using Unity 2D for my game and I'm facing a weird problem. I have a GameObject (an image) in my scene with a button attached to it. When the player is out of the screen, the image that works as a GameOver panel appears and in case of enough coins, you can resume the game. Everything works fine but when the image appears and I click on button to resume, the image doesn't disappear while the function for decreasing number of coins still works.
Here is my script in which di() is for die and but() for button:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using CodeStage.AntiCheat.ObscuredTypes;
using CodeStage.AntiCheat.Detectors;
public class UIManager2 : MonoBehaviour {
public static ObscuredInt coin_score = 0;
public Text coin_text;
public Slider slider;
public Slider slider1;
public Slider slider2;
public GameObject bul;
public bool reverse;
public float timer;
public int delay = 1;
public float speed=0.5f;
public GameObject pause;
public AudioSource[] aud=new AudioSource[3];
void Start () {
coin_score = ObscuredPrefs.GetInt ("Score");
StartCoroutine (elapsed ());
slider1.minValue = 0;
slider1.maxValue = 20;
bul = GameObject.FindGameObjectWithTag ("Player").GetComponent<Plane19> ().bullet;
}
void Update () {
timer += Time.deltaTime;
if (timer >= delay && reverse == false) {
timer = 0f;
slider2.value++;
}
if (timer >= delay && reverse == true) {
timer = 0f;
slider2.value -= speed;
}
coin_text.text = coin_score.ToString ();
ObscuredPrefs.SetInt ("Score", coin_score);
if (slider2.value == 10) {
bul.SetActive (false);
reverse = true;
}
if (slider2.value == 0) {
bul.SetActive (true);
reverse = false;
}
}
public void di(){
pause.SetActive(true);
GetComponent<AudioSource>().Pause();
Time.timeScale = 0;
aud[0].Play();
aud[1].Pause();
aud[2].Pause();
}
public void but(){
pause.SetActive(false);
Time.timeScale=1;
aud[0].Pause();
aud[1].UnPause();
aud[2].UnPause();
GetComponent<AudioSource>().UnPause();
UIManager2.coin_score-=2;
}
IEnumerator elapsed () {
yield return new WaitForSeconds (2f);
slider.value++;
StartCoroutine (elapsed ());
}
}
I have a custom view that has a blinking cursor. I make the blinking cursor using a Handler and posting a Runnable to it after a 500 milisecond delay.
When the activity that the view is in, I want to stop the blinking by removing the callbacks on the handler. However, I've noticed that when I switch to another app, the handler/runnable keep going, ie, the log says it is still blinking.
If I had control of the view I would just do something like this
#Override
protected void onPause() {
handler.removeCallbacks(runnable);
super.onPause();
}
But my custom view will be part of a library and so I don't have control over the Activities that other developers use my custom view in.
I tried onFocusChanged, onScreenStateChanged, and onDetachedFromWindow but none of these work for when the user switches to another app.
Here is my code. I simplified it by removing anything not pertinent to the problem.
public class MyCustomView extends View {
static final int BLINK = 500;
private Handler mBlinkHandler;
private void init() {
// ...
mBlinkHandler = new Handler();
mTextStorage.setOnChangeListener(new MongolTextStorage.OnChangeListener() {
#Override
public void onTextChanged(/*...*/) {
// ...
startBlinking();
}
});
}
Runnable mBlink = new Runnable() {
#Override
public void run() {
mBlinkHandler.removeCallbacks(mBlink);
if (shouldBlink()) {
// ...
Log.i("TAG", "Still blinking...");
mBlinkHandler.postDelayed(mBlink, BLINK);
}
}
};
private boolean shouldBlink() {
if (!mCursorVisible || !isFocused()) return false;
final int start = getSelectionStart();
if (start < 0) return false;
final int end = getSelectionEnd();
if (end < 0) return false;
return start == end;
}
void startBlinking() {
mBlink.run();
}
void stopBlinking() {
mBlinkHandler.removeCallbacks(mBlink);
}
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (focused) {
startBlinking();
} else {
stopBlinking();
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
#Override
public void onScreenStateChanged(int screenState) {
switch (screenState) {
case View.SCREEN_STATE_ON:
startBlinking();
break;
case View.SCREEN_STATE_OFF:
stopBlinking();
break;
}
}
public void onAttachedToWindow() {
super.onAttachedToWindow();
startBlinking();
}
#Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopBlinking();
}
}
I guess you are starting the thread separately using thread.run(), instead just make a method and call it recursively Something like this:
public void blink(){
mBlinkHandler.postDelayed(mBlink, BLINK);
}
And in the runnable:
Runnable mBlink = new Runnable() {
#Override
public void run() {
mBlinkHandler.removeCallbacks(mBlink);
if (shouldBlink()) {
// ...
Log.i("TAG", "Still blinking...");
blink();
}
}
};
As you are directly starting the thread using run method. So it won't stop by removing callbacks.
Hope this helps.
I solved the problem by following #pskink's advice in the comments and adapted the code from Android 1.6. This may be an old version of Android but the blinking cursor part works well for my purposes. Overriding onWindowFocusChanged was the key.
My full code is on GitHub. Here are the pertinent parts:
public class MyCustomView extends View {
private boolean mCursorVisible = true;
private Blink mBlink;
private long mShowCursor; // cursor blink timing based on system clock
static final int BLINK = 500;
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
mShowCursor = SystemClock.uptimeMillis();
if (focused) {
makeBlink();
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
#Override
protected void onDraw(Canvas canvas) {
int start = getSelectionStart();
int end = getSelectionEnd();
// draw the blinking cursor on top
if (start == end && blinkShouldBeOn()) {
canvas.drawRect(getCursorPath(start), mCursorPaint);
}
}
private boolean blinkShouldBeOn() {
if (!mCursorVisible || !isFocused()) return false;
return (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK;
}
private void makeBlink() {
if (!mCursorVisible) {
if (mBlink != null) {
mBlink.removeCallbacks(mBlink);
}
return;
}
if (mBlink == null)
mBlink = new Blink(this);
mBlink.removeCallbacks(mBlink);
mBlink.postAtTime(mBlink, mShowCursor + BLINK);
}
public void setCursorVisible(boolean visible) {
mCursorVisible = visible;
invalidateCursorPath();
if (visible) {
makeBlink();
} else if (mBlink != null) {
mBlink.removeCallbacks(mBlink);
}
}
#Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
if (mBlink != null) {
mBlink.uncancel();
if (isFocused()) {
mShowCursor = SystemClock.uptimeMillis();
makeBlink();
}
}
} else {
if (mBlink != null) {
mBlink.cancel();
}
hideSystemKeyboard();
}
}
private static class Blink extends Handler implements Runnable {
private WeakReference<MongolEditText> mView;
private boolean mCancelled;
Blink(MongolEditText v) {
mView = new WeakReference<>(v);
}
public void run() {
if (mCancelled) {
return;
}
removeCallbacks(Blink.this);
MongolEditText met = mView.get();
if (met != null && met.isFocused()) {
int st = met.getSelectionStart();
int en = met.getSelectionEnd();
if (st == en && st >= 0 && en >= 0) {
if (met.mLayout != null) {
met.invalidateCursorPath();
}
postAtTime(this, SystemClock.uptimeMillis() + BLINK);
}
}
}
void cancel() {
if (!mCancelled) {
removeCallbacks(Blink.this);
mCancelled = true;
}
}
void uncancel() {
mCancelled = false;
}
}
}
I am using the GoogleVR package and I have this reticle which works (in the sense that it makes an object I am looking at bigger). The behavior I would like, is to click by looking on object for about three seconds. The object currently has an event trigger, but how can I wait for three seconds and then click?
When you look at the object, the trigger is activated right?
Then when you detect the trigger up just make a function that waits for 3 seconds and and then do what you want.
Dont forget to stop the wait if the user starts looking somewhere else.
I wrote this script a while ago, it works fine with buttons:
[RequireComponent(typeof(Button))]
public class InteractiveItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
public Image progressImage; // add an image as child of button and set its image type to Filled. And assign it here.
public bool isEntered = false;
float GazeActivationTime = 3f;
float timeElapsed;
Button _button;
void Start ()
{
_button = GetComponent<Button>();
}
void fillProgress(float value)
{
if (progressImage != null)
{
progressImage.fillAmount = value;
}
}
void Update ()
{
if(isEntered)
{
timeElapsed += Time.deltaTime;
fillProgress(Mathf.Clamp(timeElapsed/GazeActivationTime,0,1));
if(timeElapsed >= GazeActivationTime)
{
timeElapsed = 0;
_button.onClick.Invoke();
fillProgress(0);
isEntered = false;
}
}
else
{
timeElapsed = 0;
}
}
void OnDisable()
{
if (this.enabled)
{
isEntered = false;
fillProgress(0);
}
}
#region IPointerEnterHandler implementation
public void OnPointerEnter (PointerEventData eventData)
{
if (_button.IsInteractable())
{
isEntered = true;
}
}
#endregion
#region IPointerExitHandler implementation
public void OnPointerExit (PointerEventData eventData)
{
if (!_button.IsInteractable())
return;
try
{
isEntered = false;
fillProgress(0);
}
catch (System.Exception ex)
{
Debug.LogError(ex.Message);
}
}
#endregion
#region IPointerClickHandler implementation
public void OnPointerClick (PointerEventData eventData)
{
isEntered = false;
timeElapsed = 0;
fillProgress(0);
}
#endregion
}
I am new to Unity, and was following the tutorials of roll a ball.
I was to create it for both mobile and desktop and it is working but the only problem I have is that I am unable to create touch keys arrows(left,right,up,down) to control the player on the touch screen devices.
Please check my code below of controlling the player:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class PlayerController : MonoBehaviour {
public float speed;
public Text countText;
public Texture2D button1; //button 1
public Texture2D button2; //button2
public Texture texture;
private Rigidbody rb;
private int count;
// Use this for initialization
void Start () {
Screen.orientation = ScreenOrientation.LandscapeLeft;
//GUITexture.texture = button1;
rb = GetComponent<Rigidbody> ();
count = 0;
SetCountText ();
}
// Update is called once per frame
void FixedUpdate () {
Screen.orientation = ScreenOrientation.LandscapeLeft;
Screen.sleepTimeout = SleepTimeout.NeverSleep;
if (SystemInfo.deviceType == DeviceType.Desktop) {
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
GetComponent<Rigidbody>().AddForce (movement * speed * Time.deltaTime);
if (Input.GetKey("escape"))
{
Application.Quit();
}
}//END Desktop
else
{
float moveHorizontal = Input.acceleration.x;
float moveVertical = Input.acceleration.y;
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
GetComponent<Rigidbody>().AddForce (movement * speed * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.Escape))
{
Application.Quit();
}
foreach(Touch touch in Input.touches)
{
//Always getting error here
if (GUITexture.HitTest(touch.position) && touch.phase !=TouchPhase.Ended)
{
GUITexture.texture = button2;
transform.Translate(Vector3.right*30*Time.smoothDeltaTime);
}else if(GUITexture.HitTest(touch.position) && touch.phase !=TouchPhase.Ended)
{
GUITexture.texture = button1;
}
}
}
// Building of force vector
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag ("Pick Up"))
{
other.gameObject.SetActive(false);
count = count + 1;
SetCountText();
}
}
void SetCountText()
{
countText.text = "Count:" + count.ToString ();
}
}
Your trying to call HitTest directly from GUITexture like a static function but HitTest isn't a static function, you need to create a variable from GUITexture class and then call HitTest function from that object like this:
public GUITexture guiT;
if (guiT.HitTest(touch.position) && touch.phase !=TouchPhase.Ended)
{
guiT.texture = button2;
transform.Translate(Vector3.right*30*Time.smoothDeltaTime);
}
else if(guiT.HitTest(touch.position) && touch.phase !=TouchPhase.Ended)
{
guiT.texture = button1;
}
don't forget to assign guiT variable to something from the Editor.
How to detect rotation around Y axis of phone?
I am novice in android. I would like to detect 180 degrees rotation. I would like to detect for example if user flip phone which lies on a table or if user rotate his phone in his pocket.
I have read a lot of articles but I really don't understand how to get phone position and then compute angle between another position.
I have found for example this article but I don't know what to do with array named orientation:
Get device angle by using getOrientation() function
Thanks!
// Here is my solution. Not perfectly logical but works quite good:
public class FlipListener implements SensorEventListener {
SensorManager sensorMgr;
FlipEventReceiver receiver;
public FlipListener(Context context, FlipEventReceiver receiver) {
this.receiver = receiver;
sensorMgr = (SensorManager) context.getSystemService(Activity.SENSOR_SERVICE);
sensorMgr.registerListener(this, sensorMgr.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_UI);
}
public void onResume() {
sensorMgr.registerListener(this, sensorMgr.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_UI);
}
public void onPause() {
sensorMgr.unregisterListener(this);
clearStack();
}
private static final int IGNORE_FLIPS_AFTER_FLIP = 2500;
private static final int SAMPLING_INTERVAL = 60;
private static final int MINIMAL_STACK_SIZE_TO_FLIP = 2; // Shouldn't be lower than 2
private static final float FLIP_RADIANS = (float)Math.toRadians(140);
private static final int STACK_MAX_SIZE = 38;
private List<Float> stack = new ArrayList<Float>();
private long lastAdd = 0;
private long lastFlip = 0;
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
rotationRateAroundYChanged((float)event.values[1]);
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
private void rotationRateAroundYChanged(float rotationRateAroundY) {
long currentTime = System.currentTimeMillis();
if (lastFlip != 0 && (currentTime - lastFlip) < IGNORE_FLIPS_AFTER_FLIP) {
return;
}
if( (currentTime - lastAdd) >= SAMPLING_INTERVAL ) {
if( Math.abs(rotationRateAroundY) > 0.3 ) { // Smaller values are unimportant. They can make only mess.
addToStack(rotationRateAroundY);
checkForFlip();
}
}
}
private void checkForFlip() {
int stackSize = stack.size();
if( stackSize < MINIMAL_STACK_SIZE_TO_FLIP ) return;
float approximateAngleSummary = 0;
float val;
for(int i = 0; i < stackSize; i++) {
val = Math.abs(stack.get(i).floatValue());
// "+ Math.pow(val/4.58, 2) )" don't have a sense. Simply it works better with it.
approximateAngleSummary += ( (val + Math.pow(val/4.58, 2) ) / 1000 ) * SAMPLING_INTERVAL;
if( approximateAngleSummary >= FLIP_RADIANS ) {
triggerFlipDetected();
clearStack();
return;
}
}
}
private void clearStack() {
stack.clear();
}
private void addToStack(float val) {
lastAdd = System.currentTimeMillis();
int stackSize = stack.size();
if( stackSize > 0 && ((stack.get(stackSize-1) > 0 ? 1 : -1) != (val>0?1:-1) || stackSize > STACK_MAX_SIZE) ) {
clearStack();
}
stack.add(val);
}
private void triggerFlipDetected() {
lastFlip = System.currentTimeMillis();
receiver.onFlipDetected();
}
public interface FlipEventReceiver {
public void onFlipDetected();
}
}
Usage:
public class FlipTestActivity extends Activity implements FlipEventReceiver {
FlipListener flipListener;
boolean flipListenerActive = true;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flip_test);
flipListener = new FlipListener(this, this);
}
public void onFlipDetected() {
// What to do when flip detected
}
#Override
protected void onResume() {
super.onResume();
if( !flipListenerActive ) {
flipListener.onResume();
flipListenerActive = true;
}
}
#Override
protected void onPause() {
super.onPause();
if( flipListenerActive ) {
flipListener.onPause();
flipListenerActive = false;
}
}
}
SensorManager is a class that lets you access the device's sensors. Check here
Here is a nice tutorial about sensors