Skip to content
This repository has been archived by the owner on Jul 31, 2020. It is now read-only.

ゲームプログラム部メモ

SAT edited this page May 6, 2020 · 1 revision

コーディング

  • コンストラクターについて
    引数なし既定のコンストラクターを自分で定義した場合、メンバー変数の初期化が行われなくなるため、すべて自分で設定する必要がある。
  • ログについて
    メッセージの文体や書式を統一する。
    • ですます調にする、句点は付ける
    • 数値は文中に[...]で挿入
      例:存在しないイベントID [1] が参照されました。
    • 文字列は改行して > から始める
      例:指定されたファイルが見つかりませんでした。
        >abc.txt

システム

  • シーン管理について
    • C++側で管理するシーンはタイトルとマップの2つのみ。
      これらのシーンに移行するとき、シーンIDが自動でセットされる。
      BGM/BGSはシーンごとに管理し、遷移するときに再生が終了する。
    • サブシーンはメインシーンを維持しながら移行する方法と、メインシーンを差し替えて移行する方法の2通りがある。
      差し替えた場合はメインシーンの描画と入力が両方とも停止する。
      あくまでもサブシーンなので、メインシーンのオブジェクトが破棄されることはない。
  • 疑似マルチスレッド
    • 共通
      一意の識別番号を持つ。
      任意のスレッド名を付けられる。これは重複が許される。
      スレッド名を指定するメソッドで、該当するスレッドがすべて操作できるようになる。
      個別変数は文字列を識別子として、型ごとに管理する。
      スレッドの追加と個別変数の追加は再帰的な戻り値を使って連続的な記述ができる。
    • C++用
      関数ポインタを使うため、グローバル関数を登録できる。
      スレッド関数の引数にはスレッドのオブジェクトが渡される。
      個別変数は識別子を指定して、参照で取得できる。
    • スクリプト用
      スクリプト上の関数名で登録するため、既にスクリプト上で読み込まれていることが前提となる。
      スレッド関数が存在しない場合はスレッド実行時に削除される。
      スレッド関数の引数にはスレッドの識別番号が渡される。
      個別変数は識別番号から取得したスレッドオブジェクト経由で、Get/Setメソッドを通して操作する。
  • ファイル操作について
    デバッグ版は読み取り専用で開く必要があるため、標準IOを使う。
    リリース版はDXライブラリアーカイブの中から開く必要があるため、DXライブラリ関数を使う。
  • 起動オプションについて
    • スクリプトテスター
      対象スクリプトファイル名は必ず指定する。
      任意パラメーターとして、テスト起動コードをセットできる。
      このコードは関数の呼び出しなどのコードを書く。
    • スクリプトコンパイラー
      出力ファイルには、コンパイルしたファイル名が何度も記述される。
      そのため、対象スクリプトファイル名は絶対パスではなく相対パスで与えること。
  • システムのキー入力割り込みに関する制約
    F10/AltはWindowsによる更なる割り込みが入るため、入力には使えない。
    F12はVSのデバッグ起動中だとブレークしてしまうため、デバッグなしで実行しているときのみ入力に使える。
  • ウィンドウモードとフルスクリーンモードで随時切り替えができるが、切り替えが起こるたびにメモリ消費量が増加する。
  • フルスクリーンモードでは消費メモリが 50MB~80MB 程度上昇する。
  • エディターとのリアルタイム通信は、以下のような事情により行わないことにした。
    ゲーム中にエディターに戻ってしまうとゲーム画面が隠れてしまうため、確認や操作がしにくくなる。
    エディターに戻っている間もゲームは動作し続けるため、コマンド指示も出しにくい。
    エディターとの情報のやり取りにメモリ操作のリスクがある。ただでさえスクリプト連携でスタック操作が頻繁に行われているので。
    エディターでの画面更新が思った以上に労力の要る作業であった。
  • ソース不明のメモリリークについて
    • 固定的に発生している [8 bytes * N] のリークに関しては無視する。
    • MapDataについてはデストラクタが呼ばれないので自分で呼ぶ必要がある。
    • vector/stringをメンバー変数として持つときは、自分でswapしてゼロクリアする必要がある。
      これが一番のリーク原因になっているらしい。
      ローカル変数のときはきちんと破棄されている。
      clearしても容量は減らず、swapで空オブジェクトにしないとゼロクリアできないことが分かった。
    • スクリプトから呼び出されたC++関数内での文字列操作がリークの原因になっている。
      ex. マップ名、歩行グラフィック名、マップのGUID
    • 外部のライブラリをふんだんに使っている都合上、自分だけでは防ぎきれないメモリリークはどうしても出てくるものらしい。
    • malloc関数は自分でデバッグ用のアロケーション関数をラップしており、ソースの場所を表示しないようにしていた。
      つまり特定不可能だし、自分の責任ではない範疇でのリークにならざるをえない。
    • デストラクタ = default; は全く効果がないのでやめた。
    • std::exitするか、普通にmain関数を抜けるかの違いだけでソース不明のリーク数が圧倒的に変わってくる。
      普通にmain関数を抜けたときはリーク数ゼロにできる。
      しかし、今回の件ではWinMain関数を抜けるように直してもリーク数は変わらなかったので、原因は別なところにあるらしい。
  • デバッグコンソールについて
    エディターとのリアルタイム通信は一切行わなずに、ゲームプログラム内で完結させる。
    コンソールは常時オープン状態とし、スクリプトデバッグを開始するキーで操作可能な状態になる。
    操作可能な状態のときに、デバッグコマンドを入力できる。
    標準コマンドはC++側で用意するが、拡張できるようにスクリプトにも委託する。
    ヘルプコマンド [h] はスクリプトにも委託される。
    操作可能でない状態のときは、イミディエイトウィンドウと同様にログ表示を行う。

データベース

  • 列情報に関する仕様
    単体データベースは列名と型名の両方を保管し、セルにはインデックスか列名でアクセスする。
    セルには型名を保管し、値のセット時に型名で代入先を判別する。
    複数値型のセルが空欄のときは、0が一つだけセットされた状態となる。
  • セーブデータへの書き出しについて
    列名や型名は含まず、行データだけを書き出す。
  • 存在しないデータベースがある場合は起動できない。
    エディターでは、データベースが空であっても必ずファイルだけは生成するようにすること。
  • 単一データベースの取得に失敗した場合、それ以上の処理を続行できないためプログラムを終了する措置を取る。
  • セル値の取得に失敗した場合、空白に相当する値を返す。
  • アクター情報について
    パーティキャラDB・エネミーDBの情報を統合する仕組み。
    クラスはパラメーター変動率の設定があるため [アドオン:パラメーター増減] が無効になる
    装備品は消耗品や貴重品が装備されることがないように、小種別が設定されていないアイテムは装備できない。
    属性はユーザーによって後から自由に追加や削除することができない。
  • ユーザーデータベースについて
    左端の列はFixedIDではなく普通の列となるが、IDをキーとして処理する関数群では左端の列をID列とみなすため、単一値のセルであれば通常通りIDとして識別できる。

C++側から見たスクリプト

  • 引数に列挙体型を含めることはできない。
    SquirrelVMが列挙体をクラスとして認識しており、関数呼出時になぜかインスタンスを作ろうとする仕様のため。
  • スクリプトコード内で、区切り記号を「|」にすると、半角全角が混合した文字列で不具合が生じるので使わないこと。
  • スクリプト上でグローバル変数として扱われる共通変数は、参照型でのバインドを行わない。
    C++上ではメンバー変数であり、それをスクリプトでグローバル変数として参照バインドすることができないため。
    セーブ&ロード時のみ、相互に値をやり取りするようにしている。
  • メンバーインスタンス変数へ参照するとコピーが生成されてしまう。
    常に同一のインスタンスに対してアクセスしたい場合は Get~~Object() などとして、そのオブジェクトのポインタを返す関数を経由させる必要がある。
    スクリプト上で変数のように扱いたい場合は、プロパティとして Getter/Setter をバインドすると良い。
  • 静的クラス変数は単なる定数として扱われるため、実用的ではない。
  • 静的クラス関数はインスタンス関数と同様の参照方法でアクセスできる。
  • 組み込み型のポインタを引数や戻り値 [int*, string*, bool*] で使うとクラスインスタンスとして扱われてエラーになる。
    この問題は、プロパティを使うことで解決できる。
  • テンプレート型を使っている [vector, map] などは扱えない。
  • 抽象クラスをバインドするには、テンプレート第2引数にアロケータを指定する必要がある。
    ここに Sqrat::NoConstructor を指定すれば、コンパイル時点でインスタンスが生成されずに済む。
  • 派生クラスは Sqrat::DerivedClass でバインドできるが、派生クラスに対して毎回親クラスのメソッドや変数をバインドする場合は通常通りのバインド方法でも問題ない。
    派生クラスでしかも抽象クラスの場合は、テンプレート第3引数にアロケータを指定すれば問題ない。
  • プロパティとしてスクリプトに公開しているメンバー変数について、クラス型であるものはSetterを作らずGetterから得られるポインタを使って操作する。
    Setterで対応しないクラス型を与えられると変換できずにエラー落ちするため。
    Getterから得られるオブジェクトを操作するだけで反映できるという直感性もあるため。

SQスクリプト

  • コンパイルエラーが発生した場合、修正されるまで実行せず待機する。
  • ランタイムエラーが発生した場合、終了を余儀なくさせる。
  • C++側が公開する関数は厳密な引数チェック(個数、型)が行われる。
  • ファイル名の頭に ! が付けられたファイルは、手動の動的リロードを行わない。
  • ステップ実行時に出力される呼び出し階層などの情報はログに含まれない。
  • イベントハンドラーとして定義された関数との名称衝突に注意する。

サウンド

  • 音量、音程、パンのパラメーターについて
    MIDIと一般サウンドで使用しているライブラリが違う。
    パラメーターはすべて異なる概念になっているが、実装メソッドでは両者を統合した共通のパラメーターで指定する。
    音量: 0~255
    音程: -240~+240
    パン: -255~+255
  • MIDIはパンに対応しない。
    GuruGuruSMFがサポートしていないため。
  • 一般サウンドの音程について
    サウンド素材クラスを間接的に利用するCSoundDataクラスのLoadメソッドでのみ設定できる。
    DXライブラリの仕様のため、再生時に音程を変更することはできない。

マップ

  • マップのループ設定についての仕様
    実装はスクリプトで行うため、頑張れば解消できる問題かもしれない。
    両端同士がくっつくとき、必ず同一タイルにする必要がある。
    そうしない場合はオートタイルが適用されない状態となる。
    主人公を追いかける日常移動: ループしないマップとみなした状態で追いかけてくるので近くても反対側に向かう。
  • マップタイルのアニメーションについて
    アニメーションのパターン数が異なっていても、すべて一律のフレーム数で一周する。
  • 影は常に下半身だけにかかり、上半身にはかからない。
  • イベントの起動条件について
    イベントから押されたら起動するイベントに、主人公側から接触した場合も起動する。
    逆にイベントに押したら起動する設定のとき、イベント側から接触しても起動しない。
  • イベントの日常移動をスクリプト指定にする場合について
    障害物により進行できない場合は、その指示内容を無視して常に次の指示へ進む設定を既定とする。
  • イベントの方向移動について
    障害物により進行できない場合、経路固定の設定があってもそれを無視して次の指示へ進む。
  • イベントの接触範囲について
    0x0 のとき、イベントの座標部分だけが接触点となる。
    この接触範囲は、あくまでもイベント起動に関わる接触範囲であり、移動に関わる当たり判定には関与しない。
  • 当たり判定について
    上のレイヤーから判定を行い、透明タイルの場合は次のレイヤーを判定する。
    最も上にあるタイルの当たり判定・通行設定が優先される。
    下位層依存のタイルに対して下位層がない場合は通行できるものとする。
    オートタイルは同一タイル内で自由に出入可能とし、異なるタイル間での移動時のみ出入設定を使って判定する。
  • MapData.Player.CharData.MoveCounterを (0, 0) から変更すると、自動で移動速度に合わせて (0, 0) に戻ろうとする