赤宿 = Red Inn

素人の試行錯誤と2Dゲームプログラミング

Top

 桟橋が試行錯誤してゲームを作るブログ。素人の視点で書かれているに過ぎないため、記事の内容には要注意!

過去に作ったゲーム(ウディタ製)

  • 羊山ゴートの冒険  初めて完成したゲーム。ウディタではゲームの動作を直接記述できたため、ステート管理などの複雑なプログラミング技術がいらなかった。ウディタが無ければ今日の自分は無い、というのは僕だけではないはず!

  • ツギハギの行方  二作目。システムはあるが、ゲームとしては遊べない。ローグライクのシステムをRPGの演出に使う方向性を考えきれず、ADV(adventure)ということにした。

現在

Diary

 僕は、ゲームプログラミングにおいて、存分にコードを組みたい。(というエゴが強い)。それに、できれば特定環境の信者になりたい。

 ……俗物だな!!

 現在はエゴに引っ張られて制作中。

Godotの思想への関心

 ――聞こえますか。俗物です。今、(ry
 ――私はハッスルするため、たまに海外の記事を読みます。
 ――中二病の延長だと、思ってください。

 Godot Engine 開発者さんの記事が良かった。一点だけ引用する。

If you are writing a game, just write things as fast as you can and as simple as you can.

 今の僕と真反対の方向性だった。でも、この方向性は――良い!

 ぶっちゃけましょう。僕は、プログラミングで『俺TUEEEE』したい。自分が今、最高の作業をしていると納得したい。もはや、そのエゴのためにプログラミングをしていると言っていい。(動機としては)。

 スーパ・クリアかつフレキシブルなコードを構成しました。ーーこれは良い。
 だが、素早くゲームを作り上げました。ーーこれも格好いい!

 Godot Engine/GDScriptが、この透徹したスタイルで設計されている、というのであれば――これは、大変にCOOLなのではないか???

 初めて、使用目的でゲームエンジンをダウンロードした。

Godot入門を挫折

 ダウンロード完了。
 まさかの1ファイルだった。それが、それだけがGodot(本体)だ。
 ――ふっ。早速COOOOOOOLなところを見せつけてくれるな……! (テンション高い)

 Tiledのマップを容易く読み込み、シーン上にインスタンス化した。prefabに近い。
 シーン上にオブジェクトを足して、マップ上のキャラとし、スクリプトを関連付けた。確かに速い。

 カレント・ディレクトリとファイルの表示が分けられたGUI。これも良い。
 問題は――他のスクリプトの読み込み。

var Stage = preload( "res://engine/world/Stage.gd" ); # Stageクラスの読み込み
var Game = preload( "res://engine/framework/RlGame.gd" );

 これを、ファイルごとに、延々と、延々と、書いていく……。  これは、あまりにも……。
 あまりにも、分割房には険しい道のりだったのだ――。

 何かの間違いであってほしい。いいところは沢山あった。
 1ファイル = 1クラス とし、インデントを削る方向性など、最高にクールであった……。

私は今、幸せです

 Nezを使って悦に浸り、のうみそを溶かしています。現在、階段を実装中。

 『ゲーム開発のスタンダード』のようなものは、まだ見えてこない。
 実は、どの環境もガラパゴスなのかもしれない。
 ウディタも、Nezも、UEも。実は等しく、ガラパゴスーー?

Diary

Elementary OS の導入

 Macの代わりとしてWindowsラップトップにインストールした。気に入って使っている。 使っているソフトが同じ場合でも、インタフェイスの違いは大きかった。

導入が早い
  • インストールは容易い:   2GBのUSBさえあればよし
  • ビルトインのソフトが良い: メモ帳とはおさらばさ
  • フォントが綺麗: 良い
  • CUIとパッケージの効力: ターミナルを起動して、コマンドを打つだけで道具が揃う
構成と表示が美しい
  • アプリが少ない:   余計なものが無い
  • フォルダ分けが綺麗: e.g. ~dev/rs/rl がローグライク開発(Rust)フォルダ
  • フォルダ表示が便利: 複数階層が1タブに表示される
  • ウィンドウ毎に複数のタブ: in フォルダ、ターミナル、エディタ etc.
  • 配置/影 /動きが良い:  おかげでセンスが回復し、iPhoneの良さを認識できるようになった
不具合はままある
  • ファイル名:   日本語はほぼ使えない
  • クラッシュする: ToME4がほぼ遊べない
  • パッケージの不具合: 自分でコンパイルしないと動かないモノがある
足りない物も多い
  • C#は諦めた:   Xamarin Studio on Linuxは無い
  • 打ち込みも断念: Dominoが無い
  • ToME4がプレイできない: Linux上の方が軽いがしかし

その他

小説の執筆

 *黒歴史製造中* どこかに応募できれば素敵じゃない?

Rustに挑戦中

 Rustには、上記OSに近い良さを感じている。導入が容易く、美しい構成を持ち、美しく構成できる。自分のセンスを遥かに超えてくれていて、期待してしまう。
 VS Code上に組み込まれたターミナルでコンパイルを指示する(cargo build)。エラーメッセージが丁寧で、IDEが無くとも不便さを感じていない。所有権とのつきあい方とECS(in Rust)が分かれば、遊び方の幅が広がるだろう。

-> (追記) 所有権の扱いの問題でゲーム制作を挫折

Binding with Observable

 申し訳程度の進捗。
 Entityを削除するとそのリストが変更され、スケジューラ(イテレータ実装)がバグる。
 そこでイテレータ内でインデクスを保持し、イテレータをActorの所有権を持つオブジェクトにバインディングしたい。(連動させたい)。

 バインディングの流れは、

Engine::Core.entities -(f)-> Engine::Scheduler.actors

 fでバインディングする。 いかにfを呼ばれるようにするかが問題。
 イベントの観測で束縛した。

// ObservableList (presudo code)  
pub enum ObservableListEvent {  
    Inserted, Removed,  
}  
pub class ObservableList<T> : IList<T> {  
    // emits events with index (where the item was inserted/removed)  
    pub emitter: Emitter<ObservableListEvent, (T, int)>;
    ...  
pub class EntityList : ObservableList<T> {  

note of topics

解決済みのトピック(良い方法かは自信が無い)

Engine and UI

 Engine = Model, UI = View

Engineの時間をリアルタイムから分離する

 UIがEngineの時間を進めるという図式( Engne::Core.tick() )。

Actionの観察

 継承で実装させたかった。
 Processer(Engine::Core)に View::ActionA as Engine::ActionA を渡す方式にした。

シーケンシャルな挙動の実現

 Sequenceと名付けられたQueueに、Viewの振る舞いを載せていく。
 Engine側の時間とViewの時間と同期させる。(カーソル上のマスの情報表示など、ViewからEngineの今の状態を参照したいから)。時間が飛ぶときは次のルーチンをラムダ式でラップして、Engineが途中から再開できるようにする。

Controlling

Control

 ControlのツリーでGameModeを暗黙的に構成する。
 "制御が働く"ニュアンス。

未解決のトピック

Flexibility

Entity非依存

 EngineがSomeFramework.Entityに依存しないようにするには。traitが無いと無理か?

データ駆動なpreference

 各モジュールはデータ駆動の部分を外から与えられるようにして、ファクトリがpreferenceオブジェクトに依存する?

データ駆動とPDS

 art/appearanceをデータ駆動にした場合、M/Vの分離があやふやにならないか?

Engine/Modelの境界

 全ローグライクで共通のシステムをEngine、ゲーム固有のシステムをModelとするつもりだったが…
 どうもうまくいかない。

HUD

Log

 ログを表示する瞬間(UI)とログを書き込む(Engine)を同期するには。

etc.

Serializing

Archiving

Updating Online

Shortcuts

 ショートカットを把握すると、自由度が増して良い。マウスと併用しても良い。また、tabキーが使えると特に便利。

Basic

 xxx + shift: 動作の反転
 ctrl + c/x/v: copy/cut/paste
 ctrl + z/y: undo/redo
 F7: カタカナ変換

 ctrl + f: find
 ctrl + h: replace

 ctrl + o: open
 ctrl + n: new

 (menu) c: close

Text Editor

 ctrl + arrow_key: カーソル移動(単語区切り)
 alt + arrow_key: カーソル移動(半角/全角区切り)
 ctrl + backspace/delete: 文字消去(単語区切り)
 ctrl + backspace/delete: 文字消去(半角/全角区切り)

Firefox

 arrow_key/space: スクロール  r_click -> C: タブを閉じる

 ctrl + t: タブを開く
 ctrl + w: タブを閉じる
 ctrl + l: URL欄を編集
 ctrl + tab: タブを移動(shiftはr_shiftがおすすめ)
 ctrl + number_key: 指定タブに移動

Windows

 win + d: すべてのタスクを隠す(デスクトップを見せる)
 win + tab: タスクの切り替え
 r_click -> m: ファイル名を変更

Control

 いかにゲームを動かすかの第一歩。制御の要素を考えていくことで、ゲーム全体の振る舞いを作ってしまう。この世界観、つまりオブジェクト指向のありかたがゲーム制作に非常に有効だと思う。
 素は例によってBob氏のコードより。氏のものはViewとしての面が強かったが、この記事では制御の要素として注目する。

Classes

 Controlの構造は、スタック式のステートマシンと木構造の親子関係を掛け合わせたもの。

Cradle

 Controlの木構造のルートにあたるクラス。(命名は気分による)。

  • controls: 登録(格納)されたControlのリスト。
  • state: 子のスタック(LIFO)。一番上のものをactiveとみなし、更新する。
  • register<T>(T): self.controlsにT: Controlを格納(登録)する。
  • push<T>(): self.controlsの中で型がTのものをスタックに積む。
  • pop<T>(): スタックから一番上のControlを取り出す。
Control

 ゲームの制御要素。

  • children: 子のリスト(オプショナル)。
  • onEnter(), onExit(): ライフサイクル(?)関数。スタート的な面。
  • update(), handleInput(): 同上

 ライフサイクル関数と言っていいのか、Cradleから呼ばれる関数を持つ。親がactiveな子は、ライフサイクル? が伝わり、親によって更新され得る。

Controlによる世界観の具体例(ローグライク)

Controlの親子関係:

  • RoguelikeControl
    • EngineControl
    • PlayerControl
      • DiagonalModeControl
      • DirectionModeControl
    • SequenceControl

各Controlの役割(責務):

 RoguelikeControl: cradle.push(child) により適切な子をactiveにする。
  EngineControl: ゲームmodelの時間を進める。
  PlayerControl: プレイヤ操作の制御をする。
   DiagonalModeControl: コンポーネント。Onなら斜め以外の方向入力をオミットする。
   DirectionModeControl: コンポーネント。Onなら方向入力で移動せず、向きのみの変更にする。
  SequenceControl: エフェクトなど、シーケンシャルな振る舞いを再生する。

 こうして、実に自然にシンプルに、ゲームの振る舞い(制御)を組んでいける。

「ステート」を振り返って

 ゲームの状態を切り替えてゲームを制御するというアイデアはよく聞く。Controlもステートに近いが、考え方が決定的に違うと思う。Controlは、

  • 制御という"もの"を考える( -> 自身の動作を定義していく: 「制御が働く」ニュアンス)
  • 責務は担当範囲を制御すること(controlling) (次行の通り、担当範囲のサイズは自由)
  • 制御を要素の集まりとして捉えやすい (a control may consist of small ones)

 一方、ゲームのステートを考える場合、

  • ある状態を考える (他者の動きを定義していく)
  • 責務はその状態におけるゲーム全体の動作を指示すること (大きい)
  • 制御を動作の集まりとして捉えにくい (Game_State/Mode_Componentの一般的な呼び名が無く、コンポーネントに分ける発想が難しい)

 小さなもの、たとえばキャラの内部状態を切り替えることは理解できる。一方、大きなもの、たとえぼゲーム全体の状態(モード)を考えるのは難しい。責務の範囲が分かりにくい。
 ゲームの状態としてPlayerControlModeを考えると、その責務は、プレイヤ操作時のゲーム全体の振る舞いのように思えるだろう。初めからプレイヤ操作だけを責務にする命名(PlayerControl)がいいと思う。
 また、ゲームの状態を見つけるのは、制御の要素を考えるよりも難しい。制御は、動作するものの方から考え始めることができるが、ステートから考え始めた場合、ゲームの状況から考える必要がある。
 制御の存在を認めると、楽に自然にコードを組んでいけると思う。viva OOP!!

まとめ

 Controlの世界観では、制御の要素を考えていく。ステートオブジェクトは存在せず、アクティブなControlを切り替えることで、ゲームの振る舞いが変わる。Controlの世界観を導入することで、自然な発想でゲームを作ることができるかもしれない。