【GameMaker:Studio】プログラマのためのGameMaker入門

投稿者: | 2013年12月13日

ここではプログラムの知識を持っている人が、GameMakerを使い始めた時に役に立つヒントを紹介したいと思います。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

★目次

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

■main関数はどこにあるのか

GameMakerでは「ルーム(=画面に当たるもの)」をまず実行します。そしてRoomsフォルダの一番上にあるものが最初に実行されるルームとなります。

001

このような配置であれば「room_startup」が最初に実行されるルームとなります

■オブジェクトとは何なのか?

GameMakerのオブジェクトはC++で言うクラスに近い動作をします。メンバ変数を持ち、コールバック関数が定義されています。

ただしメンバ変数はすべて「public」であるため、外部からのアクセスを制限することができません。

■メンバ変数を定義するには

オブジェクトに変数の代入を行うことで、メンバ変数が作られます。例えば、

  hp = 100; // メンバ変数「hp」を定義

という記述を行うことでメンバ変数「hp」が作られます。そのため変数に代入すれば、どこでもメンバ変数を定義することができます。ですが通常はCreateイベント内で定義しておいたほうが、どこに定義したのかが分からなくなることなくていいと思います。あと個人的には、メンバ変数は自分自身を表す「self」キーワードを指定して書くと可読性が上がるかな、と思っています。

  self.hp = 100; // メンバ変数「hp」を定義

若干、冗長な記述ではありますが、一時的な変数との区別がつきやすくなります。

また、メンバ変数でなく一時的なローカル変数を作成したい場合には「var」キーワードを使用します。

  var temp = 0;

ループ変数も通常「var」を使います。

  for(var i = 0; i < 10; i++)
  {
    // 何かループ処理
  }

なお未定義の変数を参照した場合は「undefined」エラーとなります。


■スタティックなオブジェクトはどうやって定義するのか?

GameMakerでは別のルームへ移動すると、インスタンスはすべて破棄されます。

002

これを回避するには、「Persistent」にチェックを入れると別のルームへ遷移しても破棄されなくなります。

そしてこのインスタンスは、オブジェクト名を直接指定してアクセスすることができます。

例えば、「obj_gameMgr」というオブジェクト名であれば、

  obj_gameMgr.something_value += 10;

という記述でアクセスできます。(なおオブジェクト名指定のアクセスは、そのオブジェクト全てへの操作を意味します)

注意点としては、シングルトン(インスタンスが1つしか生成されない)を保証するものではないので、2重に生成しないように気をつける必要があります。


■別のオブジェクトへメッセージを送るには?

オブジェクトのメンバ変数はすべてpublicなので、インスタンス番号がわかれば全てアクセス可能です。例えば、

  // 弾を撃つ (obj_bulletを生成)
  var _id = instance_create(100, 200, obj_bullet);
  
  // 種別を「2」にする
  _id.type = 2;

というように生成時のパラメータを設定することが可能です

▼withキーワードについて

一部の関数は、現在実行中のインスタンスへの操作となるものがあります。例えば、instance_destroy関数(インスタンス破棄) などです。

  // インスタンスの破棄
  instance_destroy();

と記述すると、現在実行中のインスタンスのDestroyイベントを呼び出します。

これを別のオブジェクトに適用したい場合、

  with(obj_bullet)
  {
    // obj_bulletをすべて破壊する
    instance_destroy();
  }

withキーワードを使用したブロックは、実行主体がそのオブジェクトとなります。これを利用することで、

  with(instance_create(x, y, obj_bullet)
  {
    type = 2; // 生成した弾のタイプを「2」にする
  }

という簡潔な記述を行うことが可能です。ではwithキーワードで切り替わった元の主体者の値を取得するにはどうすればいいのか?

それは「other」キーワードを使用して以下のように記述します。

  with(instance_create(x, y, obj_bullet)
  {
    type = 2; // 生成した弾のタイプを「2」にする
    direction = other.direction; // 同じ方向にする
  }

これによりobj_bulletを生成した人と同じ方向に弾を発射することができます。

▼otherキーワードについて

otherキーワードはwith句の中での元の主体者を表しますが、もう1つ使い道があります。それは衝突イベントです。

衝突イベント内では、otherにぶつかられたオブジェクトのインスタンス番号が保持されています。

例えば、obj_blockとの衝突イベントでは other には obj_block のインスタンス番号が保持されています。

  // ■obj_blockとの衝突イベント
  if(y < other.y)
  {
    // 上からブロックに乗ったのであれば着地フラグを立てる
    self.is_landing = true;
  }

■関数を定義するには?

「Scripts」フォルダにスクリプトを追加します。ここで作成したスクリプトがそのまま関数名となり、呼び出しができます。

関数はC言語などと同様、引数の受け取りと戻り値の返却ができます。ただし引数を受け取るには「argument」キーワードを使用する必要があります。

なお、GmaeMaker:Studioでは「Scripts」内のスクリプトに、日本語文字を入力すると文字化けするので要注意です。

→文字化けは修正されました。


■独自のメンバ関数を作るには?

ユーザー定義関数を定義することでメンバ関数を作ることができますが、名前でなく番号でのアクセスとなるため可読性が下がります。

そのため、スクリプトで関数を作成するのが簡単で管理が楽だと思います。

「Scripts」フォルダにスクリプトを追加し、オブジェクト名のプレフィックスをつけて呼び出し名をつけるのがいいかと思います。

003

例えば、obj_playerオブジェクトであれば、player_damage() というようなスクリプトを作成し、引数に「self」を渡すようにします

■グローバル変数を使うには?

globalキーワードを使用します。

  global.hi_score = 0;

メンバ変数と同様、代入することで新しい変数が作れます。ルーム間をまたがって参照する値を定義できます。もしくは「Persistent」であるオブジェクトに変数を持たせてもOKです


■配列を使うには

配列を使うには以下のように記述します。

  var array;
  array[15] = 0; // 16個の配列を確保する

配列の注意点ですが、配列はサイズが大きくなると動的に確保しようとします。ですので、あらかじめ使用したいサイズの最大値で確保するようにしたほうがパフォーマンスが良くなります。

■特定のオブジェクトの中で条件に一致するインスタンスを操作する

例えば、プレイヤーの半径32pixel内にいる敵弾を消す、という処理を実装します

  // 弾の存在数を取得する
  var cnt = instance_number(obj_bullet);
  for(var i = 0; i < cnt; i++)
  {
    // インスタンス番号を取得
    var _id = instance_find(obj_bullet, i);
    
    // プレイヤーからの距離を取得
    var distance = point_distance(x, y, _id.x, _id.y);
    if(distance < 32)
    {
      // 弾インスタンス削除
      with(_id) { instance_destroy(); }
    }
  }

instance_number() / instance_find() を組み合わせることで、特定のインスタンスを取得することが可能です。

(ただし、本来は1フレームだけ32pixelの円形のオブジェクトを作って、衝突イベントで判定したほうがスマートな作りになると思います)

■定数を使うには

006

メニューから「Resources > Define Constants…」を選択すると、定数が定義できます。なお文字列を定数とする場合は「”(ダブルクォート)」で囲まないと、実行時にエラーとなります

■数値を文字列に変換するには?

string()関数を使用します。

  // ”x”の値を表示
  show_message(”x = ” + string(x));

■継承を使うメリットについて

継承は一般的なプログラム言語と同様、親クラスの機能を残したまま、子クラスで機能を拡張するという使い方をします。

注意点は子クラスでコールバックイベントを定義すると、親クラスの処理は呼ばれなくなります。(オーバーライド)

例えば、子クラスでメンバ変数を拡張して持たせる場合、Createイベントにその変数だけ定義すると親クラスが持つ変数は作られません。その場合は、event_inherited()を呼び出して、親クラスのCreateイベントを明示的に呼び出します。

  // 子クラスのみの変数を定義
  self.extra_weapon = 10; // 特殊武器所持数
  
  // 親クラスの同一イベントを呼び出す
  event_inherited();

この関数は、親クラスのイベントを呼び出します。これは現在実行中のイベントを呼び出すので、Createイベント内で呼び出すことで、親クラスのCreateイベントを呼び出すことができます。

▼継承による衝突イベントの共通化

継承によるメリットはもう1つあります。それは衝突イベントの共通化です。

例えば、敵A・敵B・敵Cとプレイヤーとの衝突イベントの判定をするのに、そのまま実装するとそれぞれのイベントを定義する必要があります。

004

この数ならまだ手作業でコピーペーストすれば何とかなりますが、10を超えると結構大変ですよね。

そこで、共通の obj_enemy を親オブジェクトにして、obj_enemyA / obj_enemyB / obj_enemyC に継承させます。

005

これにより衝突判定を共通化できます。