Skip to content

第03回 攻撃入力と攻撃モーション

前回: 第02回 カメラ操作とコンポーネント分割 / 次回: 第04回 衝突判定とダメージ

今回の授業内容

  • Input Actionsへ攻撃入力を追加する
  • 入力、攻撃時間、攻撃演出を役割別のコンポーネントへ分ける
  • 攻撃中の再入力と移動を制限する
  • 既存の剣と剣閃を利用して攻撃モーションを再生する
  • 攻撃の発動とダメージ判定が別の処理であることを確認する

この回では、攻撃ボタンを押すとPlayerが一度だけ攻撃して見えるところまでを作ります。Colliderによる命中、ダメージ、HP、HPゲージは次回に分けます。

今回の配布コード

第02回までの移動とカメラへ、CharacterAttackAttackEffectSlashEffectを追加します。

配布ファイル一覧
ファイル内容
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で攻撃時間を管理する

CharacterAttackIsAttackingを公開し、現在攻撃中かどうかを他のコンポーネントから確認できるようにします。

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へ接続する

  1. Playerの子に既存のCharacter AttackまたはAttackEffect Prefabを配置します
  2. CharacterAttackを追加します
  3. attackEffectへ既存のAttackEffectを設定します
  4. PlayerController.characterAttackへ同じCharacterAttackを設定します
  5. Player Prefabへ変更をApplyします

Scene上のInstanceだけに参照を設定すると、別SceneやPrefabから作り直したPlayerへ反映されません。Prefab内で完結する参照はPrefabへ保存します。

Play Modeで確認する

次の順に一項目ずつ確認します。

  1. 攻撃ボタンを押したフレームだけ入力を取得できる
  2. IsAttackingが一定時間trueになる
  3. 攻撃中は移動しない
  4. 剣が表示されて一度だけ回転する
  5. 攻撃終了後に移動と次の攻撃が可能になる

この回ではEnemyへ触れてもHPが減らなくて正解です。

実習:攻撃の発動と見た目を調整する

  1. attackDurationを0.2秒、演出時間を0.5秒にして違いを確認します
  2. attackDurationを0.7秒、演出時間を0.3秒にして硬直を確認します
  3. 開始角度と終了角度を変更して振る方向を確認します
  4. 連打しても攻撃が途中から再生し直されないことを確認します
  5. 自分のゲームに適した値へ調整します

動作しない場合のチェックリスト

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を追加する前に、攻撃の発動と見た目を完成させた