矢印キーで左右移動しつつ、移動範囲を [-8, 8] に制限する最小実装(Mathf.Clamp)
目次
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回
ディスカッション
コメント一覧
まだ、コメントがありません