[GameMaker: Studio] PhysicsによるSTG触手ボスのサンプル

下記リンクでHTML5対応ブラウザ(IEは非対応)で実際にプレイできます。
http://labo.emilycharlotte.jp/ecl_gms_physics_arm_boss_sample/

*操作方法
カーソルキーで移動、スペースキーでショット、Dキーでデバッグ表示切り替え。
マウスでも操作できます。

ゲームにはしていないので、当たってもミスにはなりません。

プロジェクトファイル(gmz形式)はこちらからどうぞ。
http://labo.emilycharlotte.jp/ecl_gms_physics_arm_boss_sample/phy_tet_sample_100.gmz


少し前にTwitterでシューティングゲームの触手ボスの話題が上がった際に、触手をPhysicsで作ると面白いかも…という話になったので、実際に作ってみました。

今回解説するのは触手部分のみです。
その他気になるところがあったら、プロジェクトファイルを読んでがんばって解析してみてください。
(いつも通り、思いつきで作っているので無駄が多くて汚いですが)

まず前提条件ですが、roomの重力はなしとしました。
あっても面白いのですが、綺麗に動かすためには重力なしの方が都合がいいです。
重力が必要なオブジェクトにはphysics_apply_force等で個別に与えてください。


実際に動いているところを見てもらえばわかりますが、実は表示されているボスではPhysicsは使用していません。

GameMaker: Studioで採用されているPhysicsエンジン(Box2D)では、ジョイント機構はワールド(room)座標に固定して使用することになっています。
一応は移動させることもでき、慣性による変化も得られますが、極端な力が加わった際に制限値を超えてしまい、計算結果が異常な値になることがあります。

そのため、今回はPhysicsによる触手はワールドに固定して腕を振るだけにし、これ自体は描画しません(描画をオフにしても計算は行われます)

実際に見えているボスはPhysicsの計算結果だけを貰い、それに移動と回転を加えて動かしています。


次にコードの説明です。

まずは“Objects => tet => o_tet_phy_motor”Createイベントを開いてください。
色々な初期設定の後の“// make physics arms”Physics触手を作っています。

// make physics arms
c = 5;      // arms (1 to **)
s = 4;      // joints (2 to **)
ms = 16;    // motor size
as = 64;    // arm size
t = 360 / c;
a = 0;
for(i=0; i<c; i++) {
    p_make_arm(global.tet_phy_motor_px, global.tet_phy_motor_py, 
               a, id, o_tet_phy_arm, 0, 1, s, ms, as, 
               global.tet_angle_limit_arm, global.tet_list_arm, 
               global.tet_list_arm_obj);
    a += t;
}

c = 5は触手の本数、s = 4は各触手のジョイント(関節)数です。
メモリと負荷を無視すれば無限に増やすことができます。

スクリプトp_make_armで触手を1本ずつ作っています。これは次項で説明します。

// make view arms
j = ds_list_size(global.tet_list_arm_obj);
for(i=0; i<j; i++) {
    n = ds_list_find_value(global.tet_list_arm_obj, i);
    dx = n.phy_position_x - global.tet_phy_motor_px + global.tet_core_px;
    dy = n.phy_position_y - global.tet_phy_motor_py + global.tet_core_py;
    k = instance_create(dx, dy, o_tet_arm);
    k.image_index = n.image_index;
    k.my_angle = p_check_angle360(360 - n.phy_rotation);
    k.my_physics_obj_id = n;
}

作られたPhysics触手に対する、描画用の触手をここで作っています。
角度のコピーが360から引いているのは、Physicsでは回転方向が逆だからです。


続いて、“Scripts => tet => p_make_arm”を開いてください。
これがPhysics触手作成の本体です。

{
    var px = argument0;         // world pos x
    var py = argument1;         // world pos y
    var rot = argument2;        // angle (degree)
    var pobj_id = argument3;    // parent object instance id
    var cobj = argument4;       // child physics object id (not instance)
                                // *set create event : my_joint_revolute_id = -1;
    var sm0 = argument5;        // sub image 0
    var sm1 = argument6;        // sub image 1
    var cnt = argument7;        // arm count
    var clen = argument8;       // parent center offset
    var alen = argument9;       // arm length
    var lim = argument10;       // angle limit
    var dsla = argument11;      // arm joint id ds_list
    var dslo = argument12;      // arm object id ds_list

    var rn = rot;
    var vx = lengthdir_x(clen, rn) + px;
    var vy = lengthdir_y(clen, rn) + py;
    var ax = lengthdir_x(alen, rn);
    var ay = lengthdir_y(alen, rn);
    var nx = 0;
    var ny = 0;
    var n = 0;
    var h = 0;
    var k = pobj_id;
    var j = 0;
    var i = 0;
    for(i=0; i<cnt; i++) {
        nx = ax * i + vx;
        ny = ay * i + vy;
        n = instance_create(nx, ny, cobj);
        if(i < (cnt - 1)) n.image_index = sm0;
        else n.image_index = sm1;
        n.phy_rotation = p_check_angle360(360 - rn);
        j = physics_joint_revolute_create(k, n, nx, ny, -lim, lim,
                                          true, 0, 0, false, false);
        n.my_joint_revolute_id = j;
        ds_list_add(dsla, j);
        ds_list_add(dslo, n);
        k = n;
    }
}

ややこしそうに見えますが、やっていることは単純で、根元から順に関節数分のオブジェクトとジョイントを作っているだけです。

元になるオブジェクトは横向き(右向き)で原点は左端(上下中央)にしています。


作られたPhysics触手を動かしているのは、“Objects => tet => o_tet_phy_arm”Drawイベントです。

// whip
phy_rotation += global.tet_angle_req_arm;

触手の角度を変えているのはこの1行です。
ジョイントではなく、オブジェクト自体の回転角に力をかけて、ジョイントに作用させています。

ただし、Revolute jointはあまり精度が良くなく、Limit値で制限をかけてもちょっと力を加えるとすぐに制限をすり抜けてしまいますので注意が必要です。

Revolute jointにはモーター機能もありますが、一度設定するとON/OFFや上限値は変えられるものの、回転を逆方向にすることはできません。
そのため今回はモーターは使っていません。

プレイヤーのショットが描画用触手にヒットした場合、親となるPhysics触手のパーツにヒット通知を送っています。

// damage
if(my_hit_damage) {
    my_hit_damage = false;
    if(global.tet_active) {
        r = irandom_range(abs(global.tet_angle_req_arm) * 10, 
                          abs(global.tet_angle_req_arm) * 30)
                         * sign(global.tet_angle_req_arm);
        phy_rotation -= r;
    }
}

ヒット通知を受け取ると、回転角に若干の変化を加えてダメージ感をアップさせています。


最後に“Objects => tet => o_tet_arm”Drawイベントを開いてください。

// copy position of physics arm
dx = (my_physics_obj_id.phy_position_x - global.tet_phy_motor_px)
      * global.tet_core_scale;
dy = (my_physics_obj_id.phy_position_y - global.tet_phy_motor_py)
      * global.tet_core_scale;
dd = point_distance(0, 0, dx, dy);
dr = p_check_angle360(point_direction(0, 0, dx, dy)
      + global.tet_core_rot);
my_x = lengthdir_x(dd, dr) + global.tet_core_px;
my_y = lengthdir_y(dd, dr) + global.tet_core_py;
my_angle = p_check_angle360(360 - my_physics_obj_id.phy_rotation
                            + global.tet_core_rot);
image_xscale = global.tet_core_scale;

これがPhysics触手から描画用触手に座標と角度をコピーしている処理になります。

ここでは全体の回転角やコアの座標を加えたり、腕の伸縮の計算も行っています。


かなりざっくりになりましたが、単にPhysics触手を使うだけならこれくらい簡単にできてしまいます。

一番のポイントは、「Physicsは計算のみに使用して、描画は別に行う」という部分です。
この方法を応用すると、今まで自己計算で行っていた処理をPhysics任せにすることもできるようになります。

GM:SPhysicsには他にも色々な機能があるので、試してみてください。