東京ディズニーシーへ行ってきました!

というわけで、久々の「行ってきました」シリーズです。
今回は超メジャーな観光地「東京ディズニーシー」に行ってきました!

CIMG1440_R

前にディズニーリゾートへ行ったのは22年前で、かなり小さいときです。
当然シーなんてものはない頃なので、今回は未開の地へ行くような感じで斬新でした。
ディズニーキャラも「チップとデール」が好きな位で、他はほとんど興味がないor知らなかったりします。

出発~入園まで

山梨から自力で行ける気がしないので、旅行会社のツアーに参加しました。
行きも帰りも寝てればいいのでラクラクです。
朝6時からバスで出発します。

CIMG1413_R

土曜ということもあり、凄まじい人の量です。
行ってすぐ「センターオブジアース」のファストパスを取りましたが、乗れたのは15時でした。
「タワーオブテラー」や「トイストーリーマニア」など望むべくもないという感じに。
ファストパスを上手く使いつつ、時には気合いで並んでアトラクションに入ることになりました。

今回は友人と2人で行きましたが、その友人が1~2ヶ月ペースで来るほどのディズニー好きなので、横にリアルガイドがいるようで安心です。
また、ディズニーの公式サイトにスマフォでアクセスしGPSをONにすると、他のアトラクションの待ち時間やファストパス発行時の入場時間が見ることができます。
行列待ちの時とかに大変便利です。

アトラクション

大抵のアトラクションは写真撮影ができないので、乗った感想を簡単に書きます!
ほぼ乗った順です。

【マーメイドラグーンシアター】
待ち時間は60分でしたが、体感40分くらいで入ることができました。
近年リニューアルされたとのことです。

ミュージカルはディズニーお得意のものですね。
演出もさることながら、アリエル役の方の空中ワイヤーアクションが非常に綺麗でした。

【マジックランプシアター】
待ち時間60分に対し、70分くらい待つことになりました。

人生で初めて3Dメガネをしたので、そのインパクトが印象に残っています。
誇張抜きで飛び出てきます。思わず「うおっ!」となるレベルで。
3DSのがいかにチャチなものか思い知らされます。

【ディズニーシートランジットスチーマーライン】
乗ったのは1周の方、ガラガラですぐ乗ることができました。
半周の方は移動を兼ねることができるためか、かなり混んでいました。

CIMG1431_R

CIMG1432_R

良くも悪くも遊覧船です。
自分は初めてシーに来たので、園内を移動しながら紹介してくれるのが地味に有難かったです。
ただ日によってはそこそこ寒いので、着込んで乗る方が良いと思われます。

【センター・オブ・ジ・アース】
ファストパスを使用しても10分ほど待ちました。
スタンバイの方は180分待ちとかがデフォで、洞窟内に長蛇の列ができていました。

初ディズニーシーな自分、先入観なしで「何乗りたいですか?」と聞かれれば、存在感抜群の火山に行きたくなるのは必然です。
雰囲気自体は面白かったのですが、絶叫系がダメな自分は最後の「暗闇高速ループ→落下」で心臓がバクバクになってしまいました。
地底探検も楽ではないようです。

【キャラバンカルーセル】
5分ほど待機したら乗れました。
よくも悪くも普通のメリーゴーランド系のアトラクションです。
やはりというべきか、ジーニー型は競争率が高かったです。

【海底2万マイル】
ファストパスを使用&2人組だったためすぐに乗ることができました。
スタンバイでは120分、もちろん長蛇の列です。

センターオブジアースと比べればマイルドなレール型アトラクションです。
絶叫系がダメな自分のような人にはちょうどいい具合です。

【ストームライダー】
こちらもファストパスを使用、だいたい10分ほどで入れました。
やっぱりスタンバイは数時間待ちです。

「絶対安全」「乗りが良く命令無視をするパイロット」など、フラグ満点の空の旅です。
4DXのように前後左右の揺れや水しぶきがありました。
ストームディフューザーが刺さるなど芸も細かいです。

パレード・イベント

3つほど見ることができましたので、その写真と感想です!

【ファンタズミック!】
夜に行われる大きなパレートです。
映像からライティング、花火や火炎放射など、これが「国内最大級のパレード」と圧倒されます。
全体的にド派手ですが、クライマックスのミッキーとドラゴンの対決が、手持ち花火であっけなく終わったのにはシュールな笑いがでました。

CIMG1445_R

CIMG1457_R

【テーブル・イン・ウェイティング】
「ファンタズミック」が終わってすぐ見に行きました。
あまり内容を知らなかったのですが、チップとデールが出てくるようで、その点だけでも大満足です!
ということで、その2匹を撮りまくりました!

CIMG1476_R

CIMG1478_R

CIMG1479_R

CIMG1498_R

後半はパイで殴りかかったり投げつけたりと乱闘になっていました。
3階のレストランから高高度射撃するグーフィーのセンスは特筆に値します。
何気にミッキーやミニーをまともに見たのもこれが初めてです。

【ハピネス・オン・ハイ】
パレードというよりはイベントに近いものでしょうか。
BGMと共に打ち上げられる花火はなかなかに感慨深かったです。
1日見て回った後の余韻に浸るには十分。

帰りとまとめ

園内で歩いた歩数は29700歩、距離は17.5km分にもなったようです。
すごく疲れますが、よく言えばいいウォーキングにもなります。
構造上の話ではありますが、気付いたらセンターオブジアース付近を往復していまくったりしました。

お土産は家族や自分がディズニーに疎いこともあり、あまり買っていなかったりします。
自分は上で書いたように「チップとデール」が大好きなので、デールのファンキャップ(帽子)を購入しました!
普段から被るほどの勇者ではありませんが、園内で被るとテンションが上がっていい気分になります。
ついでに頭がホカホカで、夜のパレードも(頭のみ)暖かく見れました。

そのチップとデールですが、どうやらランドの方がアトラクションもグッズも豊富なようです。
また夏ごろに夢の国、いけるといいですね!

【開発メモ】アクション「空中攻撃」の実装

というわけで、今回はタイトル通り「空中攻撃」を作ってみます!

shot2ss20160321215002845

至って普通に空中で攻撃を行うものです。
単純ながら、ジャンプ可能なアクションゲームではほぼ必須アクションです。
むしろ「何故今までなかったのか」というレベルの話でもあります。

当初は単発攻撃の予定でしたが、「空中でも連続攻撃したい!」となったため、地上攻撃と同じように連続入力で派生するようにします。
アクション性の高いステージほど空中にいる時間は長くなるので、モーション自体の使いやすさも重要なところです。

スクリプト作成

まずはBlenderでアニメーションを作ります。
気合い入れて作る元気はないので、とりあえずマリンパにそれっぽく剣を振ってもらいます。
Unityへインポート後、AnimatorControllerに登録します。

空中攻撃の条件は「空中にいる時に攻撃ボタン」です。
PlayerクラスでCharacterMotorのisGrounded()から判定しても問題なさそうですが、「ダッシュ中は発動しない」等の条件も判定することを考えると、ジャンプ系のステートからスクリプトで遷移させる方がよさそうです。

そんなわけで、ボタン押下時にAnimator.Play()で空中攻撃に飛ばしてしまいます。
以下はボタン押下時にステート遷移を行う「SwitchTriggerByKey」クラスです。

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {

    public class SwitchTriggerByKey : StateMachineBehaviour {
        // 入力するキー(ProjectSettings)
        [SerializeField]
        protected string inputKey;

        [SerializeField]
        protected string parameterName;

        // Animator.Playを用いて遷移
        [SerializeField]
        protected bool useAnimatorPlay;

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

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

inputKeyに(InputManagerの)攻撃ボタン名、parameterNameにAerialAttack1~3を設定します。
攻撃モーションが終わるまで遷移しないので、useAnimatorPlayにはチェックを入れないようにします。

この記事的には空中攻撃実装のためですが、実際は他のステートでも使えます。
自分は「待機中に攻撃ボタンで~」「移動中にダッシュボタンで~」など、いろいろなステートに付けていたりします。
条件として「ボタンが離された時」を入れておくと、チャージ攻撃などの溜めが必要な入力の場合でも対応できます。
その場合は、Triggerではなくfloat等で判断した方が良いかもしれないです。

shot2ss20160321220859023

空中攻撃2~3にも同様にスクリプトを追加し、遷移条件を設定します。
基本的にはExitTimeにチェックを入れ、インスペクターで遷移タイミングを設定していい感じに繋ぎます。

攻撃判定の生成に関しては過去記事でそこそこ話題にしているので割愛します。
以下が参考になるかもしれません。

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

攻撃ヒット時の浮遊状態の実装

連続入力で(マリンパの場合は)3段目まで出せるわけですが、地上と違ってジャンプ中だったり落下中だったりするため、そのままではフルヒットさせるのは困難です。
かと言って攻撃する度に浮くようにした場合、空中で素振りしまくって延々と飛べるのも問題です。

なので、「攻撃がヒットした時」に少しだけ浮くようにします。
敵に当たればそのまま連続入力でフルヒット・・・というのが理想ですね。

ステート側のスクリプトでやるのは効率が悪いので、攻撃判定のOnTriggerEnter()で判定後、Playerクラスの関数を呼ぶようにします。

public void callAttackHit(PlayerHit playerHit) {
    if (stateInfo.IsTag("Attack")) {
        charMotor.movement.velocity.y = 20;
    }
}

CharacterMotor.movement.velocityはキャラクターの速度です。
Y軸を一時的に20に設定することで、キャラクターが少しだけ浮きます。
やや強引な方法ですが一番簡単だったのでこれでいきます!

まとめ

ということで、空中攻撃を実装してみました!
最終的にはこんな感じに。

20160321

使う機会が多いアクションなので、余裕があればもっと改良していきたいところです。

【開発メモ】アクション「壁ジャンプ」の実装

というわけで、今回のお題はこれです!

20160313

所謂「壁ジャンプ」「壁キック」と言われるものです!
壁を蹴ってジャンプし、高い足場へも登れるようにします。

実は旧ブログでこっそりと実装していたりしましたが、実用性のない微妙なものだったので、ちょっと作り変えてみました!
両側に壁がある場合に連続で壁ジャンプできるようにしたのが大きな変更です。

基本的な仕様

完全な2Dアクションならともかく、3Dアクションでどこでも壁蹴り出来るようにするのは厳しいです。
壁との接触判定が難しい上、意図しない場面で壁ジャンプが暴発する可能性もあります。

ということで、任天堂の某SFアクションと同じように「壁ジャンプ専用の壁」を用意します。
その壁に接触中にジャンプボタンを押した場合に壁ジャンプを行うようにします。

モーションはダッシュジャンプ時と一緒で、くるくる回転させます。
専用モーションを作るのが面倒なので、とりあえず回らせれば見栄えがよくなるためです!
壁ジャンプ実行前にAnimator.Play()で切り替えてしまいます。

壁ジャンプ専用オブジェクトとスクリプトの作成

まず「壁ジャンプ専用の壁」を作ります。
Cubeを生成し、適当にそれっぽいマテリアルを作って適用します。

shot2ss20160313142132946

判定をCollisionで行うのは厳しいため、壁本体の判定とは別でTrigger用の判定を付けておきます。
Triggerの判定を壁ジャンプが可能な方向に少しずらしておきます。

次にスクリプトを作成します。
以下は壁用オブジェクトに設定する「ClimbWall」クラスです。

【ClimbWallクラス】

using UnityEngine;
using System.Collections;

public class ClimbWall : MonoBehaviour {

    public Vector3 climbVect;

    private CharacterMotor charMotor;

    protected void OnTriggerStay(Collider c) {
        if (c.gameObject.tag == "Player") {
            if (Input.GetButtonDown("Jump")) {
                charMotor = c.gameObject.GetComponent<CharacterMotor>();

                if (!charMotor.IsGrounded()) {
                    c.GetComponent<Animator>().Play("DashJump");

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

                    c.transform.LookAt(c.transform.position + new Vector3(climbVect.x, 0f, climbVect.z));

                    iTween.ValueTo(c.gameObject, iTween.Hash(
                        "from", climbVect,
                        "to", Vector3.zero,
                        "time", 0.5f,
                        "delay", 0.1f,
                        "onupdate", "Thrust",
                        "onupdatetarget", c.gameObject,
                        "oncomplete", "resetJump",
                        "oncompletetarget", gameObject
                    ));
                }
            }
        }
    }

    private void resetJump() {
        charMotor.jumping.enabled = true;
    }
}

「移動系ならiTween.MoveToでOK」と思ったのですが、今回はValueToを使用しています。
理由は単純で、iTweenのMove系は接触判定を突き抜ける可能性が非常に高いためです。
恐らくpositionを直接変化させているためだと思われますが・・・。
CharacterController的には CharacterController.Move() を使う必要があるので、仕方なくPlayerクラスにMove専用の関数を設定し、そこで一定時間Moveを呼んでもらうことにします。

【Playerクラス(一部)】

public abstract class Player : MonoBehaviour {
    protected void characterMove( Vector3 moveVect) {
        charControl.Move(moveVect * Time .deltaTime * 100);
    }
}

壁ジャンプする方向は「climbVect」としてフィールドに持たせ、インスペクターから設定します。
当初はプレイヤーとオブジェクトの方向から自動的に算出しようとしましたが、2点間のベクトルに斜め方向も加わってしまい、想定した動きにならなかったので挫折。
ステージに配置する際のRotationを、なるべくカメラに対して直角になるよう調節する必要があります。
一応climbVectにY軸のみを設定することで真垂直に壁キックができるというメリットはあります。
また、CharacterControllerのジャンプ機能が働いていると大ジャンプになってしまうことがあるので、iTweenの実行中は無効化しておきます。

iTweenのonupdateやoncompleteのターゲットには注意が必要です。
ClimbWallクラスで行うのか、Playerクラスで行うのかを考えると分かりやすいです。
勿論指定を誤ると上手く動きません。
そもそも移動処理をClimbWallクラスで行うのが間違っている気がしますが、そのうちいい感じにします!

まとめ

ということで「壁ジャンプ」を実装してみました!
やや挙動がおかしい上、たまに正常に動作しなかったりしますが、とりあえずこれがあることを前提にステージを作っていきます。

【開発メモ】アクション「ダッシュジャンプ」の実装

というわけで、前回「走り移動」を追加したので、その派生アクションとして「ダッシュジャンプ」を実装します!
走り中やダッシュ中にジャンプでより遠くまで飛べるものです。

20160205_s

モーションは回転ジャンプです!
(相変わらずgif画像を撮るのが下手ですが・・・)
現状では空中攻撃や2段ジャンプに派生できないため、着地するまで回転し続けます。
空中攻撃はともかく、ダッシュジャンプ中に2段ジャンプを出来るようにするかは考える必要があります。

モーション制御とスクリプト作成

AnimatorController上で「Run」「Dash」が既にあるので、ここからジャンプ入力でダッシュジャンプへ派生するようにします。
自分の場合、ジャンプ関連のステートを「Jumping」というサブステートマシンにまとめてあるので、ここに追加する形となります。
また、新しく「DashJump」というパラメータも作っておきます。

20160306_1

DashJumpステートにスクリプトを追加します。
OnStateEnter() と OnStateExit() にそれぞれ処理を書きます。

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {

    public class DashJump : StateMachineBehaviour {

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            animator.SetBool("Run", false);
            animator.gameObject.GetComponent<Player>().getCharacterMotor().movement.maxForwardSpeed = GameConstants.DASH_MOVE_SPEED;
        }

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            animator.ResetTrigger("DashJump");
            animator.gameObject.GetComponent<Player>().getCharacterMotor().movement.maxForwardSpeed = GameConstants.DEFAULT_MOVE_SPEED;
        }
    }
}

ジャンプボタンを押したら即反応して欲しいので、Animator.Play()を使用します。
AnimatorController上からモーションの中断を行うのはかなり難しいです。
というか自分が挑戦してまともに動いたことはありませんorz
まだまだ理解が足りていない部分ですね。

これでモーション的にはダッシュジャンプになりましたが、移動速度が通常と同じなので「ダッシュしながらジャンプ」している意味がありません。
CharacterMotor の maxForwardSpeed を変更し、移動速度を変化させます。

まとめ

・走り中かダッシュ中にジャンプでダッシュジャンプ
・OnStateEnter() と OnStateExit() で移動速度を変更

ということで、ざっくりとダッシュジャンプを実装してみました。
こういうアクションを実装すると「アクションゲーム」っぽくなりますね!
リアリティのあるモッサリ感は不要なので、大胆に作っていきたいところです。

【開発メモ】アクション「走り移動」の実装

というわけで、仕事の納期間近で忙しいりべるんです。
ブログも前回からかなり間が空いてしまっています。
今やっている「敵キャラクターのロジック修正」が結構ガッツリな感じなので、仕事が落ち着くまではちょっとしたことを主題にしていこうと思います!

shot2ss20160301210901495

今回実装するのは「走り移動」です。
通常の歩き移動より早く移動できるアレです。

実は既に「短距離を高速移動する」というアクションがあり、それをダッシュ扱いにしていたりします。
挙動としてはロックマンXシリーズのダッシュに近いです。
更に攻撃後の硬直などにダッシュボタンを押した場合はローリングになります。
こちらの挙動は他の3Dゲームでいうステップに近いです。

似たようなアクションが多くなりますが、行動の選択肢は増やしていきたいので実装します。
ある程度移動アクションがあればゲーム面で不便に感じることはない・・・はず。

基本的な仕様

ダッシュボタンを押しっぱなしにすることでダッシュから派生します。
攻撃後のローリングからは派生しません。
移動入力している間は通常移動より早く移動できます。

一定時間移動を行うと自動的に走り出すゲームもありますね。
これを取り入れるかは難しいところです。
専門学校の卒研で作ったゲームには搭載しましたが、「一定時間」が短すぎると暴発してしまうことが多く、また長すぎるとそもそも入れる意味がありません。
結局提出時にはボタン長押しでの派生のみになりました。
今回はとりあえず搭載し、ゲームが出来てきてから様子を見ることにします。

モーションとスクリプトの作成

それっぽい名前で「Dash」が良かったのですが、既にダッシュ用にモーションを作ってしまっているので、素直に「Run」で作ります。

shot2ss20160301211038807

リアルな人間キャラならともかく、2~3等身のキャラの走りモーションなんて似たようなものです。
キャラの性格・設定に合わせて作るのがベストですが、そんな余裕はないので適当にします!
Unityへインポートする際はループさせるように設定します。

shot2ss20160301211118902

続いてAnimatorController上から制御します。
パラメータ「Run」「Move」を作成し、DashステートとMoveステートからの遷移を設定します。

1つは「ダッシュ後に派生」なので、Dash ステートにスクリプトを付け、そこでダッシュボタンが押下されている状態であれば遷移させます。

【Dashクラス】

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {

    public class Dash : StateMachineBehaviour {

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (Input.GetButton("Dash")) {
                animator.SetBool("Run", true);
                animator.Play("Run");
            }
        }
    }
}

「OnStateExit」はモーション終了時に呼び出されるので、そこでダッシュボタンが押下されているか判定します。
パラメータ「Run」のtrue/falseで走り状態の持続を制御します。

もう1つの条件は「一定時間移動」です。
何を持って「一定時間移動したか」を判別するのがシビアなところですね。
単にキー入力だけで判定すると、その場でぐるぐる回っていても派生してしまいます。
ということで、判定には CharacterMotor の magnitude の値を使用します。

PlayerクラスのUpdate()内に以下の処理を追加します。

【Playerクラス】

moveSpeed = charMotor.GetDirection().magnitude;

// 移動速度をAnimatorにセット
animator.SetFloat("Move", moveSpeed);

// 一定時間移動で走り移動へ遷移
if (0.5f < moveSpeed && charMotor.IsGrounded()) {
    runCount += 1;
    if (GameConstants.AUTO_RUN_TIME < runCount) {
        motionChange("Run", true);
        runCount = 0;
    }
}

最後に走っている際の移動速度を変化させます。
Run ステートにスクリプトを付け、CharacterMotor の「MaxForwardSpeed」の値を切り替えます。

【Runクラス】

using UnityEngine;
using System.Collections;

namespace StateController.PlayerAnimator {

    public class Run : StateMachineBehaviour {

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            animator.gameObject.GetComponent<Player>().getCharacterMotor().movement.maxForwardSpeed = GameConstants.DASH_MOVE_SPEED;
        }

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (animator.GetFloat("Move") <= 0) {
                animator.SetBool("Run", false);
            }
        }

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            animator.gameObject.GetComponent<Player>().getCharacterMotor().movement.maxForwardSpeed = GameConstants.DEFAULT_MOVE_SPEED;
        }
    }
}

速度を変える処理が横に長い!
現状では「Playerの参照を取っておく」といったことができない(と思われる)ため、いちいちGetComponent()する必要があります。
PlayerクラスからAnimator関連の処理を抜き出せるのはメリットですが、やや使いにくい感じがします。

「GameConstants~」系はただの定数なのでお好みで。
MoveパラメータにはPlayerクラスからセットされた magnitude が入っているので、これで走り状態の維持するかの判定します。

まとめ

・「ダッシュ」「ローリング」に加えて「走り移動」を実装
・ダッシュ後にボタン長押しか一定時間移動で派生
・移動キーが押されていない場合に通常移動に戻す

ちょっとした機能でも実際作るとガッツリだったりします。
Animatorとかスクリプトとか絡んでくると大抵こうなってしまいます。
次回はもうちょっと簡単な話題にするかもしれません。
「ジャンプと組み合わせて使えば遠くまで飛べる」とか欲しいですね。