赤宿 = Red Inn

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

Top

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

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

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

  • ツギハギの行方
     二作目。システムはあるが、ゲームとしては遊べない。つまり駄作ッッッッッ!!!!

現在

Binding with Observable

 申し訳程度の進捗。

 Entityを削除すると、スケジューラの持つ参照がnullになってバグる。そこでEntityが削除されたとき、スケジューラは、自身が持つ参照のリストを更新したい。つまり、Entityの所有権を持つコレクションに対し、スケジューラを連動させたい(バインディング)。

 ObservableList的なものを実装した(C#)。.Netにも似たものがあるようだけれど、ミニマムな自分仕様の物の方が使い勝手は良い。

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

Current Progress

 妥協案を出したトピックを上げていく。

Processing

ローグライクシステムを乗せるためのシステム。プログラミングでシーケンシャルな挙動を実現するのが難しい。現状のシステムは、まさに苦肉の策。

Engineのゲームループ (Engine = Model, UI = View)

 UIがEngineを持ち、Engne.tick()で時間を進めるという図式。行動の決定やアニメーションの再生などを終えると、UIは再びengine.tick()を呼ぶ。

Actionの観察

望ましい条件:
  • Actionの演出(View)は、Engineのコードを変更せずに差し替えられる。
  • ModelとViewは時間的に同期している。
    (=> リアルタイムでModelを表示したとき、Viewと状況が一致する)
  • ActionのViewの差し替えは簡単。
苦肉の妥協案:
  1. Actionを細切れのmotion()に分け、motionのchainとして表現する。
  2. Engineは1Motion起動ごとにUIに制御を返す。(次motionはラムダ式で保存しておく)
  3. ViewはMotionを再生して、再びengine.tick()を呼ぶ。(次motionが呼ばれる)
  4. View.Actions.XxxがEngine.Actions.Xxxを継承し、各motionをoverrideして演出を作る。
  5. Engineに渡されるActionは、View::ActionFactoryにより生成されたものにする。
  • 演出は差し替え可能
  • 時間的に同期
  • 手間は多い(Factory, chainのための制限など)
参考になりそうな記事:

Actionの演出

 ViewSequenceと名付けたQueueに、演出オブジェクトを作っては載せていく。後は再生するだけ。
 演出オブジェクトには、Tweenを使ってもいい。

Controlling

in OOP

 どうやってゲームを動かすか? オブジェクト指向で解決していく。つまり、オブジェクトを考えることで、結果的に時間的な挙動も実現させる。

 Controlという概念を持ち出す。"制御が働く"ニュアンス。Controlオブジェクトのツリーを作り、アクティブなControlが切り替わることによって、GameModeが切り替わる。コンポーネント指向のステートマシンと言えるかもしれない。ただし考えるのは、ステートではなく制御のコンポーネント。たとえば、RoguelikeModeを考えるよりも、EngineTickControl、PlayerControl、StairControlなどを考える。この方が簡単で、しかも適切だと思う。Mode, Stateと言うと、単位が大きすぎるし、分離の仕方が不適切になりやすかった。

 ViewのコンポーネントであるLogControl、AnimationControlなども加えてみた。この場合、ControlがUI世界に『参加』したような格好となる。

Cradle Framework

 UI.update()はこれの更新をするだけ。

pub class Cradle {
  ctrls: [Control];
  stack: [Control];
  pub update() {
    self.stack.peek().update();
    ...
  pub get<T: Control>() -> T? { self.ctrls.firstOrDefault{ it is T } }
  ...

HUD

LogView : Control

 Engine側で複数のログが一度に書き込まれたとき、一つ一つ順番に描画・演出していきたい。

 書き込みがあった場合、View側のキューにメッセージを保存する。ゲームループからのupdate()時、ログの演出中でなければスタックのトップをpop()し、描画を始める。このUpdatableなオブジェクトを消せると尚良いが……。

Flexibility

Entity非依存

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

Engine/Modelの境界

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

Size/Posで型を分けるか?

 今は両方Vec2自前クラスを使っている。

What Decoupling is About

Got Stuck

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

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

Woditor

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

f:id:samba_4e:20170408010606p:plain (カーソル位置/主人公方向に白枠を表示)

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

 インフラ整備的な下準備は完了した。仕様の決定で数回悩んだが、分離によって解決できた。分離により、特定の仕様と他のコードが疎結合になる(どのような仕様にも差し替えられるようになる)。このため仕様を決めるという問題が無くなり、各機能も役割がはっきりして把握しやすい。問題が消えるというのは、プログラミングで頻出のパタンだと思う。『分離』はその解決策の部分集合なのかもしれない。

 ウディタでゲームを作るときは、『オブジェクト』という概念が無かった。データしか無いのだから、当たり前だと思う。あくまで、『Actorを動かす』、『攻撃する』。神たる自分が。そこに、『オブジェクト』の存在を想定すると、『Actorが歩く』、『Actionを起動する』という風にプログラミングが変わっていく。するとコードに指向性が生まれ、いろいろと都合が良くなる――というのがボトムアップOOPなのだろうか。

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

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

Getting Started

To Hello World

 4万強円のWindows PCを買った。スピーカと画面が悪いが、その他のすべては悪くない。

ダウンロード

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

  • Xamarin Studio(MonoDevelop)
    Macでも動くIDE。MS Visual StudioなどでもOK。

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

  • Nez
    海外の個人が作った2Dゲーム用フレームワーク(Docs)。MonoGame/FNAのPCLであり、MonoGame/FNAが対応しているプラットフォームで動く。

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

Nez-Samples > Setupの指示に従う。

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

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.

Xamarinさん

Nez.Samplesがdebugできない?

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

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

Dev Tools

GUI-based Environment

  • Unity2D

Framework

The World

  • Scene

  • ECS/Actor

    • Entity

    • Component

    • System

Rendering

  • Renderer, renderLayer, layerDepth

  • Sprites

  • Transform

*Scene Graph

Utilities

  • Tweens/Actions

  • Schedulers

  • Coroutines

  • AIs (FSM, BT)