【Unity】タグの階層表示と判定方法について

というわけで、今回はUnityのタグに関するお話です!

shot2ss20151124230737479

GameObjectのFindや判定系の処理に大活躍のタグですが、量が多いと管理が面倒であったり、複数のタグを付けれなかったりします。
特に似たような特徴のタグを複数用意する際は雑多になってしまいます。

例えば「ステージ上のオブジェクト」というタグを考えた場合、その種類として
 →「破壊可能なオブジェクト」
 →「破壊不可能なオブジェクト」
 →「持って投げることが可能なオブジェクト」
 →「ブロックオブジェクト」
等が考えられます。

これらを普通に追加するとタグ設定時の選択肢が増えて見にくくなります。
また、グループ分けしての判別もできないので「ブロックオブジェクト」を「ステージ上のオブジェクト」として判定したい場合に面倒なことになります。
今回はそのあたりをいい具合になるよう考えてみました!

タグの階層表示

タグの名前に/(スラッシュ)を入れると、タグが階層構造で表示されるようになります。
メニューのEdit→ProjectSettings→Tag and Layersでタグ一覧を表示し、スラッシュ込みで入力します。

shot2ss20151124230814769

これはタグに限らず、レイヤーやシェーダーなども同様です。
自分はカスタマイズしたシェーダーを「Custom/(シェーダー名)」という名前にし、階層構造にしています。

数が多くなってくると選択するのが大変なので、分けておくと便利です。
冒頭のタグも「StageObject/(タグ名)」といった感じで整理できます。

タグ名の判定

スラッシュを入れると階層構造に出来ますが、参照の仕方は変わりません。
つまり、タグを指定する場合「StageObject/Block」といった形になります。
やや冗長な上、「親タグや子タグを参照」といったことも出来ません。

ただ階層は必ずスラッシュで区切っているので、その前後でタグを見分けることができます。
ということで、親タグ名や子タグ名を取得するUtilityクラスを作ってみました!

using UnityEngine;
using System.Collections;

public static class TagUtility {

    public static string getParentTagName(string name) {
        int pos = name.IndexOf("/");

        if (0 < pos) {
            return name.Substring(0, pos);
        } else {
            return name;
        }
    }

    public static string getParentTagName(GameObject gameObject) {
        string name = gameObject.tag;
        int pos = name.IndexOf("/");

        if (0 < pos) {
            return name.Substring(0, pos);
        } else {
            return name;
        }
    }

    public static string getChildTagName(string name) {
        int pos = name.IndexOf("/");

        if (0 < pos) {
            return name.Substring(pos + 1);
        } else {
            return name;
        }
    }

    public static string getChildTagName(GameObject gameObject) {
        string name = gameObject.tag;
        int pos = name.IndexOf("/");

        if (0 < pos) {
            return name.Substring(pos + 1);
        } else {
            return name;
        }
    }
}

実際にOnTriggerEnter等で使う場合は以下のような感じ。
TagUtilityクラスの関数を呼ぶだけ!

 
public void OnTriggerEnter(Collider c){
    if (TagUtility.getParentTagName(c.gameObject) == "StageObject") {
        // 処理
    }

    if (TagUtility.getChildTagName(c.gameObject) == "Block") {
        // 処理
    }
}

getParentTagName()で親タグ名、getChildTagName()で子タグを取得します。
指定文字列が見つからない場合(=階層構造になっていない)場合はタグ名をそのまま返却します。
文字列での指定の他、GameObjectを渡すことでも取得できるようにしました。

「StageObject/Block」の場合、「StageObject」と「Block」の2パターンで判定できることになります。
Unityは1オブジェクト1タグですが、これで親タグを使ったグループ分けができます。
擬似的ですが、複数のタグを扱っているような感じですね。

これで「ステージオブジェクト」で「ブロックオブジェクト」なものを判別できるようになりました!
他にも敵キャラクターやアイテムを判別する際にも重宝しそうです。
なかなか便利なので、今後も使っていきたいと思います!

余談

シルリスのモデリングを修正し、靴を履いているような感じにしてみました!
他2キャラと比べると適当な足だったので気になっていたところです。
パッと見では短い長靴を履いているようにしか見えませんが、とりあえずこれで十分です。

shot2ss20151124230652229

【Unity】iTweenの停止と再実行について

というわけで、今回はiTweenに関するちょっとしたメモのような感じになります!
お題はタイトル通り、iTwwenの停止と再実行に関してです。

自分のゲームでは所謂「2段ジャンプ」をiTweenで行っています。
AnimatorControllerの2段ジャンプステートにスクリプトを付与し、StateMachineBehaviourのOnStateEnter()で行っています。
Animatorの詳しい実装は割愛しますが、「ジャンプ中に再度ジャンプボタン押下」で実行します。

・・・が、実行したiTweenが残っている間に再度2段ジャンプをすると上手く動作しません。
timeで指定する値を小さくする手もありますが、全体的なアニメーションまで変わってしまい、得策とは言えません。
ここでiTween.Stop()の出番です!

using UnityEngine;

namespace StateController {

    public class DoubleJump : StateMachineBehaviour {

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

            iTween.MoveBy(animator.gameObject, iTween.Hash(
                "amount", new Vector3(0f, 25f, 0f),
                "time", 2f,
                "delay", 0.01f
            ));

            animator.ResetTrigger("Jump");
        }
    }
}

iTween.Stop()のオーバーロードは6種類あります。

・Stop()
・Stop(string type)
・Stop(GameObject target)
・Stop(GameObject target, bool includechildren)
・Stop(GameObject target, string type)
・Stop(GameObject target, string type, bool includechildren)

引数なしはシーン内の全iTweenが対象で、他は型や文字列、オブジェクトを指定して停止します。
「includechildren」で子オブジェクトを対象にするかを指定できます。
今回の対象はプレイヤー自身のiTweenなので、gameObjectを指定します。

iTween.Stop後にすぐ次のiTweenを実行すると上手くいかないようです。
自分の場合、引数として「delay」を指定し、体感できないレベルの遅延(0.01f)を入れてあげると上手くいきました!

iTweenは実行時にコンポーネントが付与されるので、それを削除・無効化するのも手かもしれません。
が、いちいち削除するのは面倒なので、自分は遅延させて実装しました。

まとめ

そんなわけで、iTweenの停止に関するちょっとしたお話でした。
iTweenは非常に便利ですが、思わぬところで詰まることがあるので、今回のような経験を覚えておきたいところです。

【開発メモ】チャージ攻撃とエフェクトの実装

というわけで、今回はボタンを押し続けて溜め、放すと攻撃する「チャージ攻撃」の実装になります!
これまたアクションゲームでは割とよくあるアクションです。

20151120_charge

現在攻撃系のボタンは「通常」と「特殊」の2つがあります。
今回実装する「通常チャージ」と「特殊チャージ」と合わせれば4パターンに分岐できます。
やや複雑ですが、幅広いアクションを持たせることができそうです。

通常チャージと特殊チャージを別々に行えるようにするか、片方のみにするかは検討中です。
変に共通化しても面倒なことになりそうなので、別々にチャージできる方がいいかなーとは思います。
ひとまず今回は通常チャージに相応する部分を作ってみました!

実装方法

PlayerクラスのUpdate()内にInput.GetButton()を入れ、攻撃ボタンが押されているか評価します。
押されている間は専用のカウントを加算します。
カウンタはAnimator.setFloat()でAnimatorに設定しておきます。

以下はPlayerクラスのUpdate()内の該当部分の処理です。

// チャージ時間をAnimatorにセット
animator.SetFloat("ChargeCount", chargeCount);
if (Input.GetButton("Attack")) {
    chargeCount++;
} else {
    // ボタンを離した場合は0に設定
    chargeCount = 0;
}

Player側で行うのはこれだけ。
アニメーションの制御はAnimatorControllerに任せてしまいます。
「パラメータが設定値を超えているか判定し、超えていたらトリガーをONにする」といった感じのスクリプトを作成します。

using UnityEngine;
using System.Collections;

namespace StateController {

    public class SwitchTriggerCondition : SwitchTriggerByKey {

        [SerializeField]
        private string conditionName;

        [SerializeField]
        private float count;

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (switchByButtonUp) {
                if (Input.GetButtonUp(inputKey)) {
                    switchTrigger(animator);
                }
            } else {
                if (Input.GetButtonDown(inputKey)) {
                    switchTrigger(animator);
                }
            }
        }

        private void switchTrigger(Animator animator) {
            if (animator.GetFloat(conditionName) > count) {
                if (useAnimatorPlay) {
                    animator.Play(parameterName);
                }
                animator.SetTrigger(parameterName);
            }
        }
    }
}

世間一般の溜め攻撃の発動タイミングは「ボタンを離したとき」ですね。
汎用性のために「押したとき」か「離したとき」かを切り替えられるようにしておきます。

また、Animator.Play()を実行するかどうかも制御できるようにしてあります。
ステートの遷移をすぐに切り替えたい時はAnimator.Play()でやってしまうのが手っ取り早いです。

このスクリプトを「チャージ攻撃へ遷移する可能性のあるステート」に付与します。
今回は待機状態であるIdleから遷移させるようにしました。
CondtionをChargeAttackにし、ExitTimeのチェックを外しておきます。

実際のところは移動中やジャンプ中、他の攻撃中でも割り込んで遷移させたいところですが・・・該当ステート全てにスクリプトを付けていくのは面倒で、よろしくない気もします。
AnyStateから遷移させれば一撃ですが、あらゆるステートから割り込めてしまうためNG。
ここに限らずAnimatorの実装に怪しい部分が多いので、一度見直したほうがいいかもしれません。

チャージ中のエフェクト

アニメーション的には出来ましたが、現状ではチャージしているかどうかが分かりにくいです。
ロックマンシリーズのようにチャージ中のエフェクトが欲しいところです。

20151120_1

RenderModeを「Stretched Billborad」に設定し、LengthScaleを調整するとこんなエフェクトができます。
チャージ量が規定値を超えたら色合いやサイズを変化させ、フルチャージが分かるようにします。
PlayerクラスのUpdate()でチャージカウントを判定し、変化させてみました。

  
if (10 < chargeCount) {
    chargeEffectParticle.enableEmission = true;
    if (GameConstants.CHARGE_COUNT < chargeCount) {
        chargeEffectParticle.startSize = 2f;
        chargeEffectParticle.startColor = new Color(0.5f, 0.5f, 1f);
    } else {
        chargeEffectParticle.startSize = 1f;
        chargeEffectParticle.startColor = new Color(0.8f, 0.95f, 1f);
    }
} else {
    chargeEffectParticle.enableEmission = false;
}

chargeEffectParticle はParticleSystem型です。
インスペクターからパーティクルを設定しておき、Start()でGetComponentしています。
CHARGE_COUNT はひとまず60で定義してあります・・・が、短い気もするので後々変えるかもです。

まとめ

ということで、ざっくりですがチャージ攻撃を実装してみました!
チャージが必要なので連発はできませんが、その分高性能で派手な攻撃にしていきたいですね。

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

2016/11/14
いろいろと問題があったため、本記事のような実装は使用していません。
「前にこんなことやっていた」という感じで、参考程度にご参照下さい。

というわけで、もう11月も半ばな今日この頃です。
モンハンクロスが近づいてくる一方で、秋が名残惜しい気もします。

今回は攻撃判定の生成・制御に関するお話です!
機能自体は前からありますが、今回はその部分のリファクタをしようと考えております。

思えば、前もソースコード構成のリファクタを行ったばかりです。
いかに初期の実装の基盤がへっぽこだったかと感じる今日この頃です。

概要

今まで攻撃判定は全てプレイヤーに付いているPlayerクラスから直接Instantiateしていました。
Playerクラスに攻撃判定と生成位置の情報を持たせ、各攻撃用の関数を使って行います。

ここで気になってくるのは、Playerクラスの役割が大きくなりすぎてしまうこと。
攻撃判定と言っても、近接攻撃から遠距離攻撃、攻撃回数や速度など、多くの要素があります。
これらの判定生成処理を全てPlayerで実装しているので、かなり長くて読みにくいソースになってしまっています。

また攻撃判定毎にエフェクトを再生しているので、「多段ヒットする極太ビーム」などを今の仕様で作ると、攻撃判定分エフェクトが出現してしまいます。
最近はEffekseerで派手なものを作ることも多くなってきたので、こちらも何とかしたいところでした。

そこでプレイヤーから直接生成するのではなく、攻撃判定を管理するオブジェクトを生成するようにします。
管理用オブジェクトから改めて攻撃判定を生成させます。

処理的には回りくどくなりますが、
・Playerクラスの攻撃判定生成を管理オブジェクトに委譲できる
・管理オブジェクトにエフェクトを付けることで、攻撃判定1つ1つにエフェクトを持つ必要がなくなる
 →上で挙げた「多段ヒットする極太ビーム」の問題が解消できます。
・判定生成時に子オブジェクトとして持つことで、攻撃判定を一括して削除できる

などなど、プレイヤーと攻撃判定の仲介役として諸問題を解決できそうです。
「管理オブジェクト分のプレハブが必要になる」「どうしても処理が遠回しになる」等の難点はありそうですが、とりあえずやってみます!

具体的な実装

例によって全て載せると長くなるので、重要な部分のみを記載します!
基本的にパラメータの宣言や初期化は載せていないので、そのあたりはStart()あたりで行うか、インスペクターから設定すれば大丈夫です。
分かりにくいようなら全ソース載せようと思いますので、コメント等でご連絡頂ければと思います!

まずはPlayerクラスで管理オブジェクトをInstantiateします。
今までは「攻撃判定オブジェクト」と「判定生成位置」を指定していましたが、前者を管理用オブジェクトを指定するように変更します。

protected void createHitManager(GameObject hitManager, GameObject hitOffset) {
    GameObject manager = Instantiate(hitManager, hitOffset.transform.position, transform.rotation) as GameObject;
    manager.transform.parent = hitOffset.transform;
    manager.GetComponent<HitManager>().setPlayer(this);
}

生成位置はキャラクターのboneに空のオブジェクトを付与し、インスペクターから設定しておきます。
自分の場合は「hitOffset」という配列にまとめて格納しました。
キャラクターのAmatureに対して生成位置を設定しないと、動きに追従してくれないので注意が必要です。
遠距離攻撃の場合は追従させる必要がないので、parentをnullにしてしまいます。

Instantiateの返り値から管理オブジェクトのコンポーネントを取得し、Playerの参照を渡しておきます。
アクションゲームで「ヒット時にプレイヤー側で何か処理をする」といった処理は多いので、この書き方は結構使います。

次に攻撃判定を制御するオブジェクト用のスクリプトを作成します。
このゲームでの攻撃判定は大きく分けて「近距離型」と「遠距離型」があります。
「近距離型」は剣や槍などの攻撃判定で、武器に動きに合わせて攻撃判定を追従させる必要のあるものです。
「遠距離型」は飛び道具のようなもので、一度発射したらキャラクターや武器の動きとは関係せずに移動します。

そんなわけで、ベースとなるクラス「HitManager」を作成し、それを継承する形で遠距離型と近距離型のクラスを作成します。
各パラメータとして攻撃判定生成に必要な情報を持たせます。
Start()内で各コルーチンを実行し、攻撃判定を生成します。

【AttackHitController】

protected IEnumerator attackInstantiate() {
    // 攻撃までの遅延時間
    yield return new WaitForSeconds(delayTime);

    GameObject hit;

// 攻撃回数分だけ攻撃判定を生成
    for (int i = 0; i < hitCount; i++) {
        hit = Instantiate(hitObject, transform.position, transform.rotation) as GameObject;
        hit.transform.parent = this.transform;
        hit.GetComponent<PlayerHit>().setPlayer(this.player);

        yield return new WaitForSeconds(GameConstants.ATTACK_HIT_INTERVAL);
    }
}

【BulletHitController】

protected IEnumerator bulletInstantiate() {
    // 発射までの遅延時間
    yield return new WaitForSeconds(delayTime);

    GameObject hit;

    // 発射数分だけ生成
    for (int i = 0; i < hitCount; i++) {
        hit = Instantiate(hitObject, transform.position, player.transform.rotation) as GameObject;
        hit.transform.parent = this.transform;
        hit.GetComponent<PlayerHit>().setPlayer(this.player).setHitManager(this);

        yield return new WaitForSeconds(hitInterval);
    }
}

今までのソースでは、例えば遠距離攻撃であれば「単発型」「連射型」「同時発射型」というように、弾の挙動によって関数を変えていましたが、ソースコード的に冗長になってしまうので1つにまとめました。
パラメータを変えることで連射数を変えられたり、発射間隔を指定できたりするイメージです。

エフェクトを再生する必要のある場合はエフェクト用のオブジェクトを付与します。
管理オブジェクトはboneに追従する以外は動かないので、エフェクト自体が飛んでいったりするような場合でも問題ありません。
ただ攻撃判定と無関係に再生されてしまうので、生成タイミングや生存時間を上手く調節して合わせる必要があります。

最後は攻撃判定用のスクリプトです。
攻撃力や弾速度などをパラメータとして持たせ、Triggerで攻撃がヒットしたかを判定します。
攻撃判定が個別にエフェクトを出す場合はその再生も行います。

以下はPlayerHitクラスのOnTriggerEnterの処理です。

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

内容は今までとあまり変わりませんが、いくつかのパラメータは管理オブジェクトに持たせるようにしています。
攻撃判定個別でエフェクトを再生する場合、こちらにエフェクトを付与します。

テスト

前回Effekseerを使って衝撃波を作ったので、マリンパから発射するようにしてみます。
「剣を横→縦に振って衝撃波を出し、前方に飛ばす」というものです。

20151117_1

剣の攻撃判定は近距離用の管理オブジェクトから生成されています。
衝撃波は遠距離用の管理オブジェクトから生成し、衝撃波のエフェクトを再生します。
攻撃判定は縦振り時までコルーチンを使って遅延させ、エフェクトの発射に合わせたタイミングで前方に飛ばします。

ゲーム的な性能としては、攻撃力と判定の大きさに優れています。
振った剣にも攻撃判定があるので、近距離で打てば追加でダメージを与えます。
ちなみにこれがマリンパ唯一の遠距離攻撃になりそうです。

まとめ

ということで、攻撃判定の制御に関するリファクタリングを行ってみました!
何よりPlayerクラスがスッキリしたのが一番大きいです。
エフェクトと攻撃判定が別になったので、「ド派手なエフェクト+攻撃判定を連続で生成」といったことも可能になりました。
総じて前のロジックより良い結果になったと思われます!

まだまだ粗い部分もありますので、これからも改修していきたいと思います!
様々な攻撃判定を制御できるようにしたいところです。

今日のイラスト

ペンタブでマリンパを描いてみました。
前に描いたシルリスと比べるとやや微妙な感じです。
今までGIMPで描いていたデジタルイラストとは違った味はあります。

marinpa19

【開発メモ】Effekseerを使ったエフェクトの作成

というわけで、まだまだPHPな日々なりべるんです。
やっとSymfony2にも慣れてきましたが、未だに詰まる部分もあります。
EntityやType、DBとの連携など、意識すべき部分が多いです。

今回はエフェクト作成ツール「Effekseer」を使ってみました!
オープンソースのフリーソフトで、GitHub上で公開・開発されています。

エフェクト作成ツール自体は前からいろいろ探しており、有名所の「Prominence」や「SpriteStudio」は訳あって断念した経緯があります。
Effekseerはオープンソースで、かつUnityとの連携プラグインもあるとのことなので、ちょっと使ってみることにしました!
基本的に作成物も含めてフリーのようですが、使用するゲームを公開する場合はコピーライト表記が必要らしいです。

Unity標準のShurikenを使っていませんが、利便上「パーティクル」カテゴリに分類してあります。
何かしっくりこないので「エフェクト」とかに名前を変えた方がいいかも。

エフェクトの作成

所謂「ソニックブーム」のような衝撃波をEffekseerで作ってみます。
「横向きの衝撃波→横向きと縦向きの衝撃波→衝撃波の移動」のような感じになります。

sordwave

下準備として、衝撃波のテクスチャを作成します。
GIMPで適当に刃の形を作り、フィルター→変形→風で調節します。

・・・透明な背景に白いテクスチャだとさっぱり分からないですねorz
一応画像にマウスポインタを乗せるとうっすらと見えます。

shot2ss20151112215027049

Effekseerを起動し、描画共通タブから衝撃波のテクスチャを読み込みます。
ノードに対して以下のように設定しました。

【共通】
・生存時間:中心、40

【回転】
X:120、Y:270、Z:0

【拡大】
X:15、Y:15、Z:12

【描画】
・描画:スプライト
・配置:固定
・ブレンド:加算
・フェードイン:あり、フレーム数10

バージョンの違いか、画面レイアウトが若干異なっている場合があります。
自分の場合、「回転」の項目がなかったので、メニューのウィンドウから表示させました。

shot2ss20151112215452840

次に縦と横両方の衝撃波を生成するため、ノードを2つ追加します。

最初の衝撃波の生存時間が40なので、生成開始時間を40にします。
適当に回転角度を付け、縦と横で十字になるように調整。
あとは衝撃波を移動させるため、「位置」タブの速度のZを1.5に設定します。
他のパラメータは最初のものとほぼ同じです。

完成間際になって重要なお話ですが、Unityへインポートした際のXYZ軸に注意する必要があります。
Effekseerビュー上の青い線の方向が前になるように作ると上手くできます。

・・・とここまで書きましたが、正直分かりにくいと思われます!
今回のサンプルとして、以下のリンクからファイルとテクスチャをダウンロードできます。
こちらを見て頂いた方が手っ取り早い・・・かも。

サンプルダウンロード

プロジェクトを開いた際にエラーが出る場合、各ノードのテクスチャを解除→再選択すると直ると思います。

インポートと実行

Effekseer公式からUnity用プラグインをダウンロードし、Assets上にインポートします。
空のGameObjectを作成し、EffekseerEmitterを付与します。

shot2ss20151112215813049

PlayOnAwakeやLoopはUnityのパーティクルと同じですね。
EffectNameにはEffekseerからエクスポートした.efkファイルの名前を指定します。
・・・のですが、デフォルトでは「/Assets/StreamingAssets/Effekseer/」直下のディレクトリを参照しにいくようです。
StreamingAssetsなんてディレクトリは作っておらず、そもそもエフェクト系は別ディレクトリで管理しているので、そこを参照しにいくようにソースコードを修正します。
EffekseerSystemの15行目付近になります。

public static string resourcePath
{
    get {
        return Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Assets\\Particle\\Effekseer");
    }
}

GetCurrentDirectory()でプロジェクトまでの絶対パスが取得できるので、あとはAssets~から先のパスを指定します。
自分は「Assets/Particle/Effekseer」にしました!
上記ディレクトリ内に.efkファイルを放り込み、再度実行してみます。

shot2ss20151112215702041

今度はしっかり再生されました!
ちょっとエフェクトを入れるだけでもかなり違うもので、前に紹介した「X-WeaponTrail」と合わせると攻撃系は別物と言えるレベルになります。

注意点として、作成時にテクスチャ等の外部ファイルを使用した場合、Effekseerのプロジェクトファイル(.efkproj)から見た相対パスで参照が保存されるようです。
そのため、出力ファイルとプロジェクトファイルのディレクトリが異なると外部ファイルを上手く読み込めません。
出力・プロジェクト共に同一ディレクトリ内に放り込んでおくのが確実かと思われます。
自分は上で参照パスを変えているので、「Assets/Particle/Effekseer」内に保存し、テクスチャはその中にtextureというディレクトリを作って参照しています。

まとめ

そんなわけで、フリーソフト「Effekseer」を使ってエフェクトを作ってみました!
単純なエフェクトならUnityで十分ですが、派手なものや複雑なものはEffekseerを活用して作っていきたいです。