デッキ構築カードゲームの作り方#10|攻撃の手応えを作ろう

「定規でペシッ」をブリキロボへ離すと、カードが小さくなりながら飛んでいき、当たった瞬間にブリキロボが白く光って後ろへのけぞる。オレンジの火花が散って、赤い「6」が飛び出し、画面が小さく揺れる。続けて「ホチキスでパチン」を使うと、もっと大きな「12」が出て、揺れも強い

前回で、敵とターンを回して戦えるようになりました。でも、カードを敵の上で離しても、HPのゲージと数字が変わるだけで、なんだか手応えがありません・・・。今回は、カードが敵へ飛んでいき、当たったら敵が白く光ってのけぞり、数字と火花が飛び、画面が揺れる「攻撃の手応え」を作ってみたいと思います!

目次

今回作るもの

  • 使ったカードが、ねらった相手へ飛んでいく
  • 当たったら、敵が白く光って、のけぞって戻る
  • ダメージの数字が飛び出して消える(大きいダメージほど大きな文字)
  • 火花のパーティクル
  • ほんの一瞬だけ時間を止める(ヒットストップ)と、画面の揺れ
スクリプト役割付ける場所
CardFly使ったカードを相手へ飛ばすCard(プレハブ)
HitReaction白く光る・のけぞる。数字や火花なども呼ぶEnemy(プレハブ)
DamagePopupダメージの数字を飛ばして消すDamagePopup(プレハブ)
EffectSpawner数字と火花を出すEffects(新しく作る空のオブジェクト)
HitStop一瞬だけ時間を止めるEffects
ScreenShake戦いの画面(Stage)を揺らすEffects
Deck・CardDrag・PlayZone(直す)カードを飛ばしてから効果を出す-
Fighter(直す)HPが減ったら HitReaction を呼ぶ-

スライムちゃんが攻撃を受けたとき・防御や回復のときの演出は、次の#11で作ります。

なぜ演出が要るの?

まずは、今の状態(#9まで)で「定規でペシッ」と「ホチキスでパチン」を使ってみたのが下図です。

#9までの状態で、「定規でペシッ」をブリキロボの上で離すとカードがその場で消え、ゲージが28/28から22/28に変わるだけ。続けて「ホチキスでパチン」を使っても、ゲージが10/28に変わるだけで、6と12の違いが分かりにくい

ルールとしては正しく動いていますが、ゲージを見ていないと「当たったのか」「どれぐらい効いたのか」が分かりません。6ダメージのカードも12ダメージのカードも、見た目は同じです。

今回は、当たった瞬間に次のことを一度に起こします。

  • カードが飛んでいく:どの敵に使ったかが分かる
  • 白く光って、のけぞる:当たったことが分かる
  • 数字が飛び出す:どれぐらい効いたかが分かる。大きいダメージほど大きな文字にする
  • 火花・ヒットストップ・揺れ:「重さ」が伝わる。揺れはダメージが大きいほど強くする

1つ1つは0.1〜0.3秒ぐらいの短い動きです。でも、全部が同じ瞬間に重なると、同じ6ダメージでもぐっと気持ちよくなります。

フェアリー

ゲームの「手応え」は、1つの派手な演出よりも、小さな反応をいくつも重ねると出てくるよ!

前回の続きから

前回の最後の状態から始めます。火花の粒の絵は、素材のzipを入れ直すと入ります。

  • DeckBuildingAssets.zip
    • DeckBuilding/Sprites/Effects/fx_spark.png(火花の粒。白い4本の光。色はパーティクルの設定で付ける)
    • 自分で入れた場合は、Sprite Mode:Singleにして Apply

パーティクルをUIの手前に出すために、Sorting Layerを1つ足しておきます。

  • Edit>Project Settings>Tags and Layers を開く
  • Sorting Layersの「+」を押して、名前をEffectにする
    • Defaultより下(後ろ)に並んでいればOK。下に並んでいるほど手前に描かれます
Tags & Layersの設定で、Sorting Layersを開いた状態。Layer 0がDefault、その下にEffectが並んでいる。Effectの行を赤枠で囲む

#1でCanvasを「Screen Space – Camera」にしたのは、ここでパーティクルをUIの手前に出すためです。Canvasは Sorting Layer:Default で描かれるので、Effect にしたパーティクルはUIより手前に表示されます。

カードを敵へ飛ばそう(CardFly)

今は、カードを離した瞬間にカードが消えて、効果が出ています。これを「カードが相手まで飛んでいって、当たったら効果が出る」順番に変えます。

  • DeckBuilding/Scriptsに「CardFly」スクリプトを作る
using System.Collections;
using UnityEngine;

// 使ったカードを、ねらった相手へ飛ばす(小さくなりながら、だんだん速く)
public class CardFly : MonoBehaviour
{
    [SerializeField] private float flySeconds = 0.2f; // 飛んでいく時間
    [SerializeField] private float endScale = 0.3f;   // 当たるときの大きさ

    public IEnumerator FlyTo(Vector3 targetPosition)
    {
        transform.SetAsLastSibling(); // 手札のほかのカードより手前に出す
        Vector3 startPosition = transform.position;
        Vector3 startScale = transform.localScale;
        Quaternion startRotation = transform.localRotation;

        float time = 0f;
        while (time < flySeconds)
        {
            time += Time.deltaTime;
            float t = Mathf.Clamp01(time / flySeconds);
            float eased = t * t; // 最初はゆっくり、だんだん速く(勢いよく当たる)
            transform.position = Vector3.Lerp(startPosition, targetPosition, eased);
            transform.localScale = Vector3.Lerp(startScale, Vector3.one * endScale, eased);
            transform.localRotation = Quaternion.Lerp(startRotation, Quaternion.Euler(0f, 0f, -30f), eased);
            yield return null;
        }
    }
}
  • FlyToはコルーチン
    • 呼ぶ側がyield return StartCoroutine(…)で、飛び終わるまで待てるようにしています
  • eased = t * t
    • 0→1の進み方を「最初はゆっくり、後半で速く」にする計算です。そのままtを使うと同じ速さで進むので、ぶつかる勢いが出ません
  • 位置・大きさ・角度を、同じeasedで一緒に変える
    • 小さくなりながら少し傾いて、奥へ飛んでいくように見えます

作ったら、Prefabs/Cardを開いて、Add Component>CardFlyを付けます。

ProjectウインドでPrefabs/Cardを選択したInspector。一番下にCard Fly (Script)があり、Fly Secondsが0.2、End Scaleが0.3。Card Flyを赤枠で囲む

Deck・CardDrag・PlayZoneを直す

カードを飛ばす間は、カードを消さずに残しておく必要があります。3つのスクリプトを直します。

まずDeckのUseCardから、カードを消す1行を消します(手札のリストから外すのはそのまま)。

        handLayout.Remove((RectTransform)view.transform);
        Destroy(view.gameObject); // ★#10 この行を消す(カードは PlayZone が飛ばしてから消す)
        UpdateCounts();

CardDragのOnEndDragは、使ったカードがマウスに反応しないように、blocksRaycasts = trueをifの中へ移します。

    public void OnEndDrag(PointerEventData eventData)
    {
        if (!isUsed)
        {
            canvasGroup.blocksRaycasts = true;   // ★#10 使ったカードは、飛んでいる間もマウスに反応させない
            handLayout.ClearHeld(rectTransform); // 使わなかったら、手札の並びに戻す
        }
    }

最後にPlayZoneです。OnDropの最後で、効果を出していたforeachを新しいコルーチンに移します。

using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
  • 一番上にusing System.Collections;を足す(コルーチンを使うため)
        card.MarkUsed();
        energy.Pay(data.cost);             // ★#7 エネルギーを払う
        deck.UseCard(card.View, targetName);
        StartCoroutine(PlayCard(card, data, enemy)); // ★#10 飛ばしてから効果を出す
    }

    // ★#10 カードを相手へ飛ばして、当たったら効果を出す
    private IEnumerator PlayCard(CardDrag card, CardData data, Fighter enemy)
    {
        Fighter target = enemy != null ? enemy : player; // 攻撃は敵へ、ほかはスライムちゃんへ
        CardFly fly = card.GetComponent<CardFly>();
        yield return StartCoroutine(fly.FlyTo(target.transform.position));
        Destroy(card.gameObject);

        // ★#7 カードの効果を、上から順番に出す
        foreach (CardEffect effect in data.effects)
        {
            effect.Apply(player, enemy, deck, energy);
        }
    }
  • OnDropのdeck.UseCard(…)より下(foreachの部分)を、StartCoroutine(PlayCard(…))の1行に置きかえる
  • 攻撃のカードは敵へ、防御や回復のカードはスライムちゃんへ飛んでいく
  • 飛び終わってからカードを消して、効果を出す

コルーチンはPlayZoneで動かしています。カードの側(CardFly)で動かすと、カードをDestroyした瞬間にコルーチンも止まってしまい、効果が出ません。消すものとは別のオブジェクトで待つのがポイントです。

「定規でペシッ」をブリキロボの上で離すと、カードが小さくなって少し傾きながら、ブリキロボへ吸いこまれるように飛んでいき、当たった瞬間に白く光る。1コマずつゆっくり再生(GIF)

白く光ってのけぞる(HitReaction)

次は、当たった敵の反応です。

  • 白く光る:0.08秒だけ、敵の形のまま真っ白にする
  • のけぞる:当たった向き(右)に40だけ押されて、0.25秒で元の位置に戻る

白く光らせるマテリアル

UIのImageのColorは「絵の色に掛け算」なので、白(255,255,255)にしても元の絵のままで、白くは光りません。そこで、絵の形だけを使って1色で塗るマテリアルを使います。

  • Projectウインドで DeckBuilding/Effects フォルダを作る
  • Effectsフォルダで右クリック>Create>Material、名前をWhiteFlash
  • Shader:GUI/Text Shader
ProjectウインドでEffects/WhiteFlashを選択したInspector。ShaderがGUI/Text Shaderになっている。Shaderの欄を赤枠で囲む

GUI/Text Shaderは、もともと文字を描くためのシェーダーです。絵の透明なところ(形)だけを使い、色はImageのColorで塗るので、「形はそのまま、色は真っ白」に出来ます。

Enemyのプレハブに光る絵を重ねる

  • Prefabs/Enemyを開く
  • Enemyの子にFlash(UI (Canvas)>Image)
    • Enemyの子の一番上に移す(体の上、ゲージや予告の下に描かれる)
    • アンカー:stretch-stretch、Left・Top・Right・Bottom:0
    • Source Image:enemy_tinrobot(仮。Preserve Aspectの項目を出すため)
    • Material:WhiteFlash
    • Color:白、Alpha:0(ふだんは見えない)
    • Raycast Targetのチェックを外す
    • Preserve Aspectにチェック(体の絵と同じ)
Enemyのプレハブの編集画面。左はHierarchyで、Enemyの子の一番上にFlash。右はFlashのInspectorで、アンカーがstretch-stretch、Source Imageがenemy_tinrobot、MaterialがWhiteFlash、Raycast Targetのチェックなし、Preserve Aspectのチェックあり。HierarchyのFlashと、Materialを赤枠で囲む

Source Imageに入れた絵は仮です。敵の絵はデータから入るので、再生したらスクリプトで体と同じ絵に入れかえます(Source Imageが空だと、Preserve Aspectの項目がInspectorに出てきません)。

  • DeckBuilding/Scriptsに「HitReaction」スクリプトを作る
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

// ダメージを受けたときの反応:白く光って、のけぞって戻る
[RequireComponent(typeof(Image))]
public class HitReaction : MonoBehaviour
{
    [SerializeField] private Image flashImage;                           // 体の上に重ねた、白く光らせる絵
    [SerializeField] private float flashSeconds = 0.08f;                 // 白く光っている時間
    [SerializeField] private Vector2 knockback = new Vector2(40f, 0f);   // のけぞる向きと距離
    [SerializeField] private float recoverSeconds = 0.25f;               // 元の位置に戻るまでの時間

    private RectTransform body;
    private Vector2 startPosition;

    private void Start()
    {
        body = (RectTransform)transform;
        startPosition = body.anchoredPosition;
        flashImage.sprite = GetComponent<Image>().sprite; // 体と同じ形で光らせる
        flashImage.color = Color.clear;
    }

    // Fighter がダメージを受けたときに呼ぶ
    public void Play(int damage)
    {
        StopAllCoroutines();
        StartCoroutine(FlashAndKnockback());
    }

    private IEnumerator FlashAndKnockback()
    {
        // 白く光って、後ろへ押される
        flashImage.color = Color.white;
        body.anchoredPosition = startPosition + knockback;
        yield return new WaitForSeconds(flashSeconds);
        flashImage.color = Color.clear;

        // 元の位置へ戻る
        float time = 0f;
        while (time < recoverSeconds)
        {
            time += Time.deltaTime;
            body.anchoredPosition = Vector2.Lerp(startPosition + knockback, startPosition, time / recoverSeconds);
            yield return null;
        }
        body.anchoredPosition = startPosition;
    }
}
  • Startで体の絵をflashImageに入れる
    • 敵の絵は、EnemyParty が敵を作った直後のSetupで入ります。Startはそのあとに呼ばれるので、ブリキロボにもびっくり箱にも、正しい形が入ります
  • StopAllCoroutines()
    • ハサミのように続けて当たったとき、前の動きを止めてから、もう一度最初から光らせます
  • 光るのは一瞬で、戻るのはゆっくり
    • 「バシッ」と押されて「スーッ」と戻ると、当たった感じが出ます

作ったら、プレハブのEnemy(一番上)に Add Component>HitReactionを付けて、Flash ImageにFlashを入れます。

Enemyのプレハブの編集画面で、Enemy(一番上)を選択したInspector。Hit Reaction (Script)のFlash ImageにFlash、Flash Secondsが0.08、KnockbackがX 40・Y 0、Recover Secondsが0.25(Hit Stop Seconds 0.06は、この記事の最後で足す項目)。Hit Reactionを赤枠で囲む

Fighterから呼ぶ

HPが減ったときに、HitReactionを呼びます。Fighterに足します。

    private int block; // 防御:ダメージを先に受けとめてくれる
    private HitReaction hitReaction; // ★#10 ダメージを受けたときの反応(付いていなければ null)
    private void Awake()
    {
        hp = maxHp;
        hitReaction = GetComponent<HitReaction>(); // ★#10
    }
        Debug.Log($"{fighterName}に{amount}ダメージ(防御で{blocked}防いだ) HP {hp}/{maxHp}");

        // ★#10 HP が減ったら、反応を出す
        if (damage > 0 && hitReaction != null)
        {
            hitReaction.Play(damage);
        }
    }
  • TakeDamageのDebug.Logの下に足す
  • スライムちゃん(PlayerArea)にはまだHitReactionを付けていないので、hitReactionはnullになり、何も起きません(#11で付けます)
  • 防御で全部防いだとき(damageが0)も反応しません。防いだときの演出は#11で作ります
ブリキロボに定規でペシッが当たった瞬間。ブリキロボが真っ白なシルエットになって、少し右にずれている。火花と数字も出始めている

ダメージの数字を出そう(DamagePopup)

次は、当たったところから飛び出す数字です。

数字を出す場所(PopupLayer)

数字は、敵やカードより手前に出したいので、Canvasの一番下(一番手前)に専用の入れ物を作ります。

  • Canvasの子にPopupLayer(Create Empty)
    • Canvasの子の一番下に置く
    • アンカー:stretch-stretch、Left・Top・Right・Bottom:0

数字のプレハブ

  • DeckBuilding/Scriptsに「DamagePopup」スクリプトを作る
using TMPro;
using UnityEngine;

// ダメージの数字:大きく出て、放物線を描いて飛び、消える
[RequireComponent(typeof(TextMeshProUGUI))]
public class DamagePopup : MonoBehaviour
{
    [SerializeField] private float lifeSeconds = 0.8f; // 消えるまでの時間
    [SerializeField] private float jumpSpeed = 600f;   // 上へ飛び出す速さ
    [SerializeField] private float gravity = 1800f;    // 下へ引っぱる強さ

    private TextMeshProUGUI text;
    private RectTransform rectTransform;
    private Vector2 velocity;
    private float time;

    public void Setup(int damage)
    {
        text = GetComponent<TextMeshProUGUI>();
        rectTransform = (RectTransform)transform;
        text.text = damage.ToString();
        text.fontSize = Mathf.Min(60f + damage * 4f, 140f); // 大きいダメージほど大きな文字に
        velocity = new Vector2(Random.Range(-150f, 150f), jumpSpeed);
    }

    private void Update()
    {
        time += Time.deltaTime;
        float t = time / lifeSeconds;

        // 放物線:上へ飛び出して、重力で落ちてくる
        velocity.y -= gravity * Time.deltaTime;
        rectTransform.anchoredPosition += velocity * Time.deltaTime;

        // 出た瞬間だけ大きく、後半で透明に
        rectTransform.localScale = Vector3.one * Mathf.Lerp(1.8f, 1f, t / 0.15f);
        text.alpha = Mathf.InverseLerp(1f, 0.6f, t);

        if (time >= lifeSeconds)
        {
            Destroy(gameObject);
        }
    }
}
  • 文字の大きさ:60 + ダメージ×4(上限140)
    • 定規の6なら84、ホチキスの12なら108。数字の大きさでダメージの差が伝わるようにしています
  • 動き:上向きの速さ(jumpSpeed)で飛び出して、毎フレームgravityで下向きに引っぱる
    • 横の速さをRandom.Rangeで少しずつ変えているので、ハサミのように続けて当たっても数字が重なりません
  • 大きさ:出た瞬間は1.8倍で、0.15秒で1倍に
  • 透明度:Mathf.InverseLerp(1f, 0.6f, t)で、残り4割の時間で1→0に
    • InverseLerp(a, b, v)は「vがaからbのどのあたりか」を0〜1で返します。aとbを逆に書くと、だんだん減る値になります

プレハブを作ります。

  • PopupLayerの子にDamagePopup(UI (Canvas)>Text – TextMeshPro)
    • Width:300、Height:160
    • テキスト:6、Font Size:84、Font Style:B、Color:#E8433A、Alignment:中央
    • Text Wrapping Mode:No Wrap(折り返さない)、Raycast Targetのチェックを外す
    • Add Component>DamagePopup
  • Prefabsフォルダへドラッグしてプレハブにして、Hierarchyからは消す
ProjectウインドでPrefabs/DamagePopupを選択したInspector。TextMeshProのテキストが6、Font Size 84、Bold、Colorが赤(#E8433A)、Alignmentが中央。一番下にDamage Popup (Script)(Life Seconds 0.8、Jump Speed 600、Gravity 1800)

数字を出す(EffectSpawner)

数字や火花を「どこに・何を出すか」を1か所にまとめます。

  • DeckBuilding/Scriptsに「EffectSpawner」スクリプトを作る
using UnityEngine;

// ダメージの数字や火花を、指定した場所に出す
public class EffectSpawner : MonoBehaviour
{
    [SerializeField] private DamagePopup damagePopupPrefab;
    [SerializeField] private RectTransform popupLayer; // 数字を出す親(Canvas の一番手前)
    [SerializeField] private ParticleSystem sparksPrefab;

    public void ShowDamage(int damage, Vector3 position)
    {
        DamagePopup popup = Instantiate(damagePopupPrefab, popupLayer);
        popup.transform.position = position;
        popup.Setup(damage);
    }

    public void PlaySparks(Vector3 position)
    {
        // 火花は出し終わったら自分で消える(Stop Action:Destroy)
        Instantiate(sparksPrefab, position, Quaternion.identity);
    }
}
  • ShowDamage:PopupLayerの子に数字を作って、敵と同じ位置に置く
    • transform.position(ワールドの位置)でそろえているので、親がちがっても同じ場所に出ます
  • PlaySparks:火花は次の節で作ります。先に書いておくだけでOK
  • HierarchyにEffects(Create Empty)を作って、Add Component>EffectSpawner
    • Damage Popup Prefab:Prefabs/DamagePopup
    • Popup Layer:PopupLayer
    • Sparks Prefab:次の節で入れる

HitReactionから呼びます。HitReactionに足します。

    private RectTransform body;
    private Vector2 startPosition;
    private EffectSpawner effectSpawner; // ★#10 数字と火花を出す
        flashImage.color = Color.clear;

        effectSpawner = FindFirstObjectByType<EffectSpawner>(); // ★#10
    }
    public void Play(int damage)
    {
        StopAllCoroutines();
        StartCoroutine(FlashAndKnockback());

        effectSpawner.ShowDamage(damage, transform.position); // ★#10 数字
    }
  • 敵はプレハブから作るので、シーンのEffectsはInspectorで入れられません。FindFirstObjectByTypeでシーンから探します(#7のCardViewでEnergyを探したのと同じです)
当たった直後。白いブリキロボの上に、赤い大きな「6」が飛び出している

火花のパーティクル

当たったところに、オレンジの火花を散らします。パーティクルの基本は12-4 パーティクルで演出しよう:Particle Systemの基本で紹介しています。

  • Hierarchyで右クリック>Effects>Particle System、名前をHitSparks
  • 下の表のとおりに設定する
モジュール項目:値
MainDuration:0.5、Looping:チェックなし
Start Lifetime:Random Between Two Constants 0.25〜0.45
Start Speed:Random Between Two Constants 6〜12
Start Size:Random Between Two Constants 0.3〜0.6
Start Rotation:Random Between Two Constants 0〜360
Start Color:Random Between Two Colors #FFE066・#FF8A3D
Gravity Modifier:1、Simulation Space:World、Stop Action:Destroy
EmissionRate over Time:0、Burstsの「+」で Time:0・Count:18
ShapeShape:Circle、Radius:0.1
Size over Lifetimeチェックを入れて、Sizeのカーブを「1から0へ下がる」形に
Texture Sheet Animationチェックを入れて、Mode:Sprites、Spriteに fx_spark
RendererMaterial:Sprite-Unlit-Default、Sorting Layer:Effect
HierarchyでHitSparksを選択したInspectorの、Mainモジュール。Duration 0.5、Loopingのチェックなし、Start Lifetime 0.25〜0.45、Start Speed 6〜12、Start Size 0.3〜0.6、Start Colorが黄色とオレンジの2色、Gravity Modifier 1、Simulation Space:World、Stop Action:Destroy
HitSparksのInspectorを下へスクロールした状態。Mainの下半分(Stop Action:Destroy)と、チェックの入ったモジュール(Emission・Shape・Size over Lifetime・Texture Sheet Animation・Renderer)、RendererのマテリアルがSprite-Unlit-Default
  • 大きさと速さの単位は、Canvasの数字(1920×1080)ではなくワールドの単位です
    • このシーンのカメラ(Orthographic Size:5)では、画面の縦が10、1がCanvasの約108に当たります。Start Speed 6〜12は、1秒に650〜1300ぐらい飛ぶ速さです
  • Sorting Layer:Effect で、UIより手前に描かれます
  • Materialは、2DのURPで確実に表示されるスプライト用の Sprite-Unlit-Default にしています
    • 選ぶときは、Materialの右の丸いボタンを押して「Sprite-Unlit-Default」を検索

設定できたら、HitSparksをPrefabsフォルダへドラッグしてプレハブにして、Hierarchyからは消します。EffectsのSparks Prefabに、Prefabs/HitSparksを入れます。

HitReactionのPlayに1行足します。

        effectSpawner.ShowDamage(damage, transform.position); // ★#10 数字
        effectSpawner.PlaySparks(transform.position);         // ★#10 火花
白い光が消えたあと。ブリキロボのまわりに、黄色〜オレンジの4本の光の粒が散らばっている

火花が見えないときは、RendererのSorting LayerがEffectになっているか確かめましょう。Defaultのままだと、Canvas(背景)の後ろに隠れてしまいます。ピンク色の四角になるときは、Materialが合っていません。

ヒットストップと画面の揺れ

最後に、当たった瞬間の「重さ」を足します。

  • ヒットストップ:当たった瞬間、0.06秒だけ時間を止める。格闘ゲームなどでおなじみの「バシッ」と止まる感じ
  • 画面の揺れ:戦いの画面(Stage)を揺らす。ダメージが大きいほど強く

HitStop

  • DeckBuilding/Scriptsに「HitStop」スクリプトを作る
using System.Collections;
using UnityEngine;

// 当たった瞬間に、ほんの少しだけ時間を止める
public class HitStop : MonoBehaviour
{
    public void Play(float seconds)
    {
        StopAllCoroutines();
        StartCoroutine(Stop(seconds));
    }

    private IEnumerator Stop(float seconds)
    {
        Time.timeScale = 0f;
        // timeScale が 0 の間も進む unscaledDeltaTime で数える
        float time = 0f;
        while (time < seconds)
        {
            time += Time.unscaledDeltaTime;
            yield return null;
        }
        Time.timeScale = 1f;
    }
}
  • Time.timeScale = 0fで、ゲームの時間が止まる
    • Time.deltaTimeが0になるので、deltaTimeで動いているもの(カード・数字・のけぞり・パーティクル)が全部止まります
  • 止まっている間の時間は、Time.unscaledDeltaTimeで数える
    • Time.deltaTimeで数えると、0のまま増えないので、いつまでも時間が戻りません

ScreenShake

  • DeckBuilding/Scriptsに「ScreenShake」スクリプトを作る
using UnityEngine;

// 戦いの画面(Stage)を揺らす。ダメージが大きいほど強く
public class ScreenShake : MonoBehaviour
{
    [SerializeField] private RectTransform stage;
    [SerializeField] private float shakeSeconds = 0.25f;    // 揺れている時間
    [SerializeField] private float strengthPerDamage = 2f;  // ダメージ1あたりの揺れの幅
    [SerializeField] private float maxStrength = 30f;       // 揺れの幅の上限

    private Vector2 startPosition;
    private float strength;
    private float timeLeft;

    private void Start()
    {
        startPosition = stage.anchoredPosition;
    }

    public void Play(int damage)
    {
        strength = Mathf.Min(damage * strengthPerDamage, maxStrength);
        timeLeft = shakeSeconds;
    }

    private void Update()
    {
        if (timeLeft <= 0f)
        {
            return;
        }
        // ヒットストップ中も揺れるように、unscaledDeltaTime で減らす
        timeLeft -= Time.unscaledDeltaTime;
        if (timeLeft <= 0f)
        {
            stage.anchoredPosition = startPosition;
            return;
        }
        float power = strength * (timeLeft / shakeSeconds); // だんだん弱く
        stage.anchoredPosition = startPosition + Random.insideUnitCircle * power;
    }
}
  • 揺れの幅:ダメージ×2(上限30)
    • 定規の6なら12、ホチキスの12なら24。重いカードほど強く揺れます
  • Random.insideUnitCircle:半径1の円の中のランダムな点。これに幅を掛けて、毎フレームちがう方向へずらします
  • 揺らすのはStageだけ
    • 手札(HandArea)やエネルギーなどのHudは揺らさないので、揺れていてもカードを操作しやすいままです
  • 揺れはunscaledDeltaTimeで数えるので、ヒットストップで止まっている間も揺れ続けます
ヒットストップ中に止まるもの・止まらないもの
  • 止まる(Time.deltaTime):飛んでいるカード、敵ののけぞり、数字、火花、手札の動き
  • 止まらない(Time.unscaledDeltaTime):画面の揺れ、ヒットストップ自身の時間
  • 「全部止まっているのに、画面だけがブルッと震える」ので、止まった瞬間が強く感じられます

どちらもEffectsに付けます。

  • Effectsに Add Component>HitStop
  • Effectsに Add Component>ScreenShake、StageにCanvas>Stageを入れる
HierarchyでEffectsを選択したInspector。Effect SpawnerのDamage Popup PrefabにDamagePopup、Popup LayerにPopupLayer、Sparks PrefabにHitSparks。その下にHit Stop (Script)、Screen Shake (Script)(StageにStage、Shake Seconds 0.25、Strength Per Damage 2、Max Strength 30)

HitReactionに足します。これでHitReactionは完成です。

    [SerializeField] private float recoverSeconds = 0.25f;               // 元の位置に戻るまでの時間
    [SerializeField] private float hitStopSeconds = 0.06f;               // ★#10 時間を止める長さ
    private EffectSpawner effectSpawner; // ★#10 数字と火花を出す
    private HitStop hitStop;             // ★#10
    private ScreenShake screenShake;     // ★#10
        effectSpawner = FindFirstObjectByType<EffectSpawner>(); // ★#10
        hitStop = FindFirstObjectByType<HitStop>();             // ★#10
        screenShake = FindFirstObjectByType<ScreenShake>();     // ★#10
    }
        effectSpawner.ShowDamage(damage, transform.position); // ★#10 数字
        effectSpawner.PlaySparks(transform.position);         // ★#10 火花
        hitStop.Play(hitStopSeconds);                         // ★#10 一瞬止める
        screenShake.Play(damage);                             // ★#10 揺らす
    }
スライム

0.06秒って、ほとんど見えない長さですよね・・・本当に効くんですか?

フェアリー

見えないぐらいがちょうどいいの!長くすると「引っかかった」ように感じるから、0.03〜0.1秒ぐらいで試してみてね

動かして確認!

再生して、「定規でペシッ」と「ホチキスでパチン」を敵に使ってみましょう。ホチキスは最初のデッキに入っていないので、確かめるときだけDeckのStarting Deckに足してください(#7と同じです)。

「定規でペシッ」をブリキロボへ離すと、カードが飛んでいき、ブリキロボが白く光ってのけぞり、火花と「6」が出て画面が小さく揺れる。続けて「ホチキスでパチン」を使うと、大きな「12」が出て、揺れも強い。ゲージは22/28、10/28と減っていく

#10の動作確認

  • カードを敵の上で離すと、カードが小さくなりながら敵へ飛んでいく
  • 防御や回復のカードは、スライムちゃんへ飛んでいく
  • 当たった瞬間に、敵が白く光って右へのけぞり、元の位置へ戻る
  • 赤い数字が飛び出して、放物線を描いて消える。12の方が6より大きい
  • オレンジの火花が散って、自然に消える(Hierarchyに HitSparks(Clone) が残り続けない)
  • 画面(Stage)が揺れる。手札とエネルギーは揺れない。12の方が6より強く揺れる
スライム

同じ6ダメージなのに、ぜんぜん気持ちよさが違いますね!

あとがき

今回は、攻撃が当たったときに、カードが飛んで、敵が光ってのけぞり、数字と火花が飛んで、画面が揺れるようにしました。1つ1つは短い動きですが、重ねると「効いた!」という手応えがしっかり出ますね。次回は、スライムちゃんが攻撃を受けたとき・防御で防いだとき・回復したときの演出を作りたいと思います!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

目次