Unityのコードはなぜ上から写してはいけないのか — 外側から書く練習法
コードを見たとき、先頭行から順番に入力していませんか?
その習慣が「全体を読む力」の成長を妨げているかもしれません。
写経との違い
プログラミング学習で「写経」は古くから行われてきた手法ですが、上から下へ順番に写すだけでは、コード全体の構造を俯瞰する力は身につきにくいです。
特にUnityのC#スクリプトは、クラス・フィールド・メソッドが階層的に入れ子になっており、「どの括弧がどこで閉じるか」が最初から見えていないと、入力中に混乱しがちです。
| 上から順番に入力する場合 | 外側から入力する場合 |
|---|---|
| 括弧の対応が追えなくなる | クラス → メソッド の階層が染みつく |
| 赤波線エラーが大量発生 | 赤波線がガイドとして機能する |
| その場しのぎの修正が増える | 補完の方向が明確になる |
| 全体構造が見えないまま終わる | インターフェース思考が養われる |
今回のサンプルコード
次のスクリプトを素材として使います。球体をWASD・ゲームパッド・マウスで動かす SphereController です。
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(Rigidbody))]
public class SphereController : MonoBehaviour
{
private Rigidbody _rigidbody;
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
}
void Update()
{
// ゲームパッドが接続されていれば
if (Gamepad.current != null)
{
var leftStickValue = Gamepad.current.leftStick.value;
Debug.Log("leftStickの傾き");
Debug.Log($"{leftStickValue}");
_rigidbody.AddForce(new Vector3(leftStickValue.x, 0, leftStickValue.y));
if (Gamepad.current.buttonSouth.wasPressedThisFrame)
{
Debug.Log("ゲームパッドのbuttonSouthがいま押されたよ");
Jump();
}
if (Gamepad.current.buttonSouth.isPressed)
{
Debug.Log("ゲームパッドのbuttonSouthが押され続けているよ!");
}
if (Gamepad.current.buttonSouth.wasReleasedThisFrame)
{
Debug.Log("ゲームパッドのbuttonSouthがいま離されたよ”);
}
}
// キーボードが接続されていれば
if (Keyboard.current != null)
{
if (Keyboard.current.wKey.isPressed)
{
Debug.Log("キーボードのwキーが押され続けているよ!");
_rigidbody.AddForce(new Vector3(0, 0, 1));
}
if (Keyboard.current[Key.A].isPressed)
{
Debug.Log("キーボードのAキーが押され続けているよ!");
_rigidbody.AddForce(new Vector3(-1, 0, 0));
}
if (Keyboard.current.sKey.isPressed)
{
Debug.Log("キーボードのSキーが押され続けているよ!");
_rigidbody.AddForce(new Vector3(0, 0, -1));
}
if (Keyboard.current[Key.D].isPressed)
{
Debug.Log("キーボードのDキーが押され続けているよ!");
_rigidbody.AddForce(new Vector3(1, 0, 0));
}
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
Debug.Log("キーボードのスペースキーが今押されたよ”);
Jump();
}
}
// マウスが接続されていれば
if (Mouse.current != null)
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
Debug.Log("マウスの左ボタンが今押されたよ!");
Jump();
}
if (Mouse.current.rightButton.isPressed)
{
Debug.Log("マウスの右ボタンが今押されたよ!");
var mousePos = Mouse.current.position.value;
Debug.Log($"マウスの座標 x:{mousePos.x}, y:{mousePos.y}");
}
if (Mouse.current.scroll.value.magnitude > 0f)
{
var scrollValue = Mouse.current.scroll.value;
Debug.Log($"マウスのホイールを回したよ! x:{scrollValue.x}");
}
}
}
private void Jump()
{
_rigidbody.AddForce(new Vector3(0, 100, 0));
}
}
コードの詳細解説
外側から書く前に、コード全体に登場するキーワードとプロパティの意味を把握しておきましょう。
クラス属性:[RequireComponent(typeof(Rigidbody))]
[RequireComponent] は、このスクリプトをアタッチするには Rigidbody コンポーネントが必須であることをUnityに伝える属性です。typeof(Rigidbody) で型を指定します。これにより:
- Rigidbody が付いていないGameObjectへのアタッチをUnityが自動的に拒否する
- スクリプトをアタッチすると Rigidbody が自動で追加される
フィールド:private Rigidbody _rigidbody
private Rigidbody _rigidbody;
クラス全体で使い回すためのインスタンス変数です。private にすることで外部のクラスからアクセスできなくなります。
アンダースコア始まり(_rigidbody)はプライベートフィールドの慣習的な命名規則です。
Start() — 初期化処理
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
}
Unityがシーン開始時に一度だけ呼ぶメソッドです。
GetComponent<Rigidbody>()は、このGameObjectに付いている Rigidbody コンポーネントを取得するStart()で取得してフィールドに保存しておくことで、Update()内で毎フレームGetComponentを呼ばずに済む(パフォーマンス上の定石)
Update() — 毎フレームの処理
void Update()
Unityが毎フレーム(通常60回/秒)自動で呼ぶメソッドです。入力検知・移動処理はここに書くのが基本です。
入力デバイス:InputSystem の基礎
このスクリプトは UnityEngine.InputSystem を使っています(旧来の Input.GetKey ではない点に注意)。
デバイスが接続されているか確認する
if (Gamepad.current != null)
if (Keyboard.current != null)
if (Mouse.current != null)
Gamepad.current / Keyboard.current / Mouse.current は、現在アクティブなデバイスの参照を返します。
デバイスが接続されていなければ null になるため、null チェックが必須です。
ゲームパッドの処理
左スティックの値を取得する
var leftStickValue = Gamepad.current.leftStick.value;
leftStickはStickControl型のプロパティ.valueで現在の傾き量をVector2(x, y の2次元ベクトル)として取得できる- 中央(未操作)で
(0, 0)、完全に右に倒すと(1, 0)になる
スティックの値を力に変換する
_rigidbody.AddForce(new Vector3(leftStickValue.x, 0, leftStickValue.y));
AddForce は Rigidbody に物理的な力を加えるメソッドです。
スティックの x をそのまま横軸(X)、スティックの y を奥行き軸(Z)に対応させています。縦軸(Y)は 0 にしてジャンプ以外では上下に動かないようにしています。
ボタンの3つの状態
InputSystem では、ボタンの状態を3段階で判定できます。
// 押した瞬間(1フレームだけ true)
if (Gamepad.current.buttonSouth.wasPressedThisFrame)
// 押し続けている間(押している全フレームで true)
if (Gamepad.current.buttonSouth.isPressed)
// 離した瞬間(1フレームだけ true)
if (Gamepad.current.buttonSouth.wasReleasedThisFrame)
| プロパティ | 意味 | trueになるタイミング |
|---|---|---|
wasPressedThisFrame | 今フレームで押した | 押した最初の1フレームのみ |
isPressed | 押し続けている | 押している全フレーム |
wasReleasedThisFrame | 今フレームで離した | 離した最初の1フレームのみ |
buttonSouth はコントローラーの下ボタン(PS系では×ボタン、Xboxでは Aボタン)に対応します。
キーボードの処理
キーの指定方法(2種類)
// プロパティでアクセスする方法
Keyboard.current.wKey.isPressed
// インデクサでアクセスする方法
Keyboard.current[Key.A].isPressed
どちらも同じ ButtonControl 型を返します。どちらを使っても動作は同じですが、プロパティ形式のほうが補完が効いて書きやすい場面が多いです。
AddForce による移動
_rigidbody.AddForce(new Vector3(0, 0, 1)); // W:前(+Z方向)
_rigidbody.AddForce(new Vector3(-1, 0, 0)); // A:左(-X方向)
_rigidbody.AddForce(new Vector3(0, 0, -1)); // S:後(-Z方向)
_rigidbody.AddForce(new Vector3(1, 0, 0)); // D:右(+X方向)
Unity の座標系では、Z が前後、X が左右です。AddForce は毎フレーム力を積み重ねるため、押し続けるほど加速します。
マウスの処理
左クリック:ジャンプ
if (Mouse.current.leftButton.wasPressedThisFrame)
{
Jump();
}
wasPressedThisFrame を使っているため、クリックの瞬間に一度だけ Jump() が呼ばれます。
右クリック:マウス座標の取得
var mousePos = Mouse.current.position.value;
Debug.Log($"マウスの座標 x:{mousePos.x}, y:{mousePos.y}");
Mouse.current.position は Vector2Control 型で、.value でスクリーン座標(Vector2)を取得できます。左下が (0, 0)、右上が画面解像度の値になります。
スクロールホイール
if (Mouse.current.scroll.value.magnitude > 0f)
{
var scrollValue = Mouse.current.scroll.value;
}
scroll.value は Vector2 です。縦スクロールは y、横スクロールは x に入ります。magnitude(ベクトルの長さ)が 0 より大きいときだけ処理することで、ホイール操作がない場合を除外しています。
Jump() メソッド
private void Jump()
{
_rigidbody.AddForce(new Vector3(0, 100, 0));
}
Y方向(上方向)に 100 の力を加えます。AddForce のデフォルトモードは ForceMode.Force(質量を考慮した連続的な力)ですが、ジャンプには瞬間的な力を与える ForceMode.Impulse を使うとより自然になります。
// より自然なジャンプにしたい場合
_rigidbody.AddForce(new Vector3(0, 5, 0), ForceMode.Impulse);
実践:外側から書く手順
コードの意味を把握したうえで、外側から入力する順番を確認しましょう。
ステップ 1 — クラスの骨格だけ作る
// まずここだけ書く
[RequireComponent(typeof(Rigidbody))]
public class SphereController : MonoBehaviour
{
}
using 宣言が足りなければ赤波線が出ます。それを手がかりに先頭に追加します。
ポイント: 赤波線エラーは「次に何を書くべきか」を教えてくれるナビゲーターです。敵ではありません。
ステップ 2 — フィールドとメソッドのシグネチャを並べる
中身(実装)は後回しにして、まずメソッドの名前と形だけを定義します。
[RequireComponent(typeof(Rigidbody))]
public class SphereController : MonoBehaviour
{
private Rigidbody _rigidbody;
void Start() { }
void Update() { }
private void Jump() { }
}
ステップ 3 — シンプルなメソッドから完成させる
Jump() は中身が1行で完結するので、先に実装します。
private void Jump()
{
_rigidbody.AddForce(new Vector3(0, 100, 0));
}
Jump() を呼び出す行を先に書いて、メソッド本体をあとで追加する、という順番でも構いません。
Visual Studio が「Jump は定義されていない」と教えてくれ、それが「次に実装すべきメソッド」の合図になります。
ステップ 4 — Start() と Update() の骨格を埋める
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
}
void Update()
{
// デバイスブロックの外枠だけ先に置く
if (Gamepad.current != null) { }
if (Keyboard.current != null) { }
if (Mouse.current != null) { }
}
ステップ 5 — 各デバイスブロックの中身を埋める
最後にキーボード・ゲームパッド・マウスそれぞれのロジックを追加します。
この段階では全体の構造がすでに完成しているため、どこに何を書くべきかが明確です。
なぜこの順番が効果的なのか
プログラムは本質的に外側のスコープが内側を包む構造です。
Unityの MonoBehaviour であれば、クラス → Start/Update → 処理内容 という階層が必ず存在します。
外側から書く練習を繰り返すと、新しいコードを読んだときに「まずクラス宣言を見て、次にメソッド一覧を見て、それから処理詳細へ」という俯瞰のスキャンが自然にできるようになります。
まとめ
- クラスの骨格(波括弧の対)を最初に書く
- メソッドのシグネチャを並べてから中身を書く
- シンプルなメソッド(
Jumpなど)から完成させる - 赤波線エラーを「次に何を書くか」のヒントとして使う
- 順番にこだわらず、書きやすいところから埋めていく
この練習法は、コードを「全体から部分へ」読み解く力を育てます。写経と異なり、入力しながら常に構造を意識するため、修行や苦行にならずに気持ちよく進められるはずです。




ディスカッション
コメント一覧
まだ、コメントがありません