UnityのアクションゲームにQTE(QuickTimeEvent)機能を取り付けたいと思います。
QTEとはアクションゲーム等で突如として現れる制限時間内にボタンを押さないと失敗とされてしまうイベントですね。
今回はQTEを簡単に複数個の設置が出来るようにし、設置したい場所に置いてインスペクタでパラメータ調整するようにします。
作成する段階ではゲームオブジェクトの連携でわかり辛い感じですが、一旦作ってしまえば設置する時に楽になります。
まぁ・・・連携がわかり辛くなってるのはわたくしの能力不足でそうなっているだけだと思うので、ご自分のわかりやすいよう修正してください。(^_^;)
QTEプレハブを作成
QTEプレハブはQTEに関するゲームオブジェクトをひとつにまとめたものでQTEを発生する場所に設置してパラメータ調整するだけでいいようにします。
まずはヒエラルキー上で右クリック→Create Emptyを選択し名前をQTEとします。
キャラクターを検知するエリアの作成
QTEの子要素に右クリック→3D Object→Cubeを選択し名前をQTESearchAreaとします。
Box ColliderのIs Triggerにチェックを入れ物理的に当たらないようにします。
Assetsフォルダで右クリック→Create→Materialを選択しMaterialの色や透明度を変更し、QTESearchAreaにドラッグ&ドロップし設定します。
このマテリアルはサンプルの為にキャラクターがQTEエリアに侵入したのがわかりやすくなるよう設定しただけで、本来であればキャラクターをサーチする為だけのエリアなので、
マテリアルを設定して色を付ける必要はありません(ゲームに使う時はQTESearchAreaは透明のはず)。
SearchCharaAndParamスクリプトは後で作成しますが、PushButtonはInputManagerで設定しているボタンの選択、Limitはボタンを押さなければいけない時間の設定になります。
作成したQTESearchAreaは↑のようになります。
このエリアにキャラクターが侵入するとQTEが発生し、設定したPushButtonに対応するボタンを押さなければいけません。
キャラの検知とQTEの設定をするSearchCharaAndParamスクリプトの作成
さきほど作成したQTESearchAreaにキャラクターを検知する処理とQTEの設定の処理をするSearchCharaAndParamスクリプトを作成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | using UnityEngine; using System.Collections; public class SearchCharaAndParam : MonoBehaviour { // InputManagerで設定したボタンを設定する public enum PushButton { Jump, Fire1 }; // QTE用のUIゲームオブジェクト [SerializeField] private GameObject QTEUI; // タイムリミットスクリプト private TimeLimit timeLimit; // QTEが終了しているかどうか private bool isFinished; // キャラクター操作スクリプト private QTECharacterMove qteChara; // どのボタンを押すか [SerializeField] private PushButton pushButton; // 制限時間 [SerializeField] private float limit = 10f; void Start () { isFinished = false; // TimeLimitスクリプトの取得 timeLimit = QTEUI.GetComponentInChildren<TimeLimit>(); } void OnTriggerEnter(Collider col) { if(isFinished) { return; } if(col.tag == "Player") { // キャラクター操作スクリプトの取得 QTECharacterMove qteChara = col.GetComponent<QTECharacterMove>(); // UIを表示 QTEUI.SetActive(true); // TimeLimitスクリプトに主人公操作スクリプト、押すべきボタン、制限時間を保存しておく timeLimit.SetParam(qteChara, pushButton, limit); // キャラクターの状態変更と自身(SearchQTEChara)を渡す qteChara.SetState(QTECharacterMove.MyStateQTE.QTE, this); } } // QTE終了設定 public void SetFinish() { // イベントが成功したら2度とイベントを発生させない isFinished = true; // QTE用のUIを非表示 QTEUI.SetActive(false); } // 押すべきボタンを返す関数 public PushButton GetPushButton() { return pushButton; } } |
PushButtonをenumで宣言し、InputManagerで設定したボタンを並べます。
QTEのボタン押しの処理(キャラクター操作スクリプト内で記述する予定)でキーボードのキーやゲームパッドのボタンを個別に選択(SPACEキーを押したら等)出来るようにしてもいいのですが、InputManagerで指定したボタンを使う事で、キーボードやマウス、ゲームパッド、どれでも操作出来るようにしています。
QTEはUIを使ってボタンを押すまでの時間を表示します。
TimeLimitスクリプトはタイマーを進めるスクリプトで後で作成します。
QTEUIゲームオブジェクトはインスペクタで設定し、TimeLimitはQTEUIの階層から取得しています。
OnTriggerEnterメソッドでキャラクターがQTESearchArea内に侵入したかどうかを判定し、キャラクターに設定しているスクリプトQTECharacterMoveの取得と
TimeLimitスクリプトへのパラメータの渡しとキャラクターの状態の変更をしています。
SetFinishメソッドはQTEが終わった時に呼び出す関数で、isFinishedフィールドをtrueにしQTE用のUIを非表示にしています。
GetPushButtonメソッドはこのQTEで押すべきボタンを返すメソッドでキャラクター操作スクリプトQTECharacterMoveスクリプトから呼び出して使います。
QTE表示用UIの作成
次にキャラクターがQTESearchAreaに侵入した時に表示するUIを作成していきます。
ヒエラルキー上で右クリック→UI→Canvasを選択し、名前をQTEUIとします。
QTEUIの子要素に右クリック→UI→Panelを選択します。
Panelの子要素に右クリック→UI→Imageを作成します。
これはボタンを押す事を促す為のイメージなので、Source Imageに方向のイメージを指定します。
下向きにする為RotationのZに-180を設定し回転させています。
Panelの子要素に右クリック→UI→Textを作成します。
テキストは押すべきボタンを表示する場所でデフォルトで『Push Space Key』という文字を設定しておきます。
テキストはスクリプトから変更するので初期値を指定しなくても大丈夫です。
Panelの子要素に右クリック→UI→Sliderを作成します。
SliderにはTimeLimitスクリプトを作成し取りつけます。
中身は後で作成しますが、TimerImageは時間がなくなった時に色を変更するイメージを指定し、LimitPerは何パーセントを超えたら色を変更するか、
Textは押すべきボタンを表示する為のTextを指定します。
Sliderの子要素にFillがありますが、これをQTEの時間経過を知らせるイメージとして使用します。
Source Imageにはストップウォッチのイメージを指定し、Image TypeをFilledにし、Fill MethodをRadial 360に変更します。
Radial 360にする事で車の速度メーターのような値を表現する事が出来るようになります。
Sliderの値が0~1(Minが0でMaxが1の時)で変化していく事で1周グルッとイメージが表示される感じになります。
作成したQTEUIの階層は
のようになります。
↑が作成したQTE用のUIです。
QTEタイマー処理をするTimeLimitスクリプトの作成
QTEのタイマー処理をするTimeLimitスクリプトを作成していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class TimeLimit : MonoBehaviour { // QTEが実行出来る制限時間 private float limitTime; // タイマーアイコン [SerializeField] private Image timerImage; // タイマーアイコンの色を変えるパーセンテージ [SerializeField] private float limitPer = 80f; // 主人公キャラ操作スクリプト private QTECharacterMove qteCharaMove; // 経過時間 private float nowTime; // スライダー private Slider slider; // タイマーが起動しているかどうか private bool startTimer = false; // 押すべきボタンを表示するテキスト [SerializeField] private Text pushButtonText; void Update () { if(startTimer) { // 制限時間をスライダーの値に反映させる if(nowTime < limitTime) { nowTime += Time.deltaTime; slider.value += slider.maxValue / limitTime * Time.deltaTime; // 危ない領域に入ったらタイマーの色を変更する if(slider.value >= (slider.maxValue * (limitPer * 0.01f))) { timerImage.color = new Color(1f, 0f, 0f, 1f); } } else { SetFinised(); qteCharaMove.SetState(QTECharacterMove.MyStateQTE.dead, null); } } } // タイマーのオンオフ void IsStartTimer(bool flag) { startTimer = flag; } // タイマーを止めてUIを非表示 void SetFinised() { IsStartTimer(false); transform.parent.parent.gameObject.SetActive(false); } // キャラクタースクリプト、制限時間を保存しておく public void SetParam(QTECharacterMove chara, SearchCharaAndParam.PushButton pushButton, float limit) { qteCharaMove = chara; this.limitTime = limit; // 押すべきボタンによってテキスト表示を変更する if(pushButton == SearchCharaAndParam.PushButton.Jump) { this.pushButtonText.text = "Push SPACE Key"; } else if(pushButton == SearchCharaAndParam.PushButton.Fire1) { this.pushButtonText.text = "Push Mouse Click"; } // 初期化処理 slider = GetComponent <Slider> (); nowTime = 0f; slider.value = 0f; // 設定値を保存したらタイマースタート IsStartTimer(true); } } |
Updateメソッド内で時間が制限時間を超えていない時で、limitPerの時間を超えたら指定したイメージの色を変更しています。
制限時間を超えたらQTEを終了し、キャラクターの状態を変更します。
SetParam関数では受け取ったQTECharacterMoveスクリプト、押すべきボタン(PushButton)、制限時間(limitTime)をTimeLimitスクリプトで管理する為に
フィールドに代入してます。
受け取ったpushButtonによって表示するテキストを変更し、IsStartTimerメソッドにtrueを渡して呼び出しタイマーをスタートさせています。
表示するテキストはSPACEやMouseといった限定した言葉になっていますが、例えばPS3用のコントローラー限定の操作だとしたら、
×ボタンを押せとか○ボタンを押せとかに変更するといいと思います。
InputManagerで設定したボタンで判断しているのはキーボードやマウス、ゲームパッドどれで操作してもスクリプトの処理を変えない為で、
表示するテキストの言葉だけ変えればいいようにしています。
また、テキストを表示するのではなく押すべきボタンのイメージを変更して表示するだけの方が視覚的にわかりやすくなります。
例えばゲームパッドの×ボタンを押さなければならないのであれば下向きのイメージ(今回の場合方向イメージにした部分)を×ボタンのイメージの表記に変えればわかりやすくなります。
QTEUIにアニメーションをさせる
さきほど作成したQTE用のUI群であるQTEUIですが、これをそのまま表示するだけでは味気ないのでアニメーションを作成しボタンを押す事を促しましょう。
QTEUIの子要素のPanelを選択します。
UnityのメニューからWindow→Animationを選択し、Animationタブを表示します。
AnimationタブのCreateボタンを押します。
新しいダイアログが表示されるので、名前を付けて保存します。
AnimationタブのCreateボタンを押すとアニメーターコントローラーとアニメーションファイルが作成されるので確認しましょう。
QTEUIアニメーターをAnimatorタブで見るとEntryからPushAnimation状態へと遷移するように作られています。
PushAnimation状態にはPushAnimationアニメーションが設定されておりキャラクターのアニメーターコントローラーと同じように作られている事がわかります。
またQTEUIオブジェクトにはAnimatorコンポーネントが設定されControllerにはQTEUIアニメーターコントローラーが設定されます。
Animationタブで録画ボタン(赤い丸印)が押されているのを確認したらフレームの0:15部分を選択します。
Panelを選択した状態でSceneタブを表示し少し下に動かします。
サンプルではY軸で下に10ほど移動させています。
録画ボタンが押されていると最初のフレーム(0:00)と現在のフレーム(0:15)にキーフレームが自動で打たれます。
最初のフレームを選択しCtrl+Cキーを押してコピーし、0:30フレームを選択した後にCtrl+Vキーを押してペーストします。
これで0:15フレームではPanelが少し下に移動し、0:30フレームで元の場所に戻るというアニメーションが作成されました。
QTEゲームオブジェクトをプレハブ化する
QTEゲームオブジェクトが完成したのでこれをプレハブ化して量産出来るようにしましょう。
QTEゲームオブジェクトを選択し、それをAssetsフォルダにドラッグ&ドロップします。
これでQTEがプレハブになります。
QTEイベントを設置したい時にはAssetsフォルダにあるQTEをヒエラルキーにドラッグ&ドロップして設置したい位置に移動させ、SearchCharaAndParamの設定値を変更します。
キャラクター操作スクリプトQTECharacterMoveスクリプトの作成
今回使用するキャラクター操作スクリプトを作成しましょう。
今回のサンプル用の操作スクリプトなので、既にキャラクター操作スクリプトを作成している場合は処理を追加する形で作成してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | using UnityEngine; using System.Collections; public class QTECharacterMove : MonoBehaviour { public enum MyStateQTE { normal, // 通常時 QTE, // QTE中 dead // 主人公が死亡 }; private Animator animator; private CharacterController cCon; private Vector3 velocity; private Vector3 input; private MyStateQTE state; private SearchCharaAndParam searchCharaAndParam; [SerializeField] private float walkSpeed = 2f; void Start () { animator = GetComponent<Animator>(); cCon = GetComponent<CharacterController>(); velocity = Vector3.zero; state = MyStateQTE.normal; } void Update () { // 地面に接地してる時は初期化 if (cCon.isGrounded) { velocity = Vector3.zero; if (state == MyStateQTE.normal) { input = new Vector3 (Input.GetAxis ("Horizontal"), 0f, Input.GetAxis ("Vertical")); // 方向キーが多少押されている if (input.magnitude > 0f) { animator.SetFloat ("Speed", input.magnitude); transform.LookAt (transform.position + input); velocity += input.normalized * walkSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat ("Speed", 0); } } else if (state == MyStateQTE.QTE) { if (Input.GetButtonDown (searchCharaAndParam.GetPushButton ().ToString ())) { searchCharaAndParam.SetFinish (); SetState (MyStateQTE.normal, null); } } else if (state == MyStateQTE.dead) { animator.SetTrigger ("Dead"); } } velocity.y += Physics.gravity.y * Time.deltaTime; cCon.Move(velocity * Time.deltaTime); } // キャラクターの状態変更関数 public void SetState(MyStateQTE state, SearchCharaAndParam searchCharaAndParam) { // 状態を変更 this.state = state; if(state == MyStateQTE.normal) { } else if(state == MyStateQTE.QTE) { this.searchCharaAndParam = searchCharaAndParam; animator.SetFloat("Speed", 0f); } else if(state == MyStateQTE.dead) { } } public MyStateQTE GetState() { return state; } } |
キャラクターが外部スクリプトからMyStateQTE.QTEの状態へと変更された場合は、Updateメソッド内でQTE用の処理をしています。
1 2 3 4 5 6 7 8 9 10 | } else if (state == MyStateQTE.QTE) { if (Input.GetButtonDown (searchCharaAndParam.GetPushButton ().ToString ())) { searchCharaAndParam.SetFinish (); SetState (MyStateQTE.normal, null); } } else if (state == MyStateQTE.dead) { animator.SetTrigger ("Dead"); } |
searchCharaAndParamスクリプトのGetPushButtonメソッドを呼び出して、押すべきボタンを取得してます。
ただGetPushButtonメソッドで戻ってくる値はPushButton型の値なのでToStringメソッドを使用して文字列へと変換してInput.GetButtonDownの引数として指定してます。
主人公キャラが死亡となった場合は自身のアニメーションパラメータのDeadトリガーを作用させアニメーションを変更しています。
主人公のアニメーターコントローラーにはDeadというTrigger型のアニメーションパラメータを作成し、
↑のようにAny StateからDeadへの遷移を作成しDeadがトリガーされたら遷移するように作成します。
QTEを確認する
QTEプレハブが出来たので設置して確認してみましょう。
QTEプレハブをヒエラルキー上にドラッグ&ドロップして設置します。
↑が設置したQTEの検知エリアです。
左側のQTEは↑のように設定しFire1ボタンを押すようにし、制限時間を10秒とします。
Fire1ボタンにはデフォルトでマウスの左クリックが設定されています。
右側のQTEは↑のように設定しJumpボタンを押すようにし、制限時間を3秒とします。
JumpボタンにはデフォルトでSPACEキー等が設定されています。
では実行してみましょう。
タイマーイメージで制限時間が視覚的にわかるようになっているのでいいですね♪
右側のQTEを見るとわかりますが80%時間が経過するとストップウォッチのイメージが赤く変化してます。
最後までイメージが表示されると主人公キャラクターが倒れています。
QTEを作り終えて
今回はQTE成功時に特に何もアクションを入れていません。
地面を飛び越える時にQTEを登場させ、成功したら地面を飛び越える、失敗したら穴に落ちるという処理を作るといいかもしれませんね。
そこら辺まで作ろうとするとまた別の処理を入れてわかり辛くなると思うし、そこはまた別の機能なので割愛させて頂きます。
今回はスクリプト間の連携がちょっとわかり辛い感じになってしまいましたが、出来てしまえば設置してパラメータを変更するだけなので、
QTEをいくつか設置したい時に便利だと思います。
ぜひ参考にして作ってみてください。(^^)/