ゴマちゃんフロンティア

気まぐれと勢いで作るUnityゲーム開発日記です

【開発メモ】被ダメージ処理とノックバック・無敵時間の実装

time 2016/01/10

というわけで、今回はキャラクターのダメージ処理に関するお話です!

shot2ss20160110205041201

敵の攻撃やトラップなどでダメージを受けると体力ゲージが減ったり、ダメージモーションとして仰け反ってしまったりします。
アクションゲームでは最早常識レベルの処理ですが、すごく適当に作って以来放置していたので、しっかり作り直すことにしました!

とは言っても、プレイヤーキャラクターと敵キャラクターでダメージ処理がそこそこ違うので、今回はプレイヤー側の処理を考えてみます。
プレイヤー側には無敵時間やノックバック処理などを搭載する必要があるため、敵側と比べて複雑になりそうです。

基本的な仕様

ダメージに関する仕様について整理してみます。
大きく分けて以下の3つでしょうか。

・ダメージ発生時にプレイヤーの体力値を減らす
ダメージを与えた側の攻撃力分だけ体力を減らします。
「防御力」や「ダメージ軽減」といった要素は入れないつもりなので、攻撃力分そのままを体力から減らします。

・ダメージを受けた際にプレイヤーをノックバックさせる
ダメージを受けたプレイヤーは後方へ後ずさりします。
ダメージモーションに切り替えつつ、iTweenで後ろに少し移動させればいけそうです。
横や上へ吹き飛ばしたりダウンなども入れたいですが、とりあえずシンプルな実装でいきます!

また、
・ノックバック中は顔のテクスチャを切り替え、痛そうな感じの表情にする
・ダメージモーション中は移動入力を受け付けなくする
等の処理も合わせて行います。

・ダメージ後一定時間は攻撃を受けない
所謂「無敵時間」です。
ダメージ発生時にコルーチンを実行し、一定時間経過後にフラグを切り替えます。
プレイヤー側に攻撃判定が当たった際にそのフラグを参照し、無敵状態であれば処理を行わないようにします。
無敵時間が分かるよう、何かエフェクトを入れたいところです。

被ダメージ処理

各キャラクタークラスには共通してdamage()という関数を持たせるようにします。
「ICharacter」インターフェースを作り、実装することを強要させておきます。

【ICharacterインターフェース】

using UnityEngine;
using System.Collections;

public interface ICharacter {
    GameObject getGameObject();
    void damage(float power, Vector3 force);
}

今は使いませんが、GameObjectのゲッタも実装させるようにします。
GameObjectを取得できればどうにでもなるのがUnityだったりするので・・・。

今回はプレイヤー側のダメージ処理なので、Playerクラスにdamage()を実装します。

【Playerクラス】

public void damage(float power, Vector3 force){
    // 無敵判定
    if(!isInvincible){
        life -= power;

        if(life < 0){
            life = 0;
        }

        // ダメージ中の移動を制限する
        switchCharacterMovement(false);

        // ダメージモーション
        motionChange("Damage", true, true);
        isInvincible = true;

        // ノックバック
        iTween.MoveTo(gameObject, iTween.Hash(
            "position", transform.position - (transform.forward * 10f),
            "time", GameConstants.DAMAGE_RESET_TIME,
            "easetype", iTween.EaseType.linear,
            "oncomplete", "onInvincibleState",
            "oncompletetarget", gameObject,
            "oncompleteparams", GameConstants.DAMAGE_INVINCIBLE_TIME
        ));

        // テクスチャ切り替え
        faceMaterial.SetTexture("_MainTex", faceTex[1]);
        StartCoroutine(damageReset(GameConstants.DAMAGE_RESET_TIME));
    }
}

damage()は攻撃判定がプレイヤーに当たった際にその判定から呼び出されます。
power(攻撃力)とforce(吹き飛ばす力)を引数に指定していますが、今回使うのは前者だけです。

しかし、この実装では後々攻撃判定のパラメータが増えた際のリファクタが面倒ですね。
そのうち「ダメージを与えるオブジェクト」用のインターフェースを作成し、実装された関数を通じて取得できるようにするのが理想でしょうか。
最近「EC-CUBE3」を触っているためか、コーディングの考え方が変わってきている気がします。

ダメージ時のノックバック

damage()内でiTween.MoveBy()を実行して後ずさりさせます。
上のソースでは 行目になります。

iTween.MoveTo(gameObject, iTween.Hash(
    "position", transform.position - (transform.forward * 10f),
    "time", GameConstants.DAMAGE_RESET_TIME,
    "easetype", iTween.EaseType.linear,
    "oncomplete", "onInvincibleState",
    "oncompletetarget", gameObject,
    "oncompleteparams", GameConstants.DAMAGE_INVINCIBLE_TIME
));

今向いている方向の逆側にノックバックさせるので、positionは「現在位置 – (前方 * ノックバック量)」を指定します。
timeは定数「DAMAGE_RESET_TIME」で0.4を指定しています。
イージングは等速がいいので、easetypeはLinearにしておきます。
iTween.EaseTypeで指定できることを知ったのは最近だったりします。

合わせて以下の処理も行います。

ノックバック中の顔のテクスチャ切り替え

PlayerクラスにHeadObjectというパラメータを持たせ、予めインスペクターから設定しておきます。
自分の場合はマテリアルの「_MainTex」に貼られているので、顔オブジェクトからテクスチャが貼られているマテリアルを探します。
他でも使いそうなのでユーティリティ化してみました!

【ObjectUtilityクラス】

using UnityEngine;
using System.Collections;

public static class ObjectUtility {
    public static Material getMaterialByObject(GameObject gameObject, string propetyName) {
        Renderer renderer = gameObject.GetComponent<Renderer>();

        for (int i = 0; i < renderer.materials.Length; i++) {
            if (renderer.materials[i].GetTexture(propetyName) != null) {
                return renderer.materials[i];
            }
        }

        return null;
    }
}

【Playerクラス_Start()内】

faceMaterial = ObjectUtility.getMaterialByObject(headObject, "_MainTex");
if (!faceMaterial) {
    throw new UnityException("headObject texture not exist!");
}

ダメージを受けた際にリセット用コルーチンを実行し、元のテクスチャに戻します。
時間はノックバック処理と共通の定数を使用しているので、ちょうどノックバックが終わった頃にリセットされます。

【Playerクラス】

private IEnumerator damageReset(float time){
    yield return new WaitForSeconds(time);

    faceMaterial.SetTexture("_MainTex", faceTex[0]);
    switchCharacterMovement(true);
}

今は目と口をテクスチャで表現していますが、あまり見栄えがよくないので口くらいはモデル側のアニメーションでやりたいところです。
バリエーションは要らないのでシェイプキーでやるのが適任でしょうか。

ダメージモーションと移動制限

モーション切り替えはPlayerクラス内にあるmotionChange()で行います。
animator.selBool()をstateに応じて実行するだけなので割愛します。
移動制限はswitchCharacterMovement()で行います。

【Playerクラス】

public void switchCharacterMovement(bool state, bool switchRotate = true) {
    charMotor.canControl = state;
    canMoveing = state;

    if (switchRotate) { 
        platForm.autoRotate = state;
    }

PlayerクラスはCharacterController、CharacterMotor、PlatformInputControllerの参照をStart()で取得しています。
その各コンポーネントの制御を切り替える役割を担う関数です。
swtichRotateはPlatformInputControllerのAutoRotateも切り替えるかを指定します。
「移動のみ制限して回転だけさせたい」という場合に使います。

ダメージ後の無敵時間

Playerクラスに「isInvincible」というフラグを持たせ、そのフラグがONの間はダメージ処理を行わないようにします。
damage()内冒頭で判定し、無敵状態であればそのまま抜けるようにします。
「Collider自体を無効化する」のも手ですが、プレイヤーの場合はCharacterControllerによる移動制御も兼ねているので得策ではありません。

また、isInvincibleがONの場合はキャラクターを点滅させるエフェクトを入れます。
iTweenのFadeToを使えばいけそうです。

細かい部分ですが、ダメージモーションが終わってから無敵のエフェクトを入れるゲームが多いような気がします。
その方が見栄えがよさそうなので、ダメージモーション終了後からエフェクトを再生するようにします。
ノックバック処理を行っているiTween.MoveToのoncompleteで呼び出します。

protected IEnumerator onInvincibleState(float time){
    iTween.FadeTo(gameObject, iTween.Hash(
        "name", "invincible",
        "alpha", 0.1f,
        "time", 0.1f,
        "looptype", iTween.LoopType.loop
    ));

    yield return new WaitForSeconds(time);

    isInvincible = false;
    iTween.StopByName("invincible");

    iTween.FadeTo(gameObject, iTween.Hash(
        "alpha", 1f, 
        "time", 0f
    ));
}

iTweenのoncompleteでコルーチンが呼び出せるか知りませんでしたが、関数名を指定すれば実行できるようです。
この場合、oncompleteparamsに無敵時間リセットまでの時間を渡しています。

iTweenのlooptypeを指定するとアニメーションを繰り返し実行してくれます。
上記のFadeToの場合、「オブジェクトの透明度を1→0.1、0.1→1にする処理を0.1秒ごとに行う」ことになります。
これによってプレイヤーオブジェクトが点滅しているかのように見えます。

looptypeを指定したiTweenは実行され続けます。
iTween.Stop()で止めることができますが、他に実行されているiTweenも全て止まってしまうので、iTween.StopByName()を使って停止しています。

まとめ

最終的にはこんな感じになりました!
まともな攻撃ロジックの敵キャラがいないため、マリンパをウニに突撃させます。

20160110

体力ゲージがないのでダメージ量が分からなかったり、どちらから攻撃を受けても後ろ側にノックバックしたりと粗はあります。
ただノックバック自体の挙動や無敵時間はしっかり出来ているので、ひとまず良しとします!
「無敵時間がないので雑魚に囲まれると瞬殺される」といった問題も解決できました。

そんなわけで、

・プレイヤーのダメージ処理は大きく3つに分けて実装
・攻撃判定が当たった際にインターフェースで定義された関数を呼び出す
・被ダメージ時のノックバック処理はiTween.MoveBy()で実装
・無敵時間中のエフェクト処理はiTween.FadeTo()のlooptypeで実装

次回はプレイヤー側のダメージ処理の延長(ダウンや吹き飛ばしなど)か、敵側のダメージ処理の実装のどちらかになると思われます!

スポンサーリンク

down

コメントする



PHP

Twitter

イラスト

日記

未分類

ツイッター