【Unity】InputFieldにフォーカスした際の全選択を無効化する方法

というわけで、今回は UI の InputField に関するお話です!
InputField にフォーカスした際、全選択状態になってしまうのを何とかする方法について調査してみました!

20170304_01

Unity5.5 では、ActiveInputField() でフォーカスすると起こるようです。
プレイヤーにクリックさせる想定ならともかく、キーボード入力時に全選択状態でフォーカスされても面倒です。
当然ですが、全選択状態のまま入力すると、入力されいた文字が上書きされてしまいます。

結論から言うと、以下のどちらかの方法でいけるようです。
・InputField を継承したクラスで LateUpdate() をオーバーライドし、MoveTextEnd() を実行する
・コルーチン等でフォーカス後のフレームで MoveTextEnd() を実行する

後述しますが、ActiveInputField() 後に MoveTextEnd() を呼んでも上手くいきません。
何れも「フォーカス後に間を置いてから実行する」のがキモのようです。

原因

BitBucket の下記URL に InputField クラスの中身が載っています。

https://bitbucket.org/Unity-Technologies/ui/src/0155c39e05ca5d7dcc97d9974256ef83bc122586/UnityEngine.UI/UI/Core/InputField.cs

それによると、onFocus() 時に SelectAll() が呼ばれているようです。
条件分岐もしていないようなので、設定でどうにか出来るものでもなさそうです。

また上でも触れましたが、以下のようなコードでは解除されません。

input_field = this.GetComponent<InputField>();
input_field.ActivateInputField();
input_field.MoveTextEnd(false);

どうやら同一フレームではラベルのアップデートの関係(?)で動作しないようです。
さくっとスクリプトで制御できるかと思いきや、思わぬ苦戦を強いられることになりました。

解決法

解除自体は MoveTextEnd() を実行するだけで良いので、その実行タイミングの問題のようです。
海外のフォーラムで同じような質問がいくつかありましたが、方法としては「InputFieldを継承したクラスでオーバーライドして実行」か「コルーチン等でフォーカス後のフレームに実行する」のどちらかが多かったです。
自分は前者の「継承してオーバーライド」でやってみました!

using UnityEngine.UI;

public class InputFieldCustom : InputField {
    protected override void LateUpdate() {
        base.LateUpdate();

        MoveTextEnd(false);
    }
}

LateUpdate() をオーバーライドし、スーパークラスを呼び出した後に MoveTextEnd() を実行すれば良いらしいです。
あれこれ処理した後に MoveTextEnd() で選択状態を解除するイメージでしょうか。

その他、コードを見る限りでは OnFocas() をオーバーライドし、SelectAll() が呼ばれないようにしても上手くいきそうな気がします。
試していないので分かりませんが、何れも継承するやり方なら簡単に解決できます。

まとめ

そんなわけで、今回は InputField のフォーカス時の挙動について調査してみました!
UnityEngine のクラスを継承したことはなかったので、今後も選択肢の1つとして覚えておきたいです。

毎回書いたスクリプトを全部載せていましたが、今回からポイントを絞って紹介しようかと思います。
面白みのないコードを載せても記事が長くなってしまうだけなので・・・。
そろそろブログのデザインも作り直そうかと考えているので、しばらく試行錯誤しながら書いてみます。

【Unity】パーティクルシステムで光剣っぽいエフェクトの作成

というわけで、仕事繁忙のため全然更新できていませんが、今回のお題はこちらです!

shot2ss20161001091241801

剣のモデルから「光の剣」のようなものを出してみました!
「武器に光が纏って強くなる」なんてシチュエーションはファンタジーなゲームでよくありますよね!
プレイヤーがこの状態になると攻撃力やリーチが上がり、見た目と相まってテンションが上がったりします。

パーティクルシステムの Renderer モジュールで、生成するパーティクルの形状を変化させることができます。
初期状態では Billborad ですが、これを Mesh にしてモデルを指定すればOKです。

このあたりはテラシュールブログさんに分かりやすく記載されております。

下準備

パーティクルのベースとなる剣のモデルを作成します。
剣といっても柄の部分は不要で、刃の部分だけそれっぽくなっていればいいので、割と適当でも問題ありません。
そんなわけで、Blender でさくっと作ってみました。

shot2ss20161001100028913

今回は専用のモデルを作りましたが、キャラクターが剣を持っている場合はそのメッシュを使ってしまっても大丈夫です。
ただし剣の刃と柄が1オブジェクトに結合されている場合、パーティクルのエフェクトに柄も反映されてしまうので注意が必要です。
専用のモデルを作ると剣の形に依存しなくもなるので、モデルの状態や作成するゲームの雰囲気に合わせて・・・といった感じでしょうか。

ベースエフェクトの作成

光の剣となるエフェクトを作成します。
新規でパーティクルシステムを作成し、パラメータを設定していきます。

shot2ss20161001092021719

shot2ss20161001092101239

ポイントはやはり Renderer モジュールのメッシュ指定です。
これを剣の位置・移動に合わせるため、 剣に近い bone の子オブジェクトに設定し微調整します。

地味に詰まった点として、RenderMode を Mesh にし、シェーダーを Particle~ 系にすると、画面上に表示されなくなってしまいました。
ProjectSettings→Player→Other Settings→Auto Graphics API for Windows にチェックを入れることで解決しました。

shot2ss20161001092242887

新規プロジェクト作成時はチェックが入っているので外れている理由が分かりませんが、Windows10 にしたから・・・かも?
7→10のアップデート時にいろいろトラブったので、関係がないとは言い切れなそうです。

shot2ss20161001093940980

出来上がったのがこちら!
ちょっと安っぽい感じはしますが、雰囲気だけならそれなりに出来ているのではないでしょうか。
マテリアルの設定とかテクスチャとか拘ればもっと見た目はよくなると思われます。

20161001_01

振り回すとこんな感じに。
見た目は剣のリーチが伸びたように見えますが、現状では攻撃判定が伴っていない詐欺エフェクトだったりします。
通常時と光剣時で攻撃判定を分ける必要がありそうです。

ちなみに剣の軌跡の部分は「X-WeaponTrail」というアセットで簡単かつ綺麗に作れます。
下の記事で紹介しています。

「X-WeaponTrail」を使った武器の軌跡について

サブエフェクトの作成

↑だけでは寂しいので、何か適当にエフェクトを追加してみます。
関連オブジェクトをまとめるための空オブジェクトを作成し、先ほどのパーティクルを子オブジェクトとした上で追加していきます。

まず簡単にできるエフェクトとして、剣から少しオーラが出ているような見た目にしてみます。
メインエフェクトを複製し、Start Speed を少しだけ上げてみました。

shot2ss20161001095516161

もし「水の剣」など流動的なものを想定する場合は Start Speed ではなく、 Gravity Modifier を使うとそれっぽい見た目になります。
上げすぎると自己主張が強くなるので、少しだけ上げましょう。

shot2ss20161001100342543

あとちょっとしたエフェクトとして、剣の周りに粒子を出してみます。
今度は Shape を Mesh にして先程と同じ剣のモデル選択し、RenderMode は Billborad にします。
その他はデフォルトのマテリアルで適当に。

shot2ss20161001095638977

エフェクトとはちょっと違いますが、光剣という名目なのでライトが欲しいところです。
適当に PointLight を追加します。

shot2ss20161001095747584

上は Range が50、 Intensity を1.5に設定してあります。
Directional Light が効いていると微妙ですが、暗い場所なら目立つ・・・かも。

まとめ

そんなわけで、パーティクルシステムを使った光の剣 (っぽいもの) について考えてみました!
攻撃時の見栄えがかなりよくなるので、強力な攻撃の際には付けると良さそうです。

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

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

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段ジャンプのステートには遷移しないようにすればいけそうです。

まとめ

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

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

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

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クラスで行うのが間違っている気がしますが、そのうちいい感じにします!

まとめ

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