TypeScript

ノベルゲームエンジンをつくろう

これはドワンゴ Advent Calendar 2017 22日目の記事です。

TL;DR

  • ノベルゲームが作りたいのでノベルゲームエンジンから作ることにした
  • TypeScriptフレンドリー
  • 絶賛開発中なのでまだまだ実用には遠い
    • 最低限紙芝居くらいはできるが、色々足りない
    • セーブ、ロード周りが悩ましい
  • 今後の目標: RPGアツマールに投稿

はじめに

ノベルゲームをプレーしたとき、数割の人はこう考えることでしょう(要出典)。

「ノベルゲームを作ってみたい!」

最近になって、私の心にも再びこのような気持ちが沸き上がりました。普通なら、じゃあ開発しようか……となるわけですが、ここで私はふと考えす。

今回の開発は趣味開発です。プライベートな時間を使って自分でタスクを考え、ツールやライブラリを選び、開発する。そこには制約はなく、やりたい放題の世界が待っているわけです。

であればやることは1つ……車輪の再発明ですね?
ノベルゲームエンジンを開発して、出来上がったエンジンでゲームを作る。
単純明快な話です。

「えー車輪の再発明ぃ?」と思われるかもしれませんが、あれはあれで良い面もあります。詳しくは プログラマが知るべき97のこと という書籍に記載されているエッセイ 車輪の再発明の効用を読んでください。

本記事では、そんな経緯で作り始めたノベルゲームエンジンで考えたことなどをつらつらと書いていきます。ちなみにノベルゲーム制作に関しては素人なので、実際のノベルゲーム製作者の考えとは異なる部分が多いかもしれません。

成果物?

https://github.com/cowlick/cowlick

開発開始から3か月くらい経ってますが、まだ完成にはほど遠いので成果とは……みたいな状態です。
この記事に間に合わせたかったのですが、セーブ・ロードが使い物にならなかったり、構文解析周りがまだザル実装なのであった……。

余談ですがcowlickはアホ毛のことです。リポジトリ名を考えていた時に、Fから始まるゲームの某騎士王アイコンが目に留まったので一旦この名前に決めました。もしかしたら名前を変えるかもしれないし、変えないかもしれません。

簡単な例

640x480の画面にいくつかの画像とテキスト(ルビもあるよ)を表示するスクリプト:

[image storage=classroom layer=background]
[image storage=tkotori layer=tkotori top=50 left=210]
[cm]
開発中の[ruby text="ル"]画[ruby text="ビ"]面

表示は以下のようになります(実際にはメッセージ枠を少しいじってます)。

cowlick-example.png

キャラクター画像はCC BY-SA 3.0で公開されている高階ことりちゃん(公式ページはこちら)を利用しています。
背景画像はこちらの教室背景をお借りしました。

ゲームエンジン選定

いくら何でもすべてを0から作り始めると無限に時間が必要になってしまうので、何かしらのエンジンをベースにすることで時間の節約を計ります……といっても、今回は始めからAkashic Engineをベースにすると決めていたので特筆すべきことはなかったりします。Akashic Engine自体の説明は……どなたかにお任せします。

開発言語

TypeScriptです。他ノベルゲームエンジンとの差別化という意味合いも強いですが、そもそも私は型がないと大ポカしてしまうタイプなので型が必要で、かつbindingを書く手間を惜しんだので選択肢がなかったというのもあります。また、Akashic Engine自体がTypeScriptで実装されていることも影響しています。

今後はもしかしたら、コアでない一部分にBuckleScriptかReasonを併用するかもしれません。これに関しては後述します。

構成

リポジトリを分けるには変更が多く、でもパッケージとしては分けておきたいと考えたのでLernaを使っています。ビルドツール等に関しては近年のJavaScript、TypeScript界隈のオーソドックスな構成からは外れていないはず。

パッケージ構成をかいつまんで説明すると、

  • cowlick-core
    • ランタイムでもCUIでも使う型やモデル、定数
  • cowlick-config
    • 設定用の型やデフォルト設定
  • cowlick-engine
    • 本体
    • Akashic Engineに密結合した部分
  • cowlick-analyzer
    • シナリオASTと意味解析器
    • 他の文法もサポートするための布石?
  • cowlick-kag-compiler
    • KAG3のサブセットを解析しJSとして出力する
    • analyzerに渡せない(変数名等の)KAG3独自の部分もここで置き換える

という構成になっています。

一応試作中のものをnpmにあげていますが、バグっている可能性が高いのであしからず。

開発順序

だいたいこんな感じです。

  1. 最初に最低限の画面表示を作る
    • これがないと確認しようがない
  2. スクリプトの基本構造を作る
    • シナリオをTypeScriptに直書きして確認
    • Viewに関係しない部分はテストを書いた
    • Viewはテスト環境を作るコストがペイできなさそうだったので自動化していない
  3. もりもりスクリプトタグを追加していく
  4. compiler部分の実装に取り掛かる
    • compiler実装時に足りないスクリプトタグは随時実装していく
  5. ある程度経った頃にmonorepoに移行
  6. サンプルシナリオをTypeScript直書きからKAG形式に書き直す

Viewのテスト自動化は未だ悩んでいます(スクリプトタグが多くなり確認が少しつらくなってきた)。もしかしたらそのうちcanvasのスクリーンショットと期待する画面構成画像を比較する形でテスト環境を構築するかもしれません。

エンジン部分

本体はAkashic Engineと密結合しています。他のエンジンと差し替えらえるようにするのはハードルが高く制約も多くなる割にメリットも少ないため、密結合させたうえでフルに機能を使ったほうがいいだろうという判断です。

ES2015で書いてES5で出力するようにtsconfigを設定していますが、そのうち出力もES2015に切り替えたいところです。

ノベルゲーム制作では独自のタグを定義して実行させたいことが多々あると思います。これをサポートするためにパッと思いつく方法としては以下の2通りです。

  • プラグイン方式を導入してタグを解釈できるようにする
  • ES2015のMapにタグと関数を登録する

今はまだプラグイン方式は過剰かなと考え、後者の方式を選択しました。engine組み込みタグの挙動も上書きできてしまいますが、そこはまぁ自己責任ということで。

セーブ・ロード

セーブ・ロードの実装は大変面倒だろうなと予想していたら案の定で、というかこの部分が完成していないので絶賛開発中の看板をおろせないのでした。

つい先日までは、型クラスもどきを用意して画面パーツをすべてシリアライズ/デシリアライズするという方法を考えていたのですが、どうがんばってもシリアライズできない部分があったので断念しました。

現在はユーザが辿ったルートを保存しておき、ロード時には保存しておいたルートから必要なスクリプトを導出して実行、画面を再構築しようと試みています。

コンパイル方式

コンパイル方式を選んだ理由は2つあります。

第1に、独自言語のインタプリタを実装したくなかったからです。昨今のJS界隈はASTを使った技術が普及しているのでそっちに乗っかったほうが楽だろうという。実際、ASTを解析してJSに落とし込む部分はたいした苦労なく実装できています。

第2に、KAG以外の文法をサポートしたいからです。吉里吉里2/KAG3をはじめとするKAG形式はいくつかのノベルゲームエンジンで採用されていますが、それぞれのエンジンで独自の拡張タグを用意したり、環境上の制約で一部タグをサポートしていなかったりします。より便利にするため(かつユーザに乗り換えてもらうため)には仕方がないこととはいえ、方言が多すぎるのもなぁと。
最短で動かせるようにするために既存の文法(のサブセット)を採用しましたが、ひと段落ついたら新しいDSLを模索したいなという気持ちがあり、文法を切り替えられるようにしたい。なら、engine部分とは文法解析部分は切り離されているべきであり、コンパイラとランタイムという構成に落ち着くのは自然なことでしょう。

選択を間違えたかなと考えている部分としては、cowlick-kag-compilerの実装にPEG.jsを使ったことです。なんというか、パーサジェネレータってこんなにつらかったっけ……と首をかしげながら実装しています(言語化できていない)。
BuckleScriptかReasonを使いたいと書いたのはこの部分で、ML系なら構文解析なんてお手の物だろう、みたいな。問題はデファクトのParser Combinatorを探せていない事ですね……誰か知ってたら教えてください。

今はまだ着手できていないこととして、プラグイン方式の導入が挙げられます。
プラグイン方式が導入できると、

  • シナリオデータに難読化を施す
    • チート対策というよりは意図しないネタバレ防止用
  • フローチャートを生成する
  • 各キャラクターの台詞集を生成する

これらが比較的簡単に行えるようになるのではないか、と考えています。

いつかの未来に実装したい何か

ティラノビルダーやVisual Novel Maker(ラノゲツクールMV)のようなGUIツールを作ってみたい気持ちがあります。
が、結構ハードルが高そうなのでそこまでたどり着けるかどうかは神のみぞ知る世界です。

あとは、ひと段落ついたら縦書き実装ですかね……canvasに縦書きとか大変そう。

おわりに

というわけでノベルゲームエンジンを自作(?)する話でした。cowlickについて気になった方はtwitterでリプを投げるかGitHubでissueをたてていただければと。

引き続き現時点の目標である”RPGアツマールへの投稿”にむけて開発を進めていきたいと思います。