読者です 読者をやめる 読者になる 読者になる

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

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

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

Unity UNET チュートリアル

いつもひよこのたまごをご利用下さいましてありがとうございます!
おかげ様でチュートリアル数も10ゲーム目となりました!
チュートリアル一覧は
こちら からどうぞ!


スポンサーリンク

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】