赤宿 = Red Inn

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

Top

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

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

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

  • ツギハギの行方  二作目。形にまとめて提出しただけ。ローグライクのシステムをRPGの演出に使う方向性を考えきれず、ADV(adventure)ということにした。

現在

Processing

 Action(行動オブジェクト)の演出をどう実装するか。必要条件にPDS(=Model/Viewの分離)があるものとする。
 HUDやログは、下の2つ目の方法を取ればObserverパタンで問題が無い見通しだ。継続して保留する。

廃案: イベントの観測 - Using An Event Queue

方法 - The Way

 ゲーム内のすべての出来事をイベントとし、イベントのキューをUIに返す。UIはイベントを可視化/演出する。
 e.g. ActionBegins, Hit, Attacked, Walked, etc.

問題 - Problems

1.ModelとViewが同期(?)していないこと。

 たとえば演出の最中にもマウス上のキャラの体力を表示する場合、UIから参照するModelの情報が未来の情報であってはならない。よって、ゲームModelは『一瞬』だけ進んでからUIに制御を返す必要がある。一気にゲームを進めてからイベントキューを返すなら、この条件を達成できない。

2.Eventのクラスを作る手間が大きい

 ちなみに:

UI側に返されたイベントを扱う関数を派生イベントごとに用意するならば、ダブルディスパッチの必要がある。僕は、visualize( someEvent as dynamic )という形で、ダックタイピングでダウンキャストしようとした。Visitorパタンで対応する方法もあるが、操作を足される元クラスがダブルディスパッチのことを『知る』必要があるため、いただけないと思う。一方Visitorパタンには、扱う派生型をこぼすことが無い、というささやかな利点がある。dynamicでダブルディスパッチをする場合は、ダックタイピングでダブルディスパッチ元の関数を呼び出してしまう可能性があるため注意。

検討中: ActionをMotionsに分けて、各Motionを繋げる

方法 - The Way

 ゲーム内の出来事、すなわちActionを瞬間単位のコードに分割しておき、viewで派生して演出を足し、modelのゲームループに渡す。その方法をより詳しく説明すると…

1.ActionをMotionsに分解する - Every Action Consists of Motions

 先の <問題1.ModelとViewの分離> の通り、ゲームModelは『一瞬』だけ進んでから止まる必要がある。そこで、まずActionを挙動(Motion)のサブルーチンに分けてみる。
 e.g. Throw, FallToFloor, HitActor, etc.

2.Motionを繋ぐ - Chain of Motions

 攻撃が命中したかどうかなどで、続くMotionは動的に決まる。Motionは続く次のMotionを何らかの方法で指定する。次回のgame.tick()時にはそのMotionが実行される。
 記法に不満はあるが、ラムダ式に頼れば動かすことはできる。

// chain next motion
return base.chain( () => someMotionMethod( andTheArgs ) );

ローグライクのゲームループだけはIEunmerableなため、ゲームループ側は実に簡単なコードのままで済む。

3.Motionの演出 - Creating Effects of Motions

 Viewから各Actionを派生する。overrideしたMotionメソッドから演出用のオブジェクトを作り、実行し、再びゲームModelの時間を進める(model::Game.tick())。
 ActionはViewから渡されたActionFactoryを通して生成する。Model側のゲームループが、View側のActionを渡されて処理をすることになる。シンプルな仕組みだろう。

問題 - Problems

手間 - The Requirements

 ActionFactoryからActionを作ることと、ActionFactoryの実装。その手間を妥協できるか。

仕様 - The Complexity

 Motionをbase.chain()するというViewのための仕様がActionに加わってしまう。できることなら、Motionsのつなぎ方はMotionから分離してしまいたい。たぶん無理だろう。
 また戻り値をゲームループが利用している場合、view側ではいったんbase.thisMotionFunction()の戻り値を保存する必要がある。

その他 - What’s Else?

 これから試していくところ。5月はターン制ゲームの実装を予定していたが、このアイディアを練るのに時間がかかり過ぎた。
 妥協した実装をする過程で考えたことが、これらのアイディアに繋がった覚えがある。今度の案はうまい仕組みとなるだろうか。

後日談 - Action Sandbox Extention

 model::actions::XxxをViewから継承するとき、多重継承できないことに気付いた。oh nooohhhhhh
 model::actions::Baseは、サンドボックスパタン的に派生クラスに機能提供を行っている。view用のサンドボックスはいかに用意するか。

砂場の機能をoverrideするには - Overriding Sandbox Functions

 たとえばlog(string)はmodel::Log.write()に委譲して、viewからLogを派生してmodelへ渡すことでログの書き込みに演出を足す。
 この例ならobserverパタンでいいとは思う。一応この手もあるということで。

砂場に機能を足すなら - Adding Sandbox Methods

 拡張メソッドでmodel::actions::Baseへのviewからの機能追加ができる。グローバルなオブジェクトから目当てのオブジェクトを辿れるならば、拡張メソッドでも機能的には遜色が無い。綺麗なやり方とは言えたものではないが…

Late April

Planning

 今年一年は下積みだと捉える。来年から数を打つべし。ゲームを売り続けて虚無を感じるのが目的(?)。

  • 4月 -> Nezの使用開始
  • 5月 -> ターン制ゲームの完成
  • 6月 -> アイテム付きゲームの完成
  • 7月 -> HUD/オプション/イベント機能付きのゲームの完成
  • 8月 -> 短編ローグライクの制作・公開(PC)
  • 9月 -> 短編のブラッシュアップ
  • 10月 -> パズルの短編(iPhone)の制作
  • 11月 -> iPhone向けの調整、オンラインアップデートなど
  • 12月 -> プラットフォーマの短編(PC)の制作・公開
  • 1月 -> 新ローグライクの骨組み
  • 2月 -> 新ローグライクの完成
  • 3月 -> 神に出会う KotlinでSeven Day Roguelikeの制作
  • 4月 -> ゲームの開発記録の公開(できれば本に)

 妄想だらけだ。自分に期待したい。

Progress

 ターン制のゲームループを走らせる前に、タイルマップとキャラの表示をした。

とんでもないミスを平気でする自分がいる

 計算順の問題で、間違った答えを返すことが多かった。デバッグは始めるために15秒ほど待たなくてはならない。ユニットテストについて学ぶべきらしい。
 発行するイベントが不適切なもののため観察できないこともあった。

Debug Console/Inspecter

 リフレクションを利用したNezの機能。たとえばinspect playerと打てば、playerという名前のエンティティのコンポーネント、そのフィールドを確認できる。非常に有効。
 リフレクションを利用すると、ある種のクラスや関数、オブジェクトを集めたり、名前から関数を呼び出すことなどができる。何でもありだ。やはりC#ならまず間違いないだろう。この言語はそろそろ嫌になってきたけれど…

Drawing TiledMap in ECS Framework

 TiledMapComponentがTiledMapへの参照を保持して描画する(アダプタパタンに近い?)。IUpdatableなコンポーネントはNez.Coreにより更新される。Nez.ComponentとしてTiledMapをラップすることで、フレームワークのゲームループに「参加」するような形になる。

キャラチップのアニメーション

 Sprite<AnimationEnum>のplay( AnimationEnum.Pattern )関数で描画する。たとえば『東方向のアニメーションを開始』とすれば、それだけでその通りになる。あらかじめ関数でセットしたデータ(アニメーションのコマのリスト)の通りに動いてくれるから非常に楽だった。
 規格の異なる画像ファイルを扱えるようにしたい。ファイルごとに情報を付ける必要があるが、どうやるか。

ActorView & Observer Pattern

 キャラチップの表示のため、(Grid)Bodyを観察する。観測可能にするため、仮にNez.Systems.Emitterを使ってみた。今後はBodyではなくAction(出来事)の方を観測するように変更する予定。Viewは、歩いた、ワープした、吹き飛ばされた、などの場所移動の文脈を知る必要がある。

Emitter<TArgs, THandler>
+ add( EventHandler )
+ emit( EventType )

body.cs

public class Body {
  public class EventType { ... }
  public class EventArgs { ... }
  public Emitter<EventType, EventArgs> emitter;
  void moveTo( Vec2 pos ) {
    var args = new EventArgs() { body = this, dirBefore = this.dir, posBefore = this.pos }
    (mutate the body itself...)
    emitter.emit( EventType.Move, args );
    ...

Next: Controling

 プレイヤやゲームのコントロール、タイトル画面の作成。その次はゲームエンジンの完成を目指す。

What Decoupling is About

Stuck

 Tiledのマップのロードで長く行き詰っていた(Nezフレームワークを使用中)。

Content.Load<TiledMap>の行で、実行時にMonoGameのContentLoadExceptionが出た。Content/binとContent/objを削除し、再びcontent.mgcbをビルドするとエラーは出なかった。理由は分かっていない。

Woditor

 ハマっていたため、ウディタローグライクを作っていた。やれば進むというのはいい。

f:id:samba_4e:20170408010606p:plain (カーソル位置にも白枠を表示)

 だが常に進むことを要求されるとうんざりすることもある。うんざりするとゲームをやりたくなるが、やりたい3Dのゲームは今プレイできない。遊びたいゲームを自分で作るしかないのだ…。

 インフラ整備的な下準備は完了した。ダンジョン生成は過去に作ったものをそのまま使っている。また簡単なプレイヤのコントローラを作った。いい開発作業場を作れたと思う。(“土台を作った"とは思わない。土台は変えられないものだから、そもそも作業場だけがある方がいい気がする)。

 オブジェクト指向を多少身に着けてからの再挑戦、まさに強くてニューゲームとなる。ウディタのコードが、前よりもはるかに分かりやすくなった。仕様の決定で数回悩んだが、分離によって解決できた。分離により、仕様のコードとつながる場所が仕様と疎結合になる(どのような仕様にも差し替えられるようになる)。これで仕様を決めるという問題が無くなり、各機能も役割がはっきりして把握しやすい。考えていたのとは別の、新たな可能性まで生まれてきた。この経験を経て、僕はプログラミングで確かなものを得たと思った。発想によって見事に問題が消えることがあるが、その1パタンとして、適切な分割を考えられるようになったのだろう。今後はさらに、レベルの高い発想を自分で生み出してプログラミングしていくようになりたい。僕にとってプログラミングで重要なのはここで、手続きではなくてプログラムの構造の方に関心がある。

(やるなら)今後の実装:Action(攻撃など), AI, Item, Trap, Inventory, HUD, Option, and the Game Contents

 余裕があれば初心に戻ってクソゲーを作り、ウディコン(2017年7月)に提出したい。ただ、もしもウディタユーザとしての自分の役割があるとしたら、ウディタを止めてプログラミングの海へ出ていくことの方だろうと思う。

Getting Started

Hello World

 4万強のPCを買った(Windows)。画面が悪いが、その他のすべてはとても良い。スピーカはあまり使わないためこだわらない。

ダウンロード

  • Visual Studio PCL関係のソフトやそれっぽいソフトと一緒にインストールしておく(30GBほどになった)。なければPCL関係のプロジェクトを読み込めず、エラーが出る。VSをアンインストールするとPCL関係のツールも消えるため注意。VSとは分けてインストールすることもできるらしい。

  • Xamarin Studio(MonoDevelop) Mac/Linuxでも動くIDE。僕はこちらを使うけれど、使うのはMS Visual Studioでも良い。

  • MonoGame 低レベルのマルチプラットフォームフレームワークver.3.5をダウンロードする。今現在のNezはver.3.6に対応していない(MonoGameの一部APIが変わったため)。ver.3.6のMonoGameをインストールしていたり、間違ってPCLのMonoGameを参照していると、ビルドは成功するが、実行時にエラーが出る。〇〇が無い、〇〇がnullだ、など間接的なエラーとなる。

  • Nez 海外の人が個人で作ったフレームワーク(ドキュメントはココ)。MonoGame(or FNA)のPCLであり、MonoGame/FNAが対応しているプラットフォームで動く。僕はこれを使う。

Nez-Samplesをダウンロードする場合

 Nez-SamplesのSetupの指示に従う。もしもgit cloneコマンドを使うなら、Git Bushなどをインストールして使用する(Windowsの場合)。手動でNezをコピーしてプロジェクトに加えるのでも良い。

デバッグできない?

 Run without Debuggingなら動くが、デバッグで実行するとエラーが出る。調査中。

自分のプロジェクトを作る場合

MonoGameの設定

 Xamarinを起動後、ホームでツール > アドイン を選択して、MonoGameのアドインを検索して加える。これでMonoGameのプロジェクトテンプレートが手に入る。新規プロジェクトでは、MonoGame Cross Platform Project(=OpenGL版)を選ぶ。(Windows版、すなわちDirectX版にはNezが対応していないため注意。実行時にエラーが出る)。そのまま実行して青い画面のウィンドウが出れば、MonoGameの設定は成功している。

Nezの設定

 Nezをコピーして、自分のソリューションに参照を加える(Nez.Portable)。Nez.PipelineToolは実際のアプリでは使用しないため、他プロジェクトからは参照しない。

Pipeline ToolのSetup(必要ならば)

 Docsより

open the Pipeline Tool by double-clicking your Content.mgcb file and add references to PipelineImporter.dll, Ionic.ZLib.dll, Newtonsoft.Json.dll and Nez.dll.

 これはNez - Getting Started - YouTubeの16:10からで解説されている。僕は設定の仕方が分からなかったため見た。動画は拡大すればくっきりと見れる。

Be sure to set the Build Action to Content and enable the “Copy to output directory” property so they get copied into your compiled game.

T4 Templteの利用

 add > new file > T4 Template で追加したファイルに、NezのT4テンプレート(.tt)の内容をコピー。右クリックの Tools > Process T4 Templateでファイルを生成してくれる。

右クリックの add files からNezのt4テンプレートファイルを加えても動作しない。このとき、ファイル名の左にプラスマークが表示されている。T4テンプレートのファイルとしてプロジェクトに認識されていないようだが、どうすればいいのか…。

 T4テンプレートで使われるデフォルトのパスはcsprojファイルと同じ階層から始まっている。windowsならば"../“で一階層前に戻ることができるため、パスを書き換えてフォルダ分けできる。 未調査: T4の更新の自動化?

Hello World

 Nez-Samplesを参考にしてゲームを作る。さあ始まりだ!

2D Game Dev Environment

Code-based Environment

TypeScript, Python, etc.

Dev Tools

GUI-based Environment

Frameworks

The World

  • Scene

  • Entity - Component - System

    • Entity

    • Component

    • System

Rendering

  • Sprites

  • Scene Graphs

Utilities

  • Timers

  • Coroutines