ドラクエ風 簡単な名前入力システムを作る

ドラクエをはじめ、名前を入力するシステムはよくありますね。
今回はなるべく簡単にその仕組を作ってみたいと思います。
完成系はこのような感じです。

今どきのゲームはよくわかりませんが、レトロゲームではおなじみの名前入力システムだと思います。

Unityスキルアップ、
始めるなら今

パズル、脱出、RPG...目標のゲームを完成させよう!

人気のUnity講座はこちら

Udemy講座

必要なものを配置

では早速やっていきましょう。

枠の作成

必要な素材とUIオブジェクトを配置していきます。
Hierarchy上に2つImageを作ります。

  • LetterPanel(文字を選択する枠)
  • NamePanel(選択した名前を表示する枠)

それぞれを程よい位置に配置します。

名前用の枠の調整

続いてNamePanelに「なまえ:」と書いてあるテキストを配置します。
NameHeaderとします。
次に実際に文字が選択されたら表示されるテキストをNamePanelに配置します。
NameTextとします。
Widthは実際に表示させたい文字数で収まるように調整して下さい。
位置を調整するときに親要素のNamePanelにHorizontalLayoutGroupコンポーネントを追加しておくと配置が楽です。
このような感じになりました。

文字選択用枠の調整

続いて文字選択用のLetterPanelを作り込んでいきます。
まずLetterPanelにGridLyaoutGroupコンポーネントを追加します。
これは格子状に子要素を並べてくれるので便利です。
続いて子要素に1文字のTextを作ります。「あ」と表示させておきましょう。
この名前をLetterとします。
選択中のカーソルが必要ですね。
カーソルとなるような素材を用意してプロジェクトにインポートしておきましょう。

カーソルの準備ができたらLetterの子要素にImageを作成しカーソルの素材を配置します。
名前はArrowとしました。
次にArrowの位置調整が必要です。
Transformで調整しても構いませんが、LetterにHorizontalLayoutGroupを追加すると楽に配置できます。
良い位置にArrowを配置してみて下さい。
現在こんな感じになっています。

Letterの位置を調整しましょう。
LetterPanelに追加したGridLayoutGroupのPaddingの値を調整すると位置を調整できます。
今回はLeftを50、Topを50にしました。

それでは子要素のLetterを複製してどのように並ぶか見てみましょう。
試しに15個並べましたが間隔が結構開いてると思います。
今回はLetterが横に10個並んで折り返しするようにしたいです。
再びLatterPanelのGridLayoutGroupを調整します。
Cell SizeというプロパティでXとYの大きさを調整します。
今回はXを55、Yを50としました。
ゲームビューとLetterPanelのInspectorはこんな感じになっています。


このあたりはお好みで調整してみて下さい。

これで配置は完成です。続いてLetterの文字の中身を「あ」ではなく五十音にしていきたいと思います。

五十音の表示

LetterのTextコンポーネントは「あ」となっていますが、それを五十音分複製し「い」「う」「え」…と手動で作っていっても良いのですが、少し手間ですのでCSVデータから自動で五十音分のLetterを作る方法をやってみたいと思います。

LetterをPrefab化

そのためにまずLetterをPrefab化しましょう。
Prefab化したLetterの文字を空にします。そして子要素のArrowを非表示にしておきましょう。
何も表示されなくなりました。

続いてLetterというC#スクリプトを作成し、LetterのPrefabにアタッチします。
Letterスクリプトの中身はこのようにします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Letter : MonoBehaviour
{
    [SerializeField] GameObject arrow;
    Text letter;
    void Start()
    {
        letter = GetComponent();
    }

    public void Selected(bool _selected)
    {
        if (_selected)
        {
            arrow.SetActive(true);
        }
        else
        {
            arrow.SetActive(false);
        }
    }

    public void SetLetter(string _letter)
    {
        letter.text = _letter;
    }

    public string SelectLetter()
    {
        return letter.text;
    }
}

SerializeFieldで子要素のArrowを取得するための変数arrow、Text型の変数letterを定義します。
Start関数で自身のTextコンポーネントを取得しletter変数に格納します。
次のSelected関数は自身の選択状態によって子要素Arrowの表示・非表示を切り替える処理をしています。
SettLetter関数は自身のTextコンポーネントの文字を設定する関数です。
最後のSelectLeterは決定されたときに文字を返す関数です。
これでLetterPrefabは完成です。

続いてLetterPanelを作っていきましょう。

LetterPanelスクリプト

新たにLetterPanelというC#スクリプトを作成し、ゲームオブジェクトLetterPanelにアタッチします。
LetterPanelスクリプトを作り込んでいきましょう。

変数の定義

まず必要となる変数を定義していきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LetterPanel : MonoBehaviour
{
    [SerializeField] Letter letter;
    [SerializeField] TextAsset letterText;
    [SerializeField] Text nameText;

    List letters;
    int maxNameLength = 8;
    int currentIndex = 0;
    string myName = "";
    
    void Start()
    {
        
    }
    void Update()
    {
        
    }
}

上から順番に、

  • LetterPrefab用の変数
  • 五十音CSV用の変数
  • 名前表示用のText型変数
  • 生成されたLetterを格納するList
  • 名前の最大文字数
  • 選択中の文字の番号用変数
  • 名前用のstring型変数

となっています。
ではUnityエディターに戻ってLetterPanelのInspectorからそれぞれアタッチしていきましょう。
Letterには先程プレファブ化したLetterを、LetterTextには五十音用CSVを、NameTextにはNamePanelの子要素であるNameTextをそれぞれ割り当てます。
五十音用のCSVは検索すればいくつかのサイトで提供しくれていますが、私の作ったCSVデータを載せておきますので、よろしければコピペして使ってみて下さい。

あ,い,う,え,お,か,き,く,け,こ
さ,し,す,せ,そ,た,ち,つ,て,と
な,に,ぬ,ね,の,は,ひ,ふ,へ,ほ
ま,み,む,め,も,や,ゆ,よ
ら,り,る,れ,ろ,わ,を,ん

CSVデータ読み込み

いよいよCSVデータを読み込んでいきます。Start関数を以下のようにします。

    void Start()
    {
        string[] letterLines = letterText.text.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
        foreach (string letterLine in letterLines)
        {
            string[] letterValues = letterLine.Split(new char[] { ',' });
            foreach (string l in letterValues)
            {
                Debug.Log(l);
            }
        }
    }

ここではCSVデータから一行ずつ分割し、さらにカンマ区切りで分割して一文字ずつ取り出しています。
詳しくは過去の記事「テキストデータからマップを作ってみる」をご覧ください。
実行してログがちゃんと出ているか確認してみましょう。

ちゃんとひらがなが一文字ずつログで表示されていますね。

ひらがなを画面に表示する

続いて画面上にこのひらがなを表示させてみましょう。
流れはこのようになっています。

  • Letterオブジェクトの生成
  • lettersに格納
  • letterに文字を送る

スクリプトはこのようになります。

    void Start()
    {
        //追加 Listを初期化
        letters = new List();
        
        string[] letterLines = letterText.text.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
        foreach (string letterLine in letterLines)
        {
            string[] letterValues = letterLine.Split(new char[] { ',' });
            foreach (string l in letterValues)
            {    
                //追加
                Letter letterObj = Instantiate(letter, transform);
                letters.Add(letterObj);
                StartCoroutine(_setLetter(l, letterObj));
            }
        }
    }

    //追加
    IEnumerator _setLetter(string _l,Letter _letterObj)
    {
        yield return null;
        _letterObj.SetLetter(_l);
    }

Start関数の最初でListを初期化しています。
続いてループ内のDebug.Logを削除し、Letterを生成、Listに格納、コルーチンで生成したLetterの文字をセットしています。
生成してすぐに letterObj.SetLetter(l); とするとNullReferenceExceptionになってしまいますのでコルーチンを挟んで処理をしています。

実行してみるとこのようになりました。

無事に五十音のひらがなが並びましたね。
次はカーソルを表示して文字を選択出来るようにしていきます。

文字の選択

ではまずLetterPanelスクリプトのUpdateでキー入力の処理を書いていきましょう。

  void Update()
    {
        
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            currentIndex++;
        }
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            currentIndex--;
        }
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            currentIndex += 10;
        }
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            currentIndex -= 10;
        }
    }

キー入力

入力したキーの方向でcurrentIndexの値を操作しています。
左で+1、右で-1、下で+10、上で-10と言った感じです。
上下に関してはもっと良いやり方もありますが、わかりやすくするためにマジックナンバーにしています。
10という値に関してはLetterPanel内の折り返し文字数が10個なのでこのようにしています。

カーソルの表示

次にLetterの子要素であるArrowの表示非表示をやっていきましょう。
LeterPanelスクリプトにShowArrowという関数を新たに追加します。
そしてStart時、Updateのキー入力時でそれぞれ呼び出すように修正します。

  void Start()
    {
        /* 略 */
        ShowArrow();
    }

/* 略 */
    
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            currentIndex++;
            ShowArrow();
        }
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            currentIndex--;
            ShowArrow();
        }
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            currentIndex += 10;
            ShowArrow();
        }
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            currentIndex -= 10;
            ShowArrow();
        }
    }


    //追加
    void ShowArrow()
    {
        currentIndex = Mathf.Clamp(currentIndex, 0, letters.Count - 1);
        for (int i = 0; i < letters.Count; i++)
        {
            if (i == currentIndex)
            {
                letters[i].Selected(true);
            }
            else
            {
                letters[i].Selected(false);
            }
        }
    }

ShowArrow関数内ではまず、currentIndexの値が範囲外にならないようにMathf.Clampで数値を丸めています。
関連記事:プレイヤーの移動範囲をMathf.Clampで制限する
その後、lettersの数だけループしcurrentIndexの値とiの値が一致した場合はLetterスクリプトのSelectedを呼んで表示、それ以外は非表示としています。

実際に動かしてみるとこのようになります。

カーソルが範囲内で正常に動いてくれています。
次は一文字ずつ決定したり、間違えた場合はバックスペースで一文字消したりしたいと思います。

文字の決定と削除

スクリプトを触る前にNameTextが「ああああああああ」となっていますので空っぽにしておきましょう。

文字を決定していく

今回はスペースキーを押したら一文字決定とし、nameTextに表示していきます。
LetterPanelスクリプトのUpdate関数に処理を追加します。

  void Update()
    {
        /* 略 */
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (myName.Length < maxNameLength)
            {
                myName += letters[currentIndex].SelectLetter();
            }
            nameText.text = myName;
        }
    }

スペースキー入力時の処理を書きました。
string型のmyNameの長さが最大文字数maxNameLengthの値より少なければmyNameに現在選択中のLetterのSelectLetter関数を呼び出し一文字追加しています。
その後nameTextのtextにその値を入れています。
実行するとこのような感じです。

文字数の制限もできているのが確認できました。

間違えた文字を消す

では最後にバックスペースキーで文字を消していく処理を追加します。
同じくLetterPanelスクリプトのUpdate関数内に記述します。

   void Update()
    {
        /* 略 */
        if (Input.GetKeyDown(KeyCode.Backspace))
        {
            if (myName.Length > 0)
            {
                myName = myName.Remove(myName.Length - 1);
            }
            nameText.text = myName;
        }
    }

これでmyNameの一番最後の一文字が削除されます。
一点注意ですがmyNameが何も無い場合、この処理を行おうとするとエラーになりますので、0以上の場合という条件をつけています。
それでは実行してみましょう。

文字を後ろから消していけることが確認できました。
濁点、半濁点、カタカナ、英数など色々改良出来ると思います。

おすすめUnityオンライン学習

一人でゲーム開発が不安な方はオンラインスクールで学習すると効率が良いと思います。
以下に当てはまる人は検討してみると良いかもしれません。

「ゲーム作りけど何から初めればいい?」
「入門書をの次に何をしたらいいの?」
「疑問や問題を一人で解決出来ない。。」
「ゲームを完成させる自信がない。。」
「気軽に質問できる環境が欲しい。。」

自分で学習することに限界を感じたら
オンライン学習しましょう!

Udemy

Udemy
特徴:教材動画買い切り型
レッスン方法:動画教材/Q&Aで質問
習得スキル:教材動画によって異なる
コース概要: ピンポイントで作りたいゲームジャンルの動画を見つけることができるかもしれません。 また頻繁にセールを開催していますのでゲーム開発講座が1500円~で購入することも可能です。
ゲームジャンル別おすすめ講座はこちら Udemy 詳細

テックアカデミー

テックアカデミー
特徴:現役エンジニア伴走型レッスン
レッスン方法:チャット(週2回)/テキストチャット/課題レビュー
習得スキル:オリジナルゲームアプリの作成
コース概要: 途中で挫折しないためにメンター(相談者)が週2回、マンツーマンであなたの質問・相談に答えてくれるので、ゲーム開発・プログラミング未経験の人でも安心してゲーム開発に取り組めます。転職したい人向けのサポートもあります。無料体験もあります。
テックアカデミー 詳細

テックスタジアム

テックスタジアム
特徴:ゲーム開発の為のオンラインスクール
レッスン方法:動画教材,Slackでの質問(24時間)
習得スキル: Unity,UnrealEngine、CG、企画、サーバー、AI、XR等
コース概要: ゲーム業界で必須な技術の習得が可能です。Slack上で待機している先生への24時間、何度でも質問できます。またIT・ゲーム業界への就職サポートが無料で受けられます。
ゲーム開発に特化したオンラインスクールですので、Unityはもとより、他のゲームエンジンやゲーム業界に興味がある方にはオススメです。
テックスタジアム 詳細

デジハリONLINE

デジハリONLINE
特徴:動画カリキュラム型レッスン
レッスン方法:動画講座/Slack/予約制Zoom相談
習得スキル:Unity基礎,実践/ハイクオリティ3Dゲーム
コース概要: デジタルハリウッド大学・大学院などを通じたデジタルコンテンツ業界とのつながりを活かし、最新かつ実践的なカリキュラムを提供しています。【基礎編】【実践編】とカリキュラムが分かれていて、それぞれに課題があり、ご自身の理解度を把握できます。最終的にはハイクオリティの3Dゲームが作れるようになります。
デジハリONLINE 詳細

関連記事

最後までご覧頂いてありがとうございました。

セール中!
C#プログラミング入門

もっと早く教えてほしかった!

Unity C#プログラミング入門