富士山方面へ行ってきました!

ゴールデンウィークということで、初日から「行ってきました」シリーズです。
今回は日本一の山「富士山」とその周辺へ行ってきました!

CIMG1547_R

富士浅間神社

河口湖から富士吉田方面に行く道の途中にあります。
すごく遅くなった「初詣」に行きました!

駐車場の位置が微妙に分かりにくいです。
河口湖側から来た場合、神社入り口の少し手前に参拝者用の駐車場があります。
交通量が多い場所なので下調べしておくと良いかもです。

CIMG1533_R

立派な松が多いです!
歴史ある神社なので、そういった意味でも趣があります。

CIMG1537_R

CIMG1539_R

神社としての敷地はそこまで広くないので、特別書くこともないです。
賽銭箱に投入してお祈りします。

CIMG1542_R

気分でおみくじを引いてみました!
中吉で「忍耐」とのことで、いろいろと耐え凌ぐ1年になりそうです。
その他「旅先で悪天候になることが多い」とのことで、4日にディズニーツアーを予約してしまった自分を見透かされたような感じになります。

富士スバルライン~富士山5号目

今回の主目的、富士山5号目までドライブに行きます。
朝は雲が多く不安でしたが、11時頃になったら富士山周りの雲がなくなっていました。

GW初日だし混んでるかと思いきや、スバルライン自体は意外と空いていました。
有料道路だけあって比較的整備されており、ドライブも快適です。

CIMG1546_R

1号目からみた富士山です。
あまり近づきすぎると見にくくなるのが山なので、このあたりから見るのが一番良かったりもします。

CIMG1551_R

お次は3号目です。
ここからの富士山は少ししか見えませんでした。

CIMG1549_R

振り返ると河口湖や富士吉田市街が見えます。
周りの山々を見ると、結構高い位置にいることを実感できます。

CIMG1561_R

そして5号目です!
写真では伝わりませんが、くっきり富士山が見えて迫力があります。

ついでにかなり寒いです。車の外気温計では5℃でした。
河口湖の方では17℃とかなので温度差が激しいです。

CIMG1564

「富士山メロンパン」なるものが売っていました。
富士山の形をしたメロンパン・・・だけかと思いきや、チョコの粉が掛かっていたりするので割と別物です。
ちょっと高めですが、記念としては食べるには良いのではないでしょうか。

山中湖 花の都公園

今はチューリップのシーズンらしいです。
見るだけなら入園しなくても大丈夫です。

CIMG1566_R

CIMG1572_R

CIMG1573_R

チューリップ自体は綺麗なのですが、いかんせんカメラの撮り方が悪いため写真上では微妙です。
もっと至近距離で撮れればよいのですが、場所の関係上それも厳しかったです。

まとめ

そんなわけで、富士山方面へドライブに行ってきました!
仕事もひと段落しているので、大型連休を満喫したいところです。

【Unity】ちょっとしたダメージエフェクトの作成

というわけで、今回はタイトル通り「ちょっとしたダメージエフェクト」を作ってみました!

shot2ss20160423223609064

今までダメージを受けた際のエフェクトは、UnityのDefaultParticleが拡散するようなしょぼいものでした。
あまりにも味気ないので、ファンタジーなゲームにありがちなエフェクトにしてみました!

エフェクト作成には「Effekseer」を使っていましたが、何故か正常にレンダリングされなくなってしまったので、今回はUnity標準のshurikenだけで作れそうなものにします!
外部ツールということでUnityやフレームワークのバージョンに左右されてしまっているのかもしれません。
Effekseer自体は良いツールだと思うので、改善されたらまた使おうと思います!

エフェクトの作成

大きく2つ、「星が拡散するエフェクト」と「ギザギザした吹き出しのエフェクト」になります。
まず前者からということで、適当に星形のテクスチャを作ります。

star

インポートしたらImportSettingsのTextureTypeを「Sprite(2D and UI)」にします。
新規にマテリアルを作成し、シェーダーを「Particle/Addtive」にし、先ほどのテクスチャを設定します。

shot2ss20160423223954164

新規にParticleSystemを作成し、生成するエフェクトのパーティクルを設定します。
キモとなるのはEmissionモジュールのBurstsで、生成された1回のみ且つ生成量を調整します。
また1点から外側へ拡散させるため、ShapeモジュールはSphereを選択、Radiusを限界まで下げておきます。

次に「ギザギザした吹き出しのようなエフェクト」を作ります。
(こういうエフェクトってなんて名前で呼べば良いのかよく分からないですorz)
こちらはダメージ発生時に一瞬だけ表示させます。

damage_shock

最近「PhotoshopElements14」を買いましたが、それにちょうどいいシェイプが入っていました。
適当に色を塗り、Unityへインポートします。
星と同じくImportSettingsのTextureTypeを「Sprite(2D and UI)」にします。

shot2ss20160423224050404

こちらは一瞬かつ1つしか表示しません。
なのでEmissionモジュールのRateを0にし、BurstsのMaxとMinを1に設定します。
DurationとStartLifetimeも0.1に設定します。

また、StartRotationを「Random Between Two Constants」にし、0と180を設定します。
これで再生されるたびにランダムで表示角度が変わります。

空のゲームオブジェクトを作り、この2つを子オブジェクトにした上でプレハブ化します。
あとはダメージ発生時にInstantiate()するだけ!
テスト用に耐久値を猛烈にあげたブロックを攻撃します。

20160423_1

ちょっと手を加えるだけでも結構見栄えが変わるものです。
緩めの「ゲームっぽさ」が表現できます。
ゲームの雰囲気やスピードに合わせてパラメータを調整すると尚良い感じです。

まとめ

ということで、「地味だけど良くなる」ちょっとしたダメージエフェクトを作ってみました!
ひさびさにUnity標準のパーティクルモジュールを使うといろいろ変わっていたりします。

私的にはShapeに「MeshRenderer」や「SkinnedMeshRenderer」が指定できるのが気になります。
時間がある時にいじってみたい部分です。

【開発メモ】敵キャラクターの行動ロジック修正 攻撃編

というわけで、今回は敵キャラクターの攻撃ロジックの修正を行います!
前回から間が空いてしまいましたが、移動ロジックを簡単に作ったので、その続きになります。

【開発メモ】敵キャラクターの行動ロジック修正 移動編

shot2ss20160415224147751

敵キャラクターの種類によって攻撃方法は異なりますが、今回はオーソドックスに「近接攻撃」と「遠距離攻撃」の2つを持ったキャラを想定します。
前回までの過程で、プレイヤーを発見するとその方向へ移動する制御まではできているので、プレイヤーとの距離に応じてどちらかを選択するようにします。

攻撃方法の判定

プレイヤーを発見した敵は移動を行い、キャラクター毎に持っている攻撃手段の射程内に入った際に攻撃を行います。
「プレイヤーが射程内にいるか」はレイキャストで判定します。
攻撃手段として2つ用意されているので、それぞれでレイキャストを行います。

Raycastのイメージは以下の矢印のような感じで、赤が近距離、青が遠距離の攻撃です。
プレイヤーキャラの判定(黄色枠)に当たった場合、その攻撃を実行します。

shot2ss20160415224814713

しかし、単純にRaycast()を行うだけでは他のオブジェクトに当たって消えてしまい、プレイヤーキャラにはほとんど当たってくれません。

対処法として以下の2つが思い付きました。
・RaycastAll()を使い、当たったオブジェクトからプレイヤーを判定する
・レイヤーマスクを使い、Raycastがプレイヤーにしか当たらないようにする

効率的にはレイヤーマスクでRaycastの判定対象を絞ったほうがよさそうです。
が、個人的にレイヤーマスクは苦手なので、素直にRaycastAll()の結果をループして判定する方法で実装しました。

以下はEnemyクラスのUpdate()の一部です。
前回やった索敵・移動部分は省略してあります。

【Enemyクラス】

protected virtual void Update () {
    if (animator && canAction) {
        stateInfo = animator.GetCurrentAnimatorStateInfo(0);

        if (stateInfo.IsTag("Attack")) {
            isAttack = true;
        } else {
            isAttack = false;
        }

        // アクション範囲内にプレイヤーがいる場合
        if (enemyActionRange.isDetectPlayer) {
            actionInterval += 1f;

            // 攻撃アクション
            if (defaultActionInterval < actionInterval && !isAttack) {
                doAttackAction();
            }

        } else {
            animator.SetBool("Move", false);
        }
    }
}

【各敵キャラクター用クラス】

protected override void doAttackAction() {
    Ray attackRay = new Ray(transform.position, transform.forward);

    // 攻撃射程内にプレイヤーがいるか判定
    if (ObjectUtility.judgeRaycastHitsTag(Physics.RaycastAll(attackRay, attackRange[0]), "Player")) {
        isAttack = true;
        motionChange("Attack1", true, true);

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

【ObjectUtilityクラス】

  
using UnityEngine;
using System.Collections;

public static class ObjectUtility {
    public static bool judgeRaycastHitsTag(RaycastHit[] hits, string tag) {
        foreach (RaycastHit hit in hits) {
            if (TagUtility.getParentTagName(hit.collider.gameObject) == tag) {
                return true;
            }
        }

        return false;
    }
}

索敵範囲は「EnemyActionRange」というクラスを付けた判定で行っています。
プレイヤーを発見した場合、isDetectPlayerがtrueになります。

doAttackAction()で攻撃射程内かの判定と、実際の攻撃処理を行います。
敵キャラクターごとに内容が異なるため、各敵キャラクター用クラスでEnemyクラスのdoAttackAction()をオーバーライドして実装します。

まずRaycast用のRayを作成します。
今回は単純に「現在位置」から「前方への位置」を指定しています。
ここで作成しなくても、RaycastAll()のオーバーロードでoriginとdistanceを指定できるので、直接指定してしまってもOKです。

次に攻撃射程の判定です。
プログラムソース上で上から順番に評価されていくので、優先させたい攻撃アクションの評価ほど上に記載します。
上の例では、
・attackRange[0] = 10 (近接攻撃の射程)
・attackRange[1] = 30 (遠距離攻撃の射程)
と設定しているので、まず近接攻撃可能な距離にいるか判定され、次に遠距離攻撃が可能かが判定されます。

ObjectUtility.judgeRaycastHitsTag()は自作した静的なメソッドで、RaycastHitの配列と文字列を渡すことで、RaycastAll()の結果から「特定のタグのオブジェクトに当たったか」を判定します。
処理自体はforeachで回して判定しているだけですが、使う機会が多そうなのでユーティリティ化しておきます。

プレイヤーが攻撃射程内にいる場合はモーションを切り替え、攻撃判定を生成します。
攻撃判定については割愛しますが、以下の記事が参考になると思われます。

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

攻撃間隔とリセット処理

攻撃を行った後、一定時間は移動・攻撃を行わないようにします。
一定時間後に再度プレイヤーを索敵→移動・攻撃を行います。

このあたりを自重せずに実装すると、「1フレーム毎に遠距離攻撃を連射」という凶悪な敵ができてしまったりするので、ゲーム的な体裁を保つ意味でも重要な間です。
特殊な敵やボスならともかく、雑魚的が連続で行動してくる必要もありません。
アクションゲーム故、敵が複数同時にいることも多いので尚更ですね。

上で載せたソースで言うと、「actionInterval」という変数で攻撃間隔を制御します。
1フレーム経過ごとにactionIntervalを加算しています。
合わせて「defaultActionInterval」というフィールドを用意しておき、attackIntervalがdefaultActionInterval以上になった場合のみdoAttackAction()を実行します。
攻撃実行後はattackIntervalを0にリセットし、再度defaultActionInterval以上になるまで攻撃は行いません。

以下はdefaultActionIntervalに120(=2秒)を指定した場合の挙動です。

20160415_1

1敵キャラクターとしてみると妥当な攻撃間隔でしょうか。
「短めの間隔で小刻みに攻撃」「長めの間隔で強力な攻撃」といった敵も作れそうです。

まとめ

そんなわけで、敵キャラクターの攻撃ロジックを考えてみました。
前回の移動ロジックと合わせて、最低限「敵」の動きになったと思われます!

実装していくと無駄な処理や「これもっとスマートにできそう」といった箇所が目についてきます。
ゲームの形にすらなっていない今はともかく、ある程度作ってきたらパフォーマンスも気にする必要がありそうです。

【開発メモ】敵キャラクターの行動ロジック修正 移動編

というわけで、新年度に入りつつもブログを放置気味なりべるんです。
やろうとしていることが難解だったり記事にしにくかったりでほとんど更新できていません。
とはいえ、「Unityゲーム開発日記」と名打っている以上、定期的に話題は出していきたいところです。

今回は敵キャラクターのロジックや行動ルーチンの見直しになります!
これまた前に適当に作って以来、手をつけていなかった部分です。

現状では、
・プレイヤーを見つけると一直線に突撃
・どの方向から近づいても瞬時にプレイヤーを発見する
・障害物があると回り込まず、完全に詰まりっぱなしになる
・プレイヤーが止まっている場合は攻撃を行わない
・ダメージモーション中も移動する
・ダメージモーションをカットして攻撃をしてくる
・押すと簡単にひっくり返る

等々、アクションゲームとしてみると欠陥品です。
行動ルーチンだけじゃどうしようもない部分もありますが、これを気に敵関連のオブジェクトやスクリプトを作り直すことにしました!

といっても全て直すのはかなり骨が折れるので、今回は移動関係のロジックを見直してみます。
ボスとか特殊な敵はひとまず考慮しないことにします。

shot2ss20160404235912272

テスト対象は↑の敵キャラクター「バブぴょん」です。
所謂スタンダードな雑魚的で、いろいろな場面に登場させる予定です。

敵キャラクター側の索敵について

基本的に雑魚敵はプレイヤーを発見した場合のみ行動を行います。
敵キャラ毎に索敵範囲を作り、それでプレイヤーが範囲内にいるかを判定します。

shot2ss20160404235946605

上はバブぴょんの場合の索敵範囲で、正面をBoxColliderでカバーしています。
ステルスアクションでも硬派なFPSでもないので、索敵に関しては割と単純です。
音とかそういう要素もありません。

敵キャラクター本体にも移動&ダメージ判定用のコライダーがあるので、索敵用オブジェクトにBoxColliderを付け、それを本体の子に設定しています。
合わせて以下のスクリプトを作成&アタッチします。

using UnityEngine;
using System.Collections;

public class EnemyActionRange : MonoBehaviour {

    public Transform lookTarget;
    public bool isDetectPlayer;

    private Enemy enemy;
    private Vector3 angle;

    void Start () {
        enemy = GetComponentInParent<Enemy>();
    }

    void Update () {

    }

    void OnTriggerEnter(Collider c){
        if (TagUtility.getParentTagName(c.gameObject) == "Player"){
            isDetectPlayer = true;
            lookTarget = c.transform;
        }
    }

    void OnTriggerExit(Collider c){
        if (TagUtility.getParentTagName(c.gameObject) == "Player") {
            isDetectPlayer = false;
            lookTarget = null;
        }
    }
}

TagUtilityに関しては以下の記事をご参照下さい。
ここでは使う意味はほとんどなかったりしますが・・・。

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

前はこれに敵本体の移動制御まで書いており、無駄に複雑な作りになっていました。
敵の移動や攻撃はEnemyクラスで行い、シンプルに「敵が索敵範囲内にいるか」のみを設定するようにします。

敵キャラクターの移動制御

プレイヤーを発見した敵キャラクターは接近して攻撃しようとします。
攻撃部分は後々実装するので、今は「移動→攻撃→一定時間停止→移動・・・」の移動部分のみを作ってみます。

以下は敵キャラクターに付与するEnemyクラスの一部です。
(他の機能も作成中なので不要な変数や処理もありますがご了承くださいorz)

 
using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Rigidbody))]

public abstract class Enemy : MonoBehaviour, ICharacter {

    public float life, attackRange, moveSpeed, rotateSpeed;
    public float defaultActionInterval;
    protected bool canAction;
    protected bool isMovement, isRotation, isAttack;

    [SerializeField]
    protected float actionInterval;

    protected Animator animator;
    protected AnimatorStateInfo stateInfo;

    protected EnemyActionRange enemyActionRange;
    protected Rigidbody rigidbody;

    protected virtual void Start () {
        actionInterval = defaultActionInterval;

        canAction = true;
        isMovement = false;
        isRotation = false;
        isAttack = false;

        animator = this.GetComponent<Animator>();
        enemyActionRange = this.GetComponentInChildren<EnemyActionRange>();
        rigidbody = this.GetComponent<Rigidbody>();
    }

    protected virtual void Update () {
        if (animator && canAction) {
            stateInfo = animator.GetCurrentAnimatorStateInfo(0);

            // アクション範囲内にプレイヤーがいる場合
            if (enemyActionRange.isDetectPlayer) {
                actionInterval += 1f;
                isMovement = true;
                isRotation = true;

                // 移動制御
                if (isMovement) {
                    Vector3 forward = new Vector3(transform.forward.x, -0.1f, transform.forward.z);
                    animator.SetBool("Move", true);
                    transform.localPosition += forward * moveSpeed;
                } else {
                    animator.SetBool("Move", false);
                }

                // 回転制御
                if (isRotation && enemyActionRange.lookTarget != null) {
                    Vector3 angle = enemyActionRange.lookTarget.position - transform.position;
                    angle.y = 0;
                    transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(angle), rotateSpeed);
                }
            } else {
                animator.SetBool("Move", false);
            }
        }
    }
}

とりあえず敵を発見したら移動、発見していないor見失った場合は止まるようにしてみました。
移動制御と回転制御を別々にしているため、後々「その場から動かないが回転だけする」といった敵を作る際にも対応できます。

実際に動かすとこんな感じ。
索敵用コライダー内にプレイヤーがいる場合のみ行動しています。

まとめ

というわけで、敵キャラクターのシンプルな移動制御について考えてみました!
次は今回の移動に攻撃ロジックを乗せていこうと思います。