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 など)から完成させる
  • 赤波線エラーを「次に何を書くか」のヒントとして使う
  • 順番にこだわらず、書きやすいところから埋めていく

この練習法は、コードを「全体から部分へ」読み解く力を育てます。写経と異なり、入力しながら常に構造を意識するため、修行や苦行にならずに気持ちよく進められるはずです。

訪問数 13 回, 今日の訪問数 13回

広告

C#,Unity

Posted by hidepon