Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
83
Help us understand the problem. What is going on with this article?
@flankids

敵のダメージリアクション・バリエーション

バトルがあるゲームの爽快感の重要な要素といえば、やっぱり敵のリアクションです(断言)
3Dゲームにおける敵のダメージリアクションといえば、ダメージモーションを再生するのが一般的・・・と思いきや、ダメージモーションって地味に取り回しが悪いですよね

確かにリアクションとしては一番丁寧な演出なのですが、モーションを流す以上、攻撃や移動などのモーションを中断しないといけないので演出の充実として実装するにしてはゲーム仕様側への影響が大きく、気軽に入れるわけにはいけません
そんなわけで一般的にはダメージモーションは大技を当てた時や、部位破壊的な特殊な状態になったときなど、「仕様に伴う特別な演出」で再生されることが多い印象です。

とはいえ一番頻度の高い「小さなダメージを与えた時」の演出が無いと爽快感がないどころか、攻撃が効いてるのかどうか分からない、なんてことになりかねません。

↑効いてる?弾かれてる?

市場のゲームでは「細かいダメージを与えた時の汎用演出」が用意されてて、いろいろあるなぁと思ったので自分が知ってるものを引き出しとして実装してみました。

■備考

本記事では演出の組み立てにDOTweenを使ってますが、同じ演出をAnimationでもベタ書きでも作れます

点滅によるダメージ表現

まずは点滅。もはや古典と言える演出ですが、攻撃が当たった結果何かが起きたことの分かりやすさはバッチリ。
RendererのON/OFFで完結するので負荷も低い(というかなんなら一時的に軽くすらなるのでは)ので、その辺は古くからの演出ならではですね。

これを汎用演出とすると他の演習も合わせてレトロなものにしないと異物感が大きいので、あえてそういう路線を攻めたりしない限りは他の演出を使ったほうが良さそう。

Enemy.cs
using UnityEngine;
using DG.Tweening;

public class Enemy : MonoBehaviour
{
    /// <summary> モデルのRenderer </summary>
    [SerializeField]
    private Renderer _renderer;

    private Sequence _seq;

    private void OnTriggerEnter(Collider other)
    {
        HitBlink();
    }

    /// <summary> 点滅によるダメージ演出再生 </summary>
    private void HitBlink()
    {
        _seq?.Kill();
        _seq = DOTween.Sequence();
        _seq.AppendCallback(() => _renderer.enabled = false);
        _seq.AppendInterval(0.07f);
        _seq.AppendCallback(() => _renderer.enabled = true);
        _seq.AppendInterval(0.07f);
        _seq.SetLoops(2);
        _seq.Play();
    }
}

赤のカラー乗算によるダメージ表現

3Dゲームの黎明期に主流だったような印象のある演出。
古臭さも薄く、実装も簡単でわかりやすいので今でも一定数見られるものだと思います。

難点としては、色の乗算なのでモデル自体が黒に近かったり、暗めの雰囲気を作っているゲームでは変化が目立ちづらいこと。
場合によっては汚れてるような印象にもなりうるので、時間が許すならもう少し踏み込んだ演出を作りたいところ。

Enemy.cs
using UnityEngine;
using DG.Tweening;

public class Enemy : MonoBehaviour
{
    /// <summary> マテリアルの色パラメータのID </summary>
    private static readonly int PROPERTY_COLOR = Shader.PropertyToID("_Color");

    /// <summary> モデルのRenderer </summary>
    [SerializeField]
    private Renderer _renderer;

    /// <summary> モデルのマテリアルの複製 </summary>
    private Material _material;

    private Sequence _seq;

    private void Awake()
    {
        // materialにアクセスして自動生成されるマテリアルを保持
        _material = _renderer.material;
    }

    private void OnTriggerEnter(Collider other)
    {
        HitFadeBlink(Color.red);
    }

    /// <summary> カラー乗算によるダメージ演出再生 </summary>
    private void HitFadeBlink(Color color)
    {
        _seq?.Kill();
        _seq = DOTween.Sequence();
        _seq.Append(DOTween.To(() => Color.white, c => _material.SetColor(PROPERTY_COLOR, c), color, 0.1f));
        _seq.Append(DOTween.To(() => color, c => _material.SetColor(PROPERTY_COLOR, c), Color.white, 0.1f));
        _seq.Play();
    }
}

白のカラー加算によるダメージ表現

最近よく見る演出。
加算で色を与えるのでモデルや画面の色味に左右されづらく、綺麗でわかりやすいです。
特にこだわりがなければこれが一番安定だと思います。

サンプルコードでは、[Create] > [Shader] > [Standard Surface Shader] で加算カラーを与えられるシェーダーを作っています。
プロパティ名は _AdditiveColor です。

StandardSurface-Additive.shader
Shader "Custom/StandardSurface-Additive"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _AdditiveColor ("Additive Color", Color) = (0,0,0) // 加算カラーのプロパティ追加
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        fixed3 _AdditiveColor; // 加算カラーのメンバ定義

        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb + _AdditiveColor.rgb; // 加算カラーを与える
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

上記のStandardSurface-Additive.shaderを敵モデルのマテリアルに与え、下記コードで明滅させます。

Enemy.cs
using UnityEngine;
using DG.Tweening;

public class Enemy : MonoBehaviour
{
    /// <summary> マテリアルの加算色パラメータのID </summary>
    private static readonly int PROPERTY_ADDITIVE_COLOR = Shader.PropertyToID("_AdditiveColor");

    /// <summary> モデルのRenderer </summary>
    [SerializeField]
    private Renderer _renderer;

    /// <summary> モデルのマテリアルの複製 </summary>
    private Material _material;

    private Sequence _seq;

    private void Awake()
    {
        // materialにアクセスして自動生成されるマテリアルを保持
        _material = _renderer.material;
    }

    private void OnTriggerEnter(Collider other)
    {
        HitFadeBlink(Color.white);
    }

    /// <summary> カラー加算によるダメージ演出再生 </summary>
    private void HitFadeBlink(Color color)
    {
        _seq?.Kill();
        _seq = DOTween.Sequence();
        _seq.Append(DOTween.To(() => Color.black, c => _material.SetColor(PROPERTY_ADDITIVE_COLOR, c), color, 0.1f));
        _seq.Append(DOTween.To(() => color, c => _material.SetColor(PROPERTY_ADDITIVE_COLOR, c), Color.black, 0.1f));
        _seq.Play();
    }
}

スケールの拡大・縮小によるダメージ表現

色ではなく動きで表現するダメージ演出。
カラー乗算・加算と組み合わせで使われることが多いかも。

大きめの敵で使うと画面上の変化量が大きすぎて、大げさに見えたり違和感があったりするので相性は悪いですが、1〜3頭身くらいのキャラ向けには向いてる演出だと思います。

Enemy.cs
using UnityEngine;
using DG.Tweening;

public class Enemy : MonoBehaviour
{
    /// <summary> 拡大・縮小するTransform </summary>
    [SerializeField]
    private Transform _model;

    private Sequence _seq;

    private void OnTriggerEnter(Collider other)
    {
        HitScaleBound();
    }

    /// <summary> スケールの拡大・縮小によるダメージ演出再生 </summary>
    private void HitScaleBound()
    {
        _seq?.Kill();
        _seq = DOTween.Sequence();
        _seq.Append(DOTween.To(() => Vector3.one, scale => _model.localScale = scale, Vector3.one * 1.05f, 0.07f));
        _seq.Append(_model.DOScale(1f, 0.07f));
        _seq.Play();
    }
}

のけぞり(ボーン回転)によるダメージ表現

本記事で紹介したかった本命のダメージ演出!
これ実は FF7 REMAKE で使われてた表現で・・・早い話がパクったネタです。
(バレットで敵に連射してるときにいい感じにガクガクしてて気持ちよかった)

攻撃によって特定のボーン(人型なら腰あたりが良い)を傾け、のけぞるような演出を与えています。
使用するボーンの選定や傾けかたをモデルごとに決めたり、量産時に規則を作ったりとやや配慮が必要ですが、変にモデルの色や大きさを変えない上、攻撃方向に応じたのけぞり方をするので、3Dゲームらしさのある自然な見た目になります。

Enemy.cs
using UnityEngine;
using DG.Tweening;

public class Enemy : MonoBehaviour
{
    /// <summary> のけぞりによるダメージ演出で回転するボーン </summary>
    [SerializeField]
    private Transform _waistBone;

    /// <summary> のけぞりによるダメージ演出によるボーンの回転角度 </summary>
    private Vector3 _offsetAnglesWaist;

    private Sequence _seq;

    private void OnTriggerEnter(Collider other)
    {
        // ヒットした攻撃判定の進行方向に向けて体を倒すため、角度を取得
        var bulletAngles = other.transform.eulerAngles;
        // X角度は無視
        bulletAngles.x = 0f;
        HitTiltWaist(Quaternion.Euler(bulletAngles) * Vector3.forward);
    }

    /// <summary> のけぞり(ボーン回転)によるダメージ演出を再生 </summary>
    /// <param name="vector">ヒットした攻撃判定の進行方向</param>
    private void HitTiltWaist(Vector3 vector)
    {
        _seq?.Kill();
        _seq = DOTween.Sequence();
        // 攻撃判定の進行方向を自身のTransformのローカル座標系に変換
        vector = transform.InverseTransformVector(vector);
        // 攻撃判定の進行方向に向けて10度傾ける
        // FIXME: Vector3のどの要素に攻撃判定の進行方向ベクトルの各要素を使うか、正/負解釈をどうするかは、モデルのボーン構造に合わせて適宜変更する
        var tiltAngles = new Vector3(0f, -vector.x, -vector.z).normalized * 10f;
        _seq.Append(DOTween.To(() => Vector3.zero, angles => _offsetAnglesWaist = angles, tiltAngles, 0.1f));
        _seq.Append(DOTween.To(() => tiltAngles, angles => _offsetAnglesWaist = angles, Vector3.zero, 0.2f));
        _seq.Play();
    }

    private void LateUpdate()
    {
        // Animatorによる今フレームのボーンの角度が決まった後、のけぞりによる回転角度を与える
        _waistBone.localEulerAngles += _offsetAnglesWaist;
    }
}

まとめ

汎用のダメージ演出はゲーム中に一番多く見ることになる演出だと思うので、いかに自然かつ小気味良いものにするか地味に重要な要素だな〜と思います。
「他にこういうのあるよ!」ってのがあったら教えてください!

83
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
flankids
スマホのゲーム作る仕事してます。最近はバーチャル空間のネタに興味あり。操作性やカメラワークに関する具体的な実装や小細工について書くことが多いです。noteに講演レポートなども書いてますので、よろしければそちらも是非ご覧ください! https://note.com/flankids

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
83
Help us understand the problem. What is going on with this article?