forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			580 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			580 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
namespace Oculus.Platform.Samples.VrHoops
 | 
						|
{
 | 
						|
	using UnityEngine;
 | 
						|
	using System.Collections.Generic;
 | 
						|
	using Oculus.Platform;
 | 
						|
	using Oculus.Platform.Models;
 | 
						|
	using System;
 | 
						|
	using UnityEngine.Assertions;
 | 
						|
 | 
						|
	// This helper class coordinates establishing Peer-to-Peer connections between the
 | 
						|
	// players in the match.  It tries to sychronize time between the devices and
 | 
						|
	// handles position update messages for the backboard and moving balls.
 | 
						|
	public class P2PManager
 | 
						|
	{
 | 
						|
		#region Member variables
 | 
						|
 | 
						|
		// helper class to hold data we need for remote players
 | 
						|
		private class RemotePlayerData
 | 
						|
		{
 | 
						|
			// the last received Net connection state
 | 
						|
			public PeerConnectionState state;
 | 
						|
			// the Unity Monobehaviour
 | 
						|
			public RemotePlayer player;
 | 
						|
			// offset from my local time to the time of the remote host
 | 
						|
			public float remoteTimeOffset;
 | 
						|
			// the last ball update remote time, used to disgard out of order packets
 | 
						|
			public float lastReceivedBallsTime;
 | 
						|
			// remote Instance ID -> local MonoBahaviours for balls we're receiving updates on
 | 
						|
			public readonly Dictionary<int, P2PNetworkBall> activeBalls = new Dictionary<int, P2PNetworkBall>();
 | 
						|
		}
 | 
						|
 | 
						|
		// authorized users to connect to and associated data
 | 
						|
		private readonly Dictionary<ulong, RemotePlayerData> m_remotePlayers = new Dictionary<ulong, RemotePlayerData>();
 | 
						|
 | 
						|
		// when to send the next update to remotes on the state on my local balls
 | 
						|
		private float m_timeForNextBallUpdate;
 | 
						|
 | 
						|
		private const byte TIME_SYNC_MESSAGE = 1;
 | 
						|
		private const uint TIME_SYNC_MESSAGE_SIZE = 1+4;
 | 
						|
		private const int TIME_SYNC_MESSAGE_COUNT = 7;
 | 
						|
		private const byte START_TIME_MESSAGE = 2;
 | 
						|
		private const uint START_TIME_MESSAGE_SIZE = 1+4;
 | 
						|
		private const byte BACKBOARD_UPDATE_MESSAGE = 3;
 | 
						|
		private const uint BACKBOARD_UPDATE_MESSAGE_SIZE = 1+4+12+12+12;
 | 
						|
		private const byte LOCAL_BALLS_UPDATE_MESSAGE = 4;
 | 
						|
		private const uint LOCAL_BALLS_UPDATE_MESSATE_SIZE_MAX = 1+4+(2*Player.MAX_BALLS*(1+4+12+12));
 | 
						|
		private const float LOCAL_BALLS_UPDATE_DELAY = 0.1f;
 | 
						|
		private const byte SCORE_UPDATE_MESSAGE = 5;
 | 
						|
		private const uint SCORE_UPDATE_MESSAGE_SIZE = 1 + 4;
 | 
						|
 | 
						|
		// cache of local balls that we are sending updates for
 | 
						|
		private readonly Dictionary<int, P2PNetworkBall> m_localBalls = new Dictionary<int, P2PNetworkBall>();
 | 
						|
 | 
						|
		// reusable buffer to read network data into
 | 
						|
		private readonly byte[] readBuffer = new byte[LOCAL_BALLS_UPDATE_MESSATE_SIZE_MAX];
 | 
						|
 | 
						|
		// temporary time-sync cache of the calculated time offsets
 | 
						|
		private readonly Dictionary<ulong, List<float>> m_remoteSyncTimeCache = new Dictionary<ulong, List<float>>();
 | 
						|
 | 
						|
		// temporary time-sync cache of the last sent message
 | 
						|
		private readonly Dictionary<ulong, float> m_remoteSentTimeCache = new Dictionary<ulong, float>();
 | 
						|
 | 
						|
		// the delegate to handle start-time coordination
 | 
						|
		private StartTimeOffer m_startTimeOfferCallback;
 | 
						|
 | 
						|
		#endregion
 | 
						|
 | 
						|
		public P2PManager()
 | 
						|
		{
 | 
						|
			Net.SetPeerConnectRequestCallback(PeerConnectRequestCallback);
 | 
						|
			Net.SetConnectionStateChangedCallback(ConnectionStateChangedCallback);
 | 
						|
		}
 | 
						|
 | 
						|
		public void UpdateNetwork()
 | 
						|
		{
 | 
						|
			if (m_remotePlayers.Count == 0)
 | 
						|
				return;
 | 
						|
 | 
						|
			// check for new messages
 | 
						|
			Packet packet;
 | 
						|
			while ((packet = Net.ReadPacket()) != null)
 | 
						|
			{
 | 
						|
				if (!m_remotePlayers.ContainsKey(packet.SenderID))
 | 
						|
					continue;
 | 
						|
 | 
						|
				packet.ReadBytes(readBuffer);
 | 
						|
 | 
						|
				switch (readBuffer[0])
 | 
						|
				{
 | 
						|
					case TIME_SYNC_MESSAGE:
 | 
						|
						Assert.AreEqual(TIME_SYNC_MESSAGE_SIZE, packet.Size);
 | 
						|
						ReadTimeSyncMessage(packet.SenderID, readBuffer);
 | 
						|
						break;
 | 
						|
 | 
						|
					case START_TIME_MESSAGE:
 | 
						|
						Assert.AreEqual(START_TIME_MESSAGE_SIZE, packet.Size);
 | 
						|
						ReceiveMatchStartTimeOffer(packet.SenderID, readBuffer);
 | 
						|
						break;
 | 
						|
 | 
						|
					case BACKBOARD_UPDATE_MESSAGE:
 | 
						|
						Assert.AreEqual(BACKBOARD_UPDATE_MESSAGE_SIZE, packet.Size);
 | 
						|
						ReceiveBackboardUpdate(packet.SenderID, readBuffer);
 | 
						|
						break;
 | 
						|
 | 
						|
					case LOCAL_BALLS_UPDATE_MESSAGE:
 | 
						|
						ReceiveBallTransforms(packet.SenderID, readBuffer, packet.Size);
 | 
						|
						break;
 | 
						|
 | 
						|
					case SCORE_UPDATE_MESSAGE:
 | 
						|
						Assert.AreEqual(SCORE_UPDATE_MESSAGE_SIZE, packet.Size);
 | 
						|
						ReceiveScoredUpdate(packet.SenderID, readBuffer);
 | 
						|
						break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (Time.time >= m_timeForNextBallUpdate && m_localBalls.Count > 0)
 | 
						|
			{
 | 
						|
				SendLocalBallTransforms();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		#region Connection Management
 | 
						|
 | 
						|
		// adds a remote player to establish a connection to, or accept a connection from
 | 
						|
		public void AddRemotePlayer(RemotePlayer player)
 | 
						|
		{
 | 
						|
			if (!m_remotePlayers.ContainsKey (player.ID))
 | 
						|
			{
 | 
						|
				m_remotePlayers[player.ID] = new RemotePlayerData();
 | 
						|
				m_remotePlayers[player.ID].state = PeerConnectionState.Unknown;
 | 
						|
				m_remotePlayers [player.ID].player = player;
 | 
						|
 | 
						|
				// ID comparison is used to decide who Connects and who Accepts
 | 
						|
				if (PlatformManager.MyID < player.ID)
 | 
						|
				{
 | 
						|
					Debug.Log ("P2P Try Connect to: " + player.ID);
 | 
						|
					Net.Connect (player.ID);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		public void DisconnectAll()
 | 
						|
		{
 | 
						|
			foreach (var id in m_remotePlayers.Keys)
 | 
						|
			{
 | 
						|
				Net.Close(id);
 | 
						|
			}
 | 
						|
			m_remotePlayers.Clear();
 | 
						|
		}
 | 
						|
 | 
						|
		void PeerConnectRequestCallback(Message<NetworkingPeer> msg)
 | 
						|
		{
 | 
						|
			if (m_remotePlayers.ContainsKey(msg.Data.ID))
 | 
						|
			{
 | 
						|
				Debug.LogFormat("P2P Accepting Connection request from {0}", msg.Data.ID);
 | 
						|
				Net.Accept(msg.Data.ID);
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				Debug.LogFormat("P2P Ignoring unauthorized Connection request from {0}", msg.Data.ID);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		void ConnectionStateChangedCallback(Message<NetworkingPeer> msg)
 | 
						|
		{
 | 
						|
			Debug.LogFormat("P2P {0} Connection state changed to {1}", msg.Data.ID, msg.Data.State);
 | 
						|
 | 
						|
			if (m_remotePlayers.ContainsKey(msg.Data.ID))
 | 
						|
			{
 | 
						|
				m_remotePlayers[msg.Data.ID].state = msg.Data.State;
 | 
						|
 | 
						|
				switch (msg.Data.State)
 | 
						|
				{
 | 
						|
				case PeerConnectionState.Connected:
 | 
						|
					if (PlatformManager.MyID < msg.Data.ID)
 | 
						|
					{
 | 
						|
						SendTimeSyncMessage(msg.Data.ID);
 | 
						|
					}
 | 
						|
					break;
 | 
						|
 | 
						|
				case PeerConnectionState.Timeout:
 | 
						|
					if (PlatformManager.MyID < msg.Data.ID)
 | 
						|
					{
 | 
						|
						Net.Connect(msg.Data.ID);
 | 
						|
					}
 | 
						|
					break;
 | 
						|
 | 
						|
				case PeerConnectionState.Closed:
 | 
						|
					m_remotePlayers.Remove(msg.Data.ID);
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		#endregion
 | 
						|
 | 
						|
		#region Time Synchronizaiton
 | 
						|
 | 
						|
		// This section implements some basic time synchronization between the players.
 | 
						|
		// The algorithm is:
 | 
						|
		//   -Send a time-sync message and receive a time-sync message response
 | 
						|
		//   -Estimate time offset
 | 
						|
		//   -Repeat several times
 | 
						|
		//   -Average values discarding any statistical anomalies
 | 
						|
		// Normally delays would be added in case there is intermittent network congestion
 | 
						|
		// however the match times are so short we don't do that here.  Also, if one client
 | 
						|
		// pauses their game and Unity stops their simulation, all bets are off for time
 | 
						|
		// synchronization.  Depending on the goals of your app, you could either reinitiate
 | 
						|
		// time synchronization, or just disconnect that player.
 | 
						|
 | 
						|
		void SendTimeSyncMessage(ulong remoteID)
 | 
						|
		{
 | 
						|
			if (!m_remoteSyncTimeCache.ContainsKey(remoteID))
 | 
						|
			{
 | 
						|
				m_remoteSyncTimeCache[remoteID] = new List<float>();
 | 
						|
			}
 | 
						|
 | 
						|
			float time = Time.realtimeSinceStartup;
 | 
						|
			m_remoteSentTimeCache[remoteID] = time;
 | 
						|
 | 
						|
			byte[] buf = new byte[TIME_SYNC_MESSAGE_SIZE];
 | 
						|
			buf[0] = TIME_SYNC_MESSAGE;
 | 
						|
			int offset = 1;
 | 
						|
			PackFloat(time, buf, ref offset);
 | 
						|
 | 
						|
			Net.SendPacket(remoteID, buf, SendPolicy.Reliable);
 | 
						|
		}
 | 
						|
 | 
						|
		void ReadTimeSyncMessage(ulong remoteID, byte[] msg)
 | 
						|
		{
 | 
						|
			if (!m_remoteSentTimeCache.ContainsKey(remoteID))
 | 
						|
			{
 | 
						|
				SendTimeSyncMessage(remoteID);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			int offset = 1;
 | 
						|
			float remoteTime = UnpackFloat(msg, ref offset);
 | 
						|
			float now = Time.realtimeSinceStartup;
 | 
						|
			float latency = (now - m_remoteSentTimeCache[remoteID]) / 2;
 | 
						|
			float remoteTimeOffset = now - (remoteTime + latency);
 | 
						|
 | 
						|
			m_remoteSyncTimeCache[remoteID].Add(remoteTimeOffset);
 | 
						|
 | 
						|
			if (m_remoteSyncTimeCache[remoteID].Count < TIME_SYNC_MESSAGE_COUNT)
 | 
						|
			{
 | 
						|
				SendTimeSyncMessage(remoteID);
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				if (PlatformManager.MyID < remoteID)
 | 
						|
				{
 | 
						|
					// this client started the sync, need to send one last message to
 | 
						|
					// the remote so they can finish their sync calculation
 | 
						|
					SendTimeSyncMessage(remoteID);
 | 
						|
				}
 | 
						|
 | 
						|
				// sort the times and remember the median
 | 
						|
				m_remoteSyncTimeCache[remoteID].Sort();
 | 
						|
				float median = m_remoteSyncTimeCache[remoteID][TIME_SYNC_MESSAGE_COUNT/2];
 | 
						|
 | 
						|
				// calucate the mean and standard deviation
 | 
						|
				double mean = 0;
 | 
						|
				foreach (var time in m_remoteSyncTimeCache[remoteID])
 | 
						|
				{
 | 
						|
					mean += time;
 | 
						|
				}
 | 
						|
				mean /= TIME_SYNC_MESSAGE_COUNT;
 | 
						|
 | 
						|
				double std_dev = 0;
 | 
						|
				foreach (var time in m_remoteSyncTimeCache[remoteID])
 | 
						|
				{
 | 
						|
					std_dev += (mean-time)*(mean-time);
 | 
						|
				}
 | 
						|
				std_dev = Math.Sqrt(std_dev)/TIME_SYNC_MESSAGE_COUNT;
 | 
						|
 | 
						|
				// time delta is the mean of the values less than 1 standard deviation from the median
 | 
						|
				mean = 0;
 | 
						|
				int meanCount = 0;
 | 
						|
				foreach (var time in m_remoteSyncTimeCache[remoteID])
 | 
						|
				{
 | 
						|
					if (Math.Abs(time-median) < std_dev)
 | 
						|
					{
 | 
						|
						mean += time;
 | 
						|
						meanCount++;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				mean /= meanCount;
 | 
						|
				Debug.LogFormat("Time offset to {0} is {1}", remoteID, mean);
 | 
						|
 | 
						|
				m_remoteSyncTimeCache.Remove(remoteID);
 | 
						|
				m_remoteSentTimeCache.Remove(remoteID);
 | 
						|
				m_remotePlayers[remoteID].remoteTimeOffset = (float)mean;
 | 
						|
 | 
						|
				// now that times are synchronized, lets try to coordinate the
 | 
						|
				// start time for the match
 | 
						|
				OfferMatchStartTime();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		float ShiftRemoteTime(ulong remoteID, float remoteTime)
 | 
						|
		{
 | 
						|
			if (m_remotePlayers.ContainsKey(remoteID))
 | 
						|
			{
 | 
						|
				return remoteTime + m_remotePlayers[remoteID].remoteTimeOffset;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				return remoteTime;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		#endregion
 | 
						|
 | 
						|
		#region Match Start Coordination
 | 
						|
 | 
						|
		// Since all the clients will calculate a slightly different start time, this
 | 
						|
		// message tries to coordinate the match start time to be the lastest of all
 | 
						|
		// the clients in the match.
 | 
						|
 | 
						|
		// Delegate to coordiate match start times - the return value is our start time
 | 
						|
		// and the argument is the remote start time, or 0 if that hasn't been given yet.
 | 
						|
		public delegate float StartTimeOffer(float remoteTime);
 | 
						|
 | 
						|
		public StartTimeOffer StartTimeOfferCallback
 | 
						|
		{
 | 
						|
			private get { return m_startTimeOfferCallback; }
 | 
						|
			set { m_startTimeOfferCallback = value; }
 | 
						|
		}
 | 
						|
 | 
						|
		void OfferMatchStartTime()
 | 
						|
		{
 | 
						|
			byte[] buf = new byte[START_TIME_MESSAGE_SIZE];
 | 
						|
			buf[0] = START_TIME_MESSAGE;
 | 
						|
			int offset = 1;
 | 
						|
			PackFloat(StartTimeOfferCallback(0), buf, ref offset);
 | 
						|
 | 
						|
			foreach (var remoteID in m_remotePlayers.Keys)
 | 
						|
			{
 | 
						|
				if (m_remotePlayers [remoteID].state == PeerConnectionState.Connected)
 | 
						|
				{
 | 
						|
					Net.SendPacket (remoteID, buf, SendPolicy.Reliable);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		void ReceiveMatchStartTimeOffer(ulong remoteID, byte[] msg)
 | 
						|
		{
 | 
						|
			int offset = 1;
 | 
						|
			float remoteTime = UnpackTime(remoteID, msg, ref offset);
 | 
						|
			StartTimeOfferCallback(remoteTime);
 | 
						|
		}
 | 
						|
 | 
						|
		#endregion
 | 
						|
 | 
						|
		#region Backboard Transforms
 | 
						|
 | 
						|
		public void SendBackboardUpdate(float time, Vector3 pos, Vector3 moveDir, Vector3 nextMoveDir)
 | 
						|
		{
 | 
						|
			byte[] buf = new byte[BACKBOARD_UPDATE_MESSAGE_SIZE];
 | 
						|
			buf[0] = BACKBOARD_UPDATE_MESSAGE;
 | 
						|
			int offset = 1;
 | 
						|
			PackFloat(time, buf, ref offset);
 | 
						|
			PackVector3(pos, buf, ref offset);
 | 
						|
			PackVector3(moveDir, buf, ref offset);
 | 
						|
			PackVector3(nextMoveDir, buf, ref offset);
 | 
						|
 | 
						|
			foreach (KeyValuePair<ulong,RemotePlayerData> player in m_remotePlayers)
 | 
						|
			{
 | 
						|
				if (player.Value.state == PeerConnectionState.Connected)
 | 
						|
				{
 | 
						|
					Net.SendPacket(player.Key, buf, SendPolicy.Reliable);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		void ReceiveBackboardUpdate(ulong remoteID, byte[] msg)
 | 
						|
		{
 | 
						|
			int offset = 1;
 | 
						|
			float remoteTime = UnpackTime(remoteID, msg, ref offset);
 | 
						|
			Vector3 pos = UnpackVector3(msg, ref offset);
 | 
						|
			Vector3 moveDir = UnpackVector3(msg, ref offset);
 | 
						|
			Vector3 nextMoveDir = UnpackVector3(msg, ref offset);
 | 
						|
 | 
						|
			var goal = m_remotePlayers [remoteID].player.Goal;
 | 
						|
			goal.RemoteBackboardUpdate(remoteTime, pos, moveDir, nextMoveDir);
 | 
						|
		}
 | 
						|
 | 
						|
		#endregion
 | 
						|
 | 
						|
		#region Ball Tansforms
 | 
						|
 | 
						|
		public void AddNetworkBall(GameObject ball)
 | 
						|
		{
 | 
						|
			m_localBalls[ball.GetInstanceID()] = ball.AddComponent<P2PNetworkBall>();
 | 
						|
		}
 | 
						|
 | 
						|
		public void RemoveNetworkBall(GameObject ball)
 | 
						|
		{
 | 
						|
			m_localBalls.Remove(ball.GetInstanceID());
 | 
						|
		}
 | 
						|
 | 
						|
		void SendLocalBallTransforms()
 | 
						|
		{
 | 
						|
			m_timeForNextBallUpdate = Time.time + LOCAL_BALLS_UPDATE_DELAY;
 | 
						|
 | 
						|
			int msgSize = 1 + 4 + (m_localBalls.Count * (1 + 4 + 12 + 12));
 | 
						|
			byte[] sendBuffer = new byte[msgSize];
 | 
						|
			sendBuffer[0] = LOCAL_BALLS_UPDATE_MESSAGE;
 | 
						|
			int offset = 1;
 | 
						|
			PackFloat(Time.realtimeSinceStartup, sendBuffer, ref offset);
 | 
						|
 | 
						|
			foreach (var ball in m_localBalls.Values)
 | 
						|
			{
 | 
						|
				PackBool(ball.IsHeld(), sendBuffer, ref offset);
 | 
						|
				PackInt32(ball.gameObject.GetInstanceID(), sendBuffer, ref offset);
 | 
						|
				PackVector3(ball.transform.localPosition, sendBuffer, ref offset);
 | 
						|
				PackVector3(ball.velocity, sendBuffer, ref offset);
 | 
						|
			}
 | 
						|
 | 
						|
			foreach (KeyValuePair<ulong, RemotePlayerData> player in m_remotePlayers)
 | 
						|
			{
 | 
						|
				if (player.Value.state == PeerConnectionState.Connected)
 | 
						|
				{
 | 
						|
					Net.SendPacket(player.Key, sendBuffer, SendPolicy.Unreliable);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		void ReceiveBallTransforms(ulong remoteID, byte[] msg, ulong msgLength)
 | 
						|
		{
 | 
						|
			int offset = 1;
 | 
						|
			float remoteTime = UnpackTime(remoteID, msg, ref offset);
 | 
						|
 | 
						|
			// because we're using unreliable networking the packets could come out of order
 | 
						|
			// and the best thing to do is just ignore old packets because the data isn't
 | 
						|
			// very useful anyway
 | 
						|
			if (remoteTime < m_remotePlayers[remoteID].lastReceivedBallsTime)
 | 
						|
				return;
 | 
						|
 | 
						|
			m_remotePlayers[remoteID].lastReceivedBallsTime = remoteTime;
 | 
						|
 | 
						|
			// loop over all ball updates in the message
 | 
						|
			while (offset != (int)msgLength)
 | 
						|
			{
 | 
						|
				bool isHeld = UnpackBool(msg, ref offset);
 | 
						|
				int instanceID = UnpackInt32(msg, ref offset);
 | 
						|
				Vector3 pos = UnpackVector3(msg, ref offset);
 | 
						|
				Vector3 vel = UnpackVector3(msg, ref offset);
 | 
						|
 | 
						|
				if (!m_remotePlayers[remoteID].activeBalls.ContainsKey(instanceID))
 | 
						|
				{
 | 
						|
					var newball = m_remotePlayers[remoteID].player.CreateBall().AddComponent<P2PNetworkBall>();
 | 
						|
					newball.transform.SetParent(m_remotePlayers[remoteID].player.transform.parent);
 | 
						|
					m_remotePlayers[remoteID].activeBalls[instanceID] = newball;
 | 
						|
				}
 | 
						|
				var ball = m_remotePlayers[remoteID].activeBalls[instanceID];
 | 
						|
				if (ball)
 | 
						|
				{
 | 
						|
					ball.ProcessRemoteUpdate(remoteTime, isHeld, pos, vel);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		#endregion
 | 
						|
 | 
						|
		#region Score Updates
 | 
						|
 | 
						|
		public void SendScoreUpdate(uint score)
 | 
						|
		{
 | 
						|
			byte[] buf = new byte[SCORE_UPDATE_MESSAGE_SIZE];
 | 
						|
			buf[0] = SCORE_UPDATE_MESSAGE;
 | 
						|
			int offset = 1;
 | 
						|
			PackUint32(score, buf, ref offset);
 | 
						|
 | 
						|
			foreach (KeyValuePair<ulong, RemotePlayerData> player in m_remotePlayers)
 | 
						|
			{
 | 
						|
				if (player.Value.state == PeerConnectionState.Connected)
 | 
						|
				{
 | 
						|
					Net.SendPacket(player.Key, buf, SendPolicy.Reliable);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		void ReceiveScoredUpdate(ulong remoteID, byte[] msg)
 | 
						|
		{
 | 
						|
			int offset = 1;
 | 
						|
			uint score = UnpackUint32(msg, ref offset);
 | 
						|
 | 
						|
			m_remotePlayers[remoteID].player.ReceiveRemoteScore(score);
 | 
						|
		}
 | 
						|
		#endregion
 | 
						|
 | 
						|
		#region Serialization
 | 
						|
 | 
						|
		// This region contains basic data serialization logic.  This sample doesn't warrant
 | 
						|
		// much optimization, but the opportunites are ripe those interested in the topic.
 | 
						|
 | 
						|
		void PackVector3(Vector3 vec, byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			PackFloat(vec.x, buf, ref offset);
 | 
						|
			PackFloat(vec.y, buf, ref offset);
 | 
						|
			PackFloat(vec.z, buf, ref offset);
 | 
						|
		}
 | 
						|
 | 
						|
		Vector3 UnpackVector3(byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			Vector3 vec;
 | 
						|
			vec.x = UnpackFloat(buf, ref offset);
 | 
						|
			vec.y = UnpackFloat(buf, ref offset);
 | 
						|
			vec.z = UnpackFloat(buf, ref offset);
 | 
						|
			return vec;
 | 
						|
		}
 | 
						|
 | 
						|
		void PackQuaternion(Quaternion quat, byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			PackFloat(quat.x, buf, ref offset);
 | 
						|
			PackFloat(quat.y, buf, ref offset);
 | 
						|
			PackFloat(quat.z, buf, ref offset);
 | 
						|
			PackFloat(quat.w, buf, ref offset);
 | 
						|
		}
 | 
						|
 | 
						|
		void PackFloat(float value, byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4);
 | 
						|
			offset = offset + 4;
 | 
						|
		}
 | 
						|
 | 
						|
		float UnpackFloat(byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			float value = BitConverter.ToSingle(buf, offset);
 | 
						|
			offset += 4;
 | 
						|
			return value;
 | 
						|
		}
 | 
						|
 | 
						|
		float UnpackTime(ulong remoteID, byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			return ShiftRemoteTime(remoteID, UnpackFloat(buf, ref offset));
 | 
						|
		}
 | 
						|
 | 
						|
		void PackInt32(int value, byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4);
 | 
						|
			offset = offset + 4;
 | 
						|
		}
 | 
						|
 | 
						|
		int UnpackInt32(byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			int value = BitConverter.ToInt32(buf, offset);
 | 
						|
			offset += 4;
 | 
						|
			return value;
 | 
						|
		}
 | 
						|
 | 
						|
		void PackUint32(uint value, byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buf, offset, 4);
 | 
						|
			offset = offset + 4;
 | 
						|
		}
 | 
						|
 | 
						|
		uint UnpackUint32(byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			uint value = BitConverter.ToUInt32(buf, offset);
 | 
						|
			offset += 4;
 | 
						|
			return value;
 | 
						|
		}
 | 
						|
 | 
						|
		void PackBool(bool value, byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			buf[offset++] = (byte)(value ? 1 : 0);
 | 
						|
		}
 | 
						|
 | 
						|
		bool UnpackBool(byte[] buf, ref int offset)
 | 
						|
		{
 | 
						|
			return buf[offset++] != 0;;
 | 
						|
		}
 | 
						|
 | 
						|
		#endregion
 | 
						|
	}
 | 
						|
}
 |