2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【第1回】UnityScreenNavigatorを使って呼び出すモーダルをInputFieldの文字数で変更する【初心者向け】

Last updated at Posted at 2024-11-13

はじめに

普段は企画職を中心に学んでいる私が、株式会社GENEROSITY様での臨地実務実習で行わせていただいた、XR技術を使ったサービスの企画発案から価値検証モデル(Minimum Viable Product, 通称:MVP)の開発内容を全3回に分けて記事にさせていただきます。
記念すべき第1回では、3Dシーンで行ったメイン体験のコアとなる要素を抜粋して説明していきたいと思います。
技術ブログの作成自体も初めての経験なので、拙い点は広い心と温かい目で見守ってくださると幸いです。

目次

概要
今回のゴール
環境
実装
準備
スクリプトの作成
実行
まとめ

概要

私は「他人に言いにくい本音を抱えているが、誰にも見られたくない」といった課題を解決するために、本音を一人で解消するためのスマホ向けアプリ「本音バルーン」を作成いたしました。
視覚的なカタルシス(爽快感やストレスの解消)を体験した人がより感じるようにするため、ARを活用したゲーミフィケーション要素を企画に取り入れました。
私が開発したMVPの全体の流れを動画にしたので、ぜひご覧ください!(0.75倍速推奨)
リリースまでは至っておらず、あくまでもMVPであることをご了承ください。

今回のゴール

ビジュアルやその他の要素(背景の風船の出現)を除いて、InputFieldに入力された文字数で呼び出されるモーダルを変更します。今回は2種類のモーダルを変えることで、InputFieldの文字数が次のシーンへ進むのに値するかしていないかを判断できるようにしたいと思います。

TrueBalloon_PT-PT.gif

環境

OS

Windows11

Unityバージョン

Unity - 2022.3.49fi

テストデバイス

Google Pixel 6(Android 12)

Unity上のSimulatorは実装されていないのでGoogle Pixel 5で行っています

実装

準備

All templates から 3D を選び、プロジェクトを作成しましょう。

スクリーンショット 2024-11-08 135730.png

プロジェクトの作成が完了したら、日本語を追加します。必要なければ飛ばしてください。
下記のjapanese_full.txtのview rawを押します。書かれているものをCtrl + a で全選択し、Ctrl + cでコピーしましょう。

既存のフォントは日本語に対応していないため、対応している好みのフォントをネットから拾ってきてください。
フォントはProject内のAssetsTextMeshProFontsあたりにドラッグ&ドロップして追加してください。
次に、画面上部のWindowからTextMeshPro内のFont AssetCreatorを選択します。
Source Font Fileでフォントを選択して、先ほどコピーしておいた日本語をCustom Character ListにCtrl + vで張り付けましょう。あとはGenerate Font Atlasを押して長めのダウンロードの後にSave as...を選択したら完了です。

スクリーンショット 2024-11-08 150142.png

UnityScreenNavigatorのダウンロード

メインであるモーダルを呼び出すための画面遷移OSSライブラリ「Unity Screen Navigator」をダウンロードしましょう。
一度READMEを読むことをおすすめしますが、読まなくても今回行いたいことは全て出来るようにするので大丈夫です。

画面UIの用意

InputField

シーン内に必要なオブジェクトを配置していきます。
Hierarchyを右クリック、UIInput Field - TextMeshProを選択するとHierarchyInputField(TMP)が追加されます。Canvasのサイズに合わせて好きに拡大しましょう。
見た目の変更は、InputField(TMP)InspectorにあるImageから行えます。
中央の文字は、PlaceholderInspectorにある'TextMeshPro - Text(UI)'のText Inputから変更できます。
スクリーンショット 2024-11-08 143041.png

InputFieldには以下の写真のような調整がしてあります。内容としては、日本語対応させたフォントへの変更(Font Asset)、入力可能な文字数の制限(Character Limit)、Content TypeをCustom、Line TypeをMulti line Newlineにしました。細かな説明は省きますが、これによりスマートフォンでの文字入力操作に対応した設定が完了となりました。

スクリーンショット 2024-11-08 143102.png

Bottun

モーダルを呼び出すための必要なUIになります。一旦このままで放置。

スクリーンショット 2024-11-08 155808.png

ModalContainer

空のオブジェクトをHierarchy上に作成してInspectorの下部分にあるAdd ComponentからModalContainerスクリプトを加えてください。

注意
ModalContainerオブジェクトはCanvas内のHierarchyの一番下に配置しましょう!
表示させるModalPrefabが既に配置されているオブジェクトに隠れてしまいます!
スクリーンショット 2024-11-11 150617.png
スクリーンショット 2024-11-11 150601.png

ModalPrefabの作成

今回は2種類のモーダルを文字数に応じて出し分けたいため、2つのModalPrefabを作成します。
Prefabの内容は基本的に変わらないので作成自体は難しくないですが、画面UIを用意したシーンで行うとHierarchyの管理が面倒くさいので別のシーンで作成することをおすすめします。

文字数が足りないときに呼び出されるModal1と先へ進めるときに呼び出されるModal2を作りました。
特徴としては、役割の違うボタンUIが2~3つ配置してあります。スクリプトの作成で紹介するボタンに役割を与えるOptionSelectorModalスクリプトHierarchyで一番上の親オブジェクトに付いています。一つ下のオブジェクトには、このPrefabをモーダルにするためのModalスクリプトが付いています。UIオブジェクトたちが子オブジェクトになる階層であれば完了です。

download.png

作成したモーダルはHierarchyからAssets内のフォルダにドラッグ&ドロップしてPrefabにしましょう。
シーン内に残っているPrefab化したオブジェクトは、Hierarchyから削除しても構いません。

注意
今回は簡易的にモーダルの呼び出しを行いたいため、シンプルで分かりやすいResourcesフォルダ内に格納したモーダルを呼び出すことにしました。

スクリプトの作成

モーダルの呼び出すために自作するスクリプトは以下の2つです。

こまめなデバッグをおすすめします。

OptionSelectorModalスクリプト

前述したように、モーダル内のボタンに役割を与えるスクリプトです。以下のブログを参考に作成しました。
ブログで作成したものと違う点としては、YesとNoのほかにExitとOKを加えたことです。ただ、今回のゴールではNoボタンにしか役割を与えていません。ここで与えられた役割を呼び出された先で実行します。

OptionSelectorModal
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using UnityScreenNavigator.Runtime.Core.Modal;

public class OptionSelectorModal : Modal
{
    [SerializeField]
    private Button _yesButton = null;

    [SerializeField]
    private Button _noButton = null;

    [SerializeField]
    private Button _exitButton = null;

    [SerializeField]
    private Button _okButton = null;

    public Button.ButtonClickedEvent OnClickYesButton => _yesButton.onClick;
    public Button.ButtonClickedEvent OnClickNoButton => _noButton.onClick;
    public Button.ButtonClickedEvent OnClickExitButton => _exitButton.onClick;
    public Button.ButtonClickedEvent OnClickOkButton => _okButton.onClick;

    private void Awake()
    {
        // 各ボタンにイベントを追加
        _yesButton.onClick.AddListener(OnYesButtonClicked);
        _noButton.onClick.AddListener(OnNoButtonClicked);
        _exitButton.onClick.AddListener(OnExitButtonClicked);
        _okButton.onClick.AddListener(OnOkButtonClicked);
    }

    private void OnDestroy()
    {
        // モーダル破棄時にイベントリスナーを削除
        _yesButton.onClick.RemoveListener(OnYesButtonClicked);
        _noButton.onClick.RemoveListener(OnNoButtonClicked);
        _exitButton.onClick.RemoveListener(OnExitButtonClicked);
        _okButton.onClick.RemoveListener(OnOkButtonClicked);
    }

    private void OnYesButtonClicked()
    {
        SceneManager.LoadScene("sceneName");
    }

    private void OnNoButtonClicked()
    {
        ModalContainer.Find("ModalContainer").Pop(true);
    }

    private void OnExitButtonClicked()
    {
        SceneManager.LoadScene("sceneName");
    }

    private void OnOkButtonClicked()
    {
        SceneManager.LoadScene("sceneName");
    }
}

}

これにより、Inspector上でモーダル内のボタンを参照させることが出来るようになりました。

SceneControllerスクリプト

SceneControllerスクリプトでは、元のシーンにあるボタンUIを押すと現在のInpuField内の文字数に応じて決められたモーダルが呼び出される機能を作成します。

using UnityScreenNavigator.Runtime.Core.Modal;private ModalContainer _modalContainer = null; がないとモーダルを呼び出す機能が使えないので絶対に忘れないでください。
また、using TMPro; も忘れるとTextMeshProを使えないので忘れないようにしましょう。
呼び出すモーダルを文字数で切り替えたいので保存する変数を加えます。

SceneController
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityScreenNavigator.Runtime.Core.Modal;

public class SceneController : MonoBehaviour
{
    [SerializeField]
    private ModalContainer _modalContainer = null;  
    public GameObject inputGameObject;
    public Button button;
    public TMP_InputField inputField;

    // テキストの文字数を保存する変数
    private int textLength;

    // シングルトンインスタンスの設定
    public static SceneController Instance { get; private set; }

Startメソッドでは、シーン開始時にModalContainerを取得し、textLengthを初期化させます。
理由としては、残ったデータが悪影響を及ぼさず、正常な状態を保つためです。

SceneController
private void Awake()
    {
        // インスタンスのセットアップ。既にある場合は削除
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    private void Start()
    {
        // モーダルコンテナを取得し、初期化
        _modalContainer = ModalContainer.Find("ModalContainer");
        textLength = 0;
    }

textLengthを管理しているメソッドです。これらの管理により、textLengthの値が様々なメソッドで扱われることを可能としています。

SceneController

 // テキスト量を設定するメソッド
    public void SetTextLength(int length) => textLength = length;

    // テキスト量を取得するメソッド
    public int GetTextLength() => textLength;

    // テキスト量をリセットするメソッド
    public void ResetTextLength() => textLength = 0;

文字数に応じたモーダルを呼び出すためのボタンUIを制御するメソッドです。コルーチンにより表示するモーダルを制御します。もしコンソールにエラーが表示されていたなら、ほとんどがこのメソッドからです。

SceneController
// NextButtonクリック時に呼ばれる
    public void OnClick_NextButton()
    {
        StartCoroutine(NextButton());
    }

    // 入力フィールドの文字数に応じてモーダルを表示するコルーチン
    private IEnumerator NextButton()
    {
        string inputText = inputField.text;  // 入力フィールドからテキストを取得
        textLength = inputText.Length;       // テキストの長さを取得

        // テキストの長さに応じて表示するモーダルを変更
        if (textLength >= 6 && textLength < 100)
        {
            // 6文字以上で100文字未満の場合にModal2を表示
            yield return ShowModalWithLoading("Modal2");
        }
        else if (textLength >= 0 && textLength < 6)
        {
            // 0文字以上で6文字未満の場合にModal3を表示
            PlaySound(checkSound);
            yield return ShowModalWithLoading("Modal1");
        }
    }

    // モーダルを表示するコルーチン
    private IEnumerator ShowModalWithLoading(string modalName)
    {
        // モーダルを非同期で表示し、完了を待機
        var handle = _modalContainer.Push(modalName, true, onLoad: x =>
        {
            var OptionSelectorModal = (OptionSelectorModal)x.modal;
        });

        // モーダルが閉じるまで待機
        yield return handle;
    }
 

最後に全体図

SceneController
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityScreenNavigator.Runtime.Core.Modal;

public class SceneController : MonoBehaviour
{
    [SerializeField]
    private ModalContainer _modalContainer = null;  
    public GameObject inputGameObject;
    public Button button;
    public TMP_InputField inputField;

    // テキストの文字数を保存する変数
    private int textLength;

    // シングルトンインスタンスの設定
    public static SceneController Instance { get; private set; }

    private void Awake()
    {
        // インスタンスのセットアップ。既にある場合は削除
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    private void Start()
    {
        // モーダルコンテナを取得し、初期化
        _modalContainer = ModalContainer.Find("ModalContainer");
        textLength = 0;
    }

    // テキスト量を設定するメソッド
    public void SetTextLength(int length) => textLength = length;

    // テキスト量を取得するメソッド
    public int GetTextLength() => textLength;

    // テキスト量をリセットするメソッド
    public void ResetTextLength() => textLength = 0;

    // NextButtonクリック時に呼ばれる
    public void OnClick_NextButton()
    {
        StartCoroutine(NextButton());
    }

    // 入力フィールドの文字数に応じてモーダルを表示するコルーチン
    private IEnumerator NextButton()
    {
        string inputText = inputField.text;  // 入力フィールドからテキストを取得
        textLength = inputText.Length;       // テキストの長さを取得

        // テキストの長さに応じて表示するモーダルを変更
        if (textLength >= 6 && textLength < 100)
        {
            // 6文字以上で100文字未満の場合にModal2を表示
            yield return ShowModalWithLoading("Modal2");
        }
        else if (textLength >= 0 && textLength < 6)
        {
            // 0文字以上で6文字未満の場合にModal3を表示
            PlaySound(checkSound);
            yield return ShowModalWithLoading("Modal3");
        }
    }

    // モーダルを表示するコルーチン
    private IEnumerator ShowModalWithLoading(string modalName)
    {
        // モーダルを非同期で表示し、完了を待機
        var handle = _modalContainer.Push(modalName, true, onLoad: x =>
        {
            var OptionSelectorModal = (OptionSelectorModal)x.modal;
        });

        // モーダルが閉じるまで待機
        yield return handle;
    }
}

実行

最後にSceneControllerスクリプトを元のシーンのボタンUIにドラッグ&ドロップすれば動画と同じ実行が出来ていると思います。
もし、コンソールにエラーが表示されているのなら、その原因のほとんどがアタッチしたスクリプトの場所が違うかインスタンスが正しく参照されていないかのどちらかなので、確認してみてください。

TrueBalloon_PT-PT.gif

まとめ

この記事では、テキストデータの文字数によって呼び出すモーダルを切り替えるところまでですが、実際の開発では先ほどのSceneControllerスクリプトをさらに改良して文字数によって呼び出すモーダルの追加とシーン移動時のローディング処理と特定の言葉が入力された時の特殊処理も行っております。

TrueBalloon_PT01.gif

第2回、第3回の記事も合わせてご覧ください

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?