今回は1つのQTE(QuickTimeEvent)が終わったら連続して次のQTEが出現する機能、
また同時に複数のQTEを出現させ順番に実行させる機能を作成していきます。
今回の機能は
の記事で作成した1つのQTE機能を改造して対応していくので、まだ見ていない方は↑の記事を参照してください。
QTEクラスを作成し情報をまとめる
1つのQTEを実行する時はあまり気にしなくてもよかったんですが、連続したり複数を同時に表示する場合は『押すべきボタン』『制限時間』等、
QTEの情報がばらばらにあると不便です。
そこで1つのQTEの情報をクラスという形でまとめてしまい使いやすくします。
新しいスクリプトQTEClassスクリプトを作成します。
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 | using UnityEngine; using System.Collections; using System; [Serializable] public class QTEClass { // InputManagerで設定したボタンを設定する public enum PushButton { Jump, Fire1 }; // どのボタンを押すか [SerializeField] private PushButton pushButton; // 制限時間 [SerializeField] private float limitTime; // QTEを表示する場所 [SerializeField] private Transform place; public PushButton GetPushButton() { return pushButton; } public float GetLimit() { return limitTime; } public Transform GetPlace() { return place; } } |
Systemをusingディレクティブで宣言し、クラス宣言の前に[Serializable]というアトリビュート(属性)を付けておきます。
そうしないとインスペクタで値を設定する事が出来ません。
placeはQTEを表示する場所をゲームオブジェクトを指定出来るようにします。
この情報を使う事でこのゲームオブジェクトが動けばQTEも一緒に動くようになります。
クラスに情報をまとめたのでインスペクタで設定する際も1つ1つのQTE情報の設定が出来ます。
QTEUIを変更する
QTEを連続、複数登場させることにより押すべきボタンのテキストなど幅を取り過ぎて邪魔になるので、
QTEUIをもっとシンプルに表示出来るようにします。
↑のようにQTEUIの子要素にあったTextを削除し、SliderをImageの上に移動します。
SliderがImageの背景に表示されるように位置を調整し、PanelのアンカーをMiddle Centerに変更し、Width、Heightを40ほどにします。
↑が作成したものでImageの周りをSliderのイメージが取り囲むという感じになりました。
さらにQTEUIの子要素のPanelをAssetsフォルダにドラッグ&ドロップし、プレハブにしてQTEイベントが発生したらインスタンス化し使用するようにします。
QTEUIの子要素のPanelは削除し、子要素に何もない状態にします。
QTE機能を改良する
QTE機能を改造していきましょう。
主人公操作スクリプトQTECharacterMoveを改造
主人公操作スクリプトを改造します。
ほぼ同じなので一部抜粋します。
1 2 3 4 5 6 7 8 9 | } else if(state == MyStateQTE.QTE) { if(Input.GetButtonDown(searchCharaAndParam.GetPushButton().ToString())) { searchCharaAndParam.SetFinish(); if(searchCharaAndParam.GetFinish()) { SetState(MyStateQTE.normal, null); } } |
QTE状態の時の処理でQTEが終了しているかどうかを確認し、終了していたら自身の状態を変更するようにします。
主人公の検知とQTEのパラメータ設定スクリプトSearchCharaAndParamを改造
主人公の検知と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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | using UnityEngine; using System.Collections; using System.Globalization; using System.Collections.Generic; public class SearchCharaAndParam : MonoBehaviour { public enum QTEMode { SameTime, Order } // QTE用のUIゲームオブジェクト [SerializeField] private GameObject QTEUI; // タイムリミットスクリプト private List<TimeLimit> timeLimit = new List<TimeLimit>(); // QTEが終了しているかどうか private bool isFinished; // キャラクター操作スクリプト private QTECharacterMove qteChara; // QTEパネルのプレハブ [SerializeField] private GameObject qtePrefab; // QTE private GameObject qteInstance; // 1つのQTEイベントで実行するQTEクラス [SerializeField] private QTEClass[] qteClasses; // 現在何番目のQTEか private int num; // QTEのタイマーを同時に起動するかどうか [SerializeField] private QTEMode qteMode = QTEMode.SameTime; void Start () { isFinished = false; num = 0; } void OnTriggerEnter(Collider col) { if(isFinished) { return; } if(col.tag == "Player") { // キャラクター操作スクリプトの取得 qteChara = col.GetComponent<QTECharacterMove> (); for (int i = 0; i < qteClasses.Length; i++) { // QTEパネルのプレハブをインスタンス化 qteInstance = Instantiate<GameObject>(qtePrefab, QTEUI.transform); timeLimit.Add (qteInstance.GetComponentInChildren <TimeLimit> ()); // 同時に起動する if (qteMode == QTEMode.SameTime) { // TimeLimitスクリプトに主人公操作スクリプト、QTEデータを保持し、全てのQTEを起動させる timeLimit[i].SetParam (qteChara, qteClasses[i], startFlag: true, reset: true); // 順番に起動する } else { // TimeLimitスクリプトに主人公操作スクリプト、QTEデータを保持し、最初のQTEだけ起動させる timeLimit[i].SetParam (qteChara, qteClasses[i], startFlag: i == 0, reset: true); } } // キャラクターの状態変更と自身(SearchCharaAndParam)を渡す qteChara.SetState (QTECharacterMove.MyStateQTE.QTE, this); // UIを表示 QTEUI.SetActive(true); } } // QTE終了設定 public void SetFinish() { // 終えたQTEのインスタンスを削除 Destroy(timeLimit[num].transform.parent.gameObject); num++; // QTEイベントが全て終了した if (num >= qteClasses.Length) { // イベントが成功したら2度とイベントを発生させない isFinished = true; // QTE用のUIを非表示 QTEUI.SetActive (false); Destroy (qteInstance); // まだQTEイベントが残っている } else { // 値のリセットはせず次のQTEをスタートさせる timeLimit[num].SetParam (qteChara, qteClasses[num], startFlag: true, reset: false); } } public bool IsFinished() { return isFinished; } // 押すべきボタンを返す関数 public QTEClass.PushButton GetPushButton() { return qteClasses[num].GetPushButton (); } } |
limitやpushButtonのフィールドを宣言し、そこにQTEの情報を入れていましたがQTEClassにまとめたのでそれらのフィールドを削除し他のメソッドを呼び出す時にQTEClassごと渡すようにします。
QTEClassは配列にしてインスペクタで複数設定出来るようにしたのでnum変数を使って順番にQTEの情報を取りだすようにしています。
SearchAndParamスクリプトを設定したゲームオブジェクトの領域に侵入した時に、一括で実行したいQTEClassをインスペクタに設定するという感じになります。
qteModeで設定したQTE全てのタイマーを同時にスタートさせるか、それとも1つ1つタイマーを起動するかどうかの設定です。
SetFinishでは連続したQTEに対応する為、設定したQTEの数を越えるまではisFinishedにtrueを入れません。
次のQTEクラスを指すようにし再度SetParamメソッドを呼び出しています。
TimeLimitスクリプトはこの後改造しますが、第3引数にはすぐにタイマーをセットするかどうかで、第4引数は今のQTEが成功した時に次のQTEイベントを押せるようにしますが、その時にタイマーをリセットするかどうかです。
qteModeがOrderの時は順番にQTEのタイマーを起動させる為、第3引数では最初のQTEのstartTimerだけをtrueにします。
最初のQTEの時はiが0の時なので渡す値にはi == 0で判別しそのまま値を渡しています。iが0以外の時はfalseが返ってきてそのままSetParamメソッドの引数として渡します。
TimeLimitスクリプトを改造
次に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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class TimeLimit : MonoBehaviour { // QTEが実行出来る制限時間 private float limit; // タイマーアイコン [SerializeField] private Image timerImage; // タイマーアイコンの色を変えるパーセンテージ [SerializeField] private float limitPer = 80f; // 主人公キャラ操作スクリプト private QTECharacterMove qteCharaMove; // 経過時間 private float nowTime; // スライダー private Slider slider; // タイマーが起動しているかどうか [SerializeField] private bool startTimer = false; // 押すべきボタンを表示するテキスト [SerializeField] private Text text; // 押すべきボタンのImage [SerializeField] private Sprite[] image; // 動かすQTEのパネル private Transform panel; // QTEを一緒に動かすゲームオブジェクト private Transform place; // アクティブなQTEの拡大率 [SerializeField] private float amplification; void Update() { // 指定したゲームオブジェクトの位置をスクリーン位置にし移動させる panel.position = Camera.main.WorldToScreenPoint(place.position); // タイマーがスタートしていない場合はこれ以降の処理を行わない if (!startTimer) { return; } // 制限時間をスライダーの値に反映させる if (nowTime < limit) { nowTime += Time.deltaTime; slider.value += slider.maxValue / limit * Time.deltaTime; // 危ない領域に入ったらタイマーの色を変更する if (slider.value >= (slider.maxValue * (limitPer * 0.01f))) { timerImage.color = new Color(1f, 0f, 0f, 1f); } // リミットを超えた } else { SetFinish(); qteCharaMove.SetState(QTECharacterMove.MyStateQTE.dead, null); // 全部のQTEインスタンスを削除 for (int i = 0; i < transform.parent.parent.childCount; i++) { Destroy(transform.parent.parent.GetChild(i).gameObject); } } } // タイマーのオンオフ void SetStartTimer(bool flag) { startTimer = flag; } // タイマーを止める void SetFinish() { SetStartTimer(false); } // キャラクタースクリプト、制限時間を保存しておく public void SetParam(QTECharacterMove chara, QTEClass qte, bool startFlag, bool reset) { qteCharaMove = chara; // 時間制限設定 this.limit = qte.GetLimit(); // QTEパネルの位置を設定 panel = transform.parent.GetComponent<RectTransform>(); this.place = qte.GetPlace(); // 押すべきボタンによってテキスト表示を変更する if (qte.GetPushButton() == QTEClass.PushButton.Jump) { this.text.text = "Push SPACE Key"; transform.parent.Find("Image").GetComponent<Image>().sprite = image[(int)QTEClass.PushButton.Jump]; } else if (qte.GetPushButton() == QTEClass.PushButton.Fire1) { this.text.text = "Push Mouse Click"; // イメージの設定 transform.parent.Find("Image").GetComponent<Image>().sprite = image[(int)QTEClass.PushButton.Fire1]; } // 連続QTEに対応する為に値をリセット // sliderがセットされていなければ最初 if (slider == null) { slider = GetComponent<Slider>(); } // 値をリセットするかどうか if (reset) { nowTime = 0f; slider.value = 0f; } // タイマーイメージの色を黄色にしておく timerImage.color = new Color(1f, 1f, 0f, 1f); // 設定値を保存したらタイマースタート if (startFlag) { SetStartTimer(true); panel.localScale *= amplification; } } } |
QTEの押すべきボタンをSprite配列として持っておき押すべきボタンに合わせて画像を変えるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 | // 押すべきボタンのImage [SerializeField] private Sprite[] image; // 動かすQTEのパネル private Transform panel; // QTEを一緒に動かすゲームオブジェクト private Transform place; // アクティブなQTEの拡大率 [SerializeField] private float amplification; |
↑のあたりが追加したフィールドですね、QTEを指定されたゲームオブジェクトの場所に表示し一緒に動かすので動かすパネルのTransform情報を使います。
Updateメソッドでは移動するゲームオブジェクトの位置に合わせてQTEのPanelも移動させる為、
1 2 3 4 5 6 7 8 9 | void Update() { // 指定したゲームオブジェクトの位置をスクリーン位置にし移動させる panel.position = Camera.main.WorldToScreenPoint(place.position); // タイマーがスタートしていない場合はこれ以降の処理を行わない if (!startTimer) { return; } |
の部分でQTEのパネルを指定したゲームオブジェクトの位置に表示します。
QTEのパネルはUIなので3D空間にあるplaceの場所をUIの位置に調整してから位置を設定しています。
それをしているのが
1 2 3 | Camera.main.WorldToScreenPoint(place.position) |
ですね。
メインカメラから指定する3D空間の位置(place.position)のスクリーン位置(カメラの描画範囲の位置)を取得出来ます。
SetParamメソッドはQTEの初期処理をしています。
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 | // キャラクタースクリプト、制限時間を保存しておく public void SetParam(QTECharacterMove chara, QTEClass qte, bool startFlag, bool reset) { qteCharaMove = chara; // 時間制限設定 this.limit = qte.GetLimit (); // QTEパネルの位置を設定 panel = transform.parent.GetComponent<RectTransform>(); this.place = qte.GetPlace(); // 押すべきボタンによってテキスト表示を変更する if(qte.GetPushButton () == QTEClass.PushButton.Jump) { this.text.text = "Push SPACE Key"; transform.parent.Find("Image").GetComponent<Image>().sprite = image[(int)QTEClass.PushButton.Jump]; } else if(qte.GetPushButton () == QTEClass.PushButton.Fire1) { this.text.text = "Push Mouse Click"; // イメージの設定 transform.parent.Find("Image").GetComponent<Image>().sprite = image[(int)QTEClass.PushButton.Fire1]; } // 連続QTEに対応する為に値をリセット // sliderがセットされていなければ最初 if(slider == null) { slider = GetComponent<Slider>(); } // 値をリセットするかどうか if(reset) { nowTime = 0f; slider.value = 0f; } // タイマーイメージの色を黄色にしておく timerImage.color = new Color(1f, 1f, 0f, 1f); // 設定値を保存したらタイマースタート if (startFlag) { SetStartTimer (true); panel.localScale *= amplification; } } |
引数で受け取ったresetがtrueの時はスライダーの値等をリセットします。
startFlagがtrueの時はタイマーをスタートさせ、Panelの大きさを変えて今押すべきQTEイベントのボタンを解りやすくします。
これでスクリプトの改造が出来ました。
連続したQTEが実行出来るか確認
機能の改造が出来たのでインスペクタで設定し実行してみましょう。
QTESearchAreaに設定しているSearchCharaAndParamのインスペクタで1つ1つのQTEの設定をします。
QTEを表示する位置のゲームオブジェクトCubeを作成し、移動させるようにしておきます。
Sliderに設定しているTimeLimitスクリプト(上の画像ではわたくしの都合上TimeLimitContinuityとなっています)は↑のような感じで設定します。
↑のように最初のQTEが主人公キャラの位置に表示され、それを成功するとCubeの位置に次のQTEが表示されました。
QTEを複数表示する
QTEを連続で表示する事は出来ましたが、あらかじめ複数のQTEを表示しておき次に押すべきボタンがわかるようにしたい事もあります。
そこでさらにさきほど作った機能を改造し複数のQTEを表示したり、同時にタイマーが起動するか順番にタイマーを起動するかを選択出来たりする機能を取りつけます。
複数のQTEを表示する機能を確認
SearchAndParamスクリプトのqteModeにチェックを入れると設定したQTEが同時にスタートするようになります。
QTESearchAreaに設定したSearchCharaAndParamスクリプトのインスペクタに設定をします。
まずはModeにチェックを入れて確認します。
↑のように順番にタイマーが起動され、今現在のQTEだけ少し大きくなっています。
次はModeのチェックを外して確認します。
↑のように同時にタイマーが起動され、全てのQTEが少し大きくなっていますが、押さなければいけないのは主人公の所に表示されているQTEです。
この今押すべきQTEの表示をもっとわかりやすく改造すると良さそうですね。
良さそうというだけで作りませんが・・・・・(;一_一)
複数のQTE対応機能を作り終えて
今回複数のQTEに対応しましたが、元々1つのQTE機能を使い勝手をそのままに改造したので中身はゴチャゴチャした感じになりましたね。
今回の場合は主人公キャラクターがずっと止まった状態なので、QTE時には自動で動くようにして次々とQTEが発生するというものもがんばれば作れると思います。
QTEの発生場所もゲームオブジェクトの位置に発生させる事ができるので、今回のように見えるゲームオブジェクトだけでなく見えないゲームオブジェクトを設置しそこに
QTEを表示させるという事も出来ます。
今回のQTE用の画像が押すべきボタンと一致していないのはご愛敬ということで・・・・(-_-)