【GameMaker:Studio】最適化・高速化させる方法

投稿者: | 2013年11月15日

Optimizing Your games in GameMaker: Studio

の翻訳となります。

  • 修正履歴
    • 2013/11/15 : 公開
    • 2013/11/23 : コードヒントにLoops(ループ)が追加されていたので追記

■はじめに

GameMaker:Studioでゲームを作る際によくある質問の1つは、ゲームを高速化・最適化する方法です。残念ながら、これに簡単な答えはありません! それは各ゲームの特性やシステムによって手法が大きく異なるためです。しかし共通して最適化に役立つ一般的な経験則があります。ここではそれを紹介したいと思います。

■グラフィックス

ゲームで処理落ちする最も一般的な原因の1つは、グラフィックスパイプラインです。ゲームロジックよりも描画がネックになるため、描画を高速化することが最適化の近道となります。ここでは、いくつかのヒントをお伝えします。

▼テクスチャページ

GameMaker:Studioは、すべてのゲームのグラフィックを「テクスチャページ」に格納しています。テクスチャページは画面に描画を行う際、すべてのグラフィックを持つ単一のイメージとなります。

さて、ゲームで多くのグラフィックを作成すると、複数のテクスチャページを取り始めます。複数のテクスチャを切り替えると、「テクスチャスワップ」が発生し、これは重たい処理なので、ラグや処理落ちが発生することがあります。

これを回避するにはどうすればいいのか? それは「テクスチャグループ」を作成することです。各場面(主に各ルーム)ごとに必要とするテクスチャ、必要としないテクスチャがいくつか分類できるはずです。それをグループ分けしてテクスチャスワップを減らすことで高速化が見込めます。

設定方法は「Global Game Settings」から「Texture Group」タブを選び、適切なグループに分類します。

これを行うことで、必要なグラフィックは同一のページに置かれ、テクスチャスワップが最小限で済むので、かなりの最適化となるはずです。

なおメモリから不要なページをフラッシュするにはルーム開始のスクリプトやCreateイベントで以下の関数を呼び出します。

  draw_texture_flush();

これはテクスチャからすべてのデータを消去します。そのためロードされていないスプライトを描画しようとしたときに再ロードが発生し、チラつきが発生することがあることに注意してください。これを防ぐには、draw関数を呼び出して強制的にロードを行います。

  draw_texture_flush();
  draw_sprite(spr_Menu1, 0, 0, 0);
  draw_sprite(spr_Cursor, 0, 0, 0);

テクスチャページがどのように作られているかは、各プラットフォームの「Graphics」タブを選択し、「Preview」ボタンから確認することができます。

▼ブレンドモード

GameMaker:Studioは、描画用のパイプラインを通してグラフィックスデータの”バッチ”をオフに送信しますが、これは可能な限り低く抑えるべきです。通常、心配する必要があるものではないですが、ブレンドモードを複数のインスタンスから呼び出すと、ゲームで処理落ちが発生することがあります。

これを解決するにはどうするのか? draw_set_blend_modeを使用する回数を抑えるように以下のように記述します。

  draw_set_blend_mode(bm_add);
  with (obj_HUD) draw_sprite(spr_Marker, 0, mx, my);
  with (obj_Player) draw_sprite(spr_HaloEffect, 0, x, y);
  with (obj_Cursor) draw_self();
  draw_set_blend_mode(bm_normal);

3つのインスタンスが個別に呼び出すのではなく、1つのインスタンスでこれを呼び出すようにして最適化をしています。

▼アルファテストとアルファブレンディング

GameMaker:Studioには、処理負荷が高く、使い方次第で劇的に描画パイプラインをスピードアップすることができる描画関数があります。

  • draw_set_alpha_test()
  • draw_enable_alphablend()

これらをどのように使うのか? draw_set_alpha_test()を使うことで「アルファテスト」が有効になります。初期値ではα値が「0」であれば描画されないようになります。なおこの閾値はdraw_set_alpha_test_ref_value()で変更することができます。

またdraw_enable_alphablend()により「アルファブレンド」のON/OFFを切り替えることができます。アルファテスト、アルファブレンドを無効にすることで、大規模なスピードアップが見込めるので可能であれば検討すべきです。

▼スケーリング

複数のプラットフォームでのゲーム開発をする場合、指定した解像度に合わせて解像度をスケールする必要があります。このスケーリングはとても重たい処理となります。

これを解消するにはviewをサーフェースをview_surface_idで取得し、DrawGUIイベントにおいて、draw_surface_stretched()で描画するようにします。

▼実行時にアセットを追加する

sprite_duplicate()を使用するとスプライトの複製ができます。しかしこれを呼び出すことで新しくテクスチャが作られるためテクスチャスワップが発生します。可能であればこれを避けることで高速化が見込めます。

▼パーティクル

パーティクルは簡単に効果的な演出を行う手法です。このパーティクルを組み込みの画像で使用している場合、パフォーマンスに悪影響を及ぼす可能性があります。それは組み込みの画像に切り替えるテクスチャスワップが発生するためです。

これを回避するにはpart_type_sprite()で同一のテクスチャグループの画像を使用する、part_type_shape()を使用することです。なお組み込みのパーティクル画像は %appdata%/GameMaker-Studio/Windows8/html5game/particles にあります。これをテクスチャグループに追加してもよいでしょう。

■サウンド

サウンドの出力設定を変更することで最適化が可能です。

  • 数秒の効果音→「uncompressed」
  • 少し長めの効果音→「compressed」
  • 大規模で頻繁に使う効果音→「compressed (uncompressed on load)」
  • BGM→「compressed (streamed from disk)」

圧縮とストリーミングのオプションを選択することもできます。MP3ファイルは22,050KHz / 56kbpsで適切な設定で、GameMaker:Studioはそれをデフォルト値として使用します

■コードヒント

コーディング上のヒントは様々な見解があり難しいですが、GameMaker:Studioで共通している高速化のテクニックがあります。

▼ifのネスト

GameMaker:Studioはif文に与えられた評価式をすべて計算します。例えば次のコードです。

  if mouse_check_button(mb_left) && mouse_x > 200 && global.canshoot == true
  {
    // do something
  }

多くの言語では、最初の式がfalseであればそれより後ろの式の評価は行いません。ですがGameMaker:Studioはすべて計算をします。そのため、以下の記述に置き換えることで、若干の高速化が見込めます。

  if mouse_check_button(mb_left)
  {
    if mouse_x > 200
    {
      if global.canshoot == true
      {
        // do something
      }
    }
  }

これは冗長に見えるかもしれませんが、このチェックが複数のところで呼び出される場合は、有効に働きます。


▼グローバル変数

グローバル変数はすべてのインスタンスがアクセス可能な変数をもっており、実効速度にペナルティをもたらします。例えば次のコードを見てみます。

  repeat(argument0)
  {
    with (obj_Parent)
    {
      if place_meeting(global.px, global.py, argument1) instance_destroy();
    }
  }

global.py / global.py はグローバル変数であり、repeat文による繰り返し回数分だけペナルティが発生します。そのためこのように書き換えると高速化が見込めます。

  var xx = global.px;
  var yy = global.py; 
  repeat(argument0)
  {
    with (obj_Parent)
    {
      if place_meeting(xx, yy, argument1) instance_destroy();
    }
  }
▼配列

配列のシンプルな最適化のトリックは、最初に最大サイズを指定したり、逆の順番でそれらを初期化することです。GameMaker:Studioは配列のサイズが大きくなるたびメモリブロックを確保するためです。例えば、100個の配列が必要であれば、

  myarray[99] = 0;

というようにあらかじめ100個確保しておきます。また、256個の配列を特定の値で初期化したい場合は以下のように記述します。

  for(var i = 255; i > -1; i--;)
  {
    myarray[i] = make_color_hsv(irandom(255), 150, 255);
  }
▼ループ

マイナーな最適化は、ループを正しく使用できているかどうか確認することです。一般的にほとんどのゲームでは、ループまたは繰り返しデータを生成するためにループを使用しており、多くの場合は適切に処理を選択しています。しかし、それらは状況により異なる動作をすることを留意すべきです。

一般的なルールは、値をインクリメントするループにおいて、以下の様な forループを使用しているものの、その値を使用していないケースです。

  for (var i = 0; i < 100000; i++;)
  {
    xx = irandom(500);
  }

これは次のように書き換えることで、高速化が見込めます。

  repeat(100000)
  {
    xx = irandom(500);
  }

しかし、このようにカウンタをループ内で使用する場合は、

  var i = 0;
  repeat(100000)
  {
    xx = irandom(i);
    i++;
  }

以下のように記述したほうが速くなります。

  for (var i = 0; i < 100000; i++;)
  {
    xx = irandom(i);
  }