Unity本格入門:いきのこバトルで学ぶプレイヤーと新 Input System
題材・出典: 技術評論社刊『作って学べる Unity本格入門[Unity 6対応版]』(賀好 昭仁 著)に基づく学習補助の解説です。書籍の代替提供を目的とせず、コード掲載は学習上必要な範囲(必要最小限)にとどめます。利用条件は書籍記載(P.4〜5)および出版社サポート情報に従ってください。本シリーズ目次(書籍・著作の注記)
対象読者:第2回まで読み、メインシーンの流れを把握したあと、操作・移動・攻撃のコードを追いたい方
PlayerController.csを中心に、PlayerInput・InputAction・CharacterControllerのつながりを読み解きます。
前提:第2回:メインシーンの時間と昼夜。PlayerStatus の満腹や死亡時の GameOver は、ここでは「結果として MainSceneController.Instance.GameOver() が呼ばれる」程度に留め、攻撃・投擲の細部は必要な範囲だけ示します。
補足:Assets/PlayerAction.inputactions でアクション名(Move, Fire, Jump, ToggleThrowAxe)が定義されています。エディターでは Player Input コンポーネントがこのアセットを参照します。
記事の目次
- この記事で扱う範囲
- コードを全部見てみよう
- クラス関係(この記事で登場する範囲)
- UMLで整理する(シーケンス)
- ポイント解説(①〜⑦)
- コードの流れを整理しよう
- 自分でカスタマイズしてみよう!
- まとめ
ポイント一覧
- ①
[RequireComponent]で依存を明示する - ②
PlayerInputとFindActionで操作を取得する - ③
ReadValue<Vector2>()と移動速度 - ④
CharacterController:重力・ジャンプ・Move - ⑤
WasPressedThisFrame/WasReleasedThisFrame - ⑥ UI 上のクリックを無視して
Fireを処理する - ⑦ 投擲モードとレイキャスト・
Instantiateの概要
この記事で扱う範囲
- 主に:
PlayerController.csのStartとUpdateの流れ - 深掘りしない:
MobAttack、ThrowAxeの内部、OwnedItemsDataの保存形式(必要な箇所はコメントで示す)

説明(学習のヒント):PlayerInput から InputAction を取り、移動・攻撃・ジャンプ・投擲へ振り分けるイメージです。UI 上のクリックと攻撃の切り分けもこの記事で触れます。
コードを全部見てみよう
PlayerController.cs(全文)
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(PlayerStatus))]
[RequireComponent(typeof(MobAttack))]
public class PlayerController : SingletonMonoBehaviourInSceneBase<PlayerController>
{
private static readonly int MoveSpeed = Animator.StringToHash("MoveSpeed");
[SerializeField] private Animator animator;
[SerializeField] private float moveSpeed = 3;
[SerializeField] private float jumpPower = 3;
[SerializeField] private ThrowAxe throwAxePrefab;
private MobAttack _attack;
private CharacterController _characterController;
private InputAction _fire;
private bool _isReadyToThrow;
private InputAction _jump;
private InputAction _move;
private Vector3 _moveVelocity;
private PlayerStatus _status;
private InputAction _toggleThrowAxe;
private Transform _transform;
public bool IsReadyToThrow
{
get => _isReadyToThrow;
set
{
_isReadyToThrow = value;
Menu.Instance.IsThrowAxeActive = _isReadyToThrow;
}
}
private void Start()
{
_characterController = GetComponent<CharacterController>();
_transform = transform;
var input = GetComponent<PlayerInput>();
input.currentActionMap.Enable();
_move = input.currentActionMap.FindAction("Move");
_fire = input.currentActionMap.FindAction("Fire");
_jump = input.currentActionMap.FindAction("Jump");
_toggleThrowAxe = input.currentActionMap.FindAction("ToggleThrowAxe");
_status = GetComponent<PlayerStatus>();
_attack = GetComponent<MobAttack>();
}
private void Update()
{
if (!EventSystem.current.IsPointerOverGameObject() && _fire.WasReleasedThisFrame())
{
if (IsReadyToThrow)
{
if (OwnedItemsData.Instance.GetItem(Item.ItemType.ThrowAxe).Number <= 0)
{
Debug.Log("投げオノを持ってないよ〜");
}
else
{
if (Camera.main != null && Mouse.current != null)
{
var mousePointerRay = Camera.main.ScreenPointToRay(Mouse.current.position.value);
if (Physics.Raycast(mousePointerRay, out var hit))
{
var throwAxe = Instantiate(throwAxePrefab);
var target = hit.collider.name == "Terrain"
? hit.point
: hit.collider.transform.position + Vector3.up;
throwAxe.Initialize(transform.position + Vector3.up, target, 20f);
OwnedItemsData.Instance.Use(Item.ItemType.ThrowAxe);
OwnedItemsData.Instance.Save();
AudioManager.Instance.Play("throw");
if (OwnedItemsData.Instance.GetItem(Item.ItemType.ThrowAxe).Number <= 0)
IsReadyToThrow = false;
}
}
}
}
else
{
_attack.AttackIfPossible();
}
}
if (_toggleThrowAxe.WasReleasedThisFrame())
ToggleReadyToThrow();
if (_status.IsMovable)
{
var moveValue = _move.ReadValue<Vector2>();
_moveVelocity.x = moveValue.x * moveSpeed;
_moveVelocity.z = moveValue.y * moveSpeed;
_transform.LookAt(_transform.position + new Vector3(_moveVelocity.x, 0, _moveVelocity.z));
}
else
{
_moveVelocity.x = 0;
_moveVelocity.z = 0;
}
if (_characterController.isGrounded)
{
if (_jump.WasPressedThisFrame())
{
Debug.Log("ジャンプ!");
_moveVelocity.y = jumpPower;
}
}
else
{
_moveVelocity.y += Physics.gravity.y * Time.deltaTime;
}
_characterController.Move(_moveVelocity * Time.deltaTime);
animator.SetFloat(MoveSpeed, new Vector3(_moveVelocity.x, 0, _moveVelocity.z).magnitude);
}
public void ToggleReadyToThrow()
{
if (OwnedItemsData.Instance.GetItem(Item.ItemType.ThrowAxe).Number <= 0)
IsReadyToThrow = false;
else
IsReadyToThrow = !IsReadyToThrow;
Debug.Log("投擲モード" + (IsReadyToThrow ? "ON" : "OFF"));
}
}クラス関係(この記事で登場する範囲)
説明(学習のヒント):PlayerController が1つに対し、[RequireComponent] で揃えたいコンポーネントが4つぶら下がるイメージです。
UMLで整理する(シーケンス)
Update 内で入力を読み、移動・攻撃・重力を CharacterController.Move にまとめる流れを簡略化したものです。
説明(学習のヒント):毎フレームのループの中で、入力を読み取り、最後に CharacterController で移動する流れです。
ポイント①:[RequireComponent] で依存を明示する
CharacterController、PlayerInput、PlayerStatus、MobAttack が無いと動かない、とコンポーネントに宣言しています。エディターでスクリプトを付けたときに同時に足りないコンポーネントが追加され、設定漏れを防ぎやすくなります。
ポイント②:PlayerInput と FindAction で操作を取得する
var input = GetComponent<PlayerInput>();
input.currentActionMap.Enable();
_move = input.currentActionMap.FindAction("Move");新 Input System では、アクション名(PlayerAction の Player マップ内)で InputAction を取得します。キーボード・パッドの割り当ては .inputactions 側で一括管理できます。
ポイント③:ReadValue<Vector2>() と移動速度
var moveValue = _move.ReadValue<Vector2>();
_moveVelocity.x = moveValue.x * moveSpeed;
_moveVelocity.z = moveValue.y * moveSpeed;2D の入力(WASD やスティック)を、XZ 平面の移動に写しています。moveValue.y を Z 方向に使っている点に注意(Input の「前後」が Z)。
ポイント④:CharacterController:重力・ジャンプ・Move
- 地上ではジャンプ入力で
_moveVelocity.yに初速を与える - 空中では
Physics.gravity.y * Time.deltaTimeで落下を加速 - 最後に
_characterController.Move(_moveVelocity * Time.deltaTime)で一括移動
教科書シリーズの Rigidbody 物理とは別系統で、キビキビした操作向きのやり方です。
ポイント⑤:WasPressedThisFrame / WasReleasedThisFrame
- ジャンプ:
WasPressedThisFrame()(押した瞬間) - 攻撃/投擲の発動:
FireはWasReleasedThisFrame()(離した瞬間)
「押しっぱなし」と「タップ」を分けたいときに使い分けます。
ポイント⑥:UI 上のクリックを無視して Fire を処理する
if (!EventSystem.current.IsPointerOverGameObject() && _fire.WasReleasedThisFrame())メニューやボタンがあると、ゲーム世界へのクリックと被ります。IsPointerOverGameObject() で「UI の上か」を判定し、UI のときは攻撃処理に入らないようにしています。
ポイント⑦:投擲モードとレイキャスト・Instantiate の概要
投擲スタンバイ中は、Camera.main.ScreenPointToRay と Physics.Raycast で地面や敵への当たりを取り、当たった位置を目標に throwAxePrefab を Instantiate しています。通常攻撃は _attack.AttackIfPossible() に任せています。Menu や OwnedItemsData は UI・所持品と連動する別クラスです。
コードの流れを整理しよう
説明(学習のヒント):Update から複数の処理が出ています。Fire の菱形は「UI の上のクリックか」で分岐するイメージです。
自分でカスタマイズしてみよう!
挑戦①:moveSpeed と jumpPower
Inspector で変えられるので、手触りを変えてみましょう。
挑戦②:移動しない条件
_status.IsMovable が false のとき水平速度を 0 にしているので、スタンなどの状態を足すときの拡張ポイントになります。
まとめ
[RequireComponent]と コンポーネントのキャッシュで、Updateを軽く保つPlayerInputからFindActionでInputActionを取得し、ReadValue/WasPressed/WasReleasedで入力を解釈するCharacterController.Moveと 重力・ジャンプで 3D 移動を構成するEventSystem.IsPointerOverGameObjectで UI とゲーム操作の衝突を避ける
次は 第4回:敵スポーンと NavMesh で、敵の出現ループを読みます。
最終更新:2026年4月