🐈

UnityでAndroidの戻るボタンに対応する

2023/08/02に公開

Androidで実行時にナビゲーションバーを表示する

Unityはデフォルトではフルスクリーンになっているため、そのままでは戻るボタンが表示できない。
そこで最初に起動するシーンのAwakeあたりでプラットフォームがAndroidの時に、Screen.fullScreen = false;を実行してフルスクリーンを解除する。

public class SampleScene : MonoBehaviour
{
    private void Awake()
    {
        // Androidの場合フルスクリーンモードを解除してナビゲーションバーを常時表示
        if (Application.platform == RuntimePlatform.Android)
        {
	    Screen.fullScreen = false;
        }
    }
}

戻るボタンが押された事を検出する方法

KeyCode.Escapeが押されているかどうかをUpdateの中で判定すればいい。
UnityEditor上の実行でもプラットフォームをAndroidにしていれば、UNITY_ANDROIDに条件がマッチする。

private void Update()
{
#if UNITY_ANDROID
    if (Input.GetKeyDown(KeyCode.Escape))
    {
       // 戻るボタンが押された時の処理をここに書く
    }
#endif
}

デバイスシミュレーターではEscを押しても反応してくれない

デバイスシミュレーターを実行時はEscキーを押してもInput.GetKeyDown(KeyCode.Escape)が常にfalseとなってしまう。
これでは非常に不便なのでAndroidのナビゲーションバーの戻るボタンを押した事にするデバイスシミュレーターのプラグインを書く。
UniRxを使ってグローバルにイベントを発行してやる必要がある。

とりあえず、このスクリプトを任意の場所に置く。
ファイル名はUniRxPublisherSubscriber.csとしているが別の名前でも問題ない。

UniRxPublisherSubscriber.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System;

/// <summary>
/// UniRxのMessageBrokerの拡張
/// 引用元:
/// https://takap-tech.com/entry/2023/01/23/234127
/// </summary>

namespace UniRx
{
    // 型を指定したpub
    public interface IMessagePublisher<T>
    {
        /// <summary>
        /// Send Message to all receiver.
        /// </summary>
        void Publish(T message);
    }

    // 型を指定したsub
    public interface IMessageReceiver<T>
    {
        /// <summary>
        /// Subscribe typed message.
        /// </summary>
        IObservable<T> Receive();
    }

    // pub/sub共通
    public interface IMessageBroker<T> : IMessagePublisher<T> , IMessageReceiver<T>
    {
        public class DefaultImpl : IMessageBroker<T>
        {
            private readonly IMessageBroker _service;
            public DefaultImpl(IMessageBroker service) => _service = service;
            public void Publish(T message) => _service.Publish(message);
            public IObservable<T> Receive() => _service.Receive<T>();
        }
    }

    // 型の決まったpub/subを取得できるようにメソッドを追加する
    public static class MessageBrokerExtensions
    {
        public static IMessagePublisher<T> GetPublisher<T>(this IMessageBroker self)
        {
            return new IMessageBroker<T>.DefaultImpl(self);
        }
        public static IMessageReceiver<T> GetSubscriber<T>(this IMessageBroker self)
        {
            return new IMessageBroker<T>.DefaultImpl(self);
        }
    }
    
    // 直接Subscribeできるようにメソッドを追加する
    public static class IMessageReceiverExtensions
    {
        public static IDisposable Subscribe<T>(this IMessageReceiver self, Action<T> action)
        {
            return self.Receive<T>().Subscribe(action);
        }
        public static IDisposable Subscribe<T>(this IMessageReceiver<T> self, Action<T> action)
        {
            return self.Receive().Subscribe(action);
        }
    }
}

イベントデータ。特にプロパティに持つものはないので空だけど用意する。

AndroidBackButtonPressedEventArgs.cs
/// <summary>
/// Androidのナビゲーションバーの戻るボタンがクリックされた時のイベント
/// デバイスシミュレーターの拡張プラグイン用
/// </summary>
public readonly struct AndroidBackButtonPressedEventArgs
{
}

そして本命のデバイスシミュレーターのプラグイン。
任意の場所に配置。

AndroidNavigationBarPlugin.cs
#if UNITY_EDITOR

using System.Collections;
using System.Collections.Generic;
using System.Windows.Input;
using UnityEditor.DeviceSimulation;
using UnityEngine;
using UnityEngine.UIElements;
using UniRx;

/// <summary>
/// Androidのナビゲーションバーのシミュレート
/// </summary>
public class AndroidNavigationBarPlugin : DeviceSimulatorPlugin
{
    public override string title => "Android NavigationBar";
    private Button _backButton;
    private IMessagePublisher<AndroidBackButtonPressedEventArgs> _androidBackButtonPressedEventPublisher = MessageBroker.Default.GetPublisher<AndroidBackButtonPressedEventArgs>();

    public override void OnCreate()
    {
    }

    public override VisualElement OnCreateUI()
    {
        VisualElement root = new VisualElement();

        _backButton = new Button { text = "Back" };
        _backButton.clicked += () =>
        {
            // キーボードを押したことにできないので代わりにイベントを送信する
            _androidBackButtonPressedEventPublisher.Publish(new AndroidBackButtonPressedEventArgs());
        };

        root.Add(_backButton);

        return root;
    }

}

#endif

これらのスクリプトを用意して実行すると、このようにAndroid NavigationBarという項目が増えていて「Back」ボタンが表示されている。
このボタンを押す事で、AndroidBackButtonPressedEventArgsのイベントが通知されデバイスシミュレーターでアプリを実行している時にも戻るボタンを押された事に出来る。

しかし当然ながらそれを受け取る処理を作らないといけないので、次にそれをやる。
例えばAndroidの戻るボタンを受け取りたいシーンではこんな感じで、本来の戻るボタンを受け取る処理と、デバイスシミュレーターのコントロールパネルの「Back」ボタンからのイベントの両方を受け取って処理する形にする。
このサンプルではシーンの親クラスでAndroidの戻るボタンの押された状態を受け取り、継承したクラスでOnBackButtonPressedをオーバーライドしてやる形を想定している。

public abstract class SceneBase : MonoBehaviour
{
    private IMessageReceiver<AndroidBackButtonPressedEventArgs> _androidBackButtonPressedEventReceiver = MessageBroker.Default.GetSubscriber<AndroidBackButtonPressedEventArgs>();

    private void Awake()
    {
        _androidBackButtonPressedEventReceiver.Subscribe((AndroidBackButtonPressedEventArgs eventArgs) => 
        {
            OnBackButtonPressed();
        }).AddTo(gameObject);
    }
    
    void Update()
    {
        if (IsAndroidBackButtonPressed())
        {
            OnBackButtonPressed();
        }
    }
    
    /// <summary>
    /// Androidの戻るボタンが押された時に呼ばれる
    /// </summary>
    public virtual void OnBackButtonPressed()
    {
    }
    
    private bool IsAndroidBackButtonPressed()
    {
#if UNITY_ANDROID
        return Input.GetKeyDown(KeyCode.Escape);
#else
        return false;
#endif
    }
}

Discussion