2007年12月1日土曜日

相互作用が働く粒子の運動

HSPコンテスト2007にて株式会社秀和システム章を受賞しました。
おにたまさんから「ある意味ではモジュールの達人」と言っていただけまして、とても嬉しいです。モジュール(変数)は1時期とっても勉強した部分なので……。
モジュール変数の扱いをもっと勉強していきたいですね。

というわけで受賞記念に、得意(?)のモジュールを利用したスクリプトを。
「遠い時はくっつきたがるけど、近い時は離れたがる」という特殊な粒子の運動のシミュレートです。現実では原子がこれにあたります。3次元ですが。

ひとつのモジュール変数でひとつの粒子を表しています。決まった距離を保つために、距離の2乗に従って速度を算出しています。力学のポテンシャルの考え方に近いでしょうか。

粒子数が同じなら、最初の条件が違っても大抵は同じ陣形に収束するのが面白いです。
#module member id, x, y, vx, vy
#include "hspmath.as"
// id     ... 個別認識用ID(int)
// x, y   ... 位置(double)
// vx, vy ... 速度(double)
#const  PARTY_DIST  120     // 仲間と保とうとする距離
#const  MAX_SPEED   4       // 最大スピード
#const  RADIUS      5       // 描画時の半径

#modinit int _x, int _y
    x = _x : y = _y
    id = next_id : next_id++
    return

#deffunc make_member double new_x, double new_y
    newmod members, member, new_x, new_y
    return

// -----------------------------
// ここから各メンバーごとに用いる命令

// IDを返す
#modfunc get_id
    return id

// 位置を返す
#modfunc get_position array v
    v = x, y
    return

// 自分自身の速度の計算
#modfunc calc_speed
    ax = 0.0 : ay = 0.0
    // 仲間との相互作用(一定の距離を保とうとする)
    foreach members
        // 自分自身とは判定を行わない
        get_id members(cnt)
        if id == stat : continue

        // 仲間との距離を計算
        this_member_pos = x, y
        get_position members(cnt), target_member_pos
        dist = distance2(this_member_pos, target_member_pos)    // 対象の仲間との距離(double)

        // 仲間との角度を計算
        theta = atan(y - target_member_pos(1), target_member_pos(0) - x)

        // 距離に応じて加速
        s = (dist - PARTY_DIST) * (dist - PARTY_DIST) * sgn(dist - PARTY_DIST) / 150
        ax += cos(theta) * s
        ay += sin(theta) * s
    loop

    // 速度の変更
    vx += ax : vy -= ay
    // 速度が大きすぎる場合は調節する
    velo = sqrt(vx * vx + vy * vy)
    if absf(velo) > MAX_SPEED {
        velo = limitf(velo, -MAX_SPEEDMAX_SPEED)
        theta = atan(vy, vx)
        vx = cos(theta) * velo
        vy = sin(theta) * velo
    }
    return

// 自分自身の移動
#modfunc move
    x = limitf(x + vx, 0ginfo_winx)
    y = limitf(y + vy, 0ginfo_winy)
    return

// 自分自身を描画
#modfunc draw
    circle x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS
    return

// -----------------------------
// ここから外部から呼び出す群れ全体を扱う命令

// 全員の速度を計算
#deffunc calc_all_speed
    foreach members
        calc_speed members(cnt)
    loop
    return

// 全員の移動
#deffunc move_all
    foreach members
        move members(cnt)
    loop
    return

// 全員の描画
#deffunc draw_all
    foreach members
        draw members(cnt)
    loop
    return

#global
    next_id@member = 0  // IDの初期化

// -----------------------------
// モジュールここまで

#const NUM_MEMBERS  12  // 群れを構成するメンバーの数
    // 群れのメンバーをランダムな位置に発生
    randomize
    repeat NUM_MEMBERS
        make_member rnd(ginfo_winx), rnd(ginfo_winy)
    loop

*main
    calc_all_speed  // 全員の速度を計算
    move_all        // 全員を移動
    redraw 0
        color : boxf : color ,,255
        draw_all        // 全員を描画
    redraw 1
    wait 3
    goto *main

2 件のコメント:

匿名 さんのコメント...

コンテスト受賞おめでとうございます
最近、気付いたんですけど
応募作品に比して受賞者の数が
けっこう多いみたいですね

僕は完成でえきなかったけど
来年もきっとあると想定して
スクリプト下書きを
コツコツ続けていきたいですね

ellerさんは作成に何時間くらいかかったんですか?
僕はというと、20時間くらいしかやってなかった気がします
ゲームじゃないんだから、20時間じゃショボショボですね

eller さんのコメント...

ありがとうございます。
主催者の方々が受賞者数を多くするために配慮してくださっているのではないかと思います。
次回はぜひりすとさんもご参加をー。

作成にかかった時間はちょっとわからないです。もともとは2次元ではねる輪を作って、きれいだったので3次元にしたのですが。
私としてはゲームをつくる方が時間がかかるし難しい気がしていますけどね。