前回までは、使い魔のないスペルばかりを作ってきました。今回からは使い魔のいるスペルを作ってみたいと思います。
script_enemy
を使えば、別の敵の挙動を設定できます。script_enemy
で作った敵は、CreateEnemyFromScript
を使って登場させられます。script_enemy
で作った敵は、script_enemy_main
の敵が倒されると自動的に倒れます。それではいってみましょう。
東方永夜抄から、使い魔という仕様が追加されました。使い魔はある敵に従属した敵であり、使い魔の親分を倒せば使い魔も倒されます。使い魔を攻撃すれば、親分にもダメージが与えられます。他にも点アイテムや刻符に関する仕様がありますが、今のところそのあたりは気にしないことにします。
使い魔によく似たものは既に東方紅魔郷の時代から存在していて、例えば咲夜はいくつかの魔方陣を従えていることがあります。この魔方陣は倒せない敵であるかのように、咲夜からはほぼ独立した振舞いを見せます。
このように、敵本体に従属する敵(または魔方陣など)を作るにはどうすればよいのでしょうか? もし、その敵に当たり判定がないのであれば、オブジェクト弾のような感じで何とかできなくもないかもしれません。しかし、当たり判定があるような敵は、今までの知識だけでは作れそうもありません。
このように、ある敵に従属する敵を作るには、敵スクリプトというものを使います。独自の弾を作るときに弾スクリプトというのがあると話しましたが、これはその敵バージョンです。
敵スクリプトはどう作るのか...というのは、実はもうほとんど知っているはずです。実際今までもずっと敵を作ってきたわけで、敵スクリプトも今までやってきたことと大して違いはありません。違う部分は、script_enemy_main
ではなく script_enemy
を使うという点と、敵を出現させるのに CreateEnemyFromScript
という関数を使うという点のみです。実に簡単ですね。
CreateEnemyFromScript(enemy, x, y, v, angle, arg);
// enemy : 敵スクリプト名(文字列)
// x, y : デフォルトの位置
// v : デフォルトの速度
// angle : デフォルトの移動方向
// arg : 何らかの値(自由に使えます)
CreateEnemyFromScript で作られた敵は、script_enemy_main
の敵が倒されると、自動的に倒されます
敵スクリプトの使用例として、今回からは東方永夜抄の野符「武烈クライシス」を作ってみましょう。赤眼催眠と違って解析が難しかったので、細かい挙動を完全に再現することは諦めて、悟入幻想のようにそれっぽいものを作るに止めることにします。
武烈クライシスでは、使い魔がボスの左右の空間から登場し、回転しながら定位置へと広がりつつ移動します。今回はこの登場の部分だけ作ってみましょう。
先ず、使い魔の登場位置はボスと画面の端との丁度真ん中です。使い魔は左右両方に対称に出現するので、x 座標は中央からの距離で扱えばいいでしょう。
let xFamCenter = (GetCenterX - GetClipMinX) / 2; let yFamCenter = yIni;
次に、使い魔をどう動かすかですが、回転しながら広がるというのはどうやって実装すればいいのでしょうか? これは、位置を x, y 座標で管理するのではなく、中心位置からの半径と角度で管理すればいいのです。毎フレーム半径を増やしつつ角度を変えていけば、回転しながら定位置へと自然に誘導できます。つまり、CreateEnemyFromScript
には速度と移動方向を指定できますが、真面目にこれを利用するのではなく、とりあえず移動方向のところにデフォルトの位置の角度情報を渡すだけにしておいて、速度は 0 にしておきましょう。
あと、使い魔は右と左で回転方向が違います。この回転方向を ±1 で表現し、使い魔のスクリプトに渡してやりたいと思います。このような、各スクリプトに特有の情報を渡すには、CreateEnemyFromScript
の最後の引数を使います。ここに渡した値は、GetArgument
という関数で取得できます。
では、使い魔を配置するサブルーチンを作ってみましょう。
// 使い魔の配置に必要な時間 let wSetFam = 30; // 使い魔をセット sub setFam { // 使い魔の三角形の中心位置 // x 座標は中心からの距離になっています。 let xFamCenter = (GetCenterX - GetClipMinX) / 2; let yFamCenter = yIni; // 使い魔の位置(中心からの向き) let angleFam = [0, 120, -120]; ascent(i in 0..3) { CreateEnemyFromScript("Familiar", GetCenterX - xFamCenter, yFamCenter, 0, angleFam[i], 1); } ascent(i in 0..3) { CreateEnemyFromScript("Familiar", GetCenterX + xFamCenter, yFamCenter, 0, angleFam[i] + 180, -1); } wait(wSetFam); }
Familiar
というのが、敵スクリプトの名前(予定)です。分かりにくくなるので、左の使い魔用の処理と右の使い魔用の処理をループでまとめるのはやめておきました。
使い魔の位置は、最初はボスの方に頂点を向けた形にしてあります。半径を無視して角度だけを見ると、次の図 1 のようになります。
|
左側の使い魔は時計回りに 210 度回転し、右側の使い魔は反時計回りに 210 度回転します。つまり、左の使い魔のうち、最初に右(0 度)にいた使い魔は、最終的に左上(210 度)に来ます。
では、敵スクリプトを書いてみましょう。敵スクリプトの書き方は script_enemy_main
の時とほとんど同じです。つまり、中に @Initialize
, @MainLoop
などを書いて、敵の挙動を指定します。少し違う点は、script_enemy
の後に敵スクリプト名を書くという点です。実際に書いてみたのが、次のプログラムになります。
// 使い魔 script_enemy Familiar { let csd = GetCurrentScriptDirectory; let imgFam = csd ~ "img\familiar.png"; // 使い魔の中心位置 let xCenter = GetX; let yCenter = GetY; // 中心位置からの距離と角度 // 実際の角度は angBase + angle になります。 let r = 0; let angBase = GetAngle; let angle = 0; // 移動方向(±1) let dir = GetArgument; // 使い魔の配置に必要な時間 let wSetFam = 30; @Initialize { SetLife(2000); SetScore(10000); SetDamageRate(50, 50); SetTexture(imgFam); setGraphicFast; TMain; } @MainLoop { SetCollisionA(GetX, GetY, 32); yield; } @DrawLoop { DrawGraphic(GetX, GetY); } task TMain { yield; standBy; } // 初期位置へ移動 sub standBy { let dr = 32 / wSetFam; // 動径方向の速度 let w = 210 / wSetFam; // 角速度 loop(wSetFam) { r += dr; move(w); yield; } } // 次の位置へ移動 // w : 角速度 function move(w) { angle += w * dir; SetX(xCenter + r * cos(angBase + angle)); SetY(yCenter + r * sin(angBase + angle)); SetGraphicAngle(0, 0, angle); // グラフィックを傾けます } // グラフィックの設定 sub setGraphicFast { SetGraphicRect( 0, 0, 48, 48); } // w フレームだけ待つ function wait(w) { loop(w) { yield; } } }
それでは、順番に見ていきましょう。先ずは、必要となる変数です。
最初にあるのは、使い魔のグラフィックへのパスです。
let csd = GetCurrentScriptDirectory; let imgFam = csd ~ "img\familiar.png";
familiar.png は次の図 2 のようなものです。
|
ここでは一番左上のグラフィックを使います。そのグラフィックを設定するのをサブルーチン化したものがこの部分です。前回話した通り、こういった画像の縦と横のピクセル数は 2 の累乗にします。ここでは縦が 128( = 27)ピクセルで、横が 256( = 28)ピクセルになっています。
// グラフィックの設定 sub setGraphicFast { SetGraphicRect( 0, 0, 48, 48); }
次にあるのは、使い魔の中心位置です。
// 使い魔の中心位置 let xCenter = GetX; let yCenter = GetY;
次にあるのは、使い魔の中心位置です。これは使い魔が登場した時の初期位置と同じです。ただ、もし初期位置での半径が 0 でない場合であっても、CreateEnemyFromScript
の x
, y
には中心位置を渡しておいたのでもいいでしょう。その場合、ちゃんとした初期位置を @Initialize
で設定すればいいのです。
このように、敵スクリプトの初期設定は @Initialize
でいくらでも変えられるので、CreateEnemyFromScript
に渡した引数というのは必ずしも本当の初期設定である必要はありません。ただ、だからといって arg
(最後の引数)の代わりのように使うのは良くありません。それはプログラムを読みにくくするだけです。多少意味の違う値を渡すにしても、ある程度の関連性のある値を渡すべきでしょう。全く関係ない値は、arg
に渡すべきです。
で、次にあるのが使い魔の位置です。
// 中心位置からの距離と角度 // 実際の角度は angBase + angle になります。 let r = 0; let angBase = GetAngle; let angle = 0;
中心からの半径と、角度で扱っています。半径は最初 0 から始まって、最終的に 32 になります。角度は、ベースとなる角度 angBase
と、そこからのズレ angle
に分けています。これは、angle
だけを別なところで使うつもりだからです。
次にあるのが移動方向です。
// 移動方向(±1) let dir = GetArgument;
移動方向というのは CreateEnemyFromScript
のどの引数とも関連性がないので、最後の引数 arg
を通して渡してやります。その最後の引数を取得するには、GetArgument
を使います。
そして、最後にあるのが使い魔の配置に必要な時間です。これは使い魔をセットするサブルーチン setFam
で使ったのと同じ値にします。
では、他の部分も見てみましょう。@MainLoop
は、敵当たり判定がない点以外は今までと同じです。@DrawLoop
は今までと全く同じですね。
ところが、@Initialize
は少し違います。スペルカード宣言や時間設定がないのは当たり前としても、LoadGraphic
もありません。LoadGraphic
がないのに使い魔のグラフィックを描画できるのでしょうか?
実は、使い魔のグラフィックのロードは script_enemy_main
で行うのです。なぜなら、script_enemy
に書いてしまうと、使い魔を呼ぶたびにグラフィックをロードすることになってしまうからです。これは非常に効率が悪いですね。
同様に、DeleteGraphic
も script_enemy_main
に書きます。これはもっと重要です。script_enemy
の @Finalize
に書いてしまうと、使い魔をどれか1体倒した時点でグラフィックが破棄されてしまうからです。
では、次に TMain
を見てみましょう。現在は、使い魔が登場する時の挙動を担当するサブルーチン standBy
を呼んでいるだけです。この standBy
は、次のようになっています。
// 初期位置へ移動 sub standBy { let dr = 32 / wSetFam; // 動径方向の速度 let w = 210 / wSetFam; // 角速度 loop(wSetFam) { r += dr; move(w); yield; } }
使い魔は、30 フレームで登場するようにしています。半径の増加速度 dr
と角度の変化速度 w
は、最終的な変化量をフレーム数で割れば得られます。この値をフレーム数倍すれば最終的な変化量になるのだから、当然ですね。で、毎フレーム半径と角度を変化させ、使い魔の位置を変えていっています。角度変化と使い魔の位置の設定は、move
という関数の中で行っています。
// 次の位置へ移動 // w : 角速度 function move(w) { angle += w * dir; SetX(xCenter + r * cos(angBase + angle)); SetY(yCenter + r * sin(angBase + angle)); SetGraphicAngle(0, 0, angle); // グラフィックを傾けます }
ここで、角度を変化させるわけですが、回転方向 dir
をかけるのを忘れないようにしましょう。そして、具体的な位置を計算して、その位置へと敵を移動させています。三角関数を使って円状の座標を求めるのはもう定型句で何度も出てきたので、そろそろ慣れてきましたか?
そして、使い魔のグラフィックを回転と同時にクルクルと回転させています。SetGraphicAngle
という関数を使えば、グラフィックの角度を変化させることができます。SetGraphicAngle
の引数は、それぞれ x 軸まわりの回転角、y 軸周りの回転角、z 軸周りの回転角です。普通は、x, y 軸まわりには回転せず、z 軸(画面に垂直な方向の軸)まわりにのみ回転させることになると思います。
それでは以上を使って、使い魔が登場するだけのプログラムを作ってみましょう。ここにそのプログラムを置いておきます。クルクルと回りながら登場することを確認して下さい。
script_enemy
を使えば、別の敵の挙動を設定できます。script_enemy
で作った敵は、CreateEnemyFromScript
を使って登場させられます。script_enemy
で作った敵は、script_enemy_main
の敵が倒されると自動的に倒れます。次回も、引き続き武烈クライシスを作っていきましょう。