「ボタンが増えて、変更したり管理がめんどくさい・・・!」そんなあなたに。
Unity向け、編集が容易なボタンのカスタムクラスのコードを紹介します。
特徴
・クリック・長押し・スワイプ判定対応のクラスです。
・CustomButtonUI.csを変更することで、同じスクリプトをアタッチしているオブジェクトのクリック時の挙動やクリック音などを一括で管理・変更可能です。
・複数クラスを作ることで、「別の挙動で統一したい!」「別の音で統一したい!」ということもできます。
使い方
①対象のオブジェクトの親にからオブジェクトを作成し、そこにアタッチして利用してください。
例:Imageオブジェクトをクリック判定させたい場合
(1)ImageGoldオブジェクト(空オブジェクトを)作り、そこにスクリプトをアタッチします。
(2)Canvas RendererとCanvas Groupを追加でアタッチします。
(3)アタッチしたスクリプトのCanvas Group変数に、自身のCanvas Groupを選択(またはドロップダウンで指定)します。
※コピペすると、そのオブジェクトも全く同じ挙動をします。
※CustomButtonUI.csを変更すると、元のオブジェクトもコピペしたオブジェクトも変更後の挙動をします。
②クリックイベントを実装してください。
アタッチしたオブジェクトに対し、クリックイベントを入れてください。
// 定義
[SerializeField] GameObject objBtnPractice;
// どこぞに入れて...
objBtnPractice.GetComponent<CustomButtonUI>().onClickCallback = () => {
// ここに実行したい関数を入れる
};
コード本体
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;
// ボタン用クラス
public class CustomButtonUI : MonoBehaviour,
IPointerClickHandler,
IPointerDownHandler,
IPointerUpHandler,
IPointerMoveHandler
{
//------------------------------------------------------//
// 設定
//------------------------------------------------------//
// 一般
[SerializeField] private CanvasGroup buttonCanvasGroup;
// クリック判定
public System.Action onClickCallback;
// 長押し判定
public System.Action onPressLongCallback;
[SerializeField] private float longPressDuration = 0.5f;
private float longPressStartTime = 0f;
private bool isPointerDown = false;
// スワイプ判定
public System.Action<SwipeDirection> onSwipeCallback;
private static Vector2 globalSwipeStartPosition; // スワイプ開始位置(全インスタンスで共有)
private static Vector2 globalSwipeCurrentPosition; // スワイプ現在位置(全インスタンスで共有)
private static bool isGlobalSwipeActive = false; // スワイプ進行中フラグ
private Vector2 swipeCurrentPosition; // このボタン用の現在位置(判定用)
[SerializeField] private bool detectSwipeOnlyAfterRelease = false;
[SerializeField] private float swipeThreshold = 20f;
//------------------------------------------------------//
// 関数:一般
//------------------------------------------------------//
private void Update()
{
// 長押し判定
if(isPointerDown == true)
{
if(Time.time - longPressStartTime > longPressDuration)
{
OnPressLong();
ResetPressLong();
}
}
// グローバルスワイプ検出(全インスタンスで共有した開始位置から検出)
if (Input.touchCount > 0) // モバイル タッチ入力
{
foreach (Touch touch in Input.touches)
{
HandleSwipeInput(touch.phase, touch.position);
}
}
else // Unity Editor マウス入力
{
if (Input.GetMouseButtonDown(0))
{
HandleSwipeInput(TouchPhase.Began, Input.mousePosition);
}
else if (Input.GetMouseButton(0) && isGlobalSwipeActive)
{
HandleSwipeInput(TouchPhase.Moved, Input.mousePosition);
}
else if (Input.GetMouseButtonUp(0))
{
HandleSwipeInput(TouchPhase.Ended, Input.mousePosition);
}
}
}
public void OnPointerDown(PointerEventData eventData)
{
transform.DOScale(0.95f, 0.24f).SetEase(Ease.OutCubic);
buttonCanvasGroup.DOFade(0.8f, 0.24f).SetEase(Ease.OutCubic);
// カウント開始
isPointerDown = true;
longPressStartTime = Time.time;
// グローバルスワイプ開始位置記録
globalSwipeStartPosition = eventData.position;
globalSwipeCurrentPosition = eventData.position;
swipeCurrentPosition = eventData.position;
}
public void OnPointerMove(PointerEventData eventData)
{
// 移動中スワイプ検出(検出後リリースのみが false の場合)
if (!detectSwipeOnlyAfterRelease && isPointerDown)
{
globalSwipeCurrentPosition = eventData.position;
swipeCurrentPosition = eventData.position;
if (IsPointerOverThisObject(eventData.position))
{
CheckSwipe();
}
}
}
public void OnPointerUp(PointerEventData eventData)
{
transform.DOScale(1f, 0.24f).SetEase(Ease.OutCubic);
buttonCanvasGroup.DOFade(1f, 0.24f).SetEase(Ease.OutCubic);
// スワイプ終了位置記録
globalSwipeCurrentPosition = eventData.position;
swipeCurrentPosition = eventData.position;
if (IsPointerOverThisObject(eventData.position))
{
CheckSwipe();
}
// リセット
ResetPressLong();
}
// スワイプ入力処理(タッチ・マウス共通)
private void HandleSwipeInput(TouchPhase phase, Vector2 position)
{
switch (phase)
{
case TouchPhase.Began:
globalSwipeStartPosition = position;
globalSwipeCurrentPosition = position;
isGlobalSwipeActive = true;
break;
case TouchPhase.Moved:
if (!detectSwipeOnlyAfterRelease && isGlobalSwipeActive)
{
globalSwipeCurrentPosition = position;
swipeCurrentPosition = position;
if (IsPointerOverThisObject(position))
{
CheckSwipe();
}
}
break;
case TouchPhase.Ended:
globalSwipeCurrentPosition = position;
swipeCurrentPosition = position;
if (isGlobalSwipeActive && IsPointerOverThisObject(position))
{
CheckSwipe();
}
isGlobalSwipeActive = false;
break;
}
}
//------------------------------------------------------//
// 関数:クリック
//------------------------------------------------------//
public void OnPointerClick(PointerEventData eventData)
{
if(onClickCallback != null)
{
// サウンド
SoundManager.instance.PlaySE(SoundDataSE.Type.ClickUI);
// invoke
onClickCallback.Invoke();
}
Logging.Log($"[{name}] クリック");
}
//------------------------------------------------------//
// 関数:長押し
//------------------------------------------------------//
public void OnPressLong()
{
if(onPressLongCallback != null)
{
// サウンド
SoundManager.instance.PlaySE(SoundDataSE.Type.ClickLong);
// invoke
onPressLongCallback.Invoke();
}
Logging.Log($"[{name}] 長押し");
}
// 状態をリセット
private void ResetPressLong()
{
isPointerDown = false;
longPressStartTime = 0f;
}
//------------------------------------------------------//
// 関数:スワイプ
//------------------------------------------------------//
// ポインターがこのオブジェクト上にあるか判定(RectTransformUtility)
private bool IsPointerOverThisObject(Vector2 pointerPosition)
{
// このオブジェクトの RectTransform を取得
RectTransform rectTransform = GetComponent<RectTransform>();
if (rectTransform == null)
{
return false;
}
// Canvas を取得
Canvas canvas = GetComponentInParent<Canvas>();
if (canvas == null)
{
return false;
}
// ポインター位置がこの RectTransform 内にあるか判定
return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, pointerPosition, canvas.worldCamera);
}
// スワイプ判定
private void CheckSwipe()
{
SwipeDirection direction = SwipeDirection.None;
// しきい値以上縦方向にスワイプしたかどうか判定する
if (GetVerticalDistance() > swipeThreshold && GetVerticalDistance() > GetHorizontalDistance())
{
if (globalSwipeCurrentPosition.y - globalSwipeStartPosition.y > 0)
{
direction = SwipeDirection.Up;
}
else if (globalSwipeCurrentPosition.y - globalSwipeStartPosition.y < 0)
{
direction = SwipeDirection.Down;
}
globalSwipeStartPosition = globalSwipeCurrentPosition;
}
// しきい値以上横方向にスワイプしたかどうか判定する
else if (GetHorizontalDistance() > swipeThreshold && GetHorizontalDistance() > GetVerticalDistance())
{
if (globalSwipeCurrentPosition.x - globalSwipeStartPosition.x > 0)
{
direction = SwipeDirection.Right;
}
else if (globalSwipeCurrentPosition.x - globalSwipeStartPosition.x < 0)
{
direction = SwipeDirection.Left;
}
globalSwipeStartPosition = globalSwipeCurrentPosition;
}
// スワイプ検出時にコールバック実行
if (direction != SwipeDirection.None)
{
OnSwipe(direction);
}
}
private float GetVerticalDistance()
{
return Mathf.Abs(globalSwipeCurrentPosition.y - globalSwipeStartPosition.y);
}
private float GetHorizontalDistance()
{
return Mathf.Abs(globalSwipeCurrentPosition.x - globalSwipeStartPosition.x);
}
private void OnSwipe(SwipeDirection direction)
{
if(onSwipeCallback != null)
{
// サウンド
SoundManager.instance.PlaySE(SoundDataSE.Type.Swipe);
// invoke
onSwipeCallback.Invoke(direction);
}
Logging.Log($"[{name}] スワイプ: {direction}");
}
//------------------------------------------------------//
}
// スワイプの方向を示すenum
public enum SwipeDirection
{
None,
Up,
Down,
Left,
Right
}
注意点
使わないものがあればコメントアウトしてください
・ボタンの動きを加えるアセットのDOTweenを使っています
・Loggingという拡張クラス(Debugのラッパー)を使ってます

