【開発メモ】ステージオブジェクト「トランポリン」の実装

というわけで、世界中がリオ五輪で盛り上がっている今日この頃!
便乗するわけではありませんが、こんなものを実装してみました!

shot2ss20160820213023487

所謂「トランポリン」です!
上に乗ると自動的にジャンプし、ジャンプボタンを押すと大ジャンプします。
割とユニークなギミックなためか、最近のゲームでは見かけなくなった気がします。

既に似たようなシステムとして、「ジャンプエリア」というものを作ってあります。
エリア内に入った状態でジャンプすると大ジャンプができます。
トランポリンとの違いは「判定なのでゲーム上では見えない」「入ってもオートでジャンプせず、手動入力した場合に否応なく大ジャンプ」といった感じです。
トランポリンを作れば要らない気もしますが、消す必要もないのでとりあえず残しておく予定です。

トランポリンのモデリングとジャンプ制御

物がないと始まらないので、トランポリンをモデリングしてみます。

shot2ss20160820213813901

うーん・・・何だかただの机に見えなくもない微妙な感じです。
まあモデリングが下手なのは今に始まったことではないので、気にせずいきます!

shot2ss20160820213611949

Unityへインポート後、各種コライダーを設定します。
外枠部分に MeshCollider、中央部分に BoxCollider を追加し、大きさや位置を微調整します。
本当は円形に合わせてコライダーを設定できればよかったのですが、楕円形のコライダーを作る方法が分からなかったので、BoxCollider でなるべく合うように調整しました。
キャラクターとトランポリンの大きさ的にこれでも問題なかったです。

トランポリンを制御するためのスクリプトを作成します。
ひとまず、「上に乗ったときに自動でジャンプする」動きを目指してみます。

using UnityEngine;
using System.Collections;

public class Trampoline : MonoBehaviour {

    public float jumpHeight;
    public float enemyJumpHeight;

    void OnTriggerEnter(Collider c) {
        if (c.gameObject.tag == "Player") {
            CharacterMotor charMotor = c.GetComponent<Player>().getCharacterMotor();

            charMotor.movement.velocity.y = 0;
            iTween.Stop(c.gameObject);
            charMotor.movement.velocity.y = jumpHeight;
        }

        if (c.gameObject.tag == "Enemy") {
            Rigidbody rb = c.GetComponent<Rigidbody>();
            iTween.Stop(c.gameObject);
            rb.velocity = new Vector3(rb.velocity.x, enemyJumpHeight, rb.velocity.z);
        }
    }
}

プレイヤーキャラは CharacterMotor、敵キャラは Rigibody を使用して制御しているため、処理を分ける必要があります。
ジャンプ時に余計な加速度が掛かっている場合、一度リセットしないと想定以上に吹っ飛んでしまったりします。
「velocity.yを0に設定」「iTween.Stop()で余計なiTweenを停止」はそのためです。
前者はともかく、後者は移動関係以外の iTween も止まってしまったりするので、他に iTween を使っている場合は気を付ける必要がありそうです。

ジャンプボタン押下で大ジャンプする処理

トランポリンと言えば、ジャンプボタン押下時に大ジャンプするものが大半です。
ということで、上のスクリプトを少し修正します。

using UnityEngine;
using System.Collections;

public class Trampoline : MonoBehaviour {

    public float jumpHeight;
    public float inputJumpHeight;

    public float enemyJumpHeight;

    void OnTriggerEnter(Collider c) {
        if (TagUtility.getParentTagName(c.gameObject) == "Player") {
            CharacterMotor charMotor = c.GetComponent<Player>().getCharacterMotor();

            charMotor.movement.velocity.y = 0;
            iTween.Stop(c.gameObject);

            if (Input.GetButton("Jump")) {
                charMotor.movement.velocity.y = inputJumpHeight;
            } else {
                charMotor.movement.velocity.y = jumpHeight;
            }
        }

        if (TagUtility.getParentTagName(c.gameObject) == "Enemy") {
            Rigidbody rb = c.GetComponent<Rigidbody>();
            iTween.Stop(c.gameObject);
            rb.velocity = new Vector3(rb.velocity.x, enemyJumpHeight, rb.velocity.z);
        }
    }
}

超シンプルに「コライダー判定時にジャンプボタンが押されているか」で行きます。
また、自動ジャンプと入力時のジャンプでフィールドを分け、区別できるようにします。
敵には入力で大ジャンプなんてないのでそのままです。

20160820_01

実際に動かすとこんな感じに。
モーションとかはさておき、挙動は問題なさそう。

ただし問題があり、2段ジャンプが有効の場合は2段が優先されてしまい、トランポリンの大ジャンプがなくなってしまいます。
今の実装の2段ジャンプはバグの温床になりえるので、いっそ無効化してしまおうかと思っていたりします。
本格的にやるならトランポリンのジャンプ専用モーションを作り、その間2段ジャンプのステートには遷移しないようにすればいけそうです。

まとめ

そんなわけで、トランポリンと使用時のジャンプ制御について作成してみました!
これでステージを作る際に高低差を激しくしても大丈夫ですね。

【開発メモ】遠距離攻撃を近接攻撃で弾く処理の実装

というわけで、今回のお題は「遠距離攻撃を近接攻撃で弾く」です

shot2ss20160811131212641

敵が放った遠距離攻撃を剣で弾いてしまうものです!
多数を相手にすることが多くなる(予定)なので、遠距離攻撃はなかなか厄介な攻撃になります。
それをパキーンと弾いたりできればテンション上がりますね!

別に弾かずに消してしまってもゲーム的には全く問題ないのですが、やはりパキーンと弾いた方が見栄えはいいです。
「ダークソウル2」でも「スペルパリィ」という似たようなアクションがありました。
(リスクとリターン全く釣り合わないアレなものでしたが・・・)

遠距離攻撃の仕組みについては (やや古いですが) 以下の記事をご参照下さい。
要約すると「発射したキャラの前方をrigidbody.velocityに設定」することで飛ばしています。

【再編集】Unity開発メモまとめ 「攻撃処理系」

剣を遠距離攻撃に当てた際の判定

手始めに「剣が遠距離攻撃に当たったら弾を消す」という処理を考えてみます。
流石に遠距離攻撃全てを弾けるのもアレなので、「パキーンできる遠距離攻撃か」を敵キャラクターの攻撃に付いているクラスに持たせてしまいます。

using UnityEngine;
using System.Collections;

public class EnemyBulletHit : EnemyHit {

    public float bulletSpeed;
    public bool canReflect;

    protected Vector3 forward;
    private Rigidbody rb;

    protected override void Start() {
        base.Start();

        forward = hitManager.transform.forward;

        rb = this.GetComponent<Rigidbody>();
    }

    protected override void Update() {
        base.Update();

        rb.velocity = forward * bulletSpeed;
    }

    protected override void OnTriggerEnter(Collider c) {
        base.OnTriggerEnter(c);
    }
}

既存で作ってあるクラスなので長いですが、canReflect というフィールドを追加しただけです。
public なので、適当に getComponent() すれば判定できます。
攻撃判定の細かい処理まで記載すると長くなるので割愛させて頂きます。

「剣(近接攻撃)の判定に当たったら削除」するので、プレイヤーの近接攻撃判定に付けているクラスに処理を加えます。
ちょうど攻撃判定処理を継承している CloseRangeHit というジャストなクラスが付いていたりします。

using UnityEngine;
using System.Collections;

public class CloseRangeHit : PlayerHit {

    protected override void Start () {
        base.Start();
    }

    protected override void Update () {
        base.Update();
    }

    protected override void OnTriggerEnter (Collider c)    {
        base.OnTriggerEnter(c);

        if (c.gameObject.tag == "EnemyHit") {
            if (c.GetComponent<EnemyBulletHit>().canReflect) {
                Destroy(c.gameObject);
            }
        }
    }
}

追加したのは OnTriggerEnter() のif文です。
「当たったオブジェクトのタグがEnemyHit」且つ「canReflect がtrueで弾くことが可能」な場合、Destory() で遠距離攻撃判定を消してしまいます。

遠距離攻撃を弾く処理

次に遠距離攻撃を消すのではなく、どこか別の方向へ弾いてしまう場合の処理を考えます。
といっても割と単純で、if文で判定後に EnemyBulletHit の forward の値を変えてしまうだけです。
EnemyBulletHit に forward の値を書き換える用の関数を作成します。

public void changeForward(Vector3 vect) {
    forward = vect;
}

本当にセットするだけなので超単純!
もし弾に生存時間がある場合、このタイミングでコルーチンを再実行するのも良さそうですね。

追加した関数を CloseRangeHit の判定後に呼び出し、forward を変えます。
本来は攻撃判定の動きに合わせた方向に弾こうかと思いましたが、仕組み的にきつかったので単に逆側に跳ね返すようにしました。
その場合、enemyBulletHit の forward を正負を逆にするだけでOKです。

protected override void OnTriggerEnter (Collider c)    {
    base.OnTriggerEnter(c);

    if (c.gameObject.tag == "EnemyHit") {
        EnemyBulletHit enemyBulletHit = c.GetComponent<EnemyBulletHit>();

        if (enemyBulletHit.canReflect) {
            enemyBulletHit.changeForward(-enemyBulletHit.transform.forward);
        }
    }
}

動かしてみるとこんな感じです。

20160811_01

やや地味ですが、現状では十分でしょう。
そのうちそれっぽいエフェクトも作って、パキーンしたことを分かりやすくしたいですね。

まとめ

そんなわけで、「遠距離攻撃を弾き返す」処理について考えてみました!
遠距離攻撃で攻撃を割り込まれるのはどのゲームでも嫌なので、まぐれでも当たったら弾けるようにしたいです。

最近はかなりネタに困っていたりします。
現在やるべきことがステージ作りや敵キャラクター作りなど、割とセンスが求められる部分が多いため難航しております。
何か面白いものが出来たら紹介したいとは考えていますが・・・。

【開発メモ】「オブジェクトを掴む・投げる」アクションの修正

というわけで、今回は「オブジェクトを掴む・投げる」というアクションの修正になります!
「修正」ということで、既にそれっぽいアクションは実装してあります。

shot2ss20160802234413497

流れとしては以下のようになっております。
1.キー入力で掴むアクションを行い、手に掴み専用の判定を出す
2.掴み判定に当たったオブジェクトを持ち上げる
3.再度キー入力でオブジェクトを投げる

これがゲーム的にはかなりの欠陥品で、
・掴み判定に当たらないと持てない
→目の前のオブジェクトでもすぐに持てない
・掴む動作自体がモッサリなので、掴み判定と相まってテンポが悪い
・持ち上げた際のオブジェクトの位置が掴んだ位置に依存
→判定の出始めや終わり際で持つと持ち上げた際にズレまくる
・一度に複数のものを掴めてしまう
・持ち上げた状態で敵を押すと楽々場外KOが可能

などなど、実用に耐えうる代物ではありません。
特に致命的なのがテンポの悪さなので、今回はそこに重点を置いて修正を考えてみます!
アクションゲームらしく、キー連打で次々と投げまくれるようなものがいいですね!

様々なクラスやオブジェクトで処理している関係上、ソースを全て書くと長くなってしまうので、最低限のものだけ載せます!

オブジェクトを掴む処理の変更

以下のようなシンプルなものに変えてみます。
・キー入力時に前方に掴むことができるオブジェクトがあるか確認する
・掴めるオブジェクトがある場合は持ち上げる

処理としては、キー入力時にプレイヤー前方に短く RayCast を行い、当たったオブジェクトを判定、掴める場合は特定の位置に移動させながらプレイヤーの子オブジェクトにします。

上記修正から、掴み判定という概念を削除します。
モッサリ感解消の他、複数オブジェクトを一度に持ち上げてしまう問題も解決できます。

語っていてもしょうがないので、レッツコーディング(`・ω・´)
Player クラスの Update() 内に処理を追加します。

if (Input.GetButtonDown("Grab") && !stateInfo.IsTag("Throw")) {
    Ray grabRay = new Ray(transform.position, transform.forward);
    if (Physics.Raycast(grabRay, out hit, 3f)) {
        if (hit.collider.tag == "ThrowableObject") {
            StartCoroutine("setThrowingTarget", hit.collider.gameObject);
        }
    }
}

1つ目のif文で「ボタン入力時」且つ「掴み&投げ系のモーションではないこと」を判定します。
予め掴み&投げ系のステートに「Throw」というタグを割り当てておく必要があります。
また、Raycast() の際は maxDistance を指定しないとあらぬところまで届いてしまうので注意します。

if文が通ったら setThrowingTarget() であれこれ処理を行います。
詳しい処理は割愛させて頂きますが、モーション切り替えや持ち上げたオブジェクトの親子設定などを行っています。

前の仕様では敵に掴み判定を当てると専用の掴み攻撃が発動しましたが、システム的に面倒な上にゲーム的にもどうでもいい機能だったため、こちらも無くしてしまいました。

連続で入力した際の挙動の調整

掴む際のプロセスが短くなりましたが、そこから投げるまでの過程はまだテンポが悪いです。
モーション的には「掴む→持ち上げて保持→投げる」となっており、各モーションが完全に終了するまで遷移しないことが問題のようです。
そこで即投げする場合は「持ち上げて保持」のモーションを経由せず、掴みから直接投げるモーションへ遷移できるようにしてみます。

shot2ss20160802234236073

上は AnimatorController の掴む&投げるアクション用のサブステートマシン内です。

制御用にbool型パラメータとして Hold を作ってあります。
trueになった際に Grab→Hold と遷移し、falseになった際には Hold→Throw と遷移します。
上記は何れも ExitTime にチェックを入れているので、モーションが終わるまでは遷移しません。

ここに Grab から Throw へ遷移を追加し、直接遷移できるようにします。
遷移条件は Hold→Throw と同じで、Hold パラメータが false になったときです。

ということで、Grab ステートに以下のスクリプトを設定します。

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {
    public class Grab : StateMachineBehaviour {
        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (Input.GetButtonDown("Grab")) {
                animator.SetBool("Hold", false);
            }
        }
    }
}

超シンプルに、掴みモーション中に入力があった場合はそのまま Hold を false にしてしまいます。
この状態で Grab→Throw への遷移で ExitTime のチェックを外せば、入力された段階でそのまま投げるモーションに遷移してくれます。

まとめ

そんなわけで、オブジェクトを掴む・投げる処理に修正を入れてみました!
システムとして要るかどうかはともかく、アクションゲームでは割とありそうなものなので、ひとまず使えるレベルのものにしておきたいです。

最近更新していなかった故に急いで書いたのでかなりざっくりです。
分かりにくいところがあればコメント等頂ければ頑張って書きます!