第01回 Input Systemとキャラクター操作
次回: 第02回 カメラ操作とコンポーネント分割。
この授業の目的
- ゲームの機能を段階的に実装しながら C#の用法と設計の基本を実践的に学ぶ
- 個人でゲームを完成させる過程を通じて ゲーム制作の経験と全体観を養う
- ソフトウェア部品の責務を理解し 保守と拡張を考慮した設計を身につける
制作するゲーム
Unity Scene上に床とPlayerを配置した完成イメージ
- 床は3DのPlaneまたはMesh
- カメラは斜め上からフィールドを見下ろす俯瞰カメラ
- プレイヤー、敵、アイテム等は3Dオブジェクト
- 移動は水平面(X-Z平面)に限定する
- ジャンプ、複雑な段差は授業内では扱わないが 実装は自由とする
全10回の流れ
- Input Systemとキャラクター操作
- カメラ操作とコンポーネント分割
- 攻撃入力と攻撃モーション
- 衝突判定とダメージ
- Enemyの探索・追跡・攻撃
- Enemyの状態制御とゲームフィール
- アイテムとオブジェクトプール
- GameManagerとゲーム状態
- UIとシーン遷移
- ビルドと公開
今回の授業内容
- Input Systemで移動入力を受け取る
- 入力値から移動方向を作る
- X-Z平面でキャラクターを移動させる
- キャラクターを移動方向へ振り向かせる
- 移動速度と回転速度をInspectorで調整できるようにする
今回の配布コード
この回で使う完成コードは、以下から個別に確認できます。
| ファイル | 内容 |
|---|---|
| Assets/Scripts/PlayerController.cs | 入力を読み取り、移動と振り向きを行う |
入力をアクションとして扱う
今回はInput Systemの Move アクションへ、WASD、方向キー、ゲームパッドの左スティックを割り当てます。アクションは「移動」のような操作の名前です。異なる入力方法を同じアクションへまとめることで、移動処理は使用中のキーやデバイスを個別に調べず、移動方向と入力の強さを受け取れます。
例えばWキーと左スティックの上方向をどちらも Move に割り当てると、移動処理はどちらからでも前方向の入力を受け取れます。次に、その対応をInput Actionsアセットへ設定します。
Input Actionsアセットを設定する
Input SystemではInput Actionsアセットで入力の種類や割り当てを定義し、スクリプトからはActionを通じて入力値を読み取ります。
Input Actionsの設定手順
今回は Move Actionで移動入力を扱います。Move Actionは、移動方向と入力の強さを表す Vector2 として読み取ります。実際の移動量は、この入力に速度と経過時間を掛けて求めます。
Project SettingsでInput System Packageを有効にした画面
Input Actionsアセットを作成しますPlayerAction MapにMoveActionを追加しますAction TypeをValue、Control TypeをVector2にします- WASD、方向キー、左スティックを割り当てます
Input ActionsからC#クラスを生成する
Input ActionsでGenerate C# Classを有効にしたInspector
Generate C# Class を有効にすると、Actionへ型安全にアクセスするクラスが生成されます。
設定変更の際にコンパイルが発生しますが、TypoやAction名の変更に強く、コード補完も効き便利です。
この教材ではクラスを生成して利用します。
クラス名は InputSystem_Actions で統一しています。
生成されたクラスから入力値を読む
Awake で InputActions を作成し、OnEnable OnDisable でAction Mapを有効化/無効化します。
有効化されたAction MapのActionは ReadValue<T>() で値を読み取ることができます。
ここでは ReadValue<Vector2>() で移動入力を読み取っています。
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private InputSystem_Actions _inputActions;
private void Awake()
{
_inputActions = new InputSystem_Actions();
}
private void OnEnable()
{
_inputActions.Player.Enable();
}
private void OnDisable()
{
_inputActions.Player.Disable();
}
private void Update()
{
var moveInput =
_inputActions.Player.Move.ReadValue<Vector2>();
// 省略2D入力を3Dの移動方向に変換する
var direction = new Vector3(
moveInput.x,
0f,
moveInput.y
);
// 斜め移動が速くなりすぎないようにする
direction = Vector3.ClampMagnitude(direction, 1f);Input Systemの移動入力は二次元の Vector2 ですが 3D 空間の移動方向は三次元の Vector3 で扱います。
ここでは入力の x をワールドの x に、入力の y をワールドの z に適用します。
入力値は-1.0~1.0の範囲で正規化されています。
そのまま移動に適用すると斜め入力時の移動の速度が √2倍(約1.414倍)になってしまいますので、長さを最大1に制限します。
設定値をInspectorで調整可能に
public class PlayerController : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float rotationSpeed = 720f;プレイ感に関わる数値は何度も調整することになります。こうした値はコードを書き換えずに変更できるようにするのがゲーム制作の基本です。
変数の定義時に SerializeField を付けると Inspector から値を設定できるようになります。
INFO
アクセス修飾子を public にしても Inspector から設定できるようになりますが、それでは変数が外部公開されてしまいます。
変数のアクセス修飾子は、特に理由がなければ private を基本とし、必要に応じて SerializeField で Inspector から設定できるようにすることをおすすめします。
プレイヤーをワールド座標基準で移動させる
public class PlayerController : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
private InputSystem_Actions _inputActions;
// Awake / OnEnable / OnDisable は省略
private void Update()
{
var moveInput =
_inputActions.Player.Move.ReadValue<Vector2>();
var direction = new Vector3(moveInput.x, 0f, moveInput.y);
direction = Vector3.ClampMagnitude(direction, 1f);
transform.position +=
direction * (moveSpeed * Time.deltaTime);
}
}Time.deltaTime はフレーム内の経過時間です。移動速度に Time.deltaTime を掛ければ、1フレームあたりの移動量を求めることができます。
まずはプレイヤーを X-Z 平面で動かしてみましょう。
入力があるときだけ移動方向に振り向く
if (direction != Vector3.zero)
{
var targetRotation =
Quaternion.LookRotation(direction);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime);
}Quaternion.LookRotation で、最終的な回転を表す Quaternion を作った後、Quaternion.RotateTowards で現在の回転から目標の回転へ、指定の速度で回転させます。rotationSpeed は1秒あたりに回転できる角度の速さ(度/秒)です。
これでプレイヤーが入力方向に指定の速度で振り向くようになります。
完成した PlayerController
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float rotationSpeed = 720f;
private InputSystem_Actions _inputActions;
private void Awake()
{
_inputActions = new InputSystem_Actions();
}
private void OnEnable()
{
_inputActions.Player.Enable();
}
private void OnDisable()
{
_inputActions.Player.Disable();
}
private void Update()
{
var moveInput =
_inputActions.Player.Move.ReadValue<Vector2>();
var direction =
new Vector3(moveInput.x, 0f, moveInput.y);
direction = Vector3.ClampMagnitude(direction, 1f);
transform.position +=
direction * (moveSpeed * Time.deltaTime);
if (direction == Vector3.zero)
return;
var targetRotation =
Quaternion.LookRotation(direction);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime);
}
}実習:プレイヤーを操作できるようにする
- Planeを配置する
- CapsuleをPlayerとして配置する
- Input Actionsアセットを作成する
MoveActionを設定する- C#コード生成を有効にして
InputSystem_Actionsを生成する PlayerControllerをPlayerに追加する- WASDまたは左スティックで 移動を確認する
- 移動方向へ振り向くことを確認する
動作しない場合のチェックリスト
-
MoveActionの型がVector2になっているか -
InputSystem_Actions.csが生成されているか -
OnEnableでPlayerAction Mapを有効化しているか -
moveSpeedが0になっていないか -
rotationSpeedが0になっていないか -
directionの値がゼロのままになっていないか
完成条件
- WASDまたはコントローラーの左スティックで移動できる
- 斜め移動が他の入力に比べて速くなっていない
- プレイヤーが移動方向へ振り向く
MoveActionをInput Systemで設定しているInputSystem_ActionsのC#コード生成を有効にしているmoveSpeedとrotationSpeedをInspectorで調整できる
まとめ
- 入力は
Input Actionから受け取る - 移動方向は入力値から作る
- 移動量は速度と時間から作る
- 回転は移動方向から作る