【開発メモ】敵キャラクターの被ダメージ処理の実装

というわけで、仕事の案件の納期が近づいてきており、なかなか時間がとれないりべるんです。
Symfony2とかDoctrineとか・・・まだまだ分からないことが多く厳しい日々が続きます。

shot2ss20160129211817219

前回まででプレイヤー側のダメージ処理が終わったので、今回は敵キャラクターのダメージ処理を実装していきます!
プレイヤーキャラとは扱いが違うため、ダメージ処理でやるべきことも変わります。
敵キャラクターがダメージ時にどうなるか(ノックバックorスーパーアーマーなど)はゲーム性に直結する重要な要素だと思いますが、難しいことは考えずにやります!

基本的な仕様

「アクションゲームの敵キャラ」ということを踏まえ、実装内容を考えてみます。

・ダメージ発生時にプレイヤーの体力値を減らす
これはプレイヤー側と一緒ですね。
ただしダウン中など「特定の条件の時にダメージを減衰させる」といった要素は入れるかも。

・ダメージ後の無敵時間はなし
プレイヤー側と最も異なる部分でしょうか。
連続攻撃や多段ヒット攻撃がある関係上、敵キャラクターにはダメージ後の無敵時間を入れない予定です。

・ダメージ発生時にダメージモーションを行う
これもプレイヤーと同じですが、ダメージモーション中に再度ダメージを受けた場合、再度ダメージモーションを行います。
つまり連続でダメージを受けている場合、ダメージモーションを取り続けて行動できなくなります。
また、ダメージ時のノックバックもありません。

・吹き飛ばし攻撃を受けるとダウンする
これまたプレイヤーと同じく、吹き飛ばし攻撃を受けたら「ダウン→着地→起き上がり」と遷移します。
ただ、所謂ボスや中ボスに相応する敵が吹き飛びまくるのも問題なので、キャラクターによっては条件を付けると思います。

・ダメージエフェクトで体色を赤くする
現状ではダメージを与えているか分かりにくいので、ダメージ時に一瞬だけ体を赤くします。
自分の知っているゲームでは「メトロイドプライム」シリーズのエフェクトに近いです。

・体力が0になったら大きく吹き飛ぶ
体力が0になった後に攻撃を受けた場合、その敵キャラは大きく吹き飛びます。
所謂「敵を倒す快感」が欲しかったので、派手に吹っ飛んでもらいます。
合わせて大き目のエフェクトを付けるとよさそうですね。

・・・ということを考慮しつつ、実際に作っていきます!
ボスキャラなどの特殊なケースは後回しとして、一般的な雑魚敵の場合を想定して実装してみます。

ダメージ処理

攻撃判定などの「ダメージを与えるもの」に関しては前回の記事で紹介しているため割愛させて頂きます。
詳しくは↓の記事をご参照下さい。

【開発メモ】被ダメージ処理とダウン状態の実装

敵キャラクターは全てEnemyクラスをスーパークラスとして持ちます。
その中にdamage()を実装します。
今はあまり関係ありませんが、インターフェース「ICharacter」でdamageの実装を強制させています。

public virtual void damage(IDamageGenerator damageGenerator){
    life -= damageGenerator.getPower();
    Vector3 force = damageGenerator.getForce();

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

        // フェードアウト
        iTween.FadeTo(gameObject, iTween.Hash(
            "alpha", 0,
            "time", 0.5f,
            "oncomplete", "EnemyDestroy",
            "oncompletetarget", gameObject
        ));

        // 吹き飛ばし処理
        if (force != Vector3.zero) {
            rb.AddForce(force * 100, ForceMode.Impulse);
        } else {
            rb.AddForce(-transform.forward * 100, ForceMode.Impulse);
        }
    }

    // ダメージエフェクト
    if (!gameObject.GetComponent<iTween>()) {
        iTween.ColorFrom(gameObject, iTween.Hash(
            "color", new Color(255, 0, 0),
            "time", 0.5f,
            "delay", 0.01f
        ));
    }

    // forceが指定されている場合はダウン処理
    if (force != Vector3.zero) {
        // ダウンモーション
        motionChange("Down", true, true);

        // 吹き飛ばし処理
        iTween.MoveTo(gameObject, iTween.Hash(
            "position", transform.position + force,
            "speed", damageGenerator.getForceSpeed(),
            "easetype", iTween.EaseType.easeOutCubic
        ));
    } else {
        // ダメージモーション
        animator.SetTrigger("Damage");
    }
}

IDamageGeneratorインターフェースから攻撃判定の攻撃力や吹き飛ばすベクトルを取得します。
基本的な部分は前回と変わらないので、敵固有の処理だけをまとめていきます!
ノックバックや無敵時間などプレイヤーキャラにあるデリケートな仕様がないので、処理だけならあちらより単純だったりします。

ダメージモーションの上書き

敵キャラの場合、ダメージモーション中でも連続してダメージモーションを再生します。
他のステートからダメージへの遷移はAnimator.Play()でOKです。
(またはAnyStateからDamageステートへの条件を設定して遷移させる方法もあります)
が、既に再生中のダメージモーションを中断して再実行するにはAnimatorController側から設定が必要です。
具体的には「Damageステートの遷移先として自身を設定」します。

shot2ss20160129212624334

同ステートへの再遷移条件用にDamageというTriggerパラメータを作成し、Conditionに設定します。
また、hasExitTimeのチェックは外しておきます。
あとはdamage()内のSetTrigger()でパラメータをONにすれば、モーションを割り込んでDamageステートの最初から再生されます。

ダメージエフェクトの実装

UnityでエフェクトといえばiTweenですね!
最早何度もお世話になっている神ライブラリです。
JavaScriptでいうjQuery的な、画期的なアセットだと思います!

ということで、iTween.ColorFrom()でオブジェクトの色を変化させます。
ColorTo()では変化させた後戻せなくなるのでNG
てっきりiTween側にreset的なものがあると思っていましたが、どうやらそれっぽいパラメータは存在しないようです。
Fromなら変化した後に元の色に戻ります。

shot2ss20160129211901283

ここで問題発生orz
画像を見ての通り、Colorが赤くなっていない部分があります。
iTweenのソースをみた感じでは、1オブジェクトに複数のマテリアルが設定されている場合になる気がします。

モデリング時に複数マテリアルを持たないように工夫すれば回避できそうですが、オブジェクトを結合したりテクスチャを貼っているとかなり厳しいです。
現状ではどうしようもないので、とりあえず保留ということで・・・。

体力0時に大きく吹き飛ばす処理の実装

Enemyクラスのフィールド「life」の値が0以下の時にダメージを受けた場合、敵をRigidbody.AddForce()で吹き飛ばします。
攻撃判定のforceが定義されている場合はその方向に、なければ敵キャラの後ろ方向へ力を掛けます。

if (force != Vector3.zero)
    rb.AddForce(force * 100, ForceMode.Impulse);
} else {
    rb.AddForce(-transform.forward * 100, ForceMode.Impulse);
}

「瞬間的な力」を加えるので、AddForceのForceModeには「Impulse」を指定します。
吹き飛ばすと同時にiTween.FateTo()でフェードアウトさせ、oncompleteで指定した関数内でオブジェクトの削除を行っています。

まとめ

・敵キャラクターにはダメージ後の無敵時間はなし
・ダメージモーション中に再度ダメージを受けた場合、モーションを上書きする
・ダメージ発生時にキャラクターの色を一時的に赤くする
・体力がなくなったときに大きく吹き飛ばす

まだ煮詰め切れて部分も多い感じですが、ひとまず形にはなりました!
そのうち「敵キャラのHP表示」「吹き飛ばし時に派手なエフェクト」などもやってみたいです。

次回は敵キャラのロジックを修正しようかと考えています。
が、結構な改修が必要になりそうなので別の話題になるかもしれません。
その場合は今回やり残した部分の実装か、ステージ関連のお話になりそうです。

今日のイラスト

先日描いたランパをちょっと修正してみました!
ランパ自体が水色なので、背景色を濃い青に変更。
スタフィーシリーズと言えば水中ということで、それっぽく泡を付けています。

数年前の描き始めた頃よりかは上達していると思われます!
時間があればブログ上部のメインキャラ3匹も描き直そうかと思います。

ranpa29

【開発メモ】被ダメージ処理とダウン状態の実装

というわけで、2016年初雪な山梨です。
電車で行ける範囲が狭い山梨は車が命ですが、メインとなる道路や雪や事故で通行止めになるとかなり厳しいことになります。

今回はプレイヤーの被ダメージ処理の続きになります!
前回の記事はこちら

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

今回は主に攻撃による吹き飛ばし&ダウン処理を実装してみます!
が、前回までの仕様では困難だったので、ダメージ処理の仕様変更を先に行います。
日に日に仕様が複雑になっている気がしますが、深く考えないようにします!

ダメージ処理用のインターフェース

ダメージ処理を行うdamage()はインターフェースで定義されており、その引数は以下のような感じです。

public void damage(float damage, Vecotor3 force) {

}

powerに攻撃力、forceに吹き飛ばす力を指定します。
前回までの実装なら問題ありませんでしたが、今回吹き飛ばしを実装するにあたり、仰け反り時間や吹き飛ばすスピードもdamage()に渡したくなったわけです。
引数やパラメータを増やす度にdamage()を呼んでいる箇所を全てリファクタするなんてやりたくないですね。

ということで、「ダメージを発生させるもの」に共通するインターフェースを作成し、そのインターフェースにパラメータのゲッタ・セッタを定義します。
以下が作成した「IDamageGenerator」インターフェースです。

【IDamageGeneratorインターフェース】

using UnityEngine;
using System.Collections;

public interface IDamageGenerator {
    /// <summary>
    /// ダメージの攻撃力を取得する
    /// </summary>
    float getPower();

    /// <summary>
    /// ダメージ対象を吹き飛ばす方向を取得する
    /// </summary>
    Vector3 getForce();

    /// <summary>
    /// ダメージ対象を吹き飛ばす速度を取得する
    /// </summary>
    float getForceSpeed();
}

ダメージ発生源が攻撃判定であれば、全攻撃判定のスーパークラスとなっているHitの参照を渡せばよかったのですが、ステージ上のトラップ・障害物から受けることもあるので、インターフェース型を渡すようにします。
そもそも「仕様なんて作る前から決めとけ」という話ですが、個人且つ趣味で作っている関係上、後々からあれこれ入れたくなることは多々あるので、パフォーマンスよりも拡張性や柔軟性を重視していきます!

ダメージを発生させるオブジェクトにIDamageGeneratorインターフェースの関数を実装します。
手始めということで、シンプルな障害物で試してみます。
以下は障害物に付与するTrapクラスです。

【Trapクラス】

using UnityEngine;
using System.Collections;

public class Trap : MonoBehaviour, IDamageGenerator {

    public float power;
    public Vector3 force;
    public float forceSpeed;

    private void OnTriggerEnter(Collider c) {
        if (c.gameObject.tag == "Player") {
            c.GetComponent<Player>().damage(this);
        }
        if (c.gameObject.tag == "Enemy") {
            c.GetComponent<Enemy>().damage(this);
        }
    }

    public float getPower() {
        return this.power;
    }

    public Vector3 getForce() {
        return this.force;
    }

    public float getForceSpeed() {
        return this.forceSpeed;
    }
}

合わせてPlayer側のdamage()も修正します。
引数としてIDamageGenerator型を受け取り、各パラメータはインターフェースに定義されている関数を通して取得します。
今更ですが、インターフェース型にすると呼び出し元の余計なパラメータや関数を参照させなくするメリットがあります。
(1人で開発しているのでほとんど意味はありませんが・・・)

【Playerクラス】

public void damage(IDamageGenerator damageGenerator){
    power = damageGenerator.getPower();
    force = damageGenerator.getForce();
    forceSpeed = damageGenerator.getForceSpeed();
}

パラメータの取得自体は単純です。
今後パラメータが増えても、インターフェースの関数を増やして取得させることで、関数呼び出し部分を修正する必要がなくなります。

吹き飛ばしとダウン処理

吹き飛ばすベクトルとスピードはIDamageGeneratorの各メソッドから取得します。
スピードはともかく、吹き飛ばすベクトルはオブジェクトによって異なります。
上で実装したTrapクラスであればインスペクターからXYZ値を入力したVector3で十分ですが、キャラクターの場合は攻撃したキャラの正面方向に吹っ飛んで欲しいものです。

ということで、getForce()の内容はオブジェクト毎に変わります。
敵用の攻撃判定であるEnemyHitクラスの場合、
「(敵キャラの正面ベクトル × 吹き飛ばす量) + 上方向へ吹き飛ばす量」
という感じ。
これをgetForce()で実装します。

【EnemyHitクラス】

using UnityEngine;
using System.Collections;

public class EnemyHit : MonoBehaviour, IDamageGenerator {

    public float power, time, forceSpeed;
    public float depthForce, upForce;

    public GameObject damageEffect;
    public Enemy enemy {get; set;}

    protected void OnTriggerEnter(Collider c){
        if(c.gameObject.tag == "Player"){
            c.GetComponent<Player>().damage(this);
            Instantiate(damageEffect, transform.position, Quaternion.identity);
        }
    }

    public float getPower() {
        return this.power;
    }

    public Vector3 getForce() {
        return (enemy.transform.forward * depthForce) + new Vector3(0f, upForce, 0f);
    }

    public float getForceSpeed() {
        return this.forceSpeed;
    }
}

次にPlayerクラスのdamage()の処理を修正します。
forceが零ベクトル(=xyz全てが0)ではない場合のみ、プレイヤーキャラを吹き飛ばす処理を行います。
敵キャラクターの攻撃の場合、depthForceとupForceが両方0の場合になります。
零ベクトルの場合は普通にノックバックを発生させます。これは前回実装している部分なので割愛します。

【Playerクラス】

public void damage(IDamageGenerator damageGenerator){
    // 無敵判定
    if(!isInvincible){
        life -= damageGenerator.getPower();

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

        isInvincible = true;

        // forceが指定されている場合はダウン処理
        Vector3 force = damageGenerator.getForce();
        if (force != Vector3.zero) {
            // ダウンモーション
            motionChange("Down", true, true);

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

            // 吹き飛ばし処理
            iTween.MoveTo(gameObject, iTween.Hash(
                "position", transform.position + force,
                "speed", damageGenerator.getForceSpeed(),
                "easetype", iTween.EaseType.easeOutCubic
            ));
        } else {
            // ダメージモーション
            motionChange("Damage", true, true);

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

            // ノックバック
            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
            ));
        }
    }
}

最後にアニメーションの遷移部分を考えます。
吹き飛ばされたプレイヤーキャラはダウン状態にし、地面に着地した際に起き上がるようにします。
受身とかそういう要素はとりあえず考えずに実装します。

AnimatorControllerにダメージ処理用のサブステートマシンを作り、制御用のパラメータとスクリプトを作成します。
基本的な遷移は「ダウン→地面に着地→起き上がり」です。
ダウンステートと起き上がりステートにそれぞれスクリプトを付けます。

【Downクラス】

public class Down : StateMachineBehaviour {

    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        if (1f < stateInfo.normalizedTime) {
            Player player = animator.gameObject.GetComponent<Player>();

            if (player.getCharacterMotor().IsGrounded()) {
                animator.SetTrigger("GetUp");
            }
        }
    }
}

【GetUpクラス】

public class GetUp : StateMachineBehaviour {

    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        animator.gameObject.SendMessage("damageReset", 0f);    
        animator.gameObject.SendMessage("onInvincibleState", GameConstants.DOWN_INVINCIBLE_TIME);
    }
}

DownクラスのOnStateUpdate()で「ダウン中かつ地面に接地しているか」を判定したかったのですが、1フレーム毎にGetComponentするという大変効率の悪いやり方になってしまっています。
スクリプト内でオブジェクトや変数の保持ができれば良いのですが・・・。
StateMachineBehaviourのベストな使い方はまだまだ分からないところですorz

AnimatorStateInfoのnormalizedTimeで「モーションがどのくらいまで再生されているか」の割合が取得できます。
この場合は1(=100%)なので、ダウンモーションが完全に再生し終わるまで判定しなくなります。

起き上がり処理のOnStateExit()で無敵時間の処理と移動制御、テクスチャのリセットを行います。
このあたりも前回とほぼ同じです。

まとめ

前に作ったビードラの攻撃にマリンパを突撃させます。
以下はdepthForceが20、upForceが10、forceSpeedが30の場合の挙動です。

20160110

横方向ならともかく、縦方向に飛ばした際の挙動がやや不自然です。
山なりに飛ばすだけならモーションパスで吹っ飛ぶ軌道をガチガチに固めれば出来ますが、実際の着地ポイントは地形に左右されるので微妙なところです。
今でも強く吹き飛ばすと判定をすり抜けてしまったりするので・・・。
ダウンから起き上がり~無敵エフェクトまでは問題なさそうですが、若干起き上がるのが遅い気がします。

ということで、
・ダメージを与えるオブジェクトにIDamageGeneratorインターフェース実装
・damage()内でインターフェースの関数を通じて各種パラメータ取得
・吹き飛ばし処理はiTween.MoveTo()を使用
・吹き飛ばされたプレイヤーキャラはダウンし、着地後に起き上がる

改善の余地は多分にありますが、ひとまず実装してみました!
次は敵キャラクターのダメージ処理の実装の話になると思われます!

今日のイラスト

新年初ランパです。
スタフィーシリーズも最新作から8年も経っていると考えると早いものです。
ランパももう「一昔前のマイナーキャラ」な感じでしょうか。

「初心者は影の濃い部分から深めに塗ると良い」と何かの本で見たので、影用レイヤーを分けて濃く付けてみました!
また塗る際のツールはエアブラシを使い、影の輪郭がぼやけるようにしています。
背景が適当なこと以外はそこそこ上手く描けている・・・気がします。

ranpa28

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

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

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で実装

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

あけましておめでとうございます!

というわけで・・・!

CIMG1372

CIMG1373

あけましておめでとうございます!
今年も「ゴマちゃんフロンティア」をよろしくお願い致します!

写真は自宅(山梨県)から撮ったものです。
自分の街からでは富士山の左側に日の出が出てしまいますが、そこそこ綺麗なので気にしないことにします!

新年初めということで、とりあえず挨拶だけ。
2016年も頑張っていきたいと思います!