箱庭ドローンの荷物運搬の設計を刷新しました


現在、大阪万博出展に向けて、箱庭ドローンシミュレータを大リファクタリングしています。今回は、その中でも特に重要な「荷物運搬」の設計を見直し、実装を進めましたので、その詳細をご紹介します。

荷物運搬の新しい設計案

以下の図は、今回の設計案をメモ書きレベルでまとめたものです。

箱庭の荷物運搬では、ドローンの自動着脱を可能にすることを目指しています。具体的には:

  • マグネット機能の実装: ドローンに装着されるマグネット(赤色のオブジェクト)により、荷物の着脱をコントロールします。
  • 荷物側の応答オブジェクト: マグネットに反応するオブジェクトを荷物上部に配置します。

これらの機能は、Unityを用いて実装しますが、箱庭依存を最小限に抑え、独立したテストや動作チェックが可能な構成としています。

デモとシミュレーション風景

この設計を基に再実装し、テストしている様子がこちらです。

  • 荷物の挙動: 初期状態では空中に浮いている荷物が、シミュレーションを開始すると重力の影響で自然に落下します。
  • マグネットのテスト: ドローンに装着されていると仮定したマグネットを上空に浮かべ、Unityエディタ上でリアルタイムに荷物の着脱を試験しています。

Unityエディタの「インスペクタビュー」を利用することで、シミュレーション中にゲームオブジェクトの状態を手動で変更し、スムーズな動作確認を行っています。この仕組みにより、プロトタイピングやデバッグが大幅に効率化されました。

ソースコード

今回のコードでは、荷物を掴むためのマグネット(HakoMagnet)と、その荷物オブジェクト(HakoBaggage)の動作を定義しています。この仕組みにより、ドローンが荷物を自動的にキャッチし運搬する動作を再現できます。

マグネット側:HakoMagnet

using UnityEngine;

public class HakoMagnet : MonoBehaviour
{
    public bool on; // MagnetのOn/Off状態(trueでOn、falseでOff)
    public float detectionRange = 5f; // 想定距離範囲(Magnetが影響を及ぼす範囲)
    private HakoBaggage currentBaggage; // 現在掴んでいるBaggageオブジェクト

    void Start()
    {
        on = false; // 初期状態はOff(Magnetが無効)
    }

    void Update()
    {
        // Magnetの状態に応じて処理を実行
        if (on && currentBaggage == null)
        {
            // MagnetがOnで、まだ何も掴んでいない場合
            FindAndGrabNearestBaggage(); // 近くのBaggageを探して掴む
        }
        else if (!on && currentBaggage != null)
        {
            // MagnetがOffで、現在掴んでいるBaggageがある場合
            ReleaseBaggage(); // Baggageをリリースする
        }
    }

    /// <summary>
    /// MagnetをOnにする
    /// 外部からこの関数を呼び出すことで、Magnetを有効化できる
    /// </summary>
    public void TurnOn()
    {
        on = true;
    }

    /// <summary>
    /// MagnetをOffにする
    /// 外部からこの関数を呼び出すことで、Magnetを無効化できる
    /// </summary>
    public void TurnOff()
    {
        on = false;
    }

    /// <summary>
    /// 想定距離範囲内にいる最も近いHakoBaggageを探し、掴む
    /// </summary>
    private void FindAndGrabNearestBaggage()
    {
        HakoBaggage nearestBaggage = null; // 最も近いBaggageを保持する変数
        float nearestDistance = detectionRange; // 検出範囲(初期値は設定された最大範囲)

        // シーン内に存在するすべてのHakoBaggageオブジェクトを取得
        HakoBaggage[] baggages = FindObjectsByType<HakoBaggage>(FindObjectsSortMode.None);

        foreach (HakoBaggage baggage in baggages)
        {
            // 掴まれていない状態かつ、自分より下に位置しているBaggageのみを対象とする
            if (baggage.IsFree() && baggage.transform.position.y < this.transform.position.y)
            {
                float distance = Vector3.Distance(transform.position, baggage.transform.position); // 自分とBaggage間の距離を計算
                if (distance < nearestDistance) // 距離が現在の最短距離よりも短い場合
                {
                    nearestDistance = distance; // 最短距離を更新
                    nearestBaggage = baggage; // 最も近いBaggageを更新
                }
            }
        }

        // 最も近いBaggageが見つかった場合、掴む
        if (nearestBaggage != null)
        {
            currentBaggage = nearestBaggage; // 現在のBaggageとして記録
            currentBaggage.Grab(this.gameObject); // BaggageのGrabメソッドを呼び出して掴む
        }
    }

    /// <summary>
    /// 現在掴んでいるBaggageをリリースする
    /// </summary>
    private void ReleaseBaggage()
    {
        if (currentBaggage != null) // 現在掴んでいるBaggageが存在する場合
        {
            currentBaggage.Release(); // BaggageのReleaseメソッドを呼び出してリリース
            currentBaggage = null; // 現在掴んでいるBaggageをリセット
        }
    }
}

荷物側:HakoBaggage

using UnityEngine;

/// <summary>
/// HakoBaggageクラス
/// ドローンや他のオブジェクトに運搬される荷物を模したオブジェクトの挙動を管理します。
/// </summary>
public class HakoBaggage : MonoBehaviour
{
    public GameObject parent = null; // 現在の親オブジェクト(運搬元)
    public float speed = 10f; // 移動速度(Lerpの補間速度)
    private Transform initial_parent; // 初期の親オブジェクト(元の状態を戻すため)
    private Rigidbody rd; // Rigidbodyコンポーネントへの参照

    /// <summary>
    /// 初期化処理
    /// </summary>
    void Start()
    {
        // 初期の親オブジェクトを保存
        initial_parent = this.transform.parent;

        // Rigidbodyコンポーネントを取得
        rd = this.GetComponentInChildren<Rigidbody>();
        if (rd == null)
        {
            // Rigidbodyが見つからない場合は例外をスロー
            throw new System.Exception($"Not found Rigidbody on {this.transform.name}");
        }
    }

    /// <summary>
    /// 他のオブジェクトに掴まれる(親として登録される)
    /// </summary>
    /// <param name="grab_parent">掴むオブジェクト</param>
    public void Grab(GameObject grab_parent)
    {
        this.parent = grab_parent; // 親オブジェクトを設定
    }

    /// <summary>
    /// 現在の親オブジェクトからリリースされる(自由な状態になる)
    /// </summary>
    public void Release()
    {
        this.parent = null; // 親オブジェクトをリセット
    }

    /// <summary>
    /// 自由な状態かどうかを確認
    /// </summary>
    /// <returns>自由であればtrue、掴まれていればfalse</returns>
    public bool IsFree()
    {
        return this.parent == null;
    }

    /// <summary>
    /// 毎フレームの更新処理
    /// </summary>
    void Update()
    {
        if (parent != null)
        {
            // 親が設定されている場合(掴まれている状態)
            if (this.transform.parent != parent.transform)
            {
                // 親オブジェクトが変更されていれば更新
                this.transform.parent = parent.transform;
            }

            // Rigidbodyを停止(物理挙動を無効化)
            this.rd.isKinematic = true;

            // 親オブジェクトの位置に向かって補間で移動
            this.transform.position = Vector3.Lerp(
                this.transform.position,
                parent.transform.position,
                Time.deltaTime * speed // 補間速度
            );

            // 親オブジェクトの回転に向かって補間
            this.transform.rotation = Quaternion.Lerp(
                this.transform.rotation,
                parent.transform.rotation,
                Time.deltaTime * speed // 補間速度
            );
        }
        else
        {
            // 親が設定されていない場合(自由な状態)
            this.transform.parent = initial_parent; // 初期の親に戻す
            this.rd.isKinematic = false; // Rigidbodyを再度有効化
        }
    }
}

今回の実装で荷物運搬のコア部分ができましたので、これらを箱庭ドローン側に持って行って呼び出すせば完成です。乞うご期待ください。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

PAGE TOP