Unity(C#)初心者・入門者向けチュートリアル ひよこのたまご

AndroidやiOS向けアプリを簡単に作れるゲーム開発環境Unity(ユニティ)の使い方を、チュートリアル方式で一緒に学びましょう!

【Unity9】UNETのSyncVarのhookの使用例と、前時代の同期方法【UNET5】

Unity5.1.1p3 Personal(2015年7月)


前回の続きです〜


前回までで遅延時間を画面に表示させ、遅延時間を短くしようと試みてきました〜
今回は更に遅延時間を短縮して効率化していくようです〜



UNET Part 5 - Almost Time Travel! - YouTube

【目標】最新の同期方法と前時代の同期方法を実装し、位置情報を同期する

①前時代の同期方法を使ってみる(HistoricalLerping)

UNETの利点を認識するため、UNETが誕生する前の前時代的な同期方法を試してみるみたいです〜


前時代の同期は、キャラクターが移動した時に位置情報をListに保存し、それを元に位置情報を修正していく手法をとっています〜


それでは、前回までに作成したPlayer_SyncPositionスクリプトに加筆していきます〜

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
//********** 開始 **********//
using System.Collections.Generic; //Listを使う時のライブラリ
//********** 終了 **********//

[NetworkSettings(channel=0, sendInterval=0.033f)]
public class Player_SyncPosition : NetworkBehaviour {
	
//********** 開始 **********//
	//hook: SyncVar変数が変更された時、指定メソッドを実行するようサーバーから全クライアントへ命令を出す
	[SyncVar (hook = "SyncPositionValues")]
//********** 終了 **********//
	private Vector3 syncPos;
	
	[SerializeField] Transform myTransform;
	
//********** 開始 **********//
	float lerpRate;
	float normalLerpRate = 15;
	float fasterLerpRate = 25;
//********** 終了 **********//

	private Vector3 lastPos;
	private float threshold = 0.5f;
	private NetworkClient nClient;
	private int latency;
	private Text latencyText;
	
//********** 開始 **********//
	//Position同期用のList
	private List<Vector3> syncPosList = new List<Vector3>();
	
	//HistoricalLerpingメソッドを使う時はtrueにする
	//SerializeFieldなのでInspectorビューから変更可能
	[SerializeField] private bool useHistoricalLerping = false;
	
	//2点間の距離を判定する時に使う
	private float closeEnough = 0.1f;
//********** 終了 **********//
	
	void Start ()
	{
		nClient = GameObject.Find("NetworkManager").GetComponent<NetworkManager>().client;
		latencyText = GameObject.Find("Latency Text").GetComponent<Text>();
//********** 開始 **********//
		lerpRate = normalLerpRate;
//********** 終了 **********//
	}

	void Update ()
	{
		LerpPosition();
		ShowLatency();
	}

	void FixedUpdate ()
	{
		TransmitPosition(); 
	}
	
	void LerpPosition ()
	{
		if (!isLocalPlayer) {
//********** 開始 **********//
			if (useHistoricalLerping) {
				//前時代の補間メソッド
				HistoricalLerping();
			}
			else
			{
				//通常の補間メソッド
				OrdinaryLerping();
			}
//********** 終了 **********//
//			Debug.Log(Time.deltaTime.ToString());
		}
	}

	[Command]
	void CmdProvidePositionToServer (Vector3 pos)
	{
		syncPos = pos;
//		Debug.Log("Command");
	}
	
	[ClientCallback]
	void TransmitPosition ()
	{
		if (isLocalPlayer && Vector3.Distance(myTransform.position, lastPos) > threshold) {
			CmdProvidePositionToServer(myTransform.position);
			lastPos = myTransform.position;
		}
	}

//********** 開始 **********//
	//クライアントのみ有効
	[Client]
	//hookで指定されたメソッド 全クライアントが実行
	void SyncPositionValues (Vector3 latestPos)
	{
		syncPos = latestPos;
		//ListにPosition追加
		syncPosList.Add(syncPos);
	}
//********** 終了 **********//
	
	void ShowLatency ()
	{
		if (isLocalPlayer) {
			latency = nClient.GetRTT();
			latencyText.text = latency.ToString();
		}
	}
	
//********** 開始 **********//
	//通常使われる補間メソッド
	void OrdinaryLerping ()
	{
		myTransform.position = Vector3.Lerp(myTransform.position, syncPos, Time.deltaTime * lerpRate);
	}
	
	//過去使用されていた補間メソッド
	void HistoricalLerping ()
	{
		//Listが1以上あったら
		if (syncPosList.Count > 0) {
			//現在位置とListの0番目の位置との中間値を補間
			myTransform.position = Vector3.Lerp (myTransform.position, syncPosList [0], Time.deltaTime * lerpRate);
			
			//2点間がcloseEnoughより小さくなった時
			if (Vector3.Distance (myTransform.position, syncPosList [0]) < closeEnough) {
				//Listの0番目を削除
				syncPosList.RemoveAt (0);
			}
			//syncPosList.Countが0に戻った時、同期が追いついたことを意味する
			Debug.Log(syncPosList.Count.ToString());
		}
	}
//********** 終了 **********//
}

Player_SyncPosition.cs


PlayerプレハブのuseHistoricalLerpingというBool型の変数をInspectorビューから切り替えることによって、かつて使われていた補間メソッドを試すことができます〜


f:id:hiyotama:20150707203511p:plain
useHistoricalLerping


UpdateからLerpPositionメソッドへ飛び、useHistoricalLerpingがtrueならHistoricalLerpingメソッドへ、falseならOrdinaryLerpingメソッドへ飛びます〜
OrdinaryLerpingメソッドの内容は、前回までと全く同じなので割愛します〜


HistoricalLerpingメソッドでは、syncPosList配列の配列数が1つ以上である場合、現在位置と配列0番目の位置とを補間し、その2点間の距離がcloseEnough(0.1)よりも小さくなった場合、配列の0番目を消去します〜
配列の0番目が消去されると、配列の1番目が繰り上がって0番目となり、次の補間が実行されます〜
全ての配列が補間されるまでこれを繰り返していきます〜


それではsyncPosList配列はどうやって追加されていくのかというと、SyncPositionValuesメソッド内で追加されています〜


それではSyncPositionValuesメソッドはどうやって呼び出されるのでしょうか〜
CmdProvidePositionToServerメソッド内でSyncVar変数であるsyncPosが変更された時、今回新たに追加されたhookが機能して、SyncPositionValuesメソッドが呼び出されます〜


hookは、SyncVarが機能してサーバーから全クライアントへ変数を同期する命令が出される代わりに、指定したメソッドを実行するように命令します〜
指定したメソッドの引数には、SyncVar変数の値が送られます〜


SyncVarにhookを付けなければ全クライアントのSyncPos変数は自動的に同期されますが、hookを使った場合は自分で同期しなければなりません〜(今回でいう「syncPos = latestPos;」の部分)
変数を全クライアントと同期するだけならhookは必要ないのですが、今回はListにSyncPosを加えるという作業が必要であるため、hookを使っています〜


すみません解説が長くなってしまいました〜
それでは前時代の同期方法で、実際に動かしてみましょう〜


f:id:hiyotama:20150708190315p:plain
コンソールに表示された配列数


動けば動くほど配列の数が増えていって、遅延がどんどんひどくなっていきます〜

①位置情報の配列増加への対応

位置情報の配列が増えてくると、遅延がきつくなってきます〜
遅延がきつくなってきたらlerpRate変数の値を変更することで対応していきます〜


HistoricalLerpingメソッドに加筆していきます〜

	void HistoricalLerping ()
	{
		if (syncPosList.Count > 0) {
			myTransform.position = Vector3.Lerp (myTransform.position, syncPosList [0], Time.deltaTime * lerpRate);
			
			if (Vector3.Distance (myTransform.position, syncPosList [0]) < closeEnough) {
				syncPosList.RemoveAt (0);
			}

//********** 開始 **********//
			if (syncPosList.Count > 10) {
				lerpRate = fasterLerpRate;
			} else {
				lerpRate = normalLerpRate;
			}
//********** 終了 **********//
			
			Debug.Log(syncPosList.Count.ToString());
		}
	}

Player_SyncPosition.cs


配列が10以上になった時、lerpRateをfasterLerpRateにして、位置情報を補間するスピードを早めています〜


それでは実行してみましょう〜すると・・・確かに位置情報の同期はかなり改善されたのですが、normalLerpRateの時よりも動きがカクカクになってしまっています〜(動画でもテレポーテーションハプニング〜と言ってます〜笑)

②前時代の同期方法をもう少し改善する

このあと動画では何点か改善して、前時代の同期方法を少しでも遅延のないように改善していきます〜


例えばnormalLerpRateやfasterLerpRate、closeEnough等の値を最適化したり、Simulated Average Latencyの値を増やして条件を悪くしたりして、テストを繰り返します〜


英語がほとんど分からないので自信はないですが、前時代の手法でも最適化すれば、悪条件でも割とスムーズに同期させることができる・・・
でも、UNETならそんな調整の手間をかけなくても、かなりの悪条件下でも、自動的にいい感じに同期してくれますよ〜ということを伝えたいんじゃないかと思います〜
気になる方は動画をチェックしてみて下さい〜


というわけで、今回はここまでです〜

ありがとうございました〜


【Unity9】UNETでマルチプレイヤーなオンラインゲーム開発【UNET1】
【Unity9】UNETでプレイヤーの動きを補間し、なめらかな動きを実現する【UNET1-2】
【Unity9】UNETでネットワーク越しに傾き(Rotation)を同期させる【UNET2】
【Unity9】UNETでオンライン開始時のプレイヤー生成位置を変更する【UNET2-2】
【Unity9】UNETのネットワークトラフィックを軽減し、効率化する【UNET3】
【Unity9】UNETのlatency(遅延時間)を改善する【UNET4】
【Unity9】UNETのlatency(遅延時間)を表示して、ちょっとだけ改善する【UNET4-2】
【Unity9】UNETのSyncVarのhookの使用例と、前時代の同期方法【UNET5】
【Unity9】UNETを使ってRotationを同期させる【UNET6】
【Unity9】UNETで各プレイヤーにPlayerIDを設定する【UNET7】
【Unity9】UNETで敵プレイヤーにダメージを与える!【UNET8】
【Unity9】UNETでHPを画面に表示し、Playerへダメージを与える【UNET9】
【Unity9】UNETでHPが0以下になった時、Playerを破壊する!【UNET10】
【Unity9】UNETで死んだPlayerを生き返らせる!【UNET11】
【Unity9】UNETでゾンビAIを出現させる!【UNET12】
【Unity9】UNETでゾンビ生成時にユニークなIDを付ける!【UNET13】
【Unity9】UNETでゾンビを撃つ!【UNET14】
【Unity9】UNETでゾンビに攻撃させる!【UNET15】
【Unity9】UNETでゾンビの動きをスムーズにシンクロさせる!【UNET16】
【Unity9】UNETでゾンビ発生地点を増やす【UNET17】
【Unity9】UNETで発生したバグを取り除く【UNET18】
【Unity9】Unity MultiPlayerを使ってネットワーク越しにマッチメイキング!【UNET19】
【Unity9】GUIを改善して、Network Managerを見やすくする!【UNET20】
【Unity9】表示したGUI(Network Manager)を機能させる!【UNET20-2】
【Unity9】UNETでAnimationを同期させる!【UNET21】