エンジニアもどきのよしなしごと

仕事や趣味の備忘録

アーケードゲーム開発におけるC++ - EDGE セッション個人的まとめ -

前回記事「Event for Diverse Game Engineersに参加してきたので個人的メモ。 - エンジニアもどきのよしなしごと

の中で、触れていた

3、「元コンシューマ系PGがアケゲ開発やってみた ~アケゲ開発でのC++~」

登壇者:shwさん。

について。個人的にまとめました。

※撮影OKのスライドに関連していることのみ抜粋しています。

 

 

4年ほど前のコンシューマ開発時の開発環境について

 開発プラットフォーム:PS3, XBOX360, Wii, Vita, 3DS など。

 コンパイラGCC、MSVC

 どちらも、C++ベースでビルドが通るように。

   ・C99コードやMSVC独自拡張等の方言禁止。

   ・アプリ側では極力ハード依存コードを書かない。

 他ライブラリ:STLやBoost等、他ライブラリの使用制限

 

 マルチプラットフォームということで、共有の財産を使用していこう。という方針になり、なるだけ、プラットフォーム固有のライブラリ等は使用しない形で開発を行うようになった。

 結果、レガシーなコーディングを強いられてしまう。

 

アーケードゲームの開発環境
 ほとんどPCゲーム開発と変わらず、タイトルごと固有の筐体の中に、PCが入っているイメージ。

 アーケードゲームの基盤スペックの一例。
  ・OS: Windows Embedded 8
  ・CPU: Intel Core i3-3220 3.30GHz
  ・GPU: NVIDIA Geforce GTX650 Ti GDDR5 VRAM1G
  ・Sound: High Definition Audio
  ・I/O:
    アナログ・デジタル出力DVI-I端子
    デジタル出力DVI-D端子
    オンボードLAN
     10BASE-T
     100BASE-T
     1000BASE-T
    業務用ゲーム入出力 JVS I/Oコネクタ
    シリアル4ch
    USB3.0*4ポート
    CAN*2ポート
  ・Storage: SATA SSD 64GB + HDD500GB
  ・Other ALL.Netサービス標準対応

 

最近のアーケードゲーム開発
 PCゲーム開発とほぼ同じなので、コンパイラもある程度自由に選択でき、また開発中の変更も可能。
 Boost, Zlibなどの、オープンソースや外部ライブラリ、ミドルウェアも柔軟に取り入れながら開発可能。
 ハードウェアも好きに設計できる。
 タッチパネル対応モニタ、ペン&レバー入力デバイスなどをタイトルにあわせて設計。

 

各種比較
 アーケードゲーム
  ・使用言語/開発環境
   ・C++C# / Unity UnrealEngineなど
  ・ハードウェア/OS
   ・PCベースのターゲット、主にWIndows
  ・顧客
   ・店舗(オペレーター)
   ・店舗に来た人(ユーザー)
  ・デバイスなど
   ・タイトルごと。
    モニタ複数枚、入力デバイスの装着可
    汎用機も有り。
  ・その他
   ・風営法等、各種法律
   ・地域ごとの出荷基準

 コンシューマ
  ・使用言語/開発環境
   ・C++C# / Unity UnrealEngineなど
  ・ハードウェア/OS
   ・各プラットフォーマーごとのターゲット、各プラットフォーマーごとのOS
  ・顧客
   ・ターゲット保有者
  ・デバイスなど
   ・ターゲット準拠機器(+周辺機器)
  ・その他
   ・各プラットフォーマーごとの作成基準
   ・CEROレーティング

 モバイル
  ・使用言語/開発環境
   ・C++C# / Unity など
  ・ハードウェア/OS
   ・各プラットフォーマーごとの端末、Android, iOS, Windowsなど
  ・顧客
   ・端末保有者
  ・デバイスなど
   ・端末ごと
  ・その他
   ・各プラットフォーマーごとの作成基準

 

使用してみたC++便利機能

 ラムダ式
  ・STLアルゴリズムと相性が良く多用。

  ・今まではコールバック関数定義箇所と使用箇所が離れていたのが一箇所にまとまってスッキリした。
  ・boost:bindだらけが少しましに。
  ・std::functionを使用して、式を関数から返すこともできる。
  ・ただ式が大きいと結局見づらくなるので程々に。

 

 型推論 auto & decltype
  ・型をコンパイラ任せに出来て記述がスッキリ。
  ・無駄なtypedefが駆逐出来てマジ便利
  ・ただしauto&&には注意が必要。
   ・書き方によってはReference Collapsingが働いたり働かなかったり。
   ・これを知らなくて少々はまった記憶が…

 

 range-based for
  ・繰り返し用変数を定義しなくて良い分シンプル
  ・autoと組み合わせればメチャ便利

 

 std::tuple
  ・複数の型をまとめて扱うのに便利。無駄な構造体定義が無くなった。
  ・std::tuple使えばstd::pairは不要??
  ・但しtupleの構造が変わったとき、参照先のstd::get, std::tie周りを合わせて変えるのが結構大変
 
 std::array
  ・イテレータを使えるし、範囲外アクセスチェックも楽。
  ・何気に全要素比較が楽。
 

 Scoped Enumeration

  ・Enumの形名で修飾しないと参照出来ないので、他との名前衝突の危険性が減った。
  ・前方宣言も可能。
  ・Enum値を配列の添え字に用いたい場合、毎回キャストするのは面倒なので、以下のようなテンプレートを用意するとちょっと幸せに。
  使用例:Scoped Enumを添え字として用いる配列ラッパークラス
  template< class T, class TEnum, std::size_t Size = static_cast<std::size_t>(TEnum::Max)>
  struct EnumindexedArray(
   typedef T ValueType;
   typedef TEnum EnumType;
   typedef typename std::underlying_type< TEnum >::type IndexType;
   std::array<T, Size> values;
   T& operator(TEnum e ){ return values[static_cast<IndexType>(e)];}
   const
   T& operator
(TEnum e )const{ return values[static_cast<IndexType>(e)];}
   T& operator(IndexType index){ return values[index];}
   const
   T& operator
(IndexType index)const{ return values[index];}
  );
 
 std::atomic
  ・ちょっとしたメンバーをアトミック操作・スレッド間通信したい場合に便利

 

 std::future & std::promise
  ・「ある処理の完了を待ち、その結果を取得」といった非同期処理するのに便利。

  ・バグの原因になりがち。(後述 その3、メモリリークを参照)
 
 static_assert
  ・コンパイル時エラーチェック。バグの芽を早めに摘むことが出来る。
  

 final & override修飾子

  ・ついていると継承時に関係がわかりやすく、継承の記載時に失敗したときにエラーを発行してくれる。
  ・なんでもvirtual修飾子しなくて済む。
 

 Boost.Property_tree
  ・XML形式のパラメータ解析に利用。
  ・ツリー構造の汎用プロパティ管理で使いやすい。
 

 Boost.Preprocessor
  ・便利なプリプロセッサマクロ群
  使用例:パラメータタイプEnumを文字列化
  class cGameObjectParameter{
   public:
   #define _EFFECTTYPE_SEQ \
    (kTYPE_STUN)/*スタン*/
    (kTYPE_SLOW)/*スロー*/
    (kTYPE_POISON)/*毒*/
      enum eEffectType{
       BOOST_PP_SEQ_ENUM( _EFFECTTYPE_SEQ )
      }
     //パラメータタイプ文字列取得
     static const
     std::string getEnumStr( const eEffectType effect_type ){
      #define _ENUM_TO_STR( unused, data,elem ) if( param == elem )o return BOOST_PP_STRINGIZE( elem );
      BOOST_PP_SEQ_FOR_EACH( _ENUM_TO_STR, -, _EFFECTTYPE_SEQ )
      return "Invalid";
      #unded _ENUM_TO_STR
     }
   #define _EFFECTTYPE_SEQ
  }

 

開発時の悩み(バグ)
 その1、特定の状況下でクラッシュ
  ・同時に大量のオブジェクトやエフェクトを出すとクラッシュ。
  ・対応策:
   大量に出そうな類のオブジェクトやエフェクトに関してはあらかじめプールしておき、必要に応じて空き枠を割り当てるように。
 

 その2、高速化
  ・扱うオブジェクト数が大量になると、コンテナへのアクセスコストがボトルネックに。
  ・対応策:
    無駄なstd::list, std::vectorに置き換え。
    上限が決まっているものはreserveまたはstd::arrayを使用するように。
    push_***、をemplase_***に置き換え。
    そもそもアルゴリズムを見直す。
     コンテナへのアクセス範囲を限定
     処理順の見直し
     キャッシュを有効利用することで無駄なループを減らす


 その3、メモリリーク
  ・std::thread, std::future, std::promiseを使うとメモリリークする。VisualStudio2012のバグ
  ・対応策:

    boostが提供している同種のものに差し替える。

 

 その他
  ・CI(Jenkins)や静的解析(Coverity)などで日々問題となりそうな箇所を炙り出していくことで、あまりバグを出さずに済んだ。

 

アーケードゲーム開発をしてみて、コンシューマと違うと感じたこと
 ・テストモード(ゲームセンターサイド向け操作モード)の実装へのオーダーが結構あり大変。
 ・筐体が場所をとってしまう。
  ・マルチプレイになると途端に台数確保が大変。
  ・さらに台数分のキーチップも必要。
 ・(TRC的な)作成基準は各社独自。
  ・風営法に抵触しないか等も含め、自己管理が求められる。
 ・1タイトルで数年単位の長期運営を前提とした開発。
 ・複数のバージョン開発が並列で動く。
  ・パラメータ配信やイベント、バージョンアップなど、細々あるので、バージョン管理に気を配る必要がある。
 ・KPI関連データの集計、分析はモバイルゲームのそれに近い。
 ・エラー周りの例外処理が複雑になりやすい。

  ・プレイヤーデータのバックアップ&復元など
  ・直接硬貨を投入して遊んでいただいているため、センシティブ。
 ・初回起動時に多少時間がかかってもあまり問題視されない
   ・コンシューマだと対外作成基準に引っかかってしまう。

 

まとめ
 ・ハードウェア/インフラ面ではアーケードゲーム特有の感はあるものの、ソフトウェア面ではコンシューマ開発とそう大差ない。
 ・アーケードゲーム開発では、コンテンツそのものが提供する体験以外に、現場体験(ライブで盛り上げるためのゲームデザイン)を重要視している。
 ・この辺を踏まえてゲームセンターに行くと、ゲームに対しての見え方が変わるかもしれない。