はじめに
今回はゲームの見た目を良くする「残像エフェクト」の実装方法を紹介します。
残像エフェクトとは、この画像のように過去の座標値に連続して描画することで、軌跡を描くものとなります。
一般的な残像の意味は、「非常に素早い動きを視覚的に表現するもの」ですが、今回は軌跡を含む広い意味を残像と定義します。
使用する機能
「配列」という機能を使います。
これは関連性のあるデータを一つにまとめて使うのに便利な機能です。
プロジェクトの新規作成
プロジェクトの新規作成をします。
GMLを使用するので、「GameMaker Language」 で作成します。
プロジェクトの設定
歯車アイコンをクリックして、 Game Options
を表示し、 Game frames per second
を 60に変更します。
プレイヤーオブジェクトの作成
オブジェクトを作成し、名前を objPlayer
とします。
スプライトを作成して、名前を sprPlayer
とし、以下の画像をインポートします。
ファイル名は player_strip2.png
として保存します。
インポートしたら、Frames per second
を 3 に変更します。
あとスプライトの原点を中央にしておきたいので、 Middle Centre
にします。
プレイヤーの移動処理の作成
objPlayer
を開いて、 Add Event > Step > Step
を選び、Stepイベントを作成します。
Stepイベントのコードは以下のものを入力します。
// 移動処理
var dx = mouse_x - x;
var dy = mouse_y - y;
x += dx * 0.1;
y += dy * 0.1;
マウスの位置と現在値との差を取り、10分の1をどんどん足し込んで、じわじわ動かす移動処理となります。
プレイヤーをルームに配置する
ルーム内に配置されていればどこに置いても問題ありません。
では、実行してプレイヤーがマウスの位置に付いてくることを確認します。
残像の実装
準備ができたので、ここから残像を実装します。
objPlayer
を開いて、 Add Event > Create
でCreateイベントを作成します。
// 残像の数
trail_size = 10;
// 残像の位置を初期設定する (配列作成)
for(var i = 0; i < trail_size; i++) {
xprev_array[i] = x;
yprev_array[i] = y;
}
残像の位置を保持するため、10個の座標を保持するようにします。
xprev_array
が 残像のX座標の配列、 yprev_array
が残像のY座標の配列となります。
イメージとしては、このような座標のハコが作られたわけです。
ここに残像の座標を入れていきます。
続けて、Stepイベントを修正します。
// 移動処理
var dx = mouse_x - x;
var dy = mouse_y - y;
x += dx * 0.1;
y += dy * 0.1;
// ※ここから追加
// 残像の更新
// 配列の先頭に前回フレームの座標を代入
xprev_array[0] = xprevious;;
yprev_array[0] = yprevious;
// 配列を後ろから更新する
for(var i = trail_size-1; i > 0; i--) {
xprev_array[i] = xprev_array[i-1];
yprev_array[i] = yprev_array[i-1];
}
残像の更新処理を追加しました。
コードだけではわかりにくいので図で説明をします。
配列の最後尾から1つ前のものを移動させるという、バケツリレーのようなことをして、
本来のプレイヤーの座標よりも少し遅れて追いかける動きとなります。
配列の先頭に設定した1フレーム前の座標が、配列の末尾に移動するには9フレーム必要となります。
これにより、配列の値は座標が1フレームずつズレたものになります。
ではこれを使って残像の描画処理を追加します。
Add Event > Draw > Draw
で描画イベントを追加します。
// 残像の描画
// 末尾から描画する
for(var i = trail_size-1; i >= 0; i--) {
var px = xprev_array[i];
var py = yprev_array[i];
// 末尾にあるほど透過値を小さくする
var alpha = 1.0 - 1.0 * (i + 1) / (trail_size + 1);
draw_sprite_ext(sprPlayer, 0, px, py, 1, 1, 0, c_white, alpha);
}
// 自身を最後に描画する
draw_self();
Stepイベントと同様、配列の末尾から操作を行っています。これは末尾にある残像ほど背面に描画したいので、先に描画をしているわけです。
あと、透過値の計算が少しややこしいですが、考え方は以下の通りです。
元となる式は以下のものです。
1.0 * i / trail_size
i
の増加率に合わせて透過値を減少する、という式です。
ただこの場合、i = 0 の時に透過値が 0.0 になって完全に透明になってしまうので、 (i + 1)
としました。
(i + 1)
とすると、分子の最大値が分母の値と同じ(=1.0 となり不透過)になるので、 (trail + 1)
としました。
そして、透過値を i
の増加率とは反比例にしたいので、1.0 から引いています。
これにより、 i
が増加するに従って、透過値が小さくなります。
移動速度が遅くても残像がわかりやすく見えるようにする
この実装の問題点は、移動速度が遅い場合に残像が見た目であまりわからないことです。
また10回描画しているため、残像が目立ちすぎているという欠点もありますし、描画の処理負荷も高くなります。
そこで、回数を減らしても残像がよく見えるようにします。
まずは、objPlayer > Createイベント
を開いて、以下のように修正します。
// 残像の数
trail_size = 5; // ※残像の数を減らす
// 残像の位置を初期設定する (配列作成)
for(var i = 0; i < trail_size; i++) {
xprev_array[i] = x;
yprev_array[i] = y;
}
残像の数を5個に減らしました。
次に、objPlayer > Stepイベント
を開いて、以下のように修正します。
// 移動処理
var dx = mouse_x - x;
var dy = mouse_y - y;
x += dx * 0.1;
y += dy * 0.1;
// 残像の更新
var decay = 0.5; // 追いかける速度
xprev_array[0] += (x - xprev_array[0]) * decay;
yprev_array[0] += (y - yprev_array[0]) * decay;
for(var i = trail_size-1; i > 0; i--) {
var xprev = xprev_array[i];
var yprev = yprev_array[i];
var xnext = xprev_array[i-1];
var ynext = yprev_array[i-1];
xprev_array[i] += (xnext - xprev) * decay;
yprev_array[i] += (ynext - yprev) * decay;
}
以前の記述は、1つ前の値をそのまま代入するだけでしたが、この修正では、1つ前の座標との差の2分の1を足しこむようにしています。 これにより、じわじわ追いかけるような動きとなります。
では、実行して、移動速度が遅くても残像が視認しやすくなっていることを確認します。
最後に
アクションゲームなどで、プレイヤーキャラクターに常に残像をつける場合は、3〜5個あたりに抑えると、プレイヤーの操作の邪魔にならず、いい感じの見た目になります。
ただ、移動速度によって追いかける速度 decay
の値を小さくしたり大きくしたりして良い感じになるよう、調整が必要となると思います。
おまけ
残像とは少し異なりますが、一定のフレーム数の間隔でエフェクトを連続して発生させると、同じような軌跡っぽいエフェクトになります。
例えば、objPlayer > Stepイベント
を開いて、以下のコードに修正します。
// 移動処理
var dx = mouse_x - x;
var dy = mouse_y - y;
x += dx * 0.1;
y += dy * 0.1;
// 残像の更新
var decay = 0.5; // 追いかける速度
for(var i = trail_size-1; i > 0; i--) {
var xprev = xprev_array[i];
var yprev = yprev_array[i];
var xnext = xprev_array[i-1];
var ynext = yprev_array[i-1];
xprev_array[i] += (xnext - xprev) * decay;
yprev_array[i] += (ynext - yprev) * decay;
}
xprev_array[0] += (x - xprev_array[0]) * decay;
yprev_array[0] += (y - yprev_array[0]) * decay;
// ※これを追加
// エフェクトを毎フレーム生成する
effect_create_below(ef_explosion, x, y, 0, c_white);
最後の部分が追加したコードとなります。
実行すると、以下のような見た目となり、派手な感じになりました。
毎フレーム出力すると描画負荷も大きくなってしまうので、数フレームごとに生成する、という修正をしてみるのも良いです。
以上、残像エフェクトの作り方でした。