NDI™で送信するRTAイベントタイマーをUnityで作る #03 ストップウォッチ編
メイン機能であるストップウォッチを作成する
下準備
名前はカウントダウンタイマーとの対比から、CountUp Timer にする
CountUp Timer Object を作成。CountUp Timer Background Image も作成。♯02と同じようにパーツを配置していく
動画の準備
タイマー(ストップウォッチ)開始時、終了時に動画を流したいので、動画を準備する。WebMファイル (コーデックがVP8のもの)を用意し、Assets > Videos に入れる。ファイル名はそれぞれ GoodLuck と GoodGame にしておく
NDI を設定したときのように、Assets > Textures に Render Texture をそれぞれに用意する。名前は GoodLuck Render Texture、GoodGame Render Texture とする。サイズはそれぞれ 960 x 270 にしておく
動画の設定
ヒエラルキーにビデオプレイヤーを2つ追加する。場所は一番上の階層にしておく。名前は GoodLuck Video Player と GoodGame Video Player にする。
両方とも、ゲーム開始時に再生のチェックを外しておく。レンダーテクスチャはそれぞれ GoodLuck Render Texture、GoodGame Render Texture と対応したものに設定しておく。オーディオ出力モードを、念のためなしに変更
CountUp Object に [UI]→[Raw 画像] を2つ追加し、それぞれ GoodLuck VideoImage と GoodGame VideoImage にしておく。位置は(0,0)サイズは960 x 270 に設定する。テクスチャをそれぞれ GoodLuck Render Texture、GoodGame Render Texture にする
CountUp Main
ステータスを管理してストップウォッチを動かす
ready : タイマー準備中。開始入力があると good_luck に遷移
good_luck : GL アニメーションを再生。そのまま running に遷移
running : 一時停止や終了入力があるまでタイマーを回し続ける
paused : 一時停止中。running や good_game に遷移可能。文字の色を灰色に
good_game : GG アニメーションを再生。そのまま finished に遷移
finished : タイマーストップ。リセットされない限りはそのまま。文字の色を黄色に
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video; // GL、GG 時に動画を流す用
public class CountUpMain : MonoBehaviour
{
[SerializeField] Image hour1_image;
[SerializeField] Image hour2_image;
[SerializeField] Image colon1_image;
[SerializeField] Image minute1_image;
[SerializeField] Image minute2_image;
[SerializeField] Image colon2_image;
[SerializeField] Image second1_image;
[SerializeField] Image second2_image;
[SerializeField] Image period_image;
[SerializeField] Image decimal_image;
[SerializeField] Sprite[] number = new Sprite[10];
[SerializeField] VideoPlayer goodluck_player;
[SerializeField] VideoPlayer goodgame_player;
private float start_time;
public float now_time { get; private set; }
public float offset_time { get; private set; }
public enum TimerStatus
{
ready,
good_luck,
running,
paused,
good_game,
finished,
}
public TimerStatus timer_status { get; private set; }
public enum InputStatus
{
ready,
start,
stop,
pause,
resume,
reset,
up_1sec,
up_10sec,
up_1min,
up_10min,
up_1hour,
down_1sec,
down_10sec,
down_1min,
down_10min,
down_1hour,
updown_reset,
}
public InputStatus input_status;
// Start is called before the first frame update
void Start()
{
start_time = 0.0f;
now_time = 0.0f;
offset_time = 0.0f;
input_status = InputStatus.ready;
InitDisplay();
timer_status = TimerStatus.ready;
}
// Update is called once per frame
void Update()
{
// タイマーの状態で場合分け
switch (timer_status)
{
case TimerStatus.ready:
// 数字を白にしておく
ChangeColor("#FFFFFFFF");
// キー入力を待つ
if (input_status == InputStatus.start)
{
start_time = Time.time;
timer_status = TimerStatus.good_luck;
break;
}
// 停止中にF17が押されたらリセット
if (input_status == InputStatus.reset)
{
timer_status = TimerStatus.ready;
InitDisplay();
break;
}
break;
case TimerStatus.good_luck:
goodluck_player.Play();
timer_status = TimerStatus.running;
break;
case TimerStatus.running:
// 数字を白にしておく
ChangeColor("#FFFFFFFF");
now_time = Time.time - start_time;
SetTime(now_time + offset_time);
// F14が押されたら終了
if (input_status == InputStatus.stop)
{
if (now_time < 10.0f)
{
break;
}
timer_status = TimerStatus.good_game;
break;
}
// 一時停止機能をつける
if (input_status == InputStatus.pause)
{
timer_status = TimerStatus.paused;
break;
}
break;
case TimerStatus.paused:
// 数字をグレーにしておく
ChangeColor("#808080FF");
// F14が押されたら終了
if (input_status == InputStatus.stop)
{
timer_status = TimerStatus.good_game;
break;
}
// 停止中にF17が押されたらリセット
if (input_status == InputStatus.reset)
{
timer_status = TimerStatus.ready;
InitDisplay();
break;
}
// もう一度一時停止が押されたら、現時点から再開する
if (input_status == InputStatus.pause)
{
start_time = Time.time - now_time;
timer_status = TimerStatus.running;
break;
}
// 間違えて止めた時用に再開ボタンを用意する
if (input_status == InputStatus.resume)
{
timer_status = TimerStatus.running;
break;
}
break;
case TimerStatus.good_game:
goodgame_player.Play();
timer_status = TimerStatus.finished;
break;
case TimerStatus.finished:
// 数字を黄色にしておく
ChangeColor("#EEB033FF");
// 停止中にF17が押されたらリセット
if (input_status == InputStatus.reset)
{
timer_status = TimerStatus.ready;
InitDisplay();
break;
}
// 間違えて止めた時用に再開ボタンを用意する
if (input_status == InputStatus.resume)
{
timer_status = TimerStatus.running;
break;
}
break;
}
// 時間修正用オフセットを変更する。タイマーも一回描画する
switch (input_status)
{
case InputStatus.up_1sec:
ChangeOffset(1f);
break;
case InputStatus.down_1sec:
ChangeOffset(-1f);
break;
case InputStatus.up_10sec:
ChangeOffset(10f);
break;
case InputStatus.down_10sec:
ChangeOffset(-10f);
break;
case InputStatus.up_1min:
ChangeOffset(60f);
break;
case InputStatus.down_1min:
ChangeOffset(-60f);
break;
case InputStatus.up_10min:
ChangeOffset(600f);
break;
case InputStatus.down_10min:
ChangeOffset(-600f);
break;
case InputStatus.up_1hour:
ChangeOffset(3600);
break;
case InputStatus.down_1hour:
ChangeOffset(-3600);
break;
case InputStatus.updown_reset:
ChangeOffset(-offset_time);
break;
}
input_status = InputStatus.ready;
}
bool isKeyDown(byte[] key, int code)
{
return ((key[code] & 0x80) != 0);
}
// 文字のカラーをすべて変更する
private void ChangeColor(string color_hex)
{
if (color_hex == null)
{
return;
}
if (ColorUtility.TryParseHtmlString(color_hex, out var color))
{
// 変換成功
hour1_image.color = color;
hour2_image.color = color;
minute1_image.color = color;
minute2_image.color = color;
second1_image.color = color;
second2_image.color = color;
decimal_image.color = color;
}
}
private void ChangeOffset(float offset)
{
offset_time = offset_time + offset;
SetTime(now_time + offset_time);
}
void InitDisplay()
{
hour1_image.enabled = false;
hour2_image.enabled = false;
colon1_image.enabled = false;
minute1_image.enabled = false;
minute2_image.enabled = false;
colon2_image.enabled = false;
second1_image.enabled = false;
second2_image.enabled = true;
period_image.enabled = true;
decimal_image.enabled = true;
start_time = 0.0f;
offset_time = 0.0f;
now_time = 0.0f;
SetTime(0.0f);
}
void SetTime(float ftime)
{
if (ftime < 0.0f)
{
ftime = 0.0f;
}
ftime = ftime * 10;
int itime = Mathf.FloorToInt(ftime);
int hour = itime / 36000;
int hour1 = hour / 10;
int hour2 = hour - hour1 * 10;
hour1_image.sprite = number[hour1];
hour2_image.sprite = number[hour2];
itime = itime - hour * 36000;
int minute = itime / 600;
int minute1 = minute / 10;
int minute2 = minute - minute1 * 10;
minute1_image.sprite = number[minute1];
minute2_image.sprite = number[minute2];
itime = itime - minute * 600;
int second = itime / 10;
int second1 = second / 10;
int second2 = second - second1 * 10;
second1_image.sprite = number[second1];
second2_image.sprite = number[second2];
int decimal1 = itime - second * 10;
decimal_image.sprite = number[decimal1];
// 表示非表示を設定する
if (hour1 > 0)
{
hour1_image.enabled = true;
hour2_image.enabled = true;
colon1_image.enabled = true;
minute1_image.enabled = true;
minute2_image.enabled = true;
colon2_image.enabled = true;
second1_image.enabled = true;
return;
}
else
{
hour1_image.enabled = false;
}
if (hour2 > 0)
{
hour2_image.enabled = true;
colon1_image.enabled = true;
minute1_image.enabled = true;
minute2_image.enabled = true;
colon2_image.enabled = true;
second1_image.enabled = true;
return;
}
else
{
hour2_image.enabled = false;
colon1_image.enabled = false;
}
if (minute1 > 0)
{
minute1_image.enabled = true;
minute2_image.enabled = true;
colon2_image.enabled = true;
second1_image.enabled = true;
return;
}
else
{
minute1_image.enabled = false;
}
if (minute2 > 0)
{
minute2_image.enabled = true;
colon2_image.enabled = true;
second1_image.enabled = true;
return;
}
else
{
minute2_image.enabled = false;
colon2_image.enabled = false;
}
if (second1 > 0)
{
second1_image.enabled = true;
}
else
{
second1_image.enabled = false;
}
}
}
コントローラーの準備
コントローラーの識別に、Unity の InputSystem を使用する
パッケージマネージャーを開き、パッケージ:Unity レジストリ から Input System を選択。インストールする
インストールするとエディターが再起動する
Controller Input のオブジェクトをヒエラルキーの一番上の階層に作成する
ControllerInput のスクリプトを作成し、以下のように記述する。北ボタン(Xbox の Y、PS の △)が押されたら開始、もう一度押されたら終了するように設定しておく
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class ControllerInput : MonoBehaviour
{
[SerializeField] CountUpMain countup;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
// ゲームパッドが接続されていないとnullになる。
if (Gamepad.current == null) return;
if (countup != null)
{
if (Gamepad.current.buttonNorth.wasPressedThisFrame)
{
if (countup.timer_status == CountUpMain.TimerStatus.ready)
{
countup.input_status = CountUpMain.InputStatus.start;
}
if (countup.timer_status == CountUpMain.TimerStatus.running)
{
countup.input_status = CountUpMain.InputStatus.stop;
}
}
}
#if UNITY_EDITOR
if (Gamepad.current.buttonNorth.wasPressedThisFrame)
{
Debug.Log("Button Northが押された!");
}
if (Gamepad.current.buttonSouth.wasReleasedThisFrame)
{
Debug.Log("Button Southが離された!");
}
#endif
}
private void OnGUI()
{
// ゲームパッドが接続されていないとnullになる。
if (Gamepad.current == null) return;
#if UNITY_EDITOR
GUILayout.Label($"leftStick: {Gamepad.current.leftStick.ReadValue()}");
GUILayout.Label($"buttonNorth: {Gamepad.current.buttonNorth.isPressed}");
GUILayout.Label($"buttonSouth: {Gamepad.current.buttonSouth.isPressed}");
GUILayout.Label($"buttonEast: {Gamepad.current.buttonEast.isPressed}");
GUILayout.Label($"buttonWest: {Gamepad.current.buttonWest.isPressed}");
GUILayout.Label($"leftShoulder: {Gamepad.current.leftShoulder.ReadValue()}");
GUILayout.Label($"leftTrigger: {Gamepad.current.leftTrigger.ReadValue()}");
GUILayout.Label($"rightShoulder: {Gamepad.current.rightShoulder.ReadValue()}");
GUILayout.Label($"rightTrigger: {Gamepad.current.rightTrigger.ReadValue()}");
#endif
}
}
Input System ではボタンが東西南北で表されている。複数コントローラーを挿していても扱いが簡単。途中で抜き差しも可能。めっちゃ便利
コントローラーでタイマーが動くことを確認する。動画も透過できた
おつかれさまでした
次はキーボード入力部分を作っていきます
この記事が気に入ったらサポートをしてみませんか?