ゴマちゃんフロンティア

アザラシが大好きなエンジニアの開発日記です

【Unity】タイピングゲームの制作 (入力とマッチング)

time 2017/02/19

というわけで、仕事繁忙期につき全然更新できていませんが、今回は所謂「タイピングゲーム」の作成に関するお話です!
かなり唐突ですが、作っていた自作ゲームの制作に詰まってしまっているので、気分転換の意味合いが強いです。
Unityの新UIもあまり慣れていなかったので、そういった意味でも勉強にはよさそうですね。

今回はタイピングゲームの基盤となる、文字の入力とそのマッチング処理について考えてみました!

文字入力と判定に必要なもの

作り始める前に、「どんなシステムでどんなクラスが必要か」を考えてみます。
システム開発では超重要なことですが、自分の作るゲームはどれも実践していません。
適当でいいので図にしてみると、あれこれ想像がしやすいです。

こういう「ちょっとした図」を書く時にはペンタブが便利です。
細かい部分直す際に苦労をしなくて済む上、レイヤーコピーで差分修正も楽々です。
紙だと取り置きしにくいので尚更です。

ざっくり且つ適当に書き起こしたメモがこちら!

gameimage

あれこれ考えながら書いたので混沌としていますが、今回のメインは「InputField」と「Matching」になります。
前者がUIの制御、後者が対象となる文字列と入力された文字を比較する処理を担います。

実際の処理の流れは、
「マッチング用クラスのインスタンス生成」→「入力文字を取得して比較」→「比較結果に応じた各種処理」
です。
ひとまずこれを目指してコーディングします!

入力欄の作成

当然ですが、プレイヤーが文字を入力する部分が必要です。
その他、日本語名と入力すべきローマ字が表示されている場合が多いです。

shot2ss20170219225624970

uGUIにおける入力欄は「InputField」を使うようです。
その他2つは「Text」で問題ないでしょう。
フォント設定やセンタリング等もお好みで大丈夫ですが、「ContentType」は「Alphanumeric」に設定します。
Alphanumeric にすることで、半角英数字しか入力できなくなります。

shot2ss20170219225706361

後々のことを考えて、1つ空オブジェクトを作成し、その子となるように各種UIを設定します。
スクリプトでの制御も親オブジェクトで行います。

shot2ss20170219225747481

親の空オブジェクトはデフォルト Transform が設定されていますが、UI上で動かすので RectTransform に入れ替えておきます。
「Add Component」から「Rect Transform」を選べば切り替わります。
ここまで出来たらプレハブ化しておきます。

スクリプトによるマッチング処理

ひとまずスクリプトを記載します。
「TargetWord」が先程のUIの親に設定するスクリプトで、「WordMatcher」は TargetWord クラスから生成されるマッチング用クラスです。

【TargetWord】

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

public class TargetWord : MonoBehaviour {

    private WordMatcher matcher;
    private InputField input_field;

    void Start () {
        input_field = this.GetComponentInChildren<InputField>();

        string target_word = "goma";
        matcher = new WordMatcher(target_word);
    }

    /// <summary>
    /// InputFiled変更時のコールバック関数
    /// </summary>
    public void onInput(string text) {
        if (text.Length == 0) {
            return;
        }

        string input_char = text.Substring(text.Length - 1, 1);
        bool result = matcher.matching(input_char);

        // 入力内容と一致しなかった場合は戻す
        if (!result) {
            input_field.text = matcher.getInputtedString();
        }
    }
}

【WordMatcher】

using System.Collections;

/// <summary>
/// 入力対象文字列と入力文字を比較するクラス
/// </summary>
public class WordMatcher {

    // 入力対象の文字列
    private string target_string;

    // 現在の入力文字位置
    private int current_position;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public WordMatcher(string target_word) {
        this.target_string = target_word;
        current_position = 0;
    }

    /// <summary>
    /// 指定された文字と現在位置の文字を比較する
    /// </summary>
    public bool matching(string input_char) {
        if (getCurrentChar() == input_char) {
            current_position += 1;
            return true;
        }
        return false;
    }

    /// <summary>
    /// 現在入力済みの文字列を返す
    /// </summary>
    public string getInputtedString() {
        return (target_string.Substring(0, current_position));
    }

    private string getCurrentChar() {
        return (target_string.Substring(current_position, 1));
    }
}

処理は TargetWord クラスの Start() から始まります。
WordMatcher クラスをインスタンス化し、入力対象となる文字列を渡します。(今回は決め打ちです)

次に、入力欄(InputField)に文字が入力されたら処理を行います。
「Inputであれこれ制御すれば・・・」とか深読みしましたが、最近は「UnityEvent」という便利なものがあるようです!

shot2ss20170219225843384

「コールバック先オブジェクト」「スクリプトの関数名」を選択するだけで、入力時に自動的に呼び出されます。
いちいち自分で「入力されたら~」という処理を実装しなくても良い上、入力された文字も送られてきます。
Unity4.1 の OnGUI() で頑張っていた人としては感涙ものです。

shot2ss20170219225926120

注意点として、関数名選択時に「Dynamic String」の方を選ぶ必要があります。
「Static Parameter」では動的に取得してくれず、引数が空っぽになってしまいます。

入力時に onInput() が呼ばれます。
先程「入力された文字が送られてくる」と書きましたが、実際は「現在 InputField に入力されている内容全て」が送られてきます。
なので入力された文字=最も右側の文字のみを取得する処理を入れます。

 
    string input_char = text.Substring(text.Length - 1, 1);
    bool result = matcher.matching(input_char);

String.Substring() の第1引数で開始位置を指定すればOKです。
文字列のインデックスは0から始まるので、text.Length で取得した文字数から1を引いた数値を指定します。

あとはマッチング処理です。
といっても「入力した文字を取得する」が出来ているので、入力対象の文字と1文字ずつ比較するだけです。

 
    /// <summary>
    /// 指定された文字と現在位置の文字を比較する
    /// </summary>
    public bool matching(string input_char) {
        if (getCurrentChar() == input_char) {
            current_position += 1;
            return true;
        }
        return false;
    }

    private string getCurrentChar() {
        return (target_string.Substring(current_position, 1));
    }

アプローチとしては「入力した分だけ入力対象文字を削除する」か「入力された位置を変数で管理する」が浮かびました。
どちらでもいける気がしますが、自分は後者で実装しています。
getCurrentChar() で現在位置の文字を取得、入力された文字と比較し、一致したら位置を1つずらします。
一致しなかったら false を返し、TargetWord クラス側で入力された文字を元通しに戻しています。

ただし、現状では input_field.text を再設定した際、再度 onInput() が呼ばれてしまいます。
どうやらキーボード入力限定ではなく、単に input_field.text に変更があったかどうかだけ見ているようです。
回避方法が分からないので、2回通っても問題ないような処理にしてあります。

shot2ss20170219230035815

実行して挙動を確認しておきましょう。
入力すべき文字以外が来ると元通しになるため、見かけ上は入力されていないように見えます。

まとめ

そんなわけで、タイピングゲームの基盤となる部分を作ってみました!
次は「ワードを打ち終えたら次のワードの出現」といった部分でしょうか。
言葉をどこから取得するかも考える必要があるので、しっかり構想が決まってから手を出してみます。

down

コメントする