ヒキニートがゲームを作るブログ

Unityでゲームを作る過程を投稿します

Chronosで時間の制御【UnityでRTSを作る 9】

f:id:MackySoft:20170502031647p:plain

前回の更新から少し間が空きましたが、生きてます。

今回はAIの改良の記事を書こうと思っていたのですが、
現在はAIの改良に悪戦苦闘しています。RTSのAIって複雑ですね。
ではAIの話じゃなければ何かというと、時間の制御のお話です。

モチベーションが下がりすぎないように切り替えていくスタイルです。

Chronosってなに?

Chronosって何かというと「ポーズ・倍速・スロー・巻戻し」などの演出を行うためのアセットです。


Chronos - Time Control for Unity

今回はこのChronosを使って「ポーズ・倍速」を実装します。

準備

Chronosを使うために最初にいくつかの準備をします。

Timekeeperをシーンに追加

まず最初にTimekeeperをシーンに追加します。
"GameObject/Timekeeper"メニュ-から追加できます。
f:id:MackySoft:20170502204755j:plain
Chronosを使うにはTimekeeperがシーンに存在している必要があります。*1

そのメニューでTimekeeperを追加すると以下のような構成になっています。
f:id:MackySoft:20170502212937j:plain

それにアタッチされているGlobalClockコンポーネントがとても重要な物です。

パラメータ 説明
Key タグみたいなもの。GlobalClockはTimekeeper.Clock(Keyの名前)で取得できる。
Parent 親となるGlobalClock。親が設定されていると親のTimeScaleの影響を受ける。
TimeScale 時間の速さ。1が通常。
Paused ポーズ

Timelineを時間を制御したいオブジェクトに追加

Chronosで時間を制御したいオブジェクトにTimelineコンポーネントを追加します。
f:id:MackySoft:20170502212823j:plain

これでChronosの準備ができました。

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;
		}
	}

}

このコンポーネントをボタンに追加し、それぞれのアイコンを設定します。
f:id:MackySoft:20170502211307j:plain

動画

*1:Timekeeperはシングルトンなのでシーンに1つだけです。