【Unity】ワールド座標とローカル座標で関節を回転させる|方向ベクトルを保つ「ツイスト」回転の実装

投稿日: 2026年6月6日(2026年10月5日更新)
対象読者: Unityでボーン/関節を手動回転させるUIを作りたいエンジニア、デッサン人形・ポーズ調整ツールを実装したい方

この記事はこんな方向け:

  • 回転ボタンを押すと、世界の上下左右では動くが「腕の向きを変えずにひねりだけ加えたい」が実現できない方
  • transform.Rotate の Space.World と Space.Self の違いを、実例で理解したい方
  • 回転の基準軸(ギズモ)をボーンに追従表示させたい方

要点

ワールド回転とローカル回転の違いは、transform.Rotate の第3引数(Space.World か Space.Self)だけです。ローカル回転では、回転軸がボーン自身の軸になります。ボーンの向きがローカルの Y 軸(up)に一致しているリグなら、ローカル Y 軸まわりの回転は、**向きを保ったままのひねり(ツイスト)**になります。PoseMirror では、この2つを1つのボタンで切り替え、回る軸を線(ギズモ)で表示しています。

はじめに:ワールド回転だけでは届かない調整

ボーンを手で回す UI を作ると、最初は「X/Y/Z のボタンで、世界の軸まわりに回す」実装になりがちです。分かりやすい反面、すぐに困る場面が出てきます。

たとえば腕をある方向へ向けたあと、「向きはそのままに、前腕のひねり(ロール)だけ少し足したい」。ところがワールドの Y 軸で回すと、腕全体が世界の鉛直軸を中心に振り回され、せっかく合わせた向き(方向ベクトル)まで動いてしまいます。

欲しいのは、方向ベクトルは変えずに、その軸まわりのひねりだけを加える操作です。これはローカル座標での回転で、Unity では Space.Self を渡すだけで実現できます。

基礎:Space.World と Space.Self の違い

transform.Rotate は第3引数で「axis をどの座標系のベクトルとして解釈するか」を指定します。同じ「Y軸で回す」でも、基準の取り方で回転軸がまったく別物になります。

Space.WorldとSpace.Selfの違い:Worldでは軸が世界に固定されボーンが傾いてもYは常に真上なので回すと向きごと動く。Selfでは軸がボーンと一緒に傾き、Y軸がボーンの向きに一致するためY回転は向きを保ったままのツイストになる

これをコードで書き分けると次のとおりです。

// ワールド座標軸まわりに回す:axis は世界の向き
transform.Rotate(Vector3.up, angle, Space.World);

// 自分(ボーン)のローカル軸まわりに回す:axis はボーン基準の向き
transform.Rotate(Vector3.up, angle, Space.Self);
  • Space.World:Vector3.up は常に世界の鉛直方向。ボーンがどんな姿勢でも、回転軸は世界の上方向に固定される
  • Space.Self:Vector3.up は、そのボーンのローカル Y 軸。ボーンが傾いていれば、回転軸も一緒に傾く

実装:1つのフラグでSpace を切り替える

PoseMirror の回転 UI では、X±/Y±/Z± のボタンがすべて共通の回転メソッドを呼びます。そこにモードを表す bool を1つ用意し、Space を出し分けます。次のコードは、実際のコードから要点だけを抜き出したものです。

// false = ワールド座標軸で回転 / true = ボーンのローカル軸で回転(起動時はローカル)
private bool localMode = true;

private void RotateBone(Vector3 axis, float angle)
{
    if (target == null) return;

    // モードに応じて、axis をどの座標系のベクトルとして扱うかを切り替える
    Space space = localMode ? Space.Self : Space.World;

    target.Rotate(axis, angle, space);
}

ボタン側は軸ベクトルを渡すだけです。

public void RotateYPlus() => RotateBone(Vector3.up,    +stepAngle);
public void RotateXPlus() => RotateBone(Vector3.right, +stepAngle);
public void RotateZPlus() => RotateBone(Vector3.forward, +stepAngle);

切り替えはトグルボタンのクリックで行います。onClick はコードからリスナー登録しておくと、シーン側でUnityEventを手配線せずに済みます。

void Start()
{
    if (modeToggleButton != null)
        modeToggleButton.onClick.AddListener(ToggleRotationMode);
    if (modeLabel != null)
        modeLabel.text = localMode ? "Local" : "World";
}

public void ToggleRotationMode()
{
    localMode = !localMode;
    if (modeLabel != null) modeLabel.text = localMode ? "Local" : "World";
    if (axisGizmo != null)  axisGizmo.useLocalAxes = localMode; // ギズモ表示も連動(後述)
}

ワールド/ローカルを切り替えるトグルボタン(操作パネル上部の「World」。下に部位選択・X/Y/Z回転ボタンが並ぶ)

回転の基準軸(ギズモ)をボーンに追従させる

ローカルモードでは、実際に回る軸がボーンと一緒に傾きます。それを目で確かめられるよう、軸の線(X=赤、Y=緑、Z=青)も同じフラグで切り替えます。軸のベクトルを、ワールドに固定するか、ボーンに合わせるかの分岐だけです。

public bool useLocalAxes = false;

void LateUpdate()
{
    if (target == null) return;
    Vector3 origin = target.position;

    // useLocalAxes = true ならボーンのローカル軸、false ならワールド固定軸
    Vector3 ax = useLocalAxes ? target.right   : Vector3.right;
    Vector3 ay = useLocalAxes ? target.up      : Vector3.up;
    Vector3 az = useLocalAxes ? target.forward : Vector3.forward;

    xLine.SetPosition(0, origin); xLine.SetPosition(1, origin + ax * length);
    yLine.SetPosition(0, origin); yLine.SetPosition(1, origin + ay * length);
    zLine.SetPosition(0, origin); zLine.SetPosition(1, origin + az * length);
}

実際の見た目の違いがこちらです。

ワールドモードでは、緑(up)は常に真上を指し、ボーンの姿勢に関係なく軸は世界に固定されています。

ワールドモードの軸ギズモ:緑のupは常に世界の鉛直方向を指す

ローカルモードでは、軸がボーンの姿勢に合わせて傾き、ワールドでは奥(カメラ方向)に隠れていた青(forward)も見えるようになります。

ローカルモードの軸ギズモ:赤・緑がボーンに合わせて傾き、青のforwardも現れる

なぜローカル Y 回転が「方向ベクトルを保つツイスト」になるのか

理由は、リグの軸の取り方にあります。PoseMirror の人形では、ボーンの向き(方向ベクトル)がローカルの Y 軸(up)に一致しています。(ボーンをこの向きに合わせる計算はオブジェクトをターゲット方向に向かせる方法で説明しています。)

つまり、ローカル Y 軸は、ボーンが指している方向そのものです。回転軸の上にあるベクトルは、その軸まわりに回しても動きません。そのため、ローカル Y 軸まわりに回しても、ボーンが指す向きは変わらず、変わるのは軸まわりのひねりだけになります。

一方、ローカルのX軸やZ軸で回せば方向ベクトル自体が動くので、向きごと変えたいときに使えます。同じローカルモードでも、軸の選び方で「ひねり」と「向き変え」を使い分けられるわけです。

つまずいた点

  • ボタンの onClick は、コード側で AddListener する。ボタンを複製したときに残りがちな古いイベント(長押しの連続回転など)を引きずらずに済む
  • 日本語のラベルが「□」になる:UI のフォント(LiberationSans)に日本語の文字が無いため。モード切り替えのラベルは、ほかのボタン(-X/+X など)に合わせて英字の「World」「Local」にした。部位名は、のちに使う文字だけを焼いた日本語フォントを足して日本語で表示している(開発で詰まった不具合の記事の⑤)
  • 胸を回すときは、回した角度を背骨に30%、上胸に30%、胸に残りの40% と分けて回している。この場合も、同じ Space を各ボーンの Rotate に渡せば、両方のモードで動く

まとめ

ワールドとローカルの切り替えは、transform.Rotate の第3引数を出し分けるだけで実装できます。効き方を決めるのは、リグの軸をどう取るかです。ボーンの向きがローカル Y 軸に一致していれば、ローカル Y 回転はひねりになり、X 軸や Z 軸を選べば向きごと変えられます。軸の線を Vector3.* から target.* に切り替えて表示すれば、どの軸で回るのかを画面で確かめられます。

この切り替えは、PoseMirror の関節の手動調整で試せます。

実装したツイスト制御を、実際に動かせます。

関節を回して、方向が保たれる挙動をブラウザで確認できます。

▶ この回転処理が入った3Dデッサン人形を触る

関連記事:

参考資料

← Kabe-Tech トップへ