縦書きノベル制作日記 その9 式の評価
今回は、なんでも自分でやれるものはやってみようの精神で、 式の評価にチャレンジ。といっても、ググったら即解決しちゃいました。括弧なしの四則演算ですが、わかりやすかったのが下のリンク。普段目にしている式をいったん逆ポーランド記法へ変換し、それを実際に演算する2ステップ方式です。
挿入記法から逆ポーランド記法への変換 【 無料でC#プログラミング実験室 】
逆ポーランド記法の数式の解を求める 【 無料でC#プログラミング実験室 】
逆ポーランド記法なんて十数年ぶりに聞いた…Z80のマシン語をいじくり回していた時以来です。とりあえず、ソースコードもあるし、利用できるものは利用させてもらうとします。括弧は使えればいいにこしたことはないですが、そこまで手をかける必要もない、余裕ができたら拡張すると心に決めて放置(おそらく永遠に…)。
ただ今回比較演算子を使えるようにしなくてはならないので、どうしたものかなぁとしばし考えましたが、なんのことはない、演算の優先度を一番低くすればいいだけでした。
優先度低
>、<、>=、<=、==
+、ー
*、/(÷)
優先度高
上記のように優先度を決めて、逆ポーランド記法へ変換すればok。
・入力(挿入記法)
1 + 2 > 3 * 5
1 2 + 3 5 * >
うん、大丈夫そう。こんな感じで、解説も適当なように、ソースもほぼ参考通りで楽勝だったのですが、2つほど問題がありました。
1)int型(整数)では演算結果に問題が出る
当たり前ですが、intでやると3÷2の結果は1になります。これが、積み重なったりすると正しい結果にならないので、結局、演算はfloatで行うことにしました。ただ、色設定などfloatの演算結果を必要とすることあるので、結果的に正しい選択になったのでよしとします。
2)通常の計算と条件式では、返り値の型を変える必要がある
演算式は、通常の計算(変数への代入など)と条件式(IF文とか)で使用するつもりでしたので、通常の計算はfloatで結果を返す、条件式ではboolで結果を返すようにする必要がありました。演算部分をうまくまとめられないなぁと思考停止の結果、コピペして通常の計算結果と条件結果を求めるメソッドを2つ別々に用意するという方法で解決。う、動けばいいよね…。
次はどうしよっかなぁ…と思って煮詰まっていた時に、つい手を出したVR(cardboard)が思いのほか楽しい! ちょっとだけ浮気しよっと、えへ。
縦書きノベル制作日記 その8 変数管理
前回「分岐によるシーン数の拡散を防ぐ方策を考える」などと言っていましたが、どうあがこうと「分岐させたらシーンが増える」これは避けられません。では、ストーリーの大きな分岐は仕方ない、と諦めるとして、せめて小さな分岐ではシーン数を減らせるようなフラグ管理の導入を考えます。ここでいう小さな分岐というのは、会話の流れの本筋は変わらなくて、途中にある相槌をうつような選択肢によって、相手の反応がちょこっと変わるみたいな感じを想定しています。これを選択肢分だけ分岐させてしまうと、以下のような感じに……。
いまは、説明用にエクセルで図式化していて、まだ全体像を目で見れる分マシですが、現在想定しているのはテキストベースでのシナリオ管理です。長くなればなるほど無理が出てくるでしょう。そこでフラグ管理をできるようにすると以下のような形にまで、まとめられるようになります。
さらに選択肢によって好感度を上下させて、その値によって分岐させたりもできますし、どのルートを辿ってきたかの判別も可能になります。ただ、フラグを管理するといっても、お仕着せのものだと融通がきかない&視認性も悪くなる(間違いなく適当な自分では、なにをやってるかわからなくなる)ということで、ここはユーザーが自由に定義できる変数を用意します。スクリプト内では、この変数を利用することでシーン内で条件分岐をしたりすることができるようしていきたいので、新規コマンドの追加とMENUコマンドの変更を行います。
◆新規コマンド </VAR:変数名:値 変数の定義(定義と同時に初期化も行うため引数の省略は不可) </LET:変数名:値 値を変数へ代入 </IF:条件式 条件式が真(0)のときは、ENDIFまでの文章を生かし、条件式が偽(-1)のときは、ENDIFまでの文章を飛ばす </ENDIF IF文終了マーカー(なお、IFコマンドは入れ子にできない) ◆変更コマンド MENU:表示する選択肢の文字列:選択肢を表示する条件:次に移行するシーン名:設定する変数:設定する数値
変数の実装自体は、変数名と値を格納する構造体を用意し、変数が追加された時にはリストをガンガンaddしていくだけ。変数の読み出しは、頭から順に変数名を検索してヒットしたら取り出すだけ。大量の変数管理を想定することになると、検索性を上げるためにいろいろなことが必要になりますが、今回は数個〜せいぜい十数個も管理できれば問題ないないので、パパパ〜っと済ませます(詳しいやり方も知らないしね)。
// //変数格納用 //Variable.name = 変数名 //Variable.num = 値 // private struct VariableData { public string name; //変数名 public int num; //値 public VariableData(string p1,int p2) { name = p1; num = p2; } } //変数格納リスト private List<VariableData> variableArray = new List<VariableData>();</span>
で、次に問題となるのは変数に入れる値をどうするかです。単純に数値を入れるだけであれば、引数を変換してぶち込めばいいのですが、先程言ったような好感度を上下させるようなことをしたい場合は、直接、値を代入するだけでは実現できません。好感度+1や好感度+2ということをやるために、式の評価をすることに…さらに条件式の評価も必要になるので、四則演算だけでなく、比較演算子も考慮する必要になります。
めんどくさい…まぁ面倒とはいっても、先人たちの知恵を借りれば余裕っしょ、ということで「式の評価」とググって本日はおしまいっ。
縦書きノベル制作日記 その7 スクリプト
以前ざっくり考えたように、ゲームの基本進行は、
文章を読ませる=>選択肢が現れる=>選んだ選択肢によって以降の文章を変える
とするようにしました。
選択肢までの文章を1つの塊(シーン)と考え、シーンのテキストを読み込んで表示=>選択肢が押されたら次のシーンを読み込んで表示、これを繰り返していくだけ。
スクリプトのイメージとしては、以下のような感じ。
</シーン1開始
□□□□□□□□□□□□メインの文章
□□□□□□□□□□□□□□□□□□□□□□□□
</選択肢情報:選択肢テキスト:選択されたときに移動するシーン番号
</シーン終了
</シーン2開始
:
スクリプトはエクセルで管理すれば、あとはと拡張もできたりして便利なのかな? と一瞬考えましたが、ここは素直にテキストファイルに、スクリプトをがしがし書いく方法を選択します(Unityへのテキスト読み込みも簡単だし)。
//テキストファイルの読み込み
TextAsset txt = Resources.Load("シナリオテキスト") as TextAsset;
//行ごとに分割
string[] allText = txt.text.Split('\n');
上記のように1回シナリオテキストを配列にぶち込んでしまえばこっちのもの。あとは、シーンごとに取り出すなり好き放題できます。
とはいえ、1つのテキストにコマンドと通常の文章を混在させるということで、それらを区別する方法が必要です。これは、先ほどスクリプトイメージに書いた通り。コマンドと通常の文章を識別するための制御用文字列を”</”として、ステートを区切る文字は”:”としました。
</コマンド:パラメータ1:パラメータ2……
こんな感じ。リッチテキストとかそういうものに準拠しておけ! という悪魔(?)のささやきが聞こえなくもないですが、そんなものを知らない私は我が道を行くことにします。また、通常の文章と混在させない、1行1命令でマルチステートメント不可、という作るのに都合のいい仕様も盛り込みます。さらに「文頭で"</"を使いたいときはどうするんだよ?」という声には「使わないようにしましょう」でOK(プログラムをオープンにするなら考える…あとで)。この制限を設けておけば、配列ごとに頭2文字が制御文字列"</"かどうかチェックして、該当すればコマンド処理、そうでなければ通常文字に振り分けるだけで済みます。世の中が思い通りにならないんだから、せめて自分のプログラムくらい、自分の都合のいいように回したいです…ええ。
んで、必要なコマンドを書き出していきます。最初に考えたのは、以下の6コマンド。
</SCENE:シーン名 シーン名の登録。
</ENDSCENE シーンエンドマーカー。
</BACKIMAGEINIT:Spriteイメージファイル名
背景を"Spriteイメージファイル名"にする。
</BACKIMAGE:Spriteイメージファイル名:変移タイム
現在ある背景画像を、変異タイムの時間をかけて"Spriteイメージファイル名"に切り替える。
</CUTIMAGE:Spriteイメージファイル名
文章の間に"Spriteイメージファイル名"の画像を挿入する。
</MENU:表示する選択肢の文字列:次に移行するシーン名
シーン最後に"表示する選択肢の文字列"の選択肢を表示し、押されたら"次に移行するシーン名"へ移動する
これだけあれば、最初に予定していた通りのゲームの流れを実現できるはず。ただ、このままでは分岐させるたびに分岐先が無限に増え、物語のシーンは拡散する一方になってしまいます。選択肢の飛び先を合流させたりすることもできますが、どのルートを経由して来たのかなどの情報はないので、シナリオの整合性がとれるかどうかも微妙。
やはり分岐を入れるからには、どこかで収束させたりしておかないと収拾がつかなくなりそうということで、上のコマンドを実装しつつ、なにが必要かを考えていくことにしよう、うん。
縦書きノベル制作日記 表示系の覚書
のんびり進行のゲーム制作ですが、本業やら風邪やらでさらに歩みが止まってしまい、いろいろ忘れそうになってきました…。ということで、ゲームのメインはあまり進んでないため、canvasやらScroll Rectコンポーネントを使った時の設定やら座標やらについての覚書ということで整理してみます。
まず、canvasですが、3Dモデルとの共存などを考慮して、カメラを使ってスクリーン空間上に描画するScreen Space - Cameraモードを選択しています。キャンバス全体の拡大・縮小をどうするかのCanvasScalerの設定は、設定した画面サイズを基準にしたScale With Screen Sizeに設定しました。
次にのぞき窓用に用意したパネルのScrollWindowオブジェクトは、width・heighともに画面いっぱいストレッチするようにアンカーを設定。天地に多少隙間を開けているのは、そこの隙間に操作ボタンなどを入れる予定だったかな…まぁ、あとあと調整する話なのでスルー。Scroll Rect・Maskなどの各種コンポーネントを追加。
最後に実際にテキストを表示していくパネルは、右端固定・天地方向へ拡大のアンカーを設定。これで、のぞき窓の役割を持つ親のScrollWindowオブジェクトの大きさを変更しても追随するようになります。
ここで、TextWindowScrollを動かすと、動かした分の座標が変化します(ScrollWindowに対するTextWindowScrolオブジェクトの位置ですね)。
このTextWindowScrollパネル内に表示したいテキストを置いていくのですが、このパネルの座標系は、中心が原点になっています。縦書きでは右上から表示させたいので、開始座標は(TextWindowScrollの幅/2、TextWindowScrollの高さ/2)になります。
また、表示量削減のためTextScrollWindow内にあるオブジェクトが、のぞき窓のScrollWindowの範囲内に入ったらテキストを表示、範囲外に出たらテキストを消去するようにしています。つまり、TextScrollWindow内にあるオブジェクトの位置が、ScrollWndowのどこにいるかを判定する必要があります。
これは、以下のような式で求められ、
(オブジェクトのx座標 - TextWindowScrollの幅 / 2) - TextWindowScrollのx座標
この値が、0以上かつScrollWindowの幅以下であれば、オブジェクトはのぞき窓の中と判断できます。
って、ここまでやってきてなんだけど、自動レイアウト機能を使えば、もっと素直でスマートにできるのかなぁ、余裕ができたら調べてみよう。日々是勉強だ。うん。
縦書きノベル制作日記 その6
表示系の仕様を一通り組み込んだということで、いまさらながらゲームとしての仕様を考えていきます。分岐のないタイプであれば、タップ=>次のページへ進行させるを繰り返すだけですみますが、それだけだとやや物足りない。少し欲張って分岐のあるアドベンチャータイプができるような物を目指すことにしました。
ノベルタイプのゲームの基本進行は、文章を読ませる=>選択肢が現れる=>選んだ選択肢によって以降の文章を変えるような物になります。メインである文章を表示させるシーンがスクロールタイプなので、選択肢のメニューをポップアップさせるようなシステムだと、やや相性が悪そう。そこで、在りし日のゲームブックのように、シーン最後に選択肢を表示させて、読み進めていけるような感じになればいいなーとポチポチした結果がコレ↓
メニュー枠は、もう少し考慮する必要があるかもですが、なんとなくそれっぽくなったかな。
あとは、文章の読みやすさを考慮しないといけませんが、演出の一つとして背景にビジュアルを置けるようにして、さらに読み進めていく途中に背景を切り替えられるようにもしておきます。あとは、挿絵も文章の間に置けるようなことも考慮しておきます。BGM・効果音処理も背景と同様にできそうなので、仕様として入れておきましょう。演出系は入れ始めるときりがなくなるので、ひとまずココまで。
あとは、スクリプトをどうするか。エクセルシートにぼんやりと考えていたシナリオ構成を書き出してみると、そこそこ面倒くさいことに…。
分岐、変数管理、式の評価などなど…これも全部入れていくと大変そうだ。スクリプトの仕様は、実際にやりたいシナリオに必要な要素に絞るとして、しばし文章作業と平行して詰めていく事にします。がんばろっと。
縦書きノベル制作日記、4回目
前回決めた基本方針どおり、ScrollRectを使ったモノを目指してゴリゴリ進めていきます。まずは、なにも考えずにどば〜〜〜〜っとテキストを表示。
クリッピングもなにもしなくても、少々のテキストなら表示しっぱなしでも一応動くなぁと、調子に乗って行数を表示していくと・・・・。
表示限界を超えちゃった♪ まぁ、表示限界を超える前に処理落ちもひどく、小細工せず無制限に表示することは無理と判明しました(当たり前だ)。
そこで、見えない物は描画しないという、ごくごく基本的な小細工を加えてみました。要は、覗き窓から外れたオブジェクトを非表示(enable=false)にするというもの。結論から言うと、多少応答性は上がりましたが、それでも十分な速度ではありません。そもそも、表示限界を超える問題の解決にはなっていませんし。
そこで、覗き窓から外に出た文字オブジェクトそのものをいったん消去(Destory)し、覗き窓に入ってきたら文字オブジェクトを再生成してやる方法にチェンジしてみました。
今回、縦組を実現するためにLineObjectPrefabを親にして、表示したい文字を入れたテキストboxを子にして並べていくようにしています。この親に覗き窓にいるかどうかの状態を判別させて、テキストboxの生成・消去を行えば、描画&同時制御するオブジェクト数を極端に変動させず、処理的に安定しそう。ということで、さっそく組み替えてみると・・・・劇的に処理が向上しました。超軽快ってほどではありませんが、スマホでも動いているレベルなので、なんとか先へ進めそう。
ちなみにスクロールビューのお勉強は、下記のサイトを参考にしました。
いろいろ見たけど、ここが一番分かりやすかったです。