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

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

【Unity9】UNETを使ってRotationを同期させる【UNET6】

Unity5.1.1p3 Personal(2015年7月)


前回の続きです〜


前回までで位置情報を、UNETを使った同期方法と前時代の同期方法を使った手法の2つを実装しました〜

今回は位置情報の時と全く同じ手順で、UNETを使った同期方法と前時代の同期方法を角度にも適用していきます〜



UNET Part 6 - Efficient Rotation Syncing - YouTube



【目標】UNETを使ってRotationの同期させる

①スクリプトの修正


位置情報の時と全く同じ形で、角度の同期方法を修正していきます〜以下ソースです〜

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

public class Player_SyncRotation : NetworkBehaviour {

//********** 開始 **********//
	//Quaternion型からfloat型に修正
	//hook: SyncVar変数が変更された時に、SyncVar変数を引数にして指定したメソッドを実行
	[SyncVar (hook = "OnPlayerRotSynced")] private float syncPlayerRotation;
	[SyncVar (hook = "OnCamRotSynced")] private float syncCamRotation;
//********** 終了 **********//
	
	[SerializeField] private Transform playerTransform;
	[SerializeField] private Transform camTransform;
//********** 開始 **********//
	//Serialize Fieldを削除
	private float lerpRate = 17;
	//Quaternion型からfloat型(オイラー角。360度のやつ。)に変更
	private float lastPlayerRot;
	private float lastCamRot;
	//しきい値を1に変更
	private float threshold = 1;
	//角度保存用のList
	private List<float> syncPlayerRotList = new List<float>();
	private List<float> syncCamRotList = new List<float>();
	//HistoricalInterpolationで角度の判定に使用
	private float closeEnough = 0.4f;
	//前時代の角度同期メソッドを使うか
	[SerializeField] private bool useHistoricalInterpolation;
//********** 終了 **********//

	void Update ()
	{
		LerpRotations();
	}
	
	void FixedUpdate () {
		TransmitRotations();
	}
	
	void LerpRotations ()
	{
		if (!isLocalPlayer) {
//********** 開始 **********//
//			playerTransform.rotation = Quaternion.Lerp (playerTransform.rotation,
//				syncPlayerRotation, Time.deltaTime * lerpRate);
//			camTransform.rotation = Quaternion.Lerp (camTransform.rotation,
//				syncCamRotation, Time.deltaTime * lerpRate);
			if (useHistoricalInterpolation) {
				//前時代の角度同期判定
				HistoricalInterpolation();
			} else {
				//UNETを使用した角度同期判定
				OrdinaryLerping();
			}
//********** 終了 **********//
		}
	}
	
//********** 開始 **********//
	
	//前時代の角度同期判定
	void HistoricalInterpolation ()
	{
		//Listが1つでもあったら
		if (syncPlayerRotList.Count > 0) {
			LerpPlayerRotation (syncPlayerRotList[0]);
			
			if (Mathf.Abs (playerTransform.localEulerAngles.y - syncPlayerRotList[0]) < closeEnough) {
				syncPlayerRotList.RemoveAt(0);
			}
			Debug.Log(syncPlayerRotList.Count.ToString() + " syncPlayerRotList Count");
		}
		
		if (syncCamRotList.Count > 0) {
			LerpCamRotation (syncCamRotList[0]);
			
			if (Mathf.Abs (camTransform.localEulerAngles.x - syncCamRotList[0]) < closeEnough) {
				syncCamRotList.RemoveAt(0);
			}
			Debug.Log(syncCamRotList.Count.ToString() + " syncCamRotList Count");
		}

	}
	
	void OrdinaryLerping()
	{
		LerpPlayerRotation(syncPlayerRotation);
		LerpCamRotation(syncCamRotation);
	}
	
	//プレイヤーの現在角度を補間
	void LerpPlayerRotation(float rotAngle){
		//引数のオイラー角を、Vector3の形で保存
		Vector3 playerNewRot = new Vector3(0, rotAngle, 0);
		//Lerp: 現在の角度と受け取った角度の補間値を求める
		//Euler: オイラー角をQuaternion角に変換してくれる
		playerTransform.rotation = Quaternion.Lerp(playerTransform.rotation, Quaternion.Euler(playerNewRot), lerpRate * Time.deltaTime);
	}
	
	//カメラの現在角度を補間
	void LerpCamRotation(float rotAngle){
		Vector3 camNewRot = new Vector3(rotAngle, 0, 0);
		//カメラは子オブジェクトのため、親オブジェクトの向きを継承するlocalRotationを使う
		camTransform.localRotation = Quaternion.Lerp(camTransform.localRotation, Quaternion.Euler(camNewRot), lerpRate * Time.deltaTime);
	}
//********** 終了 **********//
	
	[Command]
//********** 開始 **********//
	//引数をfloat型(オイラー角)に修正
	void CmdProvideRotationsToServer (float playerRot, float camRot)
	{
		syncPlayerRotation = playerRot;
		syncCamRotation = camRot;
	}
//********** 終了 **********//
	
	[Client]
	void TransmitRotations ()
	{
		if (isLocalPlayer) {
//********** 開始 **********//
//			if (Quaternion.Angle (playerTransform.rotation, lastPlayerRot) > threshold ||
//			   Quaternion.Angle (camTransform.rotation, lastCamRot) > threshold) {
			
			//localEulerAngles: Quaternion角をオイラー角(普通の360度)で回転量を表す
			if (CheckIfBeyondThreshold(playerTransform.localEulerAngles.y, lastPlayerRot) ||
				CheckIfBeyondThreshold(camTransform.localEulerAngles.x, lastCamRot)){
				//lastPlayerRotとlastCamRotを現在角度に更新
				lastPlayerRot = playerTransform.localEulerAngles.y;
				lastCamRot = camTransform.localEulerAngles.x;
				//現在角度に更新したlastPlayerRotとlastCamRotでメソッド実行
				CmdProvideRotationsToServer (lastPlayerRot, lastCamRot);
//********** 終了 **********//
			}
		}
	}

//********** 開始 **********//
	//現在角度と前フレームのオイラー角を比較し、threshold(1度)以上開きがあったらtrueを返す
	bool CheckIfBeyondThreshold (float rot1, float rot2)
	{
		//Mathf.Abs: 絶対値取得
		if (Mathf.Abs (rot1 - rot2) > threshold) {
			return true;
		} else {
			return false;
		}
	}
	
	//syncPlayerRotation変数が変更された時に実行(hook)
	//Clientのみ実行
	[Client]
	void OnPlayerRotSynced(float latestPlayerRot)
	{
		//hookは自分で同期する必要がある
		syncPlayerRotation = latestPlayerRot;
		//Listに登録
		syncPlayerRotList.Add(syncPlayerRotation);
	}
	
	//syncCamRotation変数が変更された時実行(hook)
	//Clientのみ実行
	[Client]
	void OnCamRotSynced(float latestCamRot)
	{
		//hookは自分で同期する必要がある
		syncCamRotation = latestCamRot;
		//Listに登録
		syncCamRotList.Add(syncCamRotation);
	}
//********** 終了 **********//
}

Player_SyncRotation.cs


大きく変わった点として、以前まではQuaternion型で角度の調節をしていましたが、それらを全てオイラー角(float型。360度で一周のやつ)に変更しています〜


また注意点として、Playerはy軸の値のみが変更され回転し、Cameraはx軸の値のみが変更され回転することがあげられます〜


それではスクリプトをひとつずつ見ていきましょう〜


まずはFixedUpdateメソッドから見ていきます〜


FixedUpdateメソッドでTransmitRotationsメソッドが実行され、現在角度と前フレームの角度の差がthresholdで指定した以上の開きがあったら、CmdProvideRotationsToServerを実行してSyncVar変数を変更します〜

SyncVar変数が変更されると、hookが作動し全クライアントのOnPlayerRotSyncedメソッドとOnCamRotSyncedメソッドが実行され、最新の角度が更新されます〜また、同時にListにも最新の角度が登録されます〜


続いてUpdateメソッドを見ていきます〜


UpdateメソッドでLerpRotationsメソッドが実行されると、useHistoricalInterpolationがtrueの時にはHistoricalInterpolationメソッドが、falseの時にはOrdinaryLerpingが実行されます〜


HistoricalInterpolationメソッドではListが1つ以上ある時、List先頭の角度を引数にして角度補間用のメソッドが実行されます〜

OrdinaryLerpingメソッドではSyncVar変数を引数に、角度補間用のメソッドが実行されます〜


角度保管用のメソッド(LerpPlayerRotation・LerpCamRotation)では、指定した角度をVector3型にし、Lerpメソッドを使って現在角度を更新しています〜


ゲームを実行すると、プレイヤーの角度が同期できていることが確認できます〜


f:id:hiyotama:20150711225746p:plain
同期確認


今回はここまでです〜

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


【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】