Appearance
第03回 攻撃入力と攻撃モーション
前回: 第02回 カメラ操作とコンポーネント分割 / 次回: 第04回 衝突判定とダメージ
今回の授業内容
- Input Actionsへ攻撃入力を追加する
- 入力、攻撃時間、攻撃演出を役割別のコンポーネントへ分ける
- 攻撃中の再入力と移動を制限する
- 既存の剣と剣閃を利用して攻撃モーションを再生する
- 攻撃の発動とダメージ判定が別の処理であることを確認する
この回では、攻撃ボタンを押すとPlayerが一度だけ攻撃して見えるところまでを作ります。Colliderによる命中、ダメージ、HP、HPゲージは次回に分けます。
今回の配布コード
第02回までの移動とカメラへ、CharacterAttack、AttackEffect、SlashEffectを追加します。
配布ファイル一覧
| ファイル | 内容 |
|---|---|
| PlayerController.cs | 攻撃入力を読み取り、攻撃開始を指示する |
| CharacterAttack.cs | 攻撃時間と再入力を管理する |
| AttackEffect.cs | 剣を表示して回転させる |
| SlashEffect.cs | 剣閃Materialの表示を制御する |
攻撃の発動と命中を分ける
アクションゲームの攻撃には、少なくとも次の異なる処理があります。
攻撃入力
攻撃を開始する
攻撃状態
攻撃状態
剣やAnimationを再生する
攻撃演出
攻撃演出
攻撃可能な時間だけ有効にする
Collider
Collider
対象へ触れる
ダメージ処理
ダメージ処理
ダメージを適用する
HP
このすべてを同時に実装すると、表示されない原因が入力なのか、演出なのか、Colliderなのか判断しにくくなります。今回は上から3段目までを完成させ、見た目の成立を先に確認します。
Input ActionsへAttackを追加する
Input Actionsアセットを開き、Player Action MapへButton型のAttackを追加します。Keyboardの任意のキーやGamepadのButton SouthをBindingへ設定してください。
Actionを追加したらアセットを保存し、Generate C# Classを実行します。生成されたクラスを直接書き換えると、次の生成時に消えるため、入力処理はPlayerControllerへ書きます。
cs
if (_inputActions.Player.Attack.WasPressedThisFrame())
characterAttack.Attack();IsPressed()はボタンを押している間trueになり続けます。1回の押下で一度だけ開始する攻撃にはWasPressedThisFrame()を使います。
入力と攻撃処理を分ける
PlayerControllerは入力を読み取りますが、攻撃が何秒続くか、どの演出を出すかは決めません。攻撃に関する時間管理はCharacterAttackへ渡します。
PlayerController
入力を読み取り、Attack()を呼ぶ
CharacterAttack
CharacterAttack
攻撃状態を管理し、Play()を呼ぶ
AttackEffect
AttackEffect
攻撃演出を表示する
剣と剣閃
03の時点からPlayerAttackではなくCharacterAttackという名前にします。次々回にはEnemyも同じ攻撃部品を使うため、途中でファイル名とPrefab参照を変更する必要がありません。
CharacterAttackで攻撃時間を管理する
CharacterAttackはIsAttackingを公開し、現在攻撃中かどうかを他のコンポーネントから確認できるようにします。
cs
using UnityEngine;
public class CharacterAttack : MonoBehaviour
{
[SerializeField] private AttackEffect attackEffect;
[SerializeField] private float attackDuration = 0.5f;
private float _elapsedTime;
public bool IsAttacking { get; private set; }
private void Update()
{
if (!IsAttacking)
return;
_elapsedTime += Time.deltaTime;
if (_elapsedTime >= attackDuration)
IsAttacking = false;
}
public void Attack()
{
if (IsAttacking)
return;
IsAttacking = true;
_elapsedTime = 0f;
attackEffect?.Play();
}
}攻撃中にAttack()が呼ばれてもreturnするため、連打によって経過時間や演出が毎フレーム最初へ戻りません。
攻撃中の移動を止める
攻撃入力を処理したあと、IsAttackingなら移動処理より前でreturnします。同じフレームに攻撃と移動が同時実行されない順序が重要です。
cs
if (_inputActions.Player.Attack.WasPressedThisFrame())
characterAttack.Attack();
if (characterAttack.IsAttacking)
return;
playerMove.Move(direction);配布コードではカメラ操作をreturnより前に実行し、攻撃中も視点を操作できるようにします。カメラも固定したい場合は呼び出し順を変えて比較できます。
AttackEffectで剣を表示する
成果物プロジェクトにはAttackEffect Prefab、剣Mesh、Slash Effectが用意されています。これらは削除や作り直しを行わず、そのまま利用します。
AttackEffectは待機中に剣のRendererを非表示にし、Play()が呼ばれたときだけ表示します。Coroutine内で開始角度から終了角度まで補間し、攻撃終了時に再び非表示にします。
cs
var progress = Mathf.Clamp01(elapsedTime / duration);
transform.localRotation = Quaternion.Euler(
0f,
Mathf.Lerp(angleFrom, angleTo, progress),
0f);攻撃ロジックは角度、Mesh、Materialを知りません。見た目を変更してもCharacterAttackを修正せずに済みます。
SlashEffectで剣閃を再生する
SlashEffectは剣閃用Materialの_MagをAnimationCurveに従って変化させます。AttackEffectから任意で呼び出す構成なので、剣閃を未設定にしても剣本体の演出は動作します。
cs
slashEffect?.Play(duration);Materialへ直接値を書き込むため、Instance化したMaterialはOnDestroy()で破棄します。成果物内のMaterialとShaderは変更せず利用してください。
攻撃時間と演出時間を比較する
| 設定値 | 制御するもの |
|---|---|
CharacterAttack.attackDuration | 次の行動を開始できるまでの時間 |
AttackEffect.duration | 剣と剣閃を表示する時間 |
angleFrom / angleTo | 剣を振る見た目の範囲 |
攻撃時間と演出時間は同じ値でも構いませんが、役割は異なります。演出終了後に硬直を残したい場合は、攻撃時間を演出時間より長くします。
Player Prefabへ接続する
- Playerの子に既存のCharacter AttackまたはAttackEffect Prefabを配置します
CharacterAttackを追加しますattackEffectへ既存のAttackEffectを設定しますPlayerController.characterAttackへ同じCharacterAttackを設定します- Player Prefabへ変更をApplyします
Scene上のInstanceだけに参照を設定すると、別SceneやPrefabから作り直したPlayerへ反映されません。Prefab内で完結する参照はPrefabへ保存します。
Play Modeで確認する
次の順に一項目ずつ確認します。
- 攻撃ボタンを押したフレームだけ入力を取得できる
IsAttackingが一定時間trueになる- 攻撃中は移動しない
- 剣が表示されて一度だけ回転する
- 攻撃終了後に移動と次の攻撃が可能になる
この回ではEnemyへ触れてもHPが減らなくて正解です。
実習:攻撃の発動と見た目を調整する
attackDurationを0.2秒、演出時間を0.5秒にして違いを確認しますattackDurationを0.7秒、演出時間を0.3秒にして硬直を確認します- 開始角度と終了角度を変更して振る方向を確認します
- 連打しても攻撃が途中から再生し直されないことを確認します
- 自分のゲームに適した値へ調整します
動作しない場合のチェックリスト
Attackがコードに表示されない
- Input Actionsアセットを保存したか
- Player Action MapへAttackを追加したか
- Generate C# Classを再実行したか
攻撃しても剣が表示されない
PlayerController.characterAttackを設定したかCharacterAttack.attackEffectを設定したかAttackEffect.swordMeshRendererが正しいか- Consoleに
NullReferenceExceptionがないか
攻撃後に移動できない
attackDurationが極端に大きくないかCharacterAttackが有効かPlayerControllerが有効か
剣が待機中も表示される
AttackEffect.Awake()で非表示にしているか- 指定したRendererが剣本体のものか
完成条件
- 攻撃ボタンを押すと剣または剣閃が一度再生される
- 攻撃中に連打しても処理が重複しない
- 攻撃中は移動せず、終了後に再び移動できる
- この段階ではEnemyのHPが減らなくてよい
まとめ
- 攻撃入力、攻撃時間、攻撃演出を別の役割へ分けた
- 攻撃中の再入力と移動を
IsAttackingによって制限した - 既存の剣、剣閃、Prefabを変更せず再利用した
- 命中判定とHPを追加する前に、攻撃の発動と見た目を完成させた