📗
【Unity】UniRxで自作のカスタムボタンを作ろう
はじめに
初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!
今回は
UniRxでUnityのカスタムボタン
の作り方を紹介します
補足
アニメーション管理はこちらを使ってます
合わせて使ってみてください~
Unity標準ボタンではダメ?
UnityのUIにある、Unity標準ボタンでは機能不足になることが多く、
初めの設計を間違うと、あとあと大規模改修・・・コンポーネント張り替え地獄がまってます
そうならないためにも、初めの設計(特にボタン)は重要です
Unity標準ボタンは機能不足
Unity標準ボタンの機能は約3種類扱えない
Unity標準ボタンは拡張できない
標準ボタンをラップしてできるが、それでも標準ボタンとは違うコンポーネントになるので、拡張ができない(しない)と考えてもいい思います
Unity標準ボタンはボタン処理を一括修正ができない
標準ボタンは機能を拡張できないから、処理の一括修正もできません
Unity標準ボタンはInspectorから関数を設定できる
便利じゃん?と思うかもしれませんが、これの問題点・・・
それは、プログラム側で読み取れないこと
あれ、ボタンのクリック処理おかしい・・けど、処理はあってるな?状態になる
カスタムボタンとは・・・
MonoBehaviourクラスと、各イベントのIPointer〇〇と付くインターフェースを継承して実装します。
public class ButtonEx : MonoBehaviour,
IPointerClickHandler,
IPointerDownHandler,
IPointerUpHandler
{
}
実際の処理
こちらは、クリック時、ボタンが押された時、ボタンが離された時の状態を扱えるようになってます。
必要に応じて拡張、改変してください
ButtonEx.cs
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UniRx;
namespace Utils
{
public class ButtonEx : MonoBehaviour,
IPointerClickHandler,
IPointerDownHandler,
IPointerUpHandler
{
[Header("ボタン設定")]
[SerializeField]
private bool _isInteractable = true;
[SerializeField]
private Color32 _defaultColor = new Color32(255, 255, 255, 255);
[SerializeField]
private Color32 _pointerDownColor = new Color32(255, 255, 255, 255);
[SerializeField]
private Color32 _disabledColor = new Color32(255, 255, 255, 128);
[Space]
[Header("アニメーション設定")]
[SerializeField]
private string _pointerDownStateName = "PointerDown";
[SerializeField]
private string _pointerUpStateName = "PointerUp";
[Space]
[Header("SE設定")]
[SerializeField]
private AudioData _pointerDownAudioData;
[SerializeField]
private AudioData _pointerUpAudioData;
private Image _image;
private AnimationManager _animationManager;
private Animator _animator;
private bool _isClickSubscribers = false;
private bool _isPointerDownSubscribers = false;
private bool _isPointerUpSubscribers = false;
// Subject関連
private readonly Subject<Unit> _onClickSubject = new Subject<Unit>();
private readonly Subject<Unit> _onPointerDownSubject = new Subject<Unit>();
private readonly Subject<Unit> _onPointerUpSubject = new Subject<Unit>();
/// <summary> デバウンスのミリ秒 </summary>
private const int dueMilliseconds = 500;
/// <summary>
/// 開始時
/// </summary>
private async void Start()
{
_image = GetComponent<Image>();
_animationManager = new AnimationManager();
_animator = GetComponent<Animator>();
SetColor(_defaultColor);
if (!string.IsNullOrEmpty(_pointerDownAudioData.accessKey))
{
await _pointerDownAudioData.Init(AudioType.Se);
}
if (!string.IsNullOrEmpty(_pointerUpAudioData.accessKey))
{
await _pointerUpAudioData.Init(AudioType.Se);
}
}
/// <summary>
/// 破壊時
/// </summary>
private void OnDestroy()
{
_onClickSubject?.Dispose();
_onPointerDownSubject?.Dispose();
_onPointerUpSubject?.Dispose();
}
/// <summary>
/// クリックしたときイベント
/// </summary>
public IObservable<Unit> OnClickAsObservable()
{
_isClickSubscribers = true;
return _onClickSubject.AsObservable().ThrottleFirst(TimeSpan.FromMilliseconds(dueMilliseconds));
}
/// <summary>
/// 押されたときのイベント
/// </summary>
public IObservable<Unit> OnPointerDownAsObservable()
{
_isPointerDownSubscribers = true;
return _onPointerDownSubject.AsObservable();
}
/// <summary>
/// 離されたときのイベント
/// </summary>
public IObservable<Unit> OnPointerUpAsObservable()
{
_isPointerUpSubscribers = true;
return _onPointerUpSubject.AsObservable();
}
/// <summary>
/// 有効状態の設定
/// </summary>
public void SetInteractable(bool isInteractable)
{
_isInteractable = isInteractable;
SetColor(_isInteractable ? _defaultColor : _disabledColor);
}
/// <summary>
/// 押されたときのアニメーションを変更
/// </summary>
public void SetPointerDownStateName(string stateName)
{
_pointerDownStateName = stateName;
}
/// <summary>
/// 離されたときのアニメーションを変更
/// </summary>
public void SetPointerUpStateName(string stateName)
{
_pointerUpStateName = stateName;
}
/// <summary>
/// クリック
/// </summary>
public void OnPointerClick(PointerEventData eventData)
{
if (_isInteractable)
{
if (_isClickSubscribers)
{
_onClickSubject.OnNext(Unit.Default);
}
}
}
/// <summary>
/// 押した
/// </summary>
public async void OnPointerDown(PointerEventData eventData)
{
if (_isInteractable)
{
if (_isPointerDownSubscribers)
{
_onPointerDownSubject.OnNext(Unit.Default);
}
SetColor(_pointerDownColor);
_animationManager.AddAnimationObject(gameObject, _pointerDownStateName, _animator);
if (_pointerDownAudioData.IsInitialize)
{
await AudioManager.Instance.PlayAsync(_pointerDownAudioData);
}
}
}
/// <summary>
/// 離した
/// </summary>
public async void OnPointerUp(PointerEventData eventData)
{
if (_isInteractable)
{
if (_isPointerUpSubscribers)
{
_onPointerUpSubject.OnNext(Unit.Default);
}
SetColor(_defaultColor);
_animationManager.AddAnimationObject(gameObject, _pointerUpStateName, _animator);
if (_pointerUpAudioData.IsInitialize)
{
await AudioManager.Instance.PlayAsync(_pointerUpAudioData);
}
}
}
/// <summary>
/// 色変更
/// </summary>
private void SetColor(Color32 color)
{
if (_image != null)
{
_image.color = color;
}
}
}
public static class ButtonExFunc
{
/// <summary>
/// クリックしたときイベント
/// </summary>
public static IDisposable ObserveClick(this ButtonEx button, Action action)
{
return button.OnClickAsObservable().Subscribe(_ => action());
}
/// <summary>
/// 押されたときのイベント
/// </summary>
public static IDisposable ObservePointerDown(this ButtonEx button, Action action)
{
return button.OnPointerDownAsObservable().Subscribe(_ => action());
}
/// <summary>
/// 離されたときのイベント
/// </summary>
public static IDisposable ObservePointerUp(this ButtonEx button, Action action)
{
return button.OnPointerUpAsObservable().Subscribe(_ => action());
}
/// <summary>
/// クリックしたときイベント
/// (コンポーネントのライフサイクルで購読を終了)
/// </summary>
public static void ObserveClickAddTo(this ButtonEx button, Action action, GameObject owner)
{
button.ObservePointerDown(action).AddTo(owner);
}
/// <summary>
/// クリックしたときイベント
/// (コンポーネントのライフサイクルで購読を終了)
/// </summary>
public static void ObserveClickAddTo(this ButtonEx button, Action action, Component owner)
{
button.ObservePointerDown(action).AddTo(owner);
}
/// <summary>
/// 押されたときのイベント
/// (コンポーネントのライフサイクルで購読を終了)
/// </summary>
public static void ObservePointerDownAddTo(this ButtonEx button, Action action, GameObject owner)
{
button.ObservePointerDown(action).AddTo(owner);
}
/// <summary>
/// 押されたときのイベント
/// (コンポーネントのライフサイクルで購読を終了)
/// </summary>
public static void ObservePointerDownAddTo(this ButtonEx button, Action action, Component owner)
{
button.ObservePointerDown(action).AddTo(owner);
}
/// <summary>
/// 離されたときのイベント
/// (コンポーネントのライフサイクルで購読を終了)
/// </summary>
public static void ObservePointerUpAddTo(this ButtonEx button, Action action, GameObject owner)
{
button.ObservePointerUp(action).AddTo(owner);
}
/// <summary>
/// 離されたときのイベント
/// (コンポーネントのライフサイクルで購読を終了)
/// </summary>
public static void ObservePointerUpAddTo(this ButtonEx button, Action action, Component owner)
{
button.ObservePointerUp(action).AddTo(owner);
}
}
}
使用例
using UniRxが必要なパターン
使用例1
using UniRx; // 使用先でもUniRxが必要
using UnityEngine;
using Utils;
public class Example : MonoBehaviour
{
[[SerializeField]
private ButtonEx _button1;
[SerializeField]
private ButtonEx _button2;
private void Start()
{
// ボタンクリック時の処理
_button1.OnClickAsObservable().Subscribe(_ => OnClick());
// AddTo追加版
// ボタンクリック時の処理
_button2.OnClickAsObservable().Subscribe(_ => OnClick()).AddTo(this);
}
private void OnClick()
{
Debug.Log("ボタンクリック時に実行したい処理");
}
}
using UniRxが不要なパターン
使用例2
//using UniRx; // 使用先でUniRxは不要
using UnityEngine;
using Utils;
public class Example : MonoBehaviour
{
[SerializeField]
private ButtonEx _button1;
[SerializeField]
private ButtonEx _button2;
private void Start()
{
// ボタンクリック時の処理
_button1.ObservableClick(OnClick);
// AddTo追加版
// ボタンクリック時の処理
_button2.ObservableClickAddTo(OnClick, this);
}
private void OnClick()
{
Debug.Log("ボタンクリック時に実行したい処理");
}
}
Discussion