ハコブくんと看護師が通路でお見合い状態になるのを防ぐための設定
LOG61HRI2026/2/9

ハコブくんと看護師が通路でお見合い状態になるのを防ぐための設定

ハコブくんと看護師さんが通路で「お見合い」状態(お互いに進めなくなって立ち往生する状態)になるのを防ぐための設定を行います。

これは、ハコブくんに「動く障害物」としての属性を与え、看護師さんのAI(NavMesh Agent)がそれをリアルタイムに回避するように設定することで解決するはずです。

ハコブくんに回避の目印を付ける

ハコブくんがそこにいることを、看護師さんのAIに知らせる設定です。

  • Hierarchyで Hakobu_kun を選択します。

  • Inspectorの一番下にある Add Component を押し、NavMesh Obstacle を追加します。

  • 追加されたコンポーネントの Carveという項目にチェックを入れます。

  • Carveとは: ハコブくんがいる場所の「青い床」をリアルタイムで削り取る機能です。看護師さんは「穴が開いた場所(ハコブくんの足元)」を避けて通るようになります。

看護師さんの回避感度を上げる

看護師さんがより機敏にハコブくんを避けるように調整します。

  1. Hierarchyで Nurse を選択します。

  2. NavMesh Agent コンポーネント内の以下の数値を調整してください:

  • Radius(半径): 0.4 〜 0.5 程度(看護師さんのパーソナルスペースです)。

  • Obstacle Avoidance Type: High Quality に変更(より正確に避けるようになります)。

  • Avoidance Priority: ハコブくんより数値を大きく(例:50)します。

※数値が小さいほど優先度が高いため、ハコブくん(デフォルト50)より看護師さんを大きく(例:60)すると、看護師さんが積極的に道を譲るようになります。

NavMesh だけだと、お互いに避けようとして通路の真ん中で「お見合い」をしたり、ぶつかりながら無理やり通り過ぎようとしたりすることがありそうです。

そこで、以前作った「検知して止まる」ロジックを、今の HakobuPatrol スクリプトに合体させます。

ハコブくんのスクリプトに追加する手順

以前のコード(NurseSensor や HakobuMove にあった検知機能)を、現在の HakobuPatrol.cs に組み込みます。

このコードでは、看護師さんが近づくと「物理的に停止」し、「ミントグリーンに発光」する挙動を再現します。

C#

C#
using UnityEngine;

public class HakobuPatrol : MonoBehaviour
{
    [Header("巡回設定")]
    public Transform[] waypoints;
    public float moveSpeed = 2.0f;
    public float rotationSpeed = 5.0f;
    public float stopDistance = 0.5f;

    [Header("看護師検知設定")]
    public Transform nurseTransform; // HierarchyのNurseを割り当て
    public float detectionRange = 2.5f; // 停止する距離
    public Material targetMaterial; // Side_Emission_Matなどを割り当て

    private int currentTargetIndex = 0;
    private Color originalColor;
    private bool isNurseNearby = false;

    // ミントグリーンの発光色設定
    private Color mintGreen = new Color(0.0f, 1.0f, 0.5f, 1.0f);

    void Start()
    {
        // 起動時のエミッション色を保存しておく
        if (targetMaterial != null)
        {
            // HDRカラー(Emission)を取得
            originalColor = targetMaterial.GetColor("_EmissionColor");
        }
    }

    void Update()
    {
        if (waypoints.Length == 0) return;

        CheckForNurse();

        if (isNurseNearby)
        {
            StopAndLightUp();
        }
        else
        {
            ResumePatrol();
            Patrol();
        }
    }

    void CheckForNurse()
    {
        if (nurseTransform == null) return;

        // 看護師さんとの距離を計算
        float distance = Vector3.Distance(transform.position, nurseTransform.position);
        isNurseNearby = (distance < detectionRange);
    }

    void StopAndLightUp()
    {
        // 移動処理を通さないことで「一時停止」状態にする
        if (targetMaterial != null)
        {
            // ミントグリーンで強く光らせる(強度2.0)
            targetMaterial.SetColor("_EmissionColor", mintGreen * 2.0f);
            targetMaterial.EnableKeyword("_EMISSION");
        }
    }

    void ResumePatrol()
    {
        // 元の色(消灯または通常色)に戻す
        if (targetMaterial != null)
        {
            targetMaterial.SetColor("_EmissionColor", originalColor);
        }
    }

    void Patrol()
    {
        Transform target = waypoints[currentTargetIndex];
        Vector3 targetPos = new Vector3(target.position.x, transform.position.y, target.position.z);

        // 1. 移動
        transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);

        // 2. 回転
        Vector3 direction = targetPos - transform.position;
        if (direction != Vector3.zero)
        {
            Quaternion targetRotation = Quaternion.LookRotation(direction);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        }

        // 3. 到着判定
        float distanceToTarget = Vector2.Distance(
            new Vector2(transform.position.x, transform.position.z),
            new Vector2(target.position.x, target.position.z)
        );

        if (distanceToTarget < stopDistance)
        {
            currentTargetIndex = (currentTargetIndex + 1) % waypoints.Length;
        }
    }
}

Unityエディタでの最終セットアップ手順

  • スクリプトの更新: 上記コードを HakobuPatrol.cs に上書き保存します。

  • インスペクターの確認: Hakobu_kun オブジェクトを選択し、以下の枠を埋めます。

  • Nurse Transform: Hierarchy の Nurse をドラッグ。

  • Target Material: Project ウィンドウから Side_Emission_Mat をドラッグ。

マテリアルの設定(重要)

  • Side_Emission_Mat を選択し、Inspector で Emission にチェックが入っていることを確認してください。

  • Global Illumination が None になっていると光が見えにくいので、Realtime などに設定するとより綺麗です。

ハコブくんと看護師が「ぶつかって止まる」問題の解決策

Unity画面-ハコブくんと看護師の衝突

看護師の Obstacle Avoidance(障害物回避)の Priority が 60 に設定されています。

この衝突の原因は、お互いの当たり判定(Collider)が物理的にぶつかってしまい、NavMeshによる回避が間に合っていないことにあります。以下の物理とAIの両面から調整します。

物理的な干渉を防ぐ

  • Is Trigger にチェック: Nurse オブジェクトの Capsule Collider にある Is Trigger にチェックを入れてみます。

  • これを行うことで、物理的に「カツン」とぶつかって止まることがなくなり、NavMesh Agentの計算(AI)だけでスムーズにすれ違えるようになると考えます。

回避の優先順位を明確にする

  • ハコブくんの Priority: Hakobu_kun の NavMesh Obstacle の Priority を 10(非常に高い) にします。

  • 看護師さんの Priority:  80(低い) にします。

  • 効果: これにより、看護師さんが「あ、優先車両(ハコブくん)が来た!」と判断し、より手前から大きく避けるようになります。

回避の半径(Radius)を広げる

  • 看護師さんの Radius を 0.6 程度に少し広げてください。

  • 自分自身の「パーソナルスペース」を広めに設定することで、ハコブくんに接触する前に回避行動を開始します。

NavMeshAgent and NavMeshObstacle components are active at the same time. This can lead to erroneous behavior.というエラーの対処

Unity画面-エラー表示

これは、「自分で進路を決める機能(Agent)と、そこに穴を開ける障害物としての機能(Obstacle)が両方オンになっているよ!」という警告です。これが原因で、ハコブくんがガタガタ震えたり、正常に動かなくなったりする可能性があります。

現在の「ハコブくんが止まって看護師さんを待つ」という仕様を綺麗に動かすための整理を行います。

ハコブくんの「NavMesh Obstacle」をオフにする

  • Hakobu_kun の NavMesh Obstacle コンポーネントの右上にあるチェックボックスを外して OFF にします。

  • これで警告が消え、ハコブくんの動きが安定するはずです。

ハコブくんも看護師を避けるスクリプトの設定で調整

周回路の角でどうしてもうまく動作しないので、ハコブくん側のスクリプトHakobuMove を調整して、看護師をより能動的に避けるように設定してみます。

現在のハコブくんは Nav Mesh Agent の標準機能だけで避けようとしていますが、スクリプトで「看護師が近いときは減速する、または一時停止して道を譲る」といったロジックを追加すると角での衝突が劇的に減るはずです。

C#

using UnityEngine;
using Unity.Robotics.ROSTCPConnector;
using RosMessageTypes.Geometry;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(AudioSource))]
public class HakobuMove : MonoBehaviour
{
    public ROSConnection ros;
    public string topicName = "/cmd_vel";

    private Rigidbody rb;
    private AudioSource audioSource;
    public AudioClip warningSound;
    public GameObject brakeUI;
    
    private bool isWarningPlayed = false;
    private float rawLinearSpeed;
    private float lastAngularVelocity;

    [Header("Sensor Settings")]
    public float sensorRange = 0.8f;
    public float sideRange = 0.3f; // 横の壁判定は少し短めに設定

    [Header("Patrol Settings")]
    public bool isPatrolling = true;
    public float patrolSpeed = 0.2f;
    private int patrolDirection = 1;
    private float stopTimer = 0f;

    void Start()
    {
    rb = GetComponent<Rigidbody>();
    audioSource = GetComponent<AudioSource>();
    
    rb.useGravity = true;

    // Y軸の固定(FreezeRotationY)も追加します
    rb.constraints = RigidbodyConstraints.FreezeRotationX | 
                     RigidbodyConstraints.FreezeRotationY | 
                     RigidbodyConstraints.FreezeRotationZ;

    if (ros == null) { ros = ROSConnection.GetOrCreateInstance(); }
    ros.Subscribe<TwistMsg>(topicName, MoveHakobu);

    // パトロール開始時にUIを確実に消しておく
    if (brakeUI != null) { brakeUI.SetActive(false); }

    Debug.Log("ハコブくん、全システム起動。パトロール開始時はUIを待機状態にします。");

    }

    void MoveHakobu(TwistMsg msg)
    {
        rawLinearSpeed = (float)msg.linear.x; 
        lastAngularVelocity = -(float)msg.angular.z * Mathf.Rad2Deg;
    }

    void FixedUpdate()
{
    float powerMultiplier = 3.0f;
    
    // --- 1. 速度の意思決定 ---
    float desiredSpeed = rawLinearSpeed; 
    if (isPatrolling)
    {
        desiredSpeed = patrolSpeed * patrolDirection;
    }

    float actualSpeed = desiredSpeed;

    Vector3 center = transform.position;
    RaycastHit hit;

    // レイヤーマスクの設定(Nurseレイヤーだけを特別扱いしたい場合に使用)
    int nurseLayerMask = LayerMask.GetMask("Nurse"); 

    // 各方向のセンサー
    bool frontObs = Physics.Raycast(center + transform.forward * 0.4f, transform.forward, out hit, sensorRange);
    
    // 【追加】目の前にいるのが「看護師」かどうかを判定
    bool isNurseAhead = false;
    if (frontObs && hit.collider.CompareTag("Nurse")) // Nurseオブジェクトに "Nurse" タグをつけておいてください
    {
        isNurseAhead = true;
    }

    bool backObs = Physics.Raycast(center - transform.forward * 0.4f, -transform.forward, out hit, sensorRange);
    bool leftObs = Physics.Raycast(center - transform.right * 0.4f, -transform.right, out hit, sideRange);
    bool rightObs = Physics.Raycast(center + transform.right * 0.4f, transform.right, out hit, sideRange);

    // --- 2. 回避ロジックの調整 ---
    // 横壁の判定:もし目の前に看護師がいるなら、多少横が狭くても止まらずに進む(回避行動を優先)
    bool isSideTooClose = (leftObs || rightObs) && !isNurseAhead;

    // 前方ブロック判定:看護師の場合は「停止」ではなく「超低速」にして、NavMesh Agentの回避機能に任せる
    bool isPathBlocked = (frontObs && desiredSpeed > 0 && !isNurseAhead) || (backObs && desiredSpeed < 0);

    bool isBraking = false;

    if (isPathBlocked || isSideTooClose)
    {
        actualSpeed = 0;
        isBraking = true;
    }
    else if (isNurseAhead)
    {
        // 看護師が前にいるときは、止まらずに非常にゆっくり進む(NavMeshが避ける時間を作る)
        actualSpeed = desiredSpeed * 0.3f; 
        isBraking = false; // UIやアラームは鳴らさない(またはお好みで)
    }

    // --- 3. UIと物理適用(以下、元のロジックを維持) ---
    if (brakeUI != null) { brakeUI.SetActive(isPathBlocked); }

    if (isBraking)
    {
        if (!isWarningPlayed && warningSound != null)
        {
            audioSource.PlayOneShot(warningSound);
            isWarningPlayed = true;
        }

        if (isPatrolling && isPathBlocked)
        {
            stopTimer += Time.fixedDeltaTime;
            if (stopTimer > 1.0f) 
            {
                patrolDirection *= -1;
                stopTimer = 0f;
            }
        }
    }
    else
    {
        isWarningPlayed = false;
        stopTimer = 0f;
    }

    rb.linearVelocity = transform.forward * (actualSpeed * powerMultiplier);
    rb.angularVelocity = new Vector3(0, lastAngularVelocity * Mathf.Deg2Rad, 0);

    // デバッグ表示(そのまま)
    Debug.DrawRay(center + transform.forward * 0.4f, transform.forward * sensorRange, isNurseAhead ? Color.yellow : (frontObs ? Color.red : Color.green));
    // ...残りのDrawRay...
}
} // ← この最後の閉じカッコが重要です!

修正のポイント

コード内の hit.collider.CompareTag("Nurse") を動作させる必要があります。

  • Hierarchyで Nurse オブジェクトを選択します。

  • Inspectorの一番上にある Tag 項目をクリックし、「Nurse」 を選択します。

※もしリストに "Nurse" がない場合は、Add Tag... から新規作成して割り当てます。

レイヤーの確認

コード内に int nurseLayerMask = LayerMask.GetMask("Nurse"); という記述があります(現状のロジックでは直接使われていませんが、今後拡張する際に重要です)。

  • 看護師オブジェクトの Layer が 「Nurse」 になっているか確認します。