矢印キーで左右移動しつつ、移動範囲を [-8, 8] に制限する最小実装(Mathf.Clamp)

2025年9月11日

TL;DR

  • 水平移動のあとに Mathf.Clamp で x を挟み込めば、範囲外に出ません。
  • 「カクカク1マス移動(離散)」も「スムーズ移動(連続)」も同じ考え方でOK。
  • 可視化や応用(長押し・ゲームパッド・タッチ)も最後にまとめています。

目的と前提

  • 目的:左右キーでプレイヤーを動かし、x座標を -8〜8 の範囲に固定する。
  • 前提:Unity 6.x / 旧Input(UnityEngine.Input)を使用。新Input Systemでも考え方は同じです。

完成コード(離散:キー1回で3だけ動く)

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // 調整しやすいように定数化
    const float MinX = -8f;
    const float MaxX =  8f;
    const float Step  =  3f;

    void Start()
    {
        Application.targetFrameRate = 60;
        ClampX(); // 初期位置が範囲外でも安全に
    }

    void Update()
    {
        // 左矢印
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            transform.Translate(-Step, 0f, 0f);
            ClampX();
        }

        // 右矢印
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            transform.Translate(Step, 0f, 0f);
            ClampX();
        }
    }

    // x座標を [-8, 8] に制限
    void ClampX()
    {
        var p = transform.position;
        p.x = Mathf.Clamp(p.x, MinX, MaxX);
        transform.position = p;
    }
}

なぜ「移動→Clamp」の順?

  • Step が大きくて 一瞬はみ出しても、最後に吸着させればOKだから。
  • もちろん「移動前に予定位置をClampしてから代入」でも同等です(次章)。

代替パターン:予定位置を計算してから一度に反映

// 例:右移動
var p = transform.position;
float nextX = Mathf.Clamp(p.x + Step, MinX, MaxX);
transform.position = new Vector3(nextX, p.y, p.z);
  • 「実際に範囲外へ出す瞬間すら作りたくない」設計に向きます。
  • 大きな差はありません。チームのコーディング規約に合わせて選択。

連続移動版(長押しでスムーズ)

  • 1秒あたり moveSpeed ユニット動くよう Time.deltaTime を掛けます。
using UnityEngine;

public class PlayerControllerSmooth : MonoBehaviour
{
    [SerializeField] float minX = -8f;
    [SerializeField] float maxX =  8f;
    [SerializeField] float moveSpeed = 6f; // 単位/秒

    void Update()
    {
        float dir = 0f;
        if (Input.GetKey(KeyCode.LeftArrow))  dir = -1f;
        if (Input.GetKey(KeyCode.RightArrow)) dir =  1f;

        if (dir != 0f)
        {
            var p = transform.position;
            float nextX = Mathf.Clamp(p.x + dir * moveSpeed * Time.deltaTime, minX, maxX);
            transform.position = new Vector3(nextX, p.y, p.z);
        }
    }
}

よくある疑問とハマりどころ

Q1. Translate はローカル移動?ワールド移動?

  • 何も指定しないと ローカル座標 基準です。
  • 常にワールドX方向へ動かしたいなら transform.Translate(delta, Space.World); か、transform.position を直接書き換えるのが明瞭。

Q2. 物理(Rigidbody)を使うなら?

  • 物理挙動と整合させたい場合、Rigidbody.MovePosition を FixedUpdate で行います。(Transform直書きは物理と競合しやすい)
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class PlayerControllerPhysics : MonoBehaviour
{
    [SerializeField] float minX = -8f;
    [SerializeField] float maxX =  8f;
    [SerializeField] float moveSpeed = 6f;

    Rigidbody rb;

    void Awake() => rb = GetComponent<Rigidbody>();

    void FixedUpdate()
    {
        float dir = 0f;
        if (Input.GetKey(KeyCode.LeftArrow))  dir = -1f;
        if (Input.GetKey(KeyCode.RightArrow)) dir =  1f;

        var p = rb.position;
        float nextX = Mathf.Clamp(p.x + dir * moveSpeed * Time.fixedDeltaTime, minX, maxX);
        rb.MovePosition(new Vector3(nextX, p.y, p.z));
    }
}

Q3. 画面サイズに合わせて自動で範囲を決めたい

  • カメラ幅から安全範囲を算出します(スプライトの半幅ぶんだけ余白を引く)。
// 例:Startで一度だけ決める
void SetupBoundsByCamera(float margin)
{
    float halfWidth = Camera.main.orthographicSize * Camera.main.aspect;
    float minX = -halfWidth + margin;
    float maxX =  halfWidth - margin;
    // 以降、この minX/maxX をClampに使用
}

可視化:エディタ上で範囲ラインを引く

  • デバッグ時に端が見えると調整が速いです。
#if UNITY_EDITOR
void OnDrawGizmosSelected()
{
    const float MinX = -8f;
    const float MaxX =  8f;
    var y = transform.position.y;
    var z = transform.position.z;

    Gizmos.DrawLine(new Vector3(MinX, y - 10, z), new Vector3(MinX, y + 10, z));
    Gizmos.DrawLine(new Vector3(MaxX, y - 10, z), new Vector3(MaxX, y + 10, z));
}
#endif

応用レシピ(短コード集)

1) 端で入力を無視する(「吸着」ではなく「動かない」)

if (Input.GetKeyDown(KeyCode.LeftArrow) && transform.position.x > -8f)
{
    transform.Translate(-3f, 0f, 0f);
}

2) A/D キーやゲームパッドにも対応

float dir = Input.GetAxisRaw("Horizontal"); // -1, 0, 1(Input Manager)
  • 新Input Systemなら PlayerInput と InputAction で同様に Vector2/float を受け取り、Clampは同じ。

3) タッチ操作(画面の左/右半分で移動)

if (Input.touchCount > 0)
{
    var t = Input.GetTouch(0);
    float dir = (t.position.x < Screen.width * 0.5f) ? -1f : 1f;
    // あとは連続移動版の式を流用
}

まとめ(チェックリスト)

  • 移動の直後または直前に Mathf.Clamp で x を [-8, 8] に収める
  • 離散(GetKeyDown)か連続(GetKey + deltaTime)かを用途で選択
  • 物理を使うなら Rigidbody.MovePosition + FixedUpdate
  • 画面幅に合わせるならカメラから動的に範囲を求める
  • デバッグは Gizmos で範囲を可視化

必要になったら、このサンプルをStep値範囲だけ差し替えて再利用してください。

訪問数 9 回, 今日の訪問数 7回

Unity,Unity6

Posted by hidepon