Unityを触り始めた頃って、つい何でもかんでも Update() に書いてしまいがちですよね。
プレイヤーの移動、カメラの追従、武器の回転、アニメ再生、入力処理…全部ひとつのスクリプトに押し込んでしまうと、だんだん何をどこで書いたのか分からなくなってきます。

特に「武器の見た目の制御(どこを中心に回転させるか)」までプレイヤーのスクリプトに混ぜてしまうと、

  • キャラごとに持ち手の位置が違うたびにコードを書き換える
  • 武器を変えるたびにオフセット計算がぐちゃぐちゃになる
  • アニメーターで肩の位置を変えたら、全部の計算がずれる

といった地獄が待っています。

そこでこの記事では、「WeaponPivot(武器持ち手)」コンポーネントを作って、
「親オブジェクトの中心」ではなく「肩の位置」など任意のポイントを中心に武器スプライトを回転させる方法を、コンポーネント指向でスッキリ分離して実装していきます。

【Unity】肩を軸に武器を振り回す!「WeaponPivot」コンポーネント

この WeaponPivot コンポーネントの役割はシンプルです。

  • 「持ち手(肩・手首など)」の Transform を基準に
  • マウスや入力方向に向けて武器を回転させる
  • 2D/トップダウンなどでよくある「向いている方向に武器を構える」処理をひとまとめにする

プレイヤー本体のスクリプトからは、
「武器の見た目をどう回転させるか」という責務を切り離して、
WeaponPivot が肩を中心にクルクル回す役を担当するイメージですね。


フルコード:WeaponPivot.cs


using UnityEngine;

/// <summary>
/// 武器スプライトを「親の中心」ではなく、
/// 任意の「持ち手(肩・手首など)」を中心に回転させるコンポーネント。
/// 
/// - 2Dゲーム想定(Z軸回転)
/// - マウスカーソル方向、または任意のターゲット方向を向く
/// - 左右反転にも対応(キャラの向きに合わせて武器を反転)
///
/// 責務を「武器の見た目の回転」に限定しているので、
/// 入力処理や攻撃ロジックとは分離して使うことを想定しています。
/// </summary>
[ExecuteAlways] // エディタ上でも動作させて、調整しやすくする
public class WeaponPivot : MonoBehaviour
{
    // --- 設定項目 ---

    [Header("回転の基準となる持ち手(肩・手首など)")]
    [Tooltip("このTransformを回転の中心として扱います。通常は肩や手首のボーン、または子オブジェクト。")]
    [SerializeField] private Transform pivotPoint;

    [Header("回転対象の武器オブジェクト")]
    [Tooltip("回転させたい武器のTransform。未指定なら、このコンポーネントが付いているオブジェクト自身を使います。")]
    [SerializeField] private Transform weaponTransform;

    [Header("方向の決め方")]
    [Tooltip("true: マウスカーソル方向を見る / false: 任意のターゲットTransform方向を見る")]
    [SerializeField] private bool useMouseDirection = true;

    [Tooltip("useMouseDirection = false のとき、ここに指定したTransform方向を向きます。")]
    [SerializeField] private Transform lookTarget;

    [Header("回転設定")]
    [Tooltip("回転スピード。0なら即座に向きます。")]
    [SerializeField] private float rotateSpeed = 720f;

    [Tooltip("回転に加えるオフセット角度(度数法)。武器スプライトの向き調整用。")]
    [SerializeField] private float angleOffset = 0f;

    [Header("左右反転設定")]
    [Tooltip("キャラクターの向きに合わせて武器を左右反転するかどうか。")]
    [SerializeField] private bool autoFlipByCharacterFacing = true;

    [Tooltip("キャラクターの左右反転を判定するTransform。通常はキャラ本体。")]
    [SerializeField] private Transform characterRoot;

    [Tooltip("右向きのときのローカルスケール。左右反転時にXだけ反転します。")]
    [SerializeField] private Vector3 weaponLocalScaleRight = Vector3.one;

    // 内部用:左向きのローカルスケール(Xを反転したもの)
    private Vector3 weaponLocalScaleLeft;

    // エディタ上で値が変わったときにも呼ばれる
    private void OnValidate()
    {
        // weaponTransform が未設定なら自分自身を使う
        if (weaponTransform == null)
        {
            weaponTransform = transform;
        }

        // characterRoot が未設定なら親階層から探す
        if (characterRoot == null)
        {
            characterRoot = transform.root;
        }

        // 左向きスケールを計算
        weaponLocalScaleLeft = new Vector3(
            -weaponLocalScaleRight.x,
            weaponLocalScaleRight.y,
            weaponLocalScaleRight.z
        );
    }

    private void Awake()
    {
        // 実行時にも念のため同じ初期化を行う
        if (weaponTransform == null)
        {
            weaponTransform = transform;
        }

        if (characterRoot == null)
        {
            characterRoot = transform.root;
        }

        weaponLocalScaleLeft = new Vector3(
            -weaponLocalScaleRight.x,
            weaponLocalScaleRight.y,
            weaponLocalScaleRight.z
        );
    }

    private void LateUpdate()
    {
        // エディタ上で動かしたいので、play中/非play問わず実行
        if (pivotPoint == null || weaponTransform == null)
        {
            return;
        }

        // 1. 目標方向ベクトルを取得
        Vector3 targetDirection = GetTargetDirection();
        if (targetDirection.sqrMagnitude < 0.0001f)
        {
            return; // ほぼゼロ方向なら何もしない
        }

        // 2D前提なのでZ軸回転を使う(XY平面上)
        float targetAngle = Mathf.Atan2(targetDirection.y, targetDirection.x) * Mathf.Rad2Deg;

        // 角度オフセットを加算(スプライトの向き調整用)
        targetAngle += angleOffset;

        // 2. 現在角度から目標角度へ補間回転
        float currentAngle = weaponTransform.eulerAngles.z;
        float newAngle;

        if (rotateSpeed <= 0f)
        {
            // 即座に目標角度へ
            newAngle = targetAngle;
        }
        else
        {
            // 一定スピードで補間
            newAngle = Mathf.MoveTowardsAngle(
                currentAngle,
                targetAngle,
                rotateSpeed * Time.deltaTime
            );
        }

        // 3. pivotPoint を中心に武器を回転させる
        ApplyRotationAroundPivot(newAngle);

        // 4. キャラクターの向きに合わせて左右反転
        if (autoFlipByCharacterFacing)
        {
            ApplyFlipByCharacterFacing();
        }
    }

    /// <summary>
    /// マウスまたはターゲットTransformから、pivotPoint から見た方向ベクトルを取得。
    /// </summary>
    private Vector3 GetTargetDirection()
    {
        if (useMouseDirection)
        {
            // 画面上のマウス位置をワールド座標に変換
            Camera mainCam = Camera.main;
            if (mainCam == null)
            {
                return Vector3.right; // カメラがなければとりあえず右向き
            }

            Vector3 mouseScreenPos = Input.mousePosition;
            Vector3 mouseWorldPos = mainCam.ScreenToWorldPoint(mouseScreenPos);

            // 2DなのでZをpivotPointと合わせる
            mouseWorldPos.z = pivotPoint.position.z;

            return (mouseWorldPos - pivotPoint.position).normalized;
        }
        else
        {
            if (lookTarget == null)
            {
                return Vector3.right;
            }

            Vector3 targetPos = lookTarget.position;
            targetPos.z = pivotPoint.position.z;

            return (targetPos - pivotPoint.position).normalized;
        }
    }

    /// <summary>
    /// 指定した角度で、pivotPoint を中心に weaponTransform を回転させる。
    /// </summary>
    private void ApplyRotationAroundPivot(float angleDeg)
    {
        // 現在のローカル位置を、pivotPoint基準のローカル座標として扱う
        // 1. pivotPointの位置に武器を合わせる
        Vector3 pivotPos = pivotPoint.position;
        Vector3 weaponPos = weaponTransform.position;
        Vector3 offset = weaponPos - pivotPos;

        // 2. オフセットを回転させる
        float rad = angleDeg * Mathf.Deg2Rad;
        float cos = Mathf.Cos(rad);
        float sin = Mathf.Sin(rad);

        Vector3 rotatedOffset = new Vector3(
            offset.x * cos - offset.y * sin,
            offset.x * sin + offset.y * cos,
            offset.z // 2DなのでZはそのまま
        );

        // 3. 回転後の位置を反映
        weaponTransform.position = pivotPos + rotatedOffset;

        // 4. 武器の見た目の回転も合わせる(Z軸回転)
        Vector3 euler = weaponTransform.eulerAngles;
        euler.z = angleDeg;
        weaponTransform.eulerAngles = euler;
    }

    /// <summary>
    /// キャラクターの向き(右向き or 左向き)に応じて武器のスケールを反転させる。
    /// </summary>
    private void ApplyFlipByCharacterFacing()
    {
        if (characterRoot == null || weaponTransform == null)
        {
            return;
        }

        // ここでは「右向き = localScale.x >= 0」とする簡易判定
        bool isFacingRight = characterRoot.localScale.x >= 0f;

        weaponTransform.localScale = isFacingRight
            ? weaponLocalScaleRight
            : weaponLocalScaleLeft;
    }

    /// <summary>
    /// 外部から「この方向を向いてほしい」と指示したいとき用のメソッド。
    /// 例えばゲームパッドのスティック入力ベクトルを渡すなど。
    /// </summary>
    /// <param name="worldDirection">ワールド座標系での方向ベクトル</param>
    public void SetDirection(Vector2 worldDirection)
    {
        if (pivotPoint == null || weaponTransform == null)
        {
            return;
        }

        if (worldDirection.sqrMagnitude < 0.0001f)
        {
            return;
        }

        // 直接角度を計算して即座に反映
        float angle = Mathf.Atan2(worldDirection.y, worldDirection.x) * Mathf.Rad2Deg;
        angle += angleOffset;

        ApplyRotationAroundPivot(angle);
    }
}

使い方の手順

ここでは 2D アクションゲームで「プレイヤーがマウス方向に銃を向ける」例をベースに説明します。

① シーン階層を用意する

  • プレイヤー(Player)オブジェクトを作成
  • その子に「肩」用の空オブジェクトを追加(例:ShoulderPivot
  • さらにその子か同階層に、武器スプライト用オブジェクトを配置(例:Rifle

Player
 ├─ Sprite (キャラ見た目)
 ├─ ShoulderPivot   <-- 持ち手(pivotPoint)に指定
 └─ Rifle           <-- WeaponPivot を付ける武器

ShoulderPivot は、キャラの肩・手首あたりに位置を合わせておきましょう。

② WeaponPivot コンポーネントを追加

  1. Rifle オブジェクトを選択
  2. Add Component から WeaponPivot を追加
  3. インスペクタで以下を設定
    • Pivot Point : ShoulderPivot をドラッグ&ドロップ
    • Weapon Transform : 空欄なら自動で Rifle が使われます
    • Use Mouse Direction : ON(マウス方向を向かせる)
    • Rotate Speed : 720 くらいから調整
    • Angle Offset : スプライトの向きに合わせて 90 / -90 など調整
    • Auto Flip By Character Facing : ON(プレイヤーの向きで反転)
    • Character Root : Player を指定
    • Weapon Local Scale Right : 右向き時のスケール(通常は (1,1,1))

③ プレイヤーの左右反転と連動させる

プレイヤーの移動スクリプト側で、向きに応じて PlayerlocalScale.x を反転させておきます。


public class SimplePlayerMove : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;

    private void Update()
    {
        float inputX = Input.GetAxisRaw("Horizontal");

        // 移動
        Vector3 velocity = new Vector3(inputX, 0f, 0f) * moveSpeed;
        transform.position += velocity * Time.deltaTime;

        // 向きの反転(localScale.x で判定する想定)
        if (inputX > 0.01f)
        {
            Vector3 scale = transform.localScale;
            scale.x = Mathf.Abs(scale.x);
            transform.localScale = scale;
        }
        else if (inputX < -0.01f)
        {
            Vector3 scale = transform.localScale;
            scale.x = -Mathf.Abs(scale.x);
            transform.localScale = scale;
        }
    }
}

これで WeaponPivotAuto Flip By Character Facing が、
PlayerlocalScale.x を見て自動的に武器の左右を反転してくれます。

④ 敵や動く床など別の例で使う

  • 敵のタレット
    敵オブジェクトの子に TurretPivot(回転の中心)と Gun(銃身)を作り、
    WeaponPivotGun に付けて、Use Mouse Direction = falseLook Target = プレイヤー にすると、
    「常にプレイヤーを向く砲台」が簡単に作れます。
  • 動く床の上の砲台
    動く床の子に敵タレットを置いても、WeaponPivot は「pivotPoint を中心に回転するだけ」なので、
    床の移動ロジックとは完全に独立して動かせます。

メリットと応用

WeaponPivot を導入することで、

  • プレイヤーのスクリプトが「移動・入力・攻撃ロジック」に集中できる
    見た目の回転処理は WeaponPivot に丸投げできます。
  • キャラごとに持ち手の位置が違っても、プレハブ差し替えだけで対応
    「このキャラは肩が高い」「この敵は手首持ち」なども、
    pivotPoint に違うオブジェクトを指定するだけでOKです。
  • レベルデザインが楽になる
    砲台やギミックに WeaponPivot を付けておけば、
    「どの方向を向くか」「どこを中心に回転させるか」をシーン上で視覚的に調整できます。
  • アニメーションとの相性が良い
    肩や手首のボーンを pivotPoint にすれば、アニメで腕を振っても、
    常に「その時点の手首位置」を中心に武器を回転できます。

コンポーネント単位で責務を分けておくと、
「武器の見た目の制御」だけを差し替えたり、別プロジェクトに持ち出したりしやすくなりますね。

改造案:攻撃ボタンを押したときだけ、スイング演出を入れる

例えば、WeaponPivot に「軽い振りかぶりモーション」を足したい場合、
こんな感じのメソッドを追加して、アニメーションの代わりに簡易スイングを表現するのもありです。


    /// <summary>
    /// 一瞬だけ武器を振りかぶるように角度をずらす簡易スイング。
    /// 攻撃ボタンを押した瞬間などに外部から呼び出します。
    /// </summary>
    /// <param name="swingAngle">現在の向きに対して追加する角度(度数法)</param>
    public void AddSwing(float swingAngle)
    {
        if (weaponTransform == null || pivotPoint == null)
        {
            return;
        }

        // 現在の角度を取得して、そこに swingAngle を足す
        float currentAngle = weaponTransform.eulerAngles.z;
        float targetAngle = currentAngle + swingAngle;

        // 即座に反映(必要ならコルーチンで徐々に戻す処理を足す)
        ApplyRotationAroundPivot(targetAngle);
    }

攻撃処理側からは、

  • ダメージ計算やヒット判定は「攻撃コンポーネント」
  • 見た目の振りかぶりは「WeaponPivot.AddSwing」

といった具合に、さらに責務を分けていくと、
あとから「エフェクトを変えたい」「別の武器アニメを試したい」となっても柔軟に対応できます。

小さくて役割のはっきりしたコンポーネントを積み重ねて、
巨大な God クラスから卒業していきましょう。