my space

Unity | ตรวจจับการกดปุ่มค้างไว้

unity

August 02, 2019

ใน Unity ถ้าเราต้องการตรวจจับการกดปุ่มใน Unity UI นั้นสามารถทำได้ง่ายโดยไป add event ผ่านทาง Inspector ได้เลย แต่การกดแบบนี้จะเป็นกด 1 ครั้งก็ fire event 1 ที แต่ถ้าต้องการจะตรวจจับถ้า user มีการกดค้างหรือแช่ไว้ละจะได้ทำได้ยังไง

แนวทางการ Implement นี้ได้มาจาก https://www.tantzygames.com/blog/unity-ugui-button-long-press/ เห็นว่าง่ายดีและไม่ต้องไปทำการตรวจจับอะไรให้ซับซ้อนใน Update() เลยมาจดบันทึก version ภาษาไทยไว้ที่นี่

วิธีการ

คือจะทำ Script ขึ้นมา 1 ตัวแล้วนำ Script นี้ไปผูกไว้กับ GameObject ที่ต้องการตรวจจับการกดค้าง เมื่อต้องการให้เกิดอะไรขึ้นหลังจากกดค้างก็จะมี event On Long Press ให้เรียกใช้งาน

Events

จะเห็นว่าเราไม่ได้ใส่ Logic อะไรเข้าไปใน Update() เลย

การทำงาน

เราจะใช้ประโยชน์จาก Unity Events โดยจะเห็นที่ตอนสร้าง Class เราเพิ่ม implement IPointerDownHandler, IPointerUpHandler และ IPointerExitHandler เข้าไป และทำให้ได้รับ event OnPointerDown, OnPointerUp และ OnPointerExit เมื่อมีการกด, ปล่อยและเลื่อน cursor ออกจากปุ่ม

เมื่อ user มีการกด (จะมี event ยิงมาที่ OnPointerDown()) เราใช้ประโยชน์จาก MonoBehaviour.Invoke ที่ระบุเวลาได้ว่าจะให้ Invoke เมื่อเวลาผ่านไปเท่าไร โดยเราใส่ค่า holdTime เข้าไป ดังนั้นหากไม่มีอะไรเกิดขึ้นหลังจากผ่านเวลา holdTime ไป เราก็จะ Invoke OnLongPress (ที่เป็น method ของเราและมันจะไปเรียก OnLongPress.Invoke() ใน UnityEvent ที่เราประกาศเอาไว้) ขึ้นมา แต่ถ้าในก่อนจะผ่านช่วง holdTime เกิดเหตุการณ์ OnPointerUp() หรือ OnPointerExit() เราก็จะเรียก CancelInvoke ก็จะทำให้ Invoke ที่เราตั้งไว้หยุดทำงานไปนั่นเอง

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;

public class ButtonLongPress : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
    [SerializeField]
    [Tooltip("How long must pointer be down on this object to trigger a long press")]
    private float holdTime = 1f;

    public UnityEvent onLongPress = new UnityEvent();

    public void OnPointerDown(PointerEventData eventData)
    {
        Invoke("OnLongPress", holdTime);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        CancelInvoke("OnLongPress");
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        CancelInvoke("OnLongPress");
    }

    private void OnLongPress()
    {
        onLongPress.Invoke();
    }

}

การเขียนแบบนี้อาจจะทำให้เจอจุดผิดพลาดได้นั่นคือหลังจากที่ OnLongPress ถูกเรียกไปแล้วนั้น เมื่อปล่อย mouse ออกจากปุ่มนั้นมันดันไป trigger onClick event ของ Unity UI หากสิ่งนี้เป็นสิ่งที่ไม่พึงประสงค์ ทางออกที่ง่ายอาจจะเป็นการ handle onClick เอง

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class ButtonLongPress : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
    [SerializeField]
    [Tooltip("How long must pointer be down on this object to trigger a long press")]
    private float holdTime = 1f;
    private bool held = false;
    public UnityEvent onClick = new UnityEvent();

    public UnityEvent onLongPress = new UnityEvent();

    public void OnPointerDown(PointerEventData eventData)
    {
        held = false;
        Invoke("OnLongPress", holdTime);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        CancelInvoke("OnLongPress");
        if (!held)
           onClick.Invoke();
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        CancelInvoke("OnLongPress");
    }

    private void OnLongPress()
    {
        held = true;
        onLongPress.Invoke();
    }
}

Events2

จะเห็นว่าหากเราไม่ได้กำหนดค่า On Click event ให้กับ On Click ที่มากับ Unity UI แต่มากำหนดค่า On Click ให้กับ Event ที่เราเขียนใหม่ขึ้นมาเอง


gie

Written by gie who lives and works in Bangkok. Build things by code.
my twitter | github