【開発メモ】アシストキャラクター「カンガルー」の実装

というわけで、久しぶりに Unity 側の作成になります!
今回はアシストキャラクター「カンガルー」を実装しました!

shot2ss20160613234718256

アザラシに続く2匹目のアシストキャラクターです。
あちらは遠距離タイプなので、こちらは近距離型のインファイターといった趣向で作っていきます。
見た目通りパンチとキックで戦います。
ゲームに登場するカンガルーって大抵はパンチとキックだったりしますが・・・。

【Blender】モデリング練習メモ 「カンガルー」編

モデルは前回モデリングしたカンガルーがベースです。
意外と横幅が小さかったので、全体的な体格と腕、脚を大きくなるよう修正しました。

本ゲームではアシストキャラクターの位置付けです。
プレイヤーキャラがカンガルーに乗ることで操作できます。
仕様を固めきれていませんが、概要は以下の記事をご参照下さい。

【開発メモ】アシストキャラクターの実装 その1

各種アニメーションの作成

カンガルーがベースなので他のキャラクターとはちょっと変わったモーションにします。

20160613_01

移動は両脚でぴょんぴょん跳ねながら行います。
ちょっと脚が小さい目で、モーションがぎこちないのも合わせて微妙な見栄えです。

跳ねる際にカンガルーが上下しますが、これは Blender 側のキーフレームに移動情報を登録しています。
上下移動を Unity 側で制御するのはすごく大変そうなので・・・。

ちなみに現実のカンガルーは構造上、後退が行えないらしいです。
Unity 的には PlatformInputController を使用している関係上、後退という概念自体がありません。
変なところで悩むことにならずによかったです。

shot2ss20160613235135104

攻撃は上述の通り、パンチとキックがメインです。
とりあえず通常の連続攻撃だけ作ってみましたが、手足が短いうえに間接もないため、どうしても地味になってしまいました。
ということで、体全体を大げさに動かしながら、カンガルー自身の位置も少し前に動かすことでそれっぽくしてみます。

現在は StateMachineBehaviour を継承したスクリプトを攻撃系ステートに付けており、入った際に Kangaroo クラスの attackStateEnter() が呼ばれ、そこで攻撃判定を生成しています。
このあたりの実装は以下の記事をご参照下さい。

【再編集】Unity開発メモまとめ「Animator」

【開発メモ】攻撃判定制御のリファクタリング

public override void attackStateEnter(AnimatorStateInfo stateinfo) {
    if (isRiding) {
        // 存在する攻撃判定を削除
        // destroyAttackHit();

        // 通常攻撃
        if (stateinfo.IsName("Attack1")) {
            iTween.ValueTo(gameObject, iTween.Hash(
                "from", transform.forward / 4,
                "to", Vector3.zero,
                "onupdate", "characterMove",
                "time", 1.0f,
                "easetype", "easeOutQuart")
            );

            createHitManager(attackHitManager[0], hitOffset[0]);
            createHitManager(attackHitManager[0], hitOffset[1]);
        }

        if (stateinfo.IsName("Attack2")) {
            iTween.ValueTo(gameObject, iTween.Hash(
                "from", transform.forward / 4,
                "to", Vector3.zero,
                "onupdate", "characterMove",
                "time", 1.0f,
                "easetype", "easeOutQuart")
            );

            createHitManager(attackHitManager[0], hitOffset[0]);
            createHitManager(attackHitManager[0], hitOffset[1]);
        }

        if (stateinfo.IsName("Attack3A")) {
            iTween.ValueTo(gameObject, iTween.Hash(
                "from", transform.forward / 2,
                "to", Vector3.zero,
                "onupdate", "characterMove",
                "time", 1.0f,
                "easetype", "easeOutQuart")
            );

            createHitManager(attackHitManager[0], hitOffset[2]);
        }
    }
}

攻撃判定生成前に iTween.ValueTo() でちょっとだけ前に移動させます。
MoveTo() の方が動かす量の調整がしやすいのですが、当たり判定を突き抜ける危険があるため ValueTo() にします。

20160613_02

まだ何か地味ですが、その場でブンブンするよりはマシになりました!
いいエフェクトを作って上手くごまかせればいけそうです。

ゲーム的には「リーチは短いが手数と攻撃力に優れる」といった感じで調整していきます。
カンガルー自体判定が大きいので扱いは難しくなりそうです。
その分はインファイト時の圧倒的な破壊力を持たせて埋めようと思います。

移動入力による攻撃の出し分け

これはカンガルーに限ったことではありませんが、攻撃中に移動入力されているかどうかで最後に放つ攻撃を変えたいです。
カンガルーの場合は通常攻撃3段目を分岐させ、入力なしでアッパー、入力ありでキックにしようと思います!

shot2ss20160613235536001

3段目で分岐させるので、2段目からの遷移は「Attack3A」と「Attack3B」の2つを用意します。
2段目のステートに分岐を制御するためのスクリプトを付けます。

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {

    /// <summary>
    /// 特定のキー入力時に移動キー入力に応じてTriggerパラメータを切り替えるクラス
    /// </summary>
    public class SwitchTriggerVelocity : StateMachineBehaviour {

        // 入力するキー(ProjectSettings)
        [SerializeField]
        protected string inputKey;

        [SerializeField]
        private string lowerParameterName;

        [SerializeField]
        private string upperParameterName;

        private bool isPrimaryInput = true;

        // 入力受付開始時間
        [SerializeField]
        private float receptionStartTime;

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (Input.GetButtonDown(inputKey) && isPrimaryInput) {
                if (receptionStartTime < stateInfo.normalizedTime) {
                    switchTrigger(animator);

                    // 一度入力したらExitまで変化させないようにする
                    isPrimaryInput = false;
                }
            }
        }

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            isPrimaryInput = true;
        }

        private void switchTrigger(Animator animator) {
            // 移動入力がされているか判定
            if (GameConstants.STATE_VELCOITY_BORDER < System.Math.Abs(Input.GetAxis("Horizontal")) ||
                GameConstants.STATE_VELCOITY_BORDER < System.Math.Abs(Input.GetAxis("Vertical"))) {
                animator.SetTrigger(upperParameterName);
            } else {
                animator.SetTrigger(lowerParameterName);
            }
        }
    }
}

swtichTrigger() で切り替える際、移動入力がされているかを判定しています。
Input.GetAxis() で水平・垂直の両方向の入力値が取得できるのでこれを使います。
ただし入力方向によっては負の値になるので、System.Math.Abs() を使って絶対値で判定するのがポイントです。
GameConstants.STATE_VELCOITY_BORDER はただの定数で、今回は 0.9f を指定しています。

他には isPrimaryInput という変数を使い、複数回入力が出来ないようにしています。
receptionStartTime で攻撃モーションのどのタイミングから入力受付を開始するか制御できるようにしました。
モーションとの兼ね合いもありますが、今回は0.7fに設定しているので、現モーションの7割が再生されたタイミングから入力ができるようになります。

スーパージャンプ

カンガルーってジャンプ力にも優れているというイメージがあります。
となると、「ボタン長押し→放して大ジャンプ」とか欲しいですね!
某猿の横スクロールアクションに全く同じ名前のアクションがありますが、正にそんな感じ。

制御に CharacterMotor を使っている関係で微妙に面倒です。
ジャンプボタン長押しが自然な流れですが、CharacterMotor が有無を言わさずジャンプさせてしまいます。
「一度ジャンプ→長押しで溜める→大ジャンプ」の長いプロセスはアクションゲーム的にNGです。
CharacterMotor 側をいじるのも嫌なので、ひとまずはスーパージャンプ専用のボタンを作り、そのボタンの長押しで判定します。

InputManger からFキーを専用ボタンとして設定しておきます。
AnimatorController に bool パラメータとして「SuperJumpIdle」、Trigger パラメータとして「SuperJump」を作り、遷移の条件として使用します。
SwitchBoolByKey というクラスを作成、Idle ステートに付け、専用ボタン入力時にスーパージャンプ待機用のステートへ遷移させます。

using UnityEngine;

namespace StateController.PlayerAnimator {

    public class SwitchBoolByKey : StateMachineBehaviour {

        [SerializeField]
        private string inputKey;

        [SerializeField]
        private string parameterName;

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (!animator.GetBool(parameterName)) {
                animator.SetBool(parameterName, Input.GetButtonDown(inputKey));
            }
        }
    }
}

shot2ss20160613235640913

スーパージャンプ待機→スーパージャンプへの遷移も作ります。
またまた SwitchTriggerByKey の出番で、今度はFキーを離した際(=GetButtonUp時)にトリガーをONにします。
その際に normalizeTime が0.9以上でないと遷移しないようにしておきます。

これでスーパージャンプへの遷移はできたので、あとはスーパージャンプステートにスクリプトを追加します。

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {

    public class SuperJump : StateMachineBehaviour {

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            animator.gameObject.GetComponent<CharacterMotor>().movement.velocity.y = 0;
            iTween.Stop(animator.gameObject);

            iTween.ValueTo(animator.gameObject, iTween.Hash(
                "from", animator.gameObject.transform.up * 3f,
                "to", Vector3.zero,
                "onupdate", "characterMove",
                "time", 1.0f,
                "easetype", "easeOutQuart")
            );
        }

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            animator.ResetTrigger("SuperJump");
        }
    }
}

おなじみの iTween.ValueTo() で上方向に移動させます。
CharacterMotor を使用しているため、ジャンプ前に velocity.y を0にしておきます。
ジャンプする高さは割と適当です。

これでスーパージャンプは出来ましたが、スーパージャンプ待機モーション完了前にFキーを離した場合のことも考慮します。
SuperJumpIdle ステートにスクリプトを付けます。

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {

    public class SuperJumpIdle : StateMachineBehaviour {

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (Input.GetButtonUp("UniqueAction")) {
                animator.SetBool("SuperJumpIdle", false);
            }
        }
    }
}

あとは SuperJumpIdle から Exit ステートへの遷移条件に「SuperJumpIdle が false の場合」と設定します。
これでFキーを離した際に bool が flase になり、自動的に Idle ステートへ戻ります。

まとめ

そんなわけで、カンガルーを Unity へインポートして実装してみました!
仕様が煮詰まっていないのに先取りで実装している感がありますが、やる気のあるうちに出来るところまで作ってしまいたいです。

自分の記事を見返すと、かなり説明を端折っている印象を受けます。
実際は他の実装部分がモロに絡んできていて、上手く実装機能だけを説明するのが難しい状態です。
「これじゃ分からんよ」という場合はコメントを頂ければ頑張って説明するので、お気軽にどうぞ!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*