この記事は「Unityゲーム開発者ギルド2 Advent Calender 2020」12日目の記事です。
おはようございます。こんにちは。こんばんは。そしてはじめまして。アキオと申します。ちょうど記事を公開した12月12日が私の誕生日でして、今回で26歳になりました。この26歳の間はとにかくUnityとRailsを触って、様々なゲームやサービスを多数公開運営したいと思います。
早速質問ですが、皆様はタイピングゲームはお好きでしょうか?
最近はPC離れも加速してモバイルゲームが主流となった今、タイピングゲームは減少傾向です。けれでもPCメインのunityroomは、同じPCメインのタイピングゲームとの相性は抜群です。特にunity1weekに毎回参加している人々は、タイピングゲームを作ってみたいとは思ったことがあるはずです。
けれどもunity1weekで300~500近い作品が公開される中でタイピングゲームは多くて3作品しかありません。全体では8000近いゲームがunity1weekで公開されていますが。タイピングゲームは1%しか存在しません。
では、なぜ相性抜群のタイピングゲームがunityroomに少ないかというと
「実力差が大きい」「ありがちなゲームになる」という理由もあるかと思いますが、何よりも「作るのが難しい」のではないかと私は思います。
というのも現在ネット上に公開されている日本語入力のタイピングゲームのほぼ全てが、「し」を「shi」、「つ」を「tsu」と入力できます。
そのような柔軟な入力方法を実装するのが難しいから、タイピングゲームが少ないのではないかと私は思います。
逆を言えば、その柔軟な入力方法に対応していないタイピングゲームの作成は簡単だと私は思います。けれどもそれでは「やりにくい」「今時のタイピングゲームではない」という感想を抱えて、作るのをためらうのではないでしょうか?
なので、今回はunityroom等にタイピングゲームを増やすべく、その柔軟な入力方法に対応したタイピングゲームの作り方を紹介したいと思います。
対象読者は、ある程度Unityが触れて、TextMeshPro等などが使えることを想定しています。なのでちょっと説明が雑かもしれませんが、ご了承願います。
まず普段通りにプロジェクトを作成します。(2Dでも3Dでも可能)今回はTextMeshProを使いますので、TextMeshProをインポートして、TextMeshProのGameObject「TextMeshProTitle」と「TextMeshProRoman」のオブジェクトを作成します。
そして「TypingManager.cs」を作成します。そして空のGameObject「TypingManager」をHierarchyにアタッチして、そのGameObjectに先ほど作成した「TypingManager.cs」をInspector上でアタッチします。
ここからは、延々と「TypingManager.cs」にコードを書きます。
Unityではスクリプトの生成時に最初から存在するUpdate()
というイベント関数が存在しますが、これは毎フレーム呼び出される関数なので、1フレームあたりに2回以上の入力が発生すると、2回目以降のキー入力が検知できないので、基本使うべきではありません。
なので今回は、Unityでは標準でOnGUI()
というイベント関数を使います。この関数はキーボードやマウスのクリックならびにクリック解除の時に呼び出されます。今回はあくまでもキーの入力時のみに処理を行いたいのでif
を使って以下のように書きます。
private void OnGUI()
{
if (Event.current.type == EventType.KeyDown)
{
// キーが入力された時に処理される。
}
}
OnGUI()
関数にEvent.current.type == EventType.KeyDown
という条件式を書けば、キーの入力時のみtrue
になり、処理が実行されるようになります。
もう早速ですが、タイピングゲームに使われるOnGUI()
の中身を全部書きましょう。以下のようになります。
private void OnGUI()
{
if (Event.current.type == EventType.KeyDown)
{
switch(InputKey(GetCharFromKeyCode(Event.current.keyCode)))
{
case 1:
case 2:
_romanIndex++;
if(_roman[_romanIndex] == '@')
{
InitializeQuestion();
}
else
{
_textMeshProRoman.text = GenerateRomanText();
}
break;
case 3:
// ここにミスタイプ時の処理を記述する
break;
}
}
}
当然ですがInputKey()
やGetCharFromKeyCode()
、InitializeQuestion()
、GenerateRomanText()
ならびに_roman[]
、 _romanIndex
、 _textMeshProRoman
は未定義ですので、このままでは動作しません。なのでそれぞれのメソッドや変数の解説をしながら実装したいと思います。
キーが入力されOnGUI()
が実行されるとEvent.current.keyCode
に入力されたキーコードが格納されます。型はKeyCode
です。
この型はタイピングゲームのアルゴリズム実装には向いていないので、KeyCode
をchar
に変換する関数GetCharFromKeyCode()
を実装しましょう。
以下のようになります。今回はShift入力は省略しました。
char GetCharFromKeyCode(KeyCode keyCode)
{
switch (keyCode)
{
case KeyCode.A:
return 'a';
case KeyCode.B:
return 'b';
case KeyCode.C:
return 'c';
case KeyCode.D:
return 'd';
case KeyCode.E:
return 'e';
case KeyCode.F:
return 'f';
case KeyCode.G:
return 'g';
case KeyCode.H:
return 'h';
case KeyCode.I:
return 'i';
case KeyCode.J:
return 'j';
case KeyCode.K:
return 'k';
case KeyCode.L:
return 'l';
case KeyCode.M:
return 'm';
case KeyCode.N:
return 'n';
case KeyCode.O:
return 'o';
case KeyCode.P:
return 'p';
case KeyCode.Q:
return 'q';
case KeyCode.R:
return 'r';
case KeyCode.S:
return 's';
case KeyCode.T:
return 't';
case KeyCode.U:
return 'u';
case KeyCode.V:
return 'v';
case KeyCode.W:
return 'w';
case KeyCode.X:
return 'x';
case KeyCode.Y:
return 'y';
case KeyCode.Z:
return 'z';
case KeyCode.Alpha0:
return '0';
case KeyCode.Alpha1:
return '1';
case KeyCode.Alpha2:
return '2';
case KeyCode.Alpha3:
return '3';
case KeyCode.Alpha4:
return '4';
case KeyCode.Alpha5:
return '5';
case KeyCode.Alpha6:
return '6';
case KeyCode.Alpha7:
return '7';
case KeyCode.Alpha8:
return '8';
case KeyCode.Alpha9:
return '9';
case KeyCode.Minus:
return '-';
case KeyCode.Caret:
return '^';
case KeyCode.Backslash:
return '\\';
case KeyCode.At:
return '@';
case KeyCode.LeftBracket:
return '[';
case KeyCode.Semicolon:
return ';';
case KeyCode.Colon:
return ':';
case KeyCode.RightBracket:
return ']';
case KeyCode.Comma:
return ',';
case KeyCode.Period:
return '_';
case KeyCode.Slash:
return '/';
case KeyCode.Underscore:
return '_';
case KeyCode.Backspace:
return '\b';
case KeyCode.Return:
return '\r';
case KeyCode.Space:
return ' ';
default:
return '\0';
}
}
(誰かこのコードを簡潔にできるなら教えて)
ひどいコードですね(笑)でも私の技術力ではこうなってしまいました。こうやって、KeyCode
をchar
に変換しているわけです。今回はShiftキーによる大文字入力には対応していません。Functionキーなどには入力対応しておらず、それらが入力された場合にもOnGUI()
が実行されEvent.current.keyCode
に格納され、上記の関数が実行されますが、その場合はnull文字\0
を返しています。
さて、OnGUI()
関数を確認すると、GetCharFromKeyCode()
のchar
の返り値が、InputKey()
の引数となっているわけですが、この関数は入力が正しいか否かを判断します。この関数の返り値はint
で、正しい入力があれば1
を、ミスタイプであれば3
を返し、null文字ならば0
を返します。
int InputKey(char inputChar)
{
char currentChar = _roman[_romanIndex];
if(inputChar == '\0')
{
return 0;
}
if(inputChar == currentChar)
{
return 1;
}
return 3;
}
返り値の2
については、後ほどの「柔軟な入力方法」によって解説します。
ところで「_roman
とか_romanIndex
って何?」と思った方、今から解説します。
タイピングゲームを実装するからには、当然タイピング用の状態を格納する変数が必要になります。
なので今回は_roman
とromanIndex
のインスタンス変数を定義しましょう。
public class TypingManager : MonoBehaviour
{
private List<char> _roman = new List<char>();
private int _romanIndex = 0;
}
_roman
はタイピングの処理に用いられるList<char>
のインスタンス変数で、頻繁にAdd()
やClear()
を用いますので、List<T>
となっております。
そして_romanIndex
は_roman
の参照に用いられるだけのint
型のインスタンス変数です。
どんなタイピングゲームにも「問題の初期化」というのは必ず存在しますので、それを行うための関数InitializeQuestion()
を実装します。
void InitializeQuestion()
{
Question question = _questions[UnityEngine.Random.Range(0, _questions.Length)];
_romanIndex = 0;
_roman.Clear();
char[] characters = question.roman.ToCharArray();
foreach(char character in characters)
{
_roman.Add(character);
}
_roman.Add('@');
_textMeshProTitle.text = question.title;
_textMeshProRoman.text = GenerateRomanText();
}
Clear()
メソッドで_roman
の中身を空にし、その後question
のroman
プロパティ(string
型)をToCharArray()
メソッドでchar
型の配列に変換して、foreach
で_roman
に次から次へとAdd()
メソッドで追加します。
そして_roman
の最後に@
を追加します。この@
が「タイピングの終わり」であることを示します。
ところでQuestion
が登場しました。タイピングゲームにはタイピング用の文字列のリストが必要になります。
なのでQuestion
クラスを作成して、[SerializeField]
でインスペクタ上から文字列を編集できるようにします。
_textMeshProTitle
や_textMeshProRoman
は、画面に表示するためのTextMeshProのオブジェクトが格納されたインスタンス変数です。
ついでに、TextMeshProのGameObjectを取得するコードもStart()
関数等に追加します。
using System; //追加する
using System.Collections;
using System.Collections.Generic;
using TMPro; //これも追加する
// 以下の追加する
[Serializable]
public class Question
{
public string title;
public string roman;
}
// ここまで
public class TypingManager : MonoBehaviour
{
// 以下を追加する
[SerializedField] Question[] _questions = new Question[12]; //お好きな数字をどうぞ
private TextMeshProUGUI _textMeshProTitle;
private TextMeshProUGUI _textMeshProRoman;
// ここまで
void Start()
{
// 以下を追加する
_textMeshProTitle = GameObject.Find("TextMeshProTitle").GetComponent<TextMeshProUGUI>();
_textMeshProRoman = GameObject.Find("TextMeshProRoman").GetComponent<TextMeshProUGUI>();
InitializeQuestion();
// ここまで
}
}
タイピングゲームには当たり前のように、入力後の文字と入力前の文字で色が異なりますので、それを実装するための関数を実装します。
string GenerateRomanText()
{
string text = "<style=typed>"
for (int i = 0; i < _roman.Count; i++)
{
if (_roman[i] == '@')
{
break;
}
if (i == _romanIndex)
{
text += "</style><style=untyped>"
}
text += _roman[i];
}
text += "</style>"
return text;
}
TextMeshProにはタグ機能が存在しており、特定の部分のみスタイルを変更する「」を使用しました。
ただ、このままでは画面上に「」などが表示されてしまいます。なのでUnityエディタ上で「Project Settings」→「TextMeh Pro」の「Settings」→「Default Style Sheet」の「Default Style Sheet (TMP_StyleSheet)」をダブルクリックして、Inspector上で以下の図のようにタグを追加します。
こうすることによって、「」は表示されなくなり、タイピング済の文字とそうでない文字のスタイルが変わります。
最後にUnityエディタ上の「Hierarchy」から「TypingManager」を選択して、Questionsの値をセットすれば完成です。
(Romanのセットには最短になるようセットします(「chi」→「ti」、「zyo」→「jo」等))
これで「Play」を実行しましょう!
タイピング機能はうまく動作しましたか?
(動作しなかった場合、自力でソースコード等を修正できますか?🙇♂️)
しかし先ほど作成したタイピングゲームは柔軟な入力方法には対応していません。
このままでは、非常に操作性の悪いタイピングゲームとなってしまい、今時リリースできるようなものではありません。
なので本題である柔軟な入力方法に対応させます。
タイピングの入力方法については、以下のURLが参考になります。
Windows:
https://www.cc.saga-u.ac.jp/system/CenterSystem/ime_romaji.htm
Mac:
https://support.apple.com/ja-jp/guide/japanese-input-method/jpim10277/6.2.1/mac/10.15
よくよく確認すると、WindowsとMacでは入力方法が異なることがわかります。(例:Windowsでは「ca」と入力できるが、Macではできない)
もしもWindowsおよびMac専用のソフトとしてリリースするのであれば、両方に対応する必要はないと思いますが、WebGLでビルドしてunityroomにアップロードする場合、WindowsとMacを区別する必要があります
再び「TypingManager.cs」を編集します。
public class TypingManager : MonoBehaviour
{
private bool _isWindows; //追加する
private bool _isMac; //追加する
void Start()
{
// 以下を追加する
if(SystemInfo.operatingSystem.Contains("Windows"))
{
_isWindows = true;
}
if(SystemInfo.operatingSystem.Contains("Mac"))
{
_isMac = true;
}
// ここまで
InitializeQuestion();
}
}
現在の日本語入力タイピングにおいて、柔軟な入力方法に対応させるアルゴリズムはいくつか存在します。
今回は「前後比較法(私が勝手に名付けた方法です)」を使います。
「前後比較法」は、今入力すべき文字とその文字の前後を比較することによって「どのひらがなを入力するのか」を判断して、入力してもOKな文字を抽出するアルゴリズムです。
例えば「うちわ(utiwa)」を考えましょう。
まず最初の入力文字が「u」なので、入力するひらがなは「う」であることが判明します。「う」の別入力が「wu」であるので「w」を入力しても構わないことがわかります。
当然ですが、別入力を行いますとローマ字を改良する必要があります。今回の例で「w」を入力するとローマ字表記は「wutiwa」に変化します。
次の入力文字も「u」ですが、前の文字が「w」なので「う」の入力途中であることが判明します。よって今回は「w」を入力することができません。
そして「u」の入力後、入力文字が「t」になります。この入力文字の前が母音で、後の文字が「i」なので「ち」を入力することが判明します。同様に「ち」の別入力は「chi」なので「c」を入力してもOKとなります。
もちろん「c」を入力した後は「wuchiwa」に変形する必要があります。
こんな感じでほかの文字に対応させることができます。
上記のアルゴリズムを用いてInputKey()
を改良します。
以下のコードを解説するとかなり長いので省略します。
int InputKey(char inputChar)
{
char prevChar3 = _romanIndex >= 3 ? _roman[_romanIndex - 3] : '\0';
char prevChar2 = _romanIndex >= 2 ? _roman[_romanIndex - 2] : '\0';
char prevChar = _romanIndex >= 1 ? _roman[_romanIndex - 1] : '\0';
char currentChar = _roman[_romanIndex];
char nextChar = _roman[_romanIndex + 1];
char nextChar2 = nextChar == '@' ? '@' : _roman[_romanIndex + 2];
if (inputChar == '\0')
{
return 0;
}
if (inputChar == currentChar)
{
return 1;
}
//「い」の曖昧入力判定(Windowsのみ)
if (_isWindows && inputChar == 'y' && currentChar == 'i' &&
(prevChar == '\0' || prevChar == 'a' || prevChar == 'i' || prevChar == 'u' || prevChar == 'e' ||
prevChar == 'o'))
{
_roman.Insert(_romanIndex, 'y');
return 2;
}
if (_isWindows && inputChar == 'y' && currentChar == 'i' && prevChar == 'n' && prevChar2 == 'n' &&
prevChar3 != 'n')
{
_roman.Insert(_romanIndex, 'y');
return 2;
}
if (_isWindows && inputChar == 'y' && currentChar == 'i' && prevChar == 'n' && prevChar2 == 'x')
{
_roman.Insert(_romanIndex, 'y');
return 2;
}
//「う」の曖昧入力判定(「whu」はWindowsのみ)
if (inputChar == 'w' && currentChar == 'u' && (prevChar == '\0' || prevChar == 'a' || prevChar == 'i' ||
prevChar == 'u' || prevChar == 'e' || prevChar == 'o'))
{
_roman.Insert(_romanIndex, 'w');
return 2;
}
if (inputChar == 'w' && currentChar == 'u' && prevChar == 'n' && prevChar2 == 'n' && prevChar3 != 'n')
{
_roman.Insert(_romanIndex, 'w');
return 2;
}
if (inputChar == 'w' && currentChar == 'u' && prevChar == 'n' && prevChar2 == 'x')
{
_roman.Insert(_romanIndex, 'w');
return 2;
}
if (_isWindows && inputChar == 'h' && prevChar2 != 't' && prevChar2 != 'd' && prevChar == 'w' &&
currentChar == 'u')
{
_roman.Insert(_romanIndex, 'h');
return 2;
}
//「か」「く」「こ」の曖昧入力判定(Windowsのみ)
if (_isWindows && inputChar == 'c' && prevChar != 'k' &&
currentChar == 'k' && (nextChar == 'a' || nextChar == 'u' || nextChar == 'o'))
{
_roman[_romanIndex] = 'c';
return 2;
}
//「く」の曖昧入力判定(Windowsのみ)
if (_isWindows && inputChar == 'q' && prevChar != 'k' && currentChar == 'k' && nextChar == 'u')
{
_roman[_romanIndex] = 'q';
return 2;
}
//「し」の曖昧入力判定
if (inputChar == 'h' && prevChar == 's' && currentChar == 'i')
{
_roman.Insert(_romanIndex, 'h');
return 2;
}
//「じ」の曖昧入力判定
if (inputChar == 'j' && currentChar == 'z' && nextChar == 'i')
{
_roman[_romanIndex] = 'j';
return 2;
}
//「しゃ」「しゅ」「しぇ」「しょ」の曖昧入力判定
if (inputChar == 'h' && prevChar == 's' && currentChar == 'y')
{
_roman[_romanIndex] = 'h';
return 2;
}
//「じゃ」「じゅ」「じぇ」「じょ」の曖昧入力判定
if (inputChar == 'z' && prevChar != 'j' && currentChar == 'j' &&
(nextChar == 'a' || nextChar == 'u' || nextChar == 'e' || nextChar == 'o'))
{
_roman[_romanIndex] = 'z';
_roman.Insert(_romanIndex + 1, 'y');
return 2;
}
//「し」「せ」の曖昧入力判定(Windowsのみ)
if (_isWindows && inputChar == 'c' && prevChar != 's' && currentChar == 's' &&
(nextChar == 'i' || nextChar == 'e'))
{
_roman[_romanIndex] = 'c';
return 2;
}
//「ち」の曖昧入力判定
if (inputChar == 'c' && prevChar != 't' && currentChar == 't' && nextChar == 'i')
{
_roman[_romanIndex] = 'c';
_roman.Insert(_romanIndex + 1, 'h');
return 2;
}
//「ちゃ」「ちゅ」「ちぇ」「ちょ」の曖昧入力判定
if (inputChar == 'c' && prevChar != 't' && currentChar == 't' && nextChar == 'y')
{
_roman[_romanIndex] = 'c';
return 2;
}
//「cya」=>「cha」
if (inputChar == 'h' && prevChar == 'c' && currentChar == 'y')
{
_roman[_romanIndex] = 'h';
return 2;
}
//「つ」の曖昧入力判定
if (inputChar == 's' && prevChar == 't' && currentChar == 'u')
{
_roman.Insert(_romanIndex, 's');
return 2;
}
//「つぁ」「つぃ」「つぇ」「つぉ」の分解入力判定
if (inputChar == 'u' && prevChar == 't' && currentChar == 's' &&
(nextChar == 'a' || nextChar == 'i' || nextChar == 'e' || nextChar == 'o'))
{
_roman[_romanIndex] = 'u';
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
if (inputChar == 'u' && prevChar2 == 't' && prevChar == 's' &&
(currentChar == 'a' || currentChar == 'i' || currentChar == 'e' || currentChar == 'o'))
{
_roman.Insert(_romanIndex, 'u');
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
//「てぃ」の分解入力判定
if (inputChar == 'e' && prevChar == 't' && currentChar == 'h' && nextChar == 'i')
{
_roman[_romanIndex] = 'e';
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
//「でぃ」の分解入力判定
if (inputChar == 'e' && prevChar == 'd' && currentChar == 'h' && nextChar == 'i')
{
_roman[_romanIndex] = 'e';
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
//「でゅ」の分解入力判定
if (inputChar == 'e' && prevChar == 'd' && currentChar == 'h' && nextChar == 'u')
{
_roman[_romanIndex] = 'e';
_roman.Insert(_romanIndex + 1, 'x');
_roman.Insert(_romanIndex + 2, 'y');
return 2;
}
//「とぅ」の分解入力判定
if (inputChar == 'o' && prevChar == 't' && currentChar == 'w' && nextChar == 'u')
{
_roman[_romanIndex] = 'o';
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
//「どぅ」の分解入力判定
if (inputChar == 'o' && prevChar == 'd' && currentChar == 'w' && nextChar == 'u')
{
_roman[_romanIndex] = 'o';
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
//「ふ」の曖昧入力判定
if (inputChar == 'f' && currentChar == 'h' && nextChar == 'u')
{
_roman[_romanIndex] = 'f';
return 2;
}
//「ふぁ」「ふぃ」「ふぇ」「ふぉ」の分解入力判定(一部Macのみ)
if (inputChar == 'w' && prevChar == 'f' &&
(currentChar == 'a' || currentChar == 'i' || currentChar == 'e' || currentChar == 'o'))
{
_roman.Insert(_romanIndex,'w');
return 2;
}
if (inputChar == 'y' && prevChar == 'f' && (currentChar == 'i' || currentChar == 'e'))
{
_roman.Insert(_romanIndex,'y');
return 2;
}
if (inputChar == 'h' && prevChar != 'f' && currentChar == 'f' &&
(nextChar == 'a' || nextChar == 'i' || nextChar == 'e' || nextChar == 'o'))
{
if (_isMac)
{
_roman[_romanIndex] = 'h';
_roman.Insert(_romanIndex + 1, 'w');
}
else
{
_roman[_romanIndex] = 'h';
_roman.Insert(_romanIndex + 1, 'u');
_roman.Insert(_romanIndex + 2, 'x');
}
return 2;
}
if (inputChar == 'u' && prevChar == 'f' &&
(currentChar == 'a' || currentChar == 'i' || currentChar == 'e' || currentChar == 'o'))
{
_roman.Insert(_romanIndex, 'u');
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
if (_isMac && inputChar == 'u' && prevChar == 'h' && currentChar == 'w' &&
(nextChar == 'a' || nextChar == 'i' || nextChar == 'e' || nextChar == 'o'))
{
_roman[_romanIndex] = 'u';
_roman.Insert(_romanIndex + 1, 'x');
return 2;
}
//「ん」の曖昧入力判定(「n'」には未対応)
if (inputChar == 'n' && prevChar2 != 'n' && prevChar == 'n' && currentChar != 'a' && currentChar != 'i' &&
currentChar != 'u' && currentChar != 'e' && currentChar != 'o' && currentChar != 'y')
{
_roman.Insert(_romanIndex, 'n');
return 2;
}
if (inputChar == 'x' && prevChar != 'n' && currentChar == 'n' && nextChar != 'a' && nextChar != 'i' &&
nextChar != 'u' && nextChar != 'e' && nextChar != 'o' && nextChar != 'y')
{
if (nextChar == 'n')
{
_roman[_romanIndex] = 'x';
}
else
{
_roman.Insert(_romanIndex, 'x');
}
return 2;
}
//「きゃ」「にゃ」などを分解する
if (inputChar == 'i' && currentChar == 'y' &&
(prevChar == 'k' || prevChar == 's' || prevChar == 't' || prevChar == 'n' || prevChar == 'h' ||
prevChar == 'm' || prevChar == 'r' || prevChar == 'g' || prevChar == 'z' || prevChar == 'd' ||
prevChar == 'b' || prevChar == 'p') &&
(nextChar == 'a' || nextChar == 'u' || nextChar == 'e' || nextChar == 'o'))
{
if (nextChar == 'e')
{
_roman[_romanIndex] = 'i';
_roman.Insert(_romanIndex + 1, 'x');
}
else
{
_roman.Insert(_romanIndex, 'i');
_roman.Insert(_romanIndex + 1, 'x');
}
return 2;
}
//「しゃ」「ちゃ」などを分解する
if (inputChar == 'i' &&
(currentChar == 'a' || currentChar == 'u' || currentChar == 'e' || currentChar == 'o') &&
(prevChar2 == 's' || prevChar2 == 'c') && prevChar == 'h')
{
if (nextChar == 'e')
{
_roman.Insert(_romanIndex,'i');
_roman.Insert(_romanIndex + 1, 'x');
}
else
{
_roman.Insert(_romanIndex, 'i');
_roman.Insert(_romanIndex + 1, 'x');
_roman.Insert(_romanIndex + 2, 'y');
}
return 2;
}
//「しゃ」を「c」で分解する(Windows限定)
if (_isWindows && inputChar == 'c' && currentChar == 's' && prevChar != 's' && nextChar == 'y' &&
(nextChar2 == 'a' || nextChar2 == 'u' || nextChar2 == 'e' || nextChar2 == 'o'))
{
if (nextChar2 == 'e')
{
_roman[_romanIndex] = 'c';
_roman[_romanIndex + 1] = 'i';
_roman.Insert(_romanIndex + 1, 'x');
}
else
{
_roman[_romanIndex] = 'c';
_roman.Insert(_romanIndex + 1, 'i');
_roman.Insert(_romanIndex + 2, 'x');
}
return 2;
}
//「っ」の分解入力判定
if ((inputChar == 'x' || inputChar == 'l') &&
(currentChar == 'k' && nextChar == 'k' || currentChar == 's' && nextChar == 's' ||
currentChar == 't' && nextChar == 't' || currentChar == 'g' && nextChar == 'g' ||
currentChar == 'z' && nextChar == 'z' || currentChar == 'j' && nextChar == 'j' ||
currentChar == 'd' && nextChar == 'd' || currentChar == 'b' && nextChar == 'b' ||
currentChar == 'p' && nextChar == 'p'))
{
_roman[_romanIndex] = inputChar;
_roman.Insert(_romanIndex + 1, 't');
_roman.Insert(_romanIndex + 2, 'u');
return 2;
}
//「っか」「っく」「っこ」の特殊入力(Windows限定)
if (_isWindows && inputChar == 'c' && currentChar == 'k' && nextChar == 'k' &&
(nextChar2 == 'a' || nextChar2 == 'u' || nextChar2 == 'o'))
{
_roman[_romanIndex] = 'c';
_roman[_romanIndex + 1] = 'c';
return 2;
}
//「っく」の特殊入力(Windows限定)
if (_isWindows && inputChar == 'q' && currentChar == 'k' && nextChar == 'k' && nextChar2 == 'u')
{
_roman[_romanIndex] = 'q';
_roman[_romanIndex + 1] = 'q';
return 2;
}
//「っし」「っせ」の特殊入力(Windows限定)
if (_isWindows && inputChar == 'c' && currentChar == 's' && nextChar == 's' &&
(nextChar2 == 'i' || nextChar2 == 'e'))
{
_roman[_romanIndex] = 'c';
_roman[_romanIndex + 1] = 'c';
return 2;
}
//「っちゃ」「っちゅ」「っちぇ」「っちょ」の曖昧入力判定
if (inputChar == 'c' && currentChar == 't' && nextChar == 't' && nextChar2 == 'y')
{
_roman[_romanIndex] = 'c';
_roman[_romanIndex + 1] = 'c';
return 2;
}
//「っち」の曖昧入力判定
if (inputChar == 'c' && currentChar == 't' && nextChar == 't' && nextChar2 == 'i')
{
_roman[_romanIndex] = 'c';
_roman[_romanIndex + 1] = 'c';
_roman.Insert(_romanIndex + 2, 'h');
return 2;
}
//「l」と「x」の完全互換性
if (inputChar == 'x' && currentChar == 'l')
{
_roman[_romanIndex] = 'x';
return 2;
}
if (inputChar == 'l' && currentChar == 'x')
{
_roman[_romanIndex] = 'l';
return 2;
}
return 3;
}
こうすることによって、「し」を「shi」、「ふ」を「fu」と入力できるようになります。
実際、上記のコードは完璧ではありませんが、これでもゲームとして十分だと思います。
私は文章下手なので、少々読みにくい点はあったかもしれませんが、それでも、長々とした記事をお読みいただき、誠にありがとうございます。
上記のコードは自由にアレンジしても、そのままコピペしても構いません。
むしろ、それでタイピングゲームの製作に関心を持ってもらえたら幸いです。
サンプルはunityroom上にて公開しております。
なお、今回はミスタイプ数などの要素が加わています。
https://unityroom.com/games/typingsample
内容は、上記のサンプルのプロジェクトです。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント