Chronosで時間の制御【UnityでRTSを作る 9】
前回の更新から少し間が空きましたが、生きてます。
今回はAIの改良の記事を書こうと思っていたのですが、
現在はAIの改良に悪戦苦闘しています。RTSのAIって複雑ですね。
ではAIの話じゃなければ何かというと、時間の制御のお話です。
モチベーションが下がりすぎないように切り替えていくスタイルです。
Chronosってなに?
Chronosって何かというと「ポーズ・倍速・スロー・巻戻し」などの演出を行うためのアセットです。
Chronos - Time Control for Unity
今回はこのChronosを使って「ポーズ・倍速」を実装します。
準備
Chronosを使うために最初にいくつかの準備をします。
Timekeeperをシーンに追加
まず最初にTimekeeperをシーンに追加します。
"GameObject/Timekeeper"メニュ-から追加できます。
Chronosを使うにはTimekeeperがシーンに存在している必要があります。*1
そのメニューでTimekeeperを追加すると以下のような構成になっています。
それにアタッチされているGlobalClockコンポーネントがとても重要な物です。
パラメータ | 説明 |
---|---|
Key | タグみたいなもの。GlobalClockはTimekeeper.Clock(Keyの名前)で取得できる。 |
Parent | 親となるGlobalClock。親が設定されていると親のTimeScaleの影響を受ける。 |
TimeScale | 時間の速さ。1が通常。 |
Paused | ポーズ |
Chronosをゲームに適用
ここから実際にChronosによる時間の制御を行います。
まずGameManagerに便利プロパティを用意しました。
GameManager.cs
using Chronos; public static class GameManager { public static GlobalClock Clock { get { return _Clock ? _Clock : (_Clock = Timekeeper.instance.Clock("Root")); } } private static GlobalClock _Clock = null; public static TimeState TimeState { get { return Timekeeper.GetTimeState(Clock.localTimeScale); } } }
移動や回転
速さにChronosを適用するには
Time.deltaTime
ではなく
Timeline.deltaTIme
を使用します。
CubeKun.cs(重要部分を抜粋)
[RequireComponent(typeof(Timeline))] public abstract class CubeKun : AIPath { public float sleepVelocity = 0.4f; public float armSpeed = 10; public Timeline Timeline { get; private set; } protected override void Awake () { base.Awake(); Timeline = GetComponent<Timeline>(); } protected virtual void Update () { RotateArm(); } public void RotateArm () { if (Target) { for (int i = 0;Arms.Length > i;i++) { var dir = Target.Tr.position - Arms[i].position; dir.y = 0; var newDir = Vector3.RotateTowards( Arms[i].forward, dir, armSpeed * Timeline.deltaTime, 0 ); Arms[i].rotation = Quaternion.LookRotation(newDir); } } else { for (int i = 0;Arms.Length > i;i++) { Arms[i].localRotation = Quaternion.Euler(Vector3.zero); } } } public void Move () { if (!canMove || !target) return; var dir = CalculateVelocity(GetFeetPosition()); RotateTowards(targetDirection); dir.y = 0; if (dir.sqrMagnitude > sleepVelocity * sleepVelocity) { } else { dir = Vector3.zero; } tr.Translate(dir * Timeline.deltaTime,Space.World); } public override Vector3 GetFeetPosition () { return tr.position; } }
Bulletの移動処理にはGameManager.ClockのdeltaTimeを使います。
Bullet.cs(重要部分を抜粋)
public class Bullet : MonoBehaviour { private void Update () { Tr.Translate(Tr.forward * speed * GameManager.Clock.deltaTime,Space.World); } }
コルーチン
WeaponのリロードやShootコルーチンにChronosを適用していきます。
そこで、現在使用されている
new WaitForSeconds(seconds);
ではなく
Timeline.WaitForSeconds(seconds);
を使用します。
Weapon.cs(重要部分を抜粋)
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Weapon : MonoBehaviour { public float interval = 0.5f; public float reload = 2; public CubeKun parent; private IEnumerator ShootCoroutine () { Magazine--; while (!IsEmpty) { Shoot(); yield return parent.Timeline.WaitForSeconds(interval); } } private IEnumerator ReloadCoroutine () { if (IsEmpty || IsReloading) yield break; ShootStop(); IsReloading = true; yield return parent.Timeline.WaitForSeconds(reload); Ammo = I_Ammo; IsReloading = false; ShootStart(); } }
時間制御用のボタン
停止・通常・倍速を切り替える為のボタンを作りました。
TimeControlButton.cs
using UnityEngine; using UnityEngine.UI; using Chronos; [RequireComponent(typeof(Button))] public class TimeControlButton : MonoBehaviour { public Sprite pausedIcon; public Sprite normalIcon; public Sprite acceleratedIcon; private Button button; private Image image; private void Awake () { button = GetComponent<Button>(); image = transform.GetChild(0).GetComponent<Image>(); } private void Start () { button.onClick.AddListener(OnClick); SetIcon(); } private void Update () { if (Input.GetKeyDown(KeyCode.T)) { OnClick(); } } private void OnClick () { switch (GameManager.TimeState) { case TimeState.Paused: GameManager.Clock.localTimeScale = 1; break; case TimeState.Normal: GameManager.Clock.localTimeScale = 2; break; case TimeState.Accelerated: GameManager.Clock.localTimeScale = 0; break; } SetIcon(); } private void SetIcon () { switch (GameManager.TimeState) { case TimeState.Paused: image.sprite = pausedIcon; break; case TimeState.Normal: image.sprite = normalIcon; break; case TimeState.Accelerated: image.sprite = acceleratedIcon; break; } } }
このコンポーネントをボタンに追加し、それぞれのアイコンを設定します。
動画
*1:Timekeeperはシングルトンなのでシーンに1つだけです。