赤宿 = Red Inn

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

Top

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

過去に作ったゲーム

 ウディタローグライク:

  • 羊山ゴートの冒険 [2013/8]
     一作目。ウディタではゲームの流れを直接記述できたのが良かった。

  • ツギハギの行方 [2015/8]
     二作目。UIは良くなったが、ゲームと呼べない完成度。

現在

Next Stage!!

 ゲーム開発に久しく進展。いつか、ハマりどころをまとめた記事を作られたら楽しい。利用者がいるかは別として……

  • MonoGameの更新に成功
  • .Netの更新に成功 なぜか
  • 画面のちらつきを無くし、FPSも30 -> 60に
    • ラップトップのビデオカードのVSyncの設定が悪かった? そこでVSyncを切る(?):
      • GraphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
    • この時点で画面のちらつきがなくなり、144FPSに
    • Game.IsFixedTimeStep = true; // これで60 FPS
    • 端数まで正確に、たとえば50FPSに設定する方法:
      Game.TargetElapsedTime = System.TimeSpan.FromTicks( (long)10_000_000 / (long)50 );
  • MonoGame (Nez) のデフォルトのalpha値はpremultiplied alpha だった
    • つまり BlendState.AlphaBlend は premultiplied alpha を使用
    • アルファ値 ≠ 不透明度 不透明度としてアルファ値を作用させるには、
      あらかじめ color.RGB *= color.A としておく
    • c.f. Blending on XNA
    • Sprite.setAlpha() ではなく Sprite.setOpacity() を用意する
      • 不透明度を変更すると、正確な元のRGB値が失われる問題?

 これで、最も困っていた三点: カクつき、FPS、アルファ値の問題が解決できた。これらの問題には年単位で困っていたけれど、何が問題か分からないか、別の問題と誤認したのが遅れた最大の原因だった。

 これでゲーム開発を再開できる。また、嬉しいことに、ウディコンも近い。面白くなってきたか……!!

 作ったテキストエディタは、公開するにはまだまだ杜撰。作り価値があるが、Macでは他のソフトに霞む程度のものだと思う。
 キーバインディングを学ぶため、自分がEMacsVimを使用できればさらに面白い。文節単位の移動などが元のQtには無いため、そこは妥協するしかないかもしれない。

A Plain Text Editor in Progress

 PyQt5で横書きのテキストエディタを組んでいるところ。

 ゲ制に関しては、細かいところでハマったままで、さらにMonoGameと.Netのバージョンアップも失敗している。Hello Worldから始めねばならない。

 いやぁ。Mac買いたいなぁ。

Binding as an 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 it 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をmotionFuncのシーケンスと見る。
  2. EngineはsomeMotionFunc()ごとに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.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 {
  storage: [Control];
  stack: [Control]; // a stack of active controls.
  pub update() {
    self.stack.peek()?.update();
    ...
  pub get<T: Control>() -> T? {
    self.storage.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))
    Windows版は更新が終わったので、使うべきではない。

  • MonoGame
    低レベルのマルチプラットフォームフレームワークNezが対応しているMonoGameをインストールする。対応していないver.のMGを使うと、ビルドは成功するが、実行時にエラーが出る。エラーは間接的に伝えられる: 〇〇が無い、〇〇がnullだ、など。

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

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

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

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

VS (Visual Studio) の設定

 MGのテンプレートを引っ張ってくる。
 T4テンプレートの快適な利用?

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.

Hello World

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