【GameMaker:Studio2】GameMaker:Studio2のTIPS

久しぶりにGameMaker:Studio2を使ってみて気がついたことをまとめていきます。

ここでは描画周りとか、ホットリロード、SyncGroup、Spineの制御について書いていきます。

# 加算ブレンドで描画する方法
ブレンドモードを設定する関数が `draw_set_blend_mode()` から `gpu_set_blendmode()` に変わったようです。
なので、例えば Drawイベント で以下の記述にすると加算ブレンドでスプライトを描画できます。

```js
gpu_set_blendmode(bm_add); // 加算ブレンドに変更
draw_self(); // スプライト描画
gpu_set_blendmode(bm_normal); // 通常のブレンドモードに戻す
```
 
 ちなみによくやりがちなミスとして、補完機能で入力すると `gpu_set_blendenable()` （ブレンドモードの有効・無効を切り替える）を使ってしまうことに注意です。


# ホットリロード対応
GameMaker:Studioはデバッグ実行の起動に少し時間がかかります。これだとトライ＆エラーに時間がかかってしまうので実行中にデータを書き換えたいと思って調べたところ、MacOSXの場合にはデバッグ実行環境は以下のディレクトリにコピーされるようです。

```
%HOME/Library/Application Support/com.yoyogames.macyoyorunner/game/
```

さらに `Included Files` の内容は以下にコピーされます。

```
%HOME/Library/Application Support/com.yoyogames.macyoyorunner/game/assets/
```

なので、データコンバート時にこのフォルダにコピーするようにしたら、デバッグ実行中でも書き換えることが可能となりました。
ちなみに、`Included Files` 以外は実行中に変更・更新することはできないようです。
（ひょっとしたら Sandbox機能を使うと、ローカルのファイルを別途ロードできるようになるのかもしれません[※未調査]）

# テクスチャ単色化シェーダー

![mono.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/6ac45a1c-b0ea-6554-1f06-7a0a41aac930.gif)

こんな感じで、キャラクターを白く点滅させたかったのですが、どうもブレンドモードの変更だけではうまくできなかったのでシェーダーを使いました。

白点滅させるシェーダー (正確にはテクスチャを単色にするシェーダー) の名前は「shMonoColour」として、以下のように記述します。

まずは vsh（頂点シェーダー） 側から。

```glsl:shMonoColour.vsh
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
attribute vec2 in_TextureCoord;              // (u,v)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)   vertex colour.

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    vec4 pos  = vec4( in_Position.xyz, 1.0 );
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * pos;
    
    v_vColour   = in_Colour; // 頂点カラーをコピー
    v_vTexcoord = in_TextureCoord;
}
```

fsh（フラグメントシェーダー） 側を以下のように記述。

```glsl:shMonoColour.fsh
//
// Simple passthrough fragment shader
//
varying vec2  v_vTexcoord;
varying vec4  v_vColour;

void main()
{
	// テクスチャカラーを取得
	vec4 colour = texture2D( gm_BaseTexture, v_vTexcoord );
	
	// RGBを頂点カラーで上書き
	colour.rgb = v_vColour.rgb;
	
	// α値は残す.
	colour.a *= v_vColour.a;
	
	// フラグメントカラーを設定.
	gl_FragColor = colour;
}
```

Drawイベントで以下のように記述すると白色で点滅させることができます。

```js:Drawイベント

// 更新用のフレームカウンタ
timer++;

// 元となるスプライトを描画
image_alpha = 1;
draw_self();

// 重ねるスプライトの描画
// シェーダーを設定
shader_set(shMonoColour);

// 加算ブレンドにする
gpu_set_blendmode(bm_add);

// α値を計算
image_alpha = 0.5 * abs(dsin(timer * 4));
draw_self();

// ブレンドーモードを元に戻す
gpu_set_blendmode(bm_normal);

// シェーダーを元に戻す
shader_reset();
```

# フォントテクスチャが大きくなると文字が正しく表示されない

例えば第一水準の漢字をすべて登録すると、テクスチャサイズが大きくなり、正しく表示されなくなることがあります。

![Font1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/2b81cd91-c323-9d55-729f-c3e106714f43.png)


その場合は、ゲームオプション(歯車アイコン)の Platform Setings > 各環境 > Graphics から Texture page size をフォントテクスチャのサイズにします。

![Font2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/46703bd8-7e53-b302-30cc-9a77a7b4ca4f.png)

(この例では フォントテクスチャのサイズが 4096x2048 になっていたので、4096x4096 に拡張）
そうすると正しく表示されるようになります。
ただあまり大きくするとグラフィックボードが対応していない場合にうまく読み込めなくなる可能性があるので注意ですね……。

![Font3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/2da8169b-eefc-2fd3-bded-d8925d78ddc1.png)
 
# SyncGroupを使用したBGMの再生
SyncGroupを使うと、複数のサウンドファイルの再生タイミングを同期できます。
例えばサウンドを Drumトラック・Bassトラック、Guitarトラック に分けて、特定のトラックのみ再生します。
これにより、ゲーム進行の盛り上がりに合わせて、リアルタイムで曲を変化させていくことが可能となります。

![SyncGroup](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/ffdc8b5c-34e5-4964-0921-1c5ef14c0b68.png)

実装例は以下のとおりです。

```js:Createイベント
// SyncGroup生成
audiogroup = audio_create_sync_group(true);

// サウンド登録
tracks = [sndDrum, sndBass, sndGuitar];
for(var i = 0; i < array_length_1d(tracks); i++) {
	audio_play_in_sync_group(audiogroup, tracks[i]);
}

audio_start_sync_group(audiogroup);
```

特定のトラックを再生するには以下のように記述します。（以下の例は 2番目 (Guitarトラック) を再生しています。）

```js:特定のトラックを再生
var idx = 2; // 2番目(Guitarトラック)
var volume = 1; // 音量を1にする
var time = 1; // 1秒かけてフェードイン
audio_sound_gain(tracks[idx], volume, time);
```

特定のトラックを停止するには以下のように記述します。（以下の例は 0番目 (Drumトラック) を停止しています。）

```js:特定のトラックを停止
var idx = 0; // 0番目(Drumトラック)
var volume = 0; // 音量を0にする
var time = 1; // 1秒かけてフェードアウト
audio_sound_gain(tracks[idx], volume, time);
```

最後に Destroyイベントで SyncGroup を破棄します。

```js:Destroyイベント
// いったん停止
audio_stop_sync_group(audiogroup);
// SyncGroupを破棄
audio_destroy_sync_group(audiogroup);
```

## SyncGroupを使うときの注意点

* 使用するサウンドファイルの圧縮設定を有効にしないと使用できない
* WAVファイル以外を指定すると、ループ時に少し音が止まる
  * 気になる場合は WAVファイルを使う
  * GameMaker:Studio側のサウンド圧縮を有効にする必要があるので、ファイルサイズはあまり気にならないかも
    * ただ圧縮の音質はあまり良くないかもしれない（何となくの印象）

# Spineを使う場合の注意点と制御

## Spineのバージョンについて

 GameMaker:Studioで Spineを使う場合は Spineのバージョンに注意します。
 2020/3/17現在の GameMaker:Studio側のバージョン [ v2.2.5.481 ] だと、Spineの最新版のデータを読み込むことができません。
 
 Spineを起動して、メニューから「設定」を選び、バージョンを「3.7.94」に変更します。

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/a64bbf10-3481-acb1-ec49-6f56c6debbfe.png)

そして Spine を再起動するとバージョンが変更されます。
Spine で作成したデータは古いバージョンに戻せないので、新しいバージョンで作らないように注意ですね……。

## ライセンスの同意
GameMaker:Studioで読み込むには Spineライセンスに同意する必要があります。

![Spineライセンス.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/41f692ad-87be-2aaf-f6d9-f6000e0af365.png)


## GameMaker:Studioでの Spine のインポート
スプライトを作成して、スプライトエディタのimportボタンから Spine側でエクスポートした　*.json ファイルを読み込むだけ。すごい楽。

## アニメーションの再生
アニメーションの再生は `skeleton_animation_set()` でアニメーション名を指定するだけで再生できます。

```js
// アニメーション "animation" を再生する
skeleton_animation_set("animation");
```

## Spineの描画
Spineスプライトは、通常のスプライトと同じなので、例えば Draw イベントで、`draw_self()` で描画できます。

```js:Drawイベント
draw_self();
```

## 1回のみ再生する処理を行う

GameMaker:Studio で Spine を再生すると、必ずループ再生を行います。これを「1回だけ再生」する場合には、"Animation End" イベントで、`image_speed = 0;` とすると 1回だけ再生することが可能です。

```Js:Animation_Endイベント
// アニメーション速度を 0 にすることで停止
image_speed = 0;
```

ちなみに Spineスプライト は通常のスプライトと似たような動きをするようになっているので、以下の Spriteプロパティでアニメーションの制御が可能です。

* **sprite_index**: Spineスプライトの番号
* **image_alpha**: 透過値
* **image_angle**: 回転角度
* **image_blend**: 色味
* **image_xscale**: スケール値(X)
* **image_yscale**: スケール値(Y)
* **image_speed**: アニメーション再生速度 (デフォルト "1")
* **image_index**: 再生中のSpineアニメーションフレーム数
* **image_number**: 再生中のSpineアニメーション フレームの総数

## Spine側で埋め込んだ Eventの取得

Spineには Eventという仕組みがあり、アニメーションの特定フレームでエフェクトを発生させたり、サウンドを再生するための情報を埋め込むことができます。

GameMaker:Studio側でそれを受け取るには 「Animation Event」イベントを作成します。すると `event_data` からパラメータを受け取ることができます。

```js:Animation_Eventイベント
// 各種パラメータ受け取り
var name = event_data[? "name"]; // イベント名
var track = event_data[? "track"]; // track数
var integer = event_data[? "integer"]; // 整数値パラメータ
var float = event_data[? "float"]; // 浮動小数値パラメータ
var str = event_data[? "string"]; // 文字列パラメータ
```

# 余談
GameMaker:Studio2 の次のマイナーバージョンアップ (v2.3) では、GMLがかなり強化され、関数オブジェクトがサポートされるようになります。

* [GML UPDATES IN 2019](https://www.yoyogames.com/blog/514/gml-updates-in-2019)

これはどういうことかというと、今までオブジェクトに独自の関数を追加する場合に、スクリプトを追加していたのが不要になるということです。

```js
log = function(a) {
      show_debug_message( a )
}

log( “Hello World” );
```

こんな風に書けます。あとクラス的な記述も可能。

```js
function Vector3( _x, _y, _z )
{
    x = _x;
    y = _y;
    z = _z;

    add = function( _v ) {
        x += _v.x;
        y += _v.y;
        z += _v.z;
    }
}

var vec = new Vector3( 10, 20, 30 );
```

これはヤバイ！！
……まあ、JavaScript とか Lua をやっている人には当たり前の書き方ですが、長年GMLの記述力の低さが問題となっていたので、そこが解消されるのは大きい気がしますね。

そして、肝心のリリース時期ですが……

![GMS2_Roadmap_–_YoYo_Games.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/a7b99b66-4ecb-d942-e41e-e9055c6a46fe.png)

* [GMS2 Roadmap](https://help.yoyogames.com/hc/en-us/articles/231719448-GMS2-Roadmap)

そろそろ正式リリースされそうなので楽しみです！