[GameMaker: Studio] スプライン曲線を使った移動(修正あり)

*2017/2/12 – ver 0.9.0

  • 一部計算式が間違っていたので修正しました。
  • 若干ですが計算量を減らして高速化しました。
  • 返値のlengthを、現ポイントから次ポイントの長さに変更しました。

なんとなく思いつきで作ってみたのですが、せっかくなので公開してみます。

このコードはedo_m18氏が書かれた下記の記事をベースに作成しています。
http://qiita.com/edo_m18/items/f2f0c6bf9032b0ec12d4


まずこのサンプルを試してみてください。
*中継点の情報表示は間引いてます。このサンプルの表示上のangleは不正確なので無視してください。返される配列内のangleは正確です。

クリックすると中継点を設置します。中継点は最大で10個までに制限しています。
中継点をクリックすると消去することができます。

[Crease][Smooth]で曲線の解像度(Resolution)を変更することができます。
低いほど折れ線に近くなり計算量が減りますが、配置によっては中継点に届かずに曲がってしまいます。
高くすると滑らかになりますが、計算量が増大します。


■基本的な使用方法

【1】 ゲームの初期化処理でecllib_script_spline_init()を一度だけ呼び出します。

【2】 座標を格納するためのds_listを2つ作成します。一つはX座標、もう一つはY座標を格納するためのものです。

【3】 2で作成したds_listに、始点~中継点(任意数)~終点のXY座標を順番に格納します。

【4】 変数n = ecllib_script_spline_make(引数)を呼び出します。引数は以下の通りです。

  • resolution : 曲線解像度。始点から終点までをこの数値分の直線で分割します。最低値は3で、大きいほど滑らかになりますが計算量が増加します。
  • pos-x ds_list id : X座標が格納されたds_listのIDです。
  • pos-y ds_list id : Y座標が格納されたds_listのIDです。

【5】 4で変数nにはds_gridのIDが返されます。
ds_gridのwidth列は曲線解像度と同数です。曲線解像度が50ならds_gridのwidth列は0~49となります。
ds_gridのheight行には各ポイントの情報が格納されます。格納内容は以下の通りです。

  • global.ecllib_const_spline_pointarray_grid_x : X座標です。
  • global.ecllib_const_spline_pointarray_grid_y : Y座標です。
  • global.ecllib_const_spline_pointarray_grid_distance : このポイントまでの累計距離です。終点のこの値が全体の距離になります。
  • global.ecllib_const_spline_pointarray_grid_angle : このポイントから次のポイントを見た角度です。Degree(0~360度)で真右が0度で反時計回り(CCW)です。
  • global.ecllib_const_spline_pointarray_grid_length : 直前このポイントからこの次のポイントまでの距離です。
  • global.ecllib_const_spline_pointarray_grid_ratio : length/全体距離です。

【6】 5の情報を使って、必要な移動や描画を行います。

【7】 ds_grid_destroy(変数n)で結果を破棄します。これを忘れるとメモリリークの原因となりますので注意してください。

【8】 必要に応じて2で作成した2つのds_listをクリアするか破棄します。

【9】 2、もしくは3に戻ります。


一番上のサンプルではポイントを単純に辿っていますが、各ポイント間を補間すると解像度を低くしても滑らかに移動させることができます。

ratioの値で移動速度を補正すると全体距離やポイント間距離に影響されず一定速度で移動させることも可能です。

もしroomサイズが不定なら、XY座標の代わりにテクスチャのUV座標のように0.0~1.0の値を渡してもいいかもしれません。


[ecllib_script_spline_init]

///ecllib_script_spline_init()
{    global.ecllib_const_spline_libver = "0.9.0";
    global.ecllib_const_spline_libdate = "2017/02/12 07:00";    // ymdhi

    global.ecllib_const_spline_pointarray_grid_x            = 0;
    global.ecllib_const_spline_pointarray_grid_y            = 1;
    global.ecllib_const_spline_pointarray_grid_distance     = 2;
    global.ecllib_const_spline_pointarray_grid_angle        = 3;
    global.ecllib_const_spline_pointarray_grid_length       = 4;
    global.ecllib_const_spline_pointarray_grid_ratio        = 5;
    global.ecllib_const_spline_pointarray_grid_max          = 6;

    global.ecllib_const_spline_local_splprm_0x = 0;
    global.ecllib_const_spline_local_splprm_0y = 1;
    global.ecllib_const_spline_local_splprm_1x = 2;
    global.ecllib_const_spline_local_splprm_1y = 3;
    global.ecllib_const_spline_local_splprm_2x = 4;
    global.ecllib_const_spline_local_splprm_2y = 5;
    global.ecllib_const_spline_local_splprm_3x = 6;
    global.ecllib_const_spline_local_splprm_3y = 7;
    global.ecllib_const_spline_local_splprm_max = 8;

    global.ecllib_const_spline_debug_tick_start = 0;
    global.ecllib_const_spline_debug_tick_end = 0;
    global.ecllib_const_spline_debug_tick_time = 0;
}

[ecllib_script_spline_make]

///ecllib_script_spline_make(resolution,pos-x ds_list-id,pos-y ds_list-id)
//-----------------------------------
// [References]
// Generation of cubic spline curve(Japanese) - http://qiita.com/edo_m18/items/f2f0c6bf9032b0ec12d4
//-----------------------------------
{
    var local_resolution = floor(argument0);
    var local_basearray_x = argument1;
    var local_basearray_y = argument2;

    global.ecllib_const_spline_debug_tick_start = get_timer();
    global.ecllib_const_spline_debug_tick_end = global.ecllib_const_spline_debug_tick_start;
    global.ecllib_const_spline_debug_tick_time = 0;

    var local_const_0 = 0.0;
    var local_const_1 = 1.0;
    var local_const_2 = 2.0;
    var local_const_3 = 3.0;
    var local_const_4 = 4.0;
    if(local_resolution < 3) local_resolution = 3;
    local_resolution--;
    var local_st = 1.0 / local_resolution;
    var local_retarray = ds_grid_create(local_resolution + 1, global.ecllib_const_spline_pointarray_grid_max);
    var local_i = local_const_0;
    var local_j = local_const_0;
    var local_d = local_const_0;
    var local_l = local_const_0;
    var local_k = true;
    var local_b0 = local_const_0;
    var local_b1 = local_const_0;
    var local_b2 = local_const_0;
    var local_vx = local_const_0;
    var local_vy = local_const_0;
    var local_nx = local_const_0;
    var local_ny = local_const_0;
    var local_splsiz = ds_list_size(local_basearray_x);
    var local_splmax = local_splsiz - 1;
    var local_splmax2 = local_splsiz - 2;
    var local_splbuf = ds_grid_create(local_splsiz, global.ecllib_const_spline_local_splprm_max);
    ds_grid_clear(local_splbuf, local_const_0);
    for(local_i=1; local_i<local_splmax; local_i++) {
        local_nx = ds_grid_get(local_splbuf, local_i - local_const_1, global.ecllib_const_spline_local_splprm_0x);
        local_ny = ds_grid_get(local_splbuf, local_i - local_const_1, global.ecllib_const_spline_local_splprm_0y);
        local_vx = local_const_4 - local_nx;
        local_vy = local_const_4 - local_ny;
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2x, (
                    ((ds_list_find_value(local_basearray_x, local_i - local_const_1) - 
                    (ds_list_find_value(local_basearray_x, local_i) * local_const_2) + 
                    ds_list_find_value(local_basearray_x, local_i + local_const_1)) * local_const_3) - 
                    ds_grid_get(local_splbuf, local_i - local_const_1, global.ecllib_const_spline_local_splprm_2x)) / local_vx);
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2y, (
                    ((ds_list_find_value(local_basearray_y, local_i - local_const_1) - 
                    (ds_list_find_value(local_basearray_y, local_i) * local_const_2) + 
                    ds_list_find_value(local_basearray_y, local_i + local_const_1)) * local_const_3) - 
                    ds_grid_get(local_splbuf, local_i - local_const_1, global.ecllib_const_spline_local_splprm_2y)) / local_vy);
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_0x, local_const_1 / local_vx);
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_0y, local_const_1 / local_vy);
    }
    for(local_i=local_splmax2; local_i>0; local_i--) {
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2x, 
                    ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2x) - 
                    (ds_grid_get(local_splbuf, local_i + local_const_1, global.ecllib_const_spline_local_splprm_2x) * 
                    ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_0x)));
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2y, 
                    ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2y) - 
                    (ds_grid_get(local_splbuf, local_i + local_const_1, global.ecllib_const_spline_local_splprm_2y) * 
                    ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_0y)));
    }
    for(local_i=0; local_i<local_splmax; local_i++) {
        local_nx = ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2x);
        local_ny = ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_2y);
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_3x, 
                    (ds_grid_get(local_splbuf, local_i + local_const_1, global.ecllib_const_spline_local_splprm_2x) - local_nx) / local_const_3);
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_3y, 
                    (ds_grid_get(local_splbuf, local_i + local_const_1, global.ecllib_const_spline_local_splprm_2y) - local_ny) / local_const_3);
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_1x, 
                    ds_list_find_value(local_basearray_x, local_i + local_const_1) - ds_list_find_value(local_basearray_x, local_i) - 
                    local_nx - ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_3x));
        ds_grid_set(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_1y, 
                    ds_list_find_value(local_basearray_y, local_i + local_const_1) - ds_list_find_value(local_basearray_y, local_i) - 
                    local_ny - ds_grid_get(local_splbuf, local_i, global.ecllib_const_spline_local_splprm_3y));
    }
    local_i = local_const_0;
    local_vx = ds_list_find_value(local_basearray_x, 0);
    local_vy = ds_list_find_value(local_basearray_y, 0);
    ds_grid_set(local_retarray, 0, global.ecllib_const_spline_pointarray_grid_x, local_vx);
    ds_grid_set(local_retarray, 0, global.ecllib_const_spline_pointarray_grid_y, local_vy);
    ds_grid_set(local_retarray, 0, global.ecllib_const_spline_pointarray_grid_distance, local_const_0);
    while(local_k) {
        local_j++;
        local_i = clamp(local_i + local_st, local_const_0, local_const_1);
        if(local_i >= local_const_1) local_k = false;
        local_b0 = local_splmax * local_i;
        local_b1 = floor(local_b0);
        local_b2 = local_b0 - local_b1;
        local_nx = ((ds_grid_get(local_splbuf, local_b1, global.ecllib_const_spline_local_splprm_3x) * local_b2 + 
                    ds_grid_get(local_splbuf, local_b1, global.ecllib_const_spline_local_splprm_2x)) * local_b2 + 
                    ds_grid_get(local_splbuf, local_b1, global.ecllib_const_spline_local_splprm_1x)) * local_b2 + 
                    ds_list_find_value(local_basearray_x, local_b1);
        local_ny = ((ds_grid_get(local_splbuf, local_b1, global.ecllib_const_spline_local_splprm_3y) * local_b2 + 
                    ds_grid_get(local_splbuf, local_b1, global.ecllib_const_spline_local_splprm_2y)) * local_b2 + 
                    ds_grid_get(local_splbuf, local_b1, global.ecllib_const_spline_local_splprm_1y)) * local_b2 + 
                    ds_list_find_value(local_basearray_y, local_b1);
        local_l = point_distance(local_vx, local_vy, local_nx, local_ny);
        local_d += local_l;
        ds_grid_set(local_retarray, local_j, global.ecllib_const_spline_pointarray_grid_x, local_nx);
        ds_grid_set(local_retarray, local_j, global.ecllib_const_spline_pointarray_grid_y, local_ny);
        ds_grid_set(local_retarray, local_j, global.ecllib_const_spline_pointarray_grid_distance, local_d);
        ds_grid_set(local_retarray, local_j - local_const_1, global.ecllib_const_spline_pointarray_grid_length, local_l);
        local_vx = local_nx;
        local_vy = local_ny;
    }
    ds_grid_set(local_retarray, local_j, global.ecllib_const_spline_pointarray_grid_length, local_const_0);
    ds_grid_destroy(local_splbuf);
    local_vx = ds_grid_get(local_retarray, 0, global.ecllib_const_spline_pointarray_grid_x);
    local_vy = ds_grid_get(local_retarray, 0, global.ecllib_const_spline_pointarray_grid_y);
    for(local_i=1; local_i<=local_resolution; local_i++) {
        local_nx = ds_grid_get(local_retarray, local_i, global.ecllib_const_spline_pointarray_grid_x);
        local_ny = ds_grid_get(local_retarray, local_i, global.ecllib_const_spline_pointarray_grid_y);
        ds_grid_set(local_retarray, local_i - local_const_1, global.ecllib_const_spline_pointarray_grid_angle, 
                    point_direction(local_vx, local_vy, local_nx, local_ny));
        ds_grid_set(local_retarray, local_i - local_const_1, global.ecllib_const_spline_pointarray_grid_ratio, 
                    ds_grid_get(local_retarray, local_i - local_const_1, global.ecllib_const_spline_pointarray_grid_length) / local_d);
        local_vx = local_nx;
        local_vy = local_ny;
    }

    global.ecllib_const_spline_debug_tick_end = get_timer();
    global.ecllib_const_spline_debug_tick_time = (global.ecllib_const_spline_debug_tick_end - global.ecllib_const_spline_debug_tick_start) / 1000.0;

    return local_retarray;
}