赤宿 = Red Inn

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

Top

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

過去に作ったゲーム

  ウディタローグライク

現在

Binding with Observable

 申し訳程度の進捗。

 Entityを削除すると、スケジューラの持つEntity(参照型)が無効になってバグる。そこで、Entityの所有権を持つコレクションに対し、スケジューラを連動させたい(バインディング)。

 ObservableList的なものを実装した(C#)。.Netにも似たものがあるようだけれど、ミニマムな自分仕様の物の方が使い勝手が良いため、今後も手製コードをためらわずに使う方針。

pub enum ObservableListEvent {  
    Inserted, Removed,  
}  
/// Emits ObservableListEvent  
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 = Model, UI = View

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

Actionの演出

達成すべき条件:
  • Actionの演出(View)は、Engineのコードを変更せずに差し替えられる。
  • ModelとViewは時間的に同期している。
    (=> リアルタイムでModelを表示したとき、Viewと状況が一致する)
苦肉の妥協案:
手続き
  1. Actionを細切れのmotionFunc()に分け、Actionをmotionのシーケンスと見る。
  2. Engineは1motion()ごとにUIに制御を返す。Actionは次motion()の呼び出しを予約できる。(ラムダ式を用いる)
  3. Viewはmotionの演出を行い、再びengine.tick()を呼ぶ。(予約された次motion()が呼ばれる)
演出方法

 ViewSequence(Queue)に、演出オブジェクトを載せていく。後は再生するだけ。
 演出オブジェクトは、overriddenMotionFunc()内で作る。(View.Actions.XxxがEngine.Actions.Xxxを継承)

Actions差し替えの策

 Actionの生成でActionFactoryを経由する。View::Actions::XxxがEngineの手に渡る。

成果
  • Actionの演出は差し替え可能(ActionFactoryを差し替える)
  • Model, Viewが時間的に同期する
  • 手間が多い (Factoryを書くこと, chainのための制限など)
参考になりそうな記事:

Controlling

in OOP

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

 Controlという概念を持ち出す。"制御が働く"ニュアンス。Controlオブジェクトのツリーを作り、アクティブなControlが切り替わることによって、結果的にGameModeが切り替わる。コンポーネント指向のステートマシンと言えるのかもしれない(?)。ただし考えるのは、《状態》ではなく《制御》のコンポーネント。たとえば、RoguelikeModeを考えるよりも、EngineTickControl、PlayerControl、StairControlなどを考える。この方が発想が簡単で、しかも分離が適切になりやすいと思う。

Cradle Framework

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

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

HUD

LogView : Control

 問: Engine側で複数のログが一度に書き込まれたとき、一つ一つ順番に描画・演出していきたい。
 案: Tweenではなくupdate()内で対応する。キューに残っていれば演出を始め、終わったらpop()。
 ?: ウディタユーザとしては、LogView.update()が常に呼ばれる違和感がある。

Flexibility

Entity非依存

 EC(S)を使って、Engineを含むコードを組んでいる。
 EngineがSomeFramework.Entityに依存しないようにするには。traitが無いと無理か?

Engine/Modelの境界

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

Size, Posで型を分けるか?

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

What Decoupling is About

Stuck

 (Nezフレームワークを使用中)。Tiledのマップのロードで長く行き詰っていた。
 今後も何度もハマるのだろう。

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
    低レベルのマルチプラットフォームフレームワークNezが対応しているMonoGameをインストールする。別ver.のMGを使ってコードを書くと、ビルドは成功するが、実行時にエラーが出る。エラーは間接的に伝えられる:〇〇が無い、〇〇が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.Portable内.dllへの参照を加える。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

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)