UnityでmechArm 10体をアルゴリズムで個体差設計する|マルチロボットシミュレーション
すべての個体が鏡合わせのように全く同じタイミングで、同じ軌跡を描いて動いていました。この「完璧すぎる同期」は、シミュレーション上では正解でも、運用者の目にはかえって不自然な、冷ややかなノイズとして映ります。開発の現場で直面したのは、この機械的な一貫性をいかに崩し、各個体に「生命感ある揺らぎ」を宿すかという課題でした。
RobotPersonality.cs の改修
統一された挙動を解体するオフセットの導入
一律の動きを規定していたロジックに対し、個体ごとに異なる時間軸と振幅を与えるためのパラメータを組み込みました。
実装のステップ: Hierarchy上の各ロボットが生成されるタイミングで Random.value を呼び出し個体固有の Speed と Phase Offset を生成して内部変数に保持させた上で Update 関数内のサイン波計算にこれらの係数を乗算し各個体の関節が異なる周期で駆動するようコードを書き換えました。
設計の意図: 全個体が同じコードを共有しながらも、内部に保持する「記憶(変数)」をランダム化することで、計算負荷を抑えたまま視覚的な多様性を生み出すためです。これにより、一斉に動き出すのではなく、バラバラのリズムで呼吸を始めるような自然な群れの動きを目指しました。
物理パラメータの固定
物理憲法と制御ロジックの分離
個性を出す過程で最も重要視したのは、ロボットの安定性を支える「物理的な骨格」を汚さないことです。
実装のステップ: ArticulationBody コンポーネントにアクセスする際は XDrive の Target 値の変更のみに限定しプロジェクトの物理憲法で定められた Stiffness の 20,000 および Damping の 2,000 という数値には一切触れずに制御計算のみを完結させました。
設計の意図: 物理パラメータ(剛性と減衰)は、いわばロボットの「肉体的な限界」を定義する憲法です。ここを動的に書き換えてしまうと、個体ごとに挙動の信頼性が崩れ、予期せぬ発振や崩壊を招く恐れがあります。ソフトウェア的な「命令(Target)」と物理的な「耐性(Stiffness/Damping)」を厳格に分離することで、安全性を担保したまま表現の幅を広げました。
Clineへの指示プロンプト
以下の内容をClineへ伝えます。
Markdown
# タスク:RobotPersonality.cs の高度化(個体別の非同期動作)
## 1. 現状の動作仕様 現在、10台のロボットは以下のルールで一斉に動いています。 * 右側の個体群:左右方向の反復運動 * 左側の個体群:上下方向の反復運動
## 2. 修正内容:個体別の「ゆらぎ」の実装 全てのロボットが軍隊のように揃って動くのではなく、以下の要素を `RobotPersonality.cs` に追加して個体差を生んでください。 動作の非同期化: `Start()` 時に各インスタンス固有の `_timeOffset = Random.Range(0f, 10f);` を生成し、`Update()` 内の計算に使用してください。 スピードの多様化**: 一律の速度ではなく、基本速度に対して ±20% 程度のランダムな倍率を個別に持たせてください。 * **実装の注意**: 既存の「右は左右、左は上下」という基本ルールは維持したまま、その動きの「タイミング(位相)」と「速さ」に個性を出してください。
## 3. 物理憲法の遵守(絶対条件) ArticuationBody の保護: いかなる理由があっても、スクリプトから Stiffness (20,000) や Damping (2,000) を上書き・変更してはいけません。制御方式: TargetPosition または TargetVelocity の書き換えのみで「動きの個性」を表現してください。
## 4. 完了報告 作業完了後、GitHubのコミットメッセージ(箇条書き形式)を出力してください。
今回の改修により、実験場 Experimental_Setup_Day1 に並ぶ10台は、単なるコピーではなく、それぞれが固有のリズムで動く「個体」へと進化しました。このわずかなリズムのズレが、運用者にとっては「どのロボットがどの状態にあるか」を直感的に識別する手がかりとなり、システムへの信頼感をより強固なものへと変えていくはずです。
差をもう少し出してわかりやすくする。
現状、individualDelay の差が小さく、動きが揃って見えてしまっているようですね。
Clineへの修正指示プロンプト
Markdown
# タスク:RobotPersonality.cs のさらなる個性化(動きの多様化)
## 1. 現状の課題 各個体の動きに差はついているが、微差であるため視覚的に判別しづらい。よりダイナミックな個性を追加してください。
## 2. 修正内容:3つの強化ポイント 以下の変更を `RobotPersonality.cs` に加えてください。
1. 位相(タイミング)の拡大: `individualDelay` の範囲を広げ、各ロボットが完全にバラバラなタイミングで動くようにしてください(例:`Random.Range(0f, Mathf.PI * 2)`)。
2. サブ動作の追加(第3の関節): 現在は1つの関節のみ動かしていますが、右側なら「上下+先端の微動」、左側なら「左右+第2関節の微動」のように、別の関節(例:`link3`)も連動させてください。
3. 振幅のランダム化: 一律の `m_verticalSwingRange` ではなく、個体ごとに±30%程度の振幅の差を持たせてください。
## 3. 物理憲法の厳守(絶対) 不変**: ArticulationBody の Stiffness (20,000) と Damping (2,000) は絶対に書き換えないこと。 安全: 動作角が大きくなりすぎないよう、関節の可動域内での制御を徹底してください。
## 4. 完了報告 作業完了後、GitHubコミット用の要約を出力してください。
RobotPersonality.cs
using UnityEngine;
public class RobotPersonality : MonoBehaviour
{
// 物理パラメータの保護定数(憲法遵守)
private const float JOINT_STIFFNESS = 20000f;
private const float JOINT_DAMPING = 2000f;
private const float JOINT_FORCE_LIMIT = 1000f;
private const float MASS = 1.0f;
private const float ANGULAR_DRAG = 0.05f;
[Header("Movement Parameters")]
[SerializeField] private float m_speedMultiplier = 1.0f;
[SerializeField] private float m_horizontalSwingRange = 30.0f;
[SerializeField] private float m_verticalSwingRange = 30.0f;
private const int BASE_JOINT_INDEX = 1;
private const int SHOULDER_JOINT_INDEX = 2;
private ArticulationBody[] joints;
private float m_individualPhaseOffset;
private float m_individualSpeedOffset;
void Start()
{
joints = GetComponentsInChildren<ArticulationBody>();
ApplyProtectedPhysicsParameters();
// 完全にバラバラにするためのランダム設定
m_individualPhaseOffset = Random.Range(0f, Mathf.PI * 2);
m_individualSpeedOffset = Random.Range(0.5f, 2.0f);
}
void Update()
{
if (joints == null || joints.Length <= SHOULDER_JOINT_INDEX) return;
float time = Time.time * m_speedMultiplier * m_individualSpeedOffset + m_individualPhaseOffset;
// 【上下左右ミックス制御】
// 左右(Link1)の駆動
var baseDrive = joints[BASE_JOINT_INDEX].xDrive;
baseDrive.target = Mathf.Sin(time) * m_horizontalSwingRange;
joints[BASE_JOINT_INDEX].xDrive = baseDrive;
// 上下(Link2)の駆動
var shoulderDrive = joints[SHOULDER_JOINT_INDEX].xDrive;
shoulderDrive.target = Mathf.Cos(time * 0.8f) * m_verticalSwingRange;
joints[SHOULDER_JOINT_INDEX].xDrive = shoulderDrive;
}
private void ApplyProtectedPhysicsParameters()
{
foreach (var joint in joints)
{
if (joint == null) continue;
joint.mass = MASS;
joint.angularDamping = ANGULAR_DRAG;
if (joint.jointType != ArticulationJointType.FixedJoint)
{
var drive = joint.xDrive;
drive.stiffness = JOINT_STIFFNESS;
drive.damping = JOINT_DAMPING;
drive.forceLimit = JOINT_FORCE_LIMIT;
joint.xDrive = drive;
}
}
}
}