Advertisement

Unity Network - Rigidbody synchronize problem (ownership)

Started by June 03, 2013 08:22 AM
2 comments, last by brunopava 11 years, 2 months ago

Hi, I’m new to the Unity network API. I’m trying to develop a game similar to Air Soccer Fever (https://play.google.com/store/apps/details?id=com.danglingconcepts.airsoccerfever&hl=es).

I’m currently synchronizing the balls by using a NetworkView per Ball, each one with the owner has the machine that run the team. The balls are rigidbodies and a synchronize only the position making a Lerp so it is more or less smooth (I don’t know if this is the usual way when working with RigidBodies in Unity).

My problem comes when ball from say team A hits a balls from team B, because the balls from team B have as owner his player and the writer of the position is that client then even when the ball from team A moves the ball from team B it will automatically reposition because the client that “writes” the position is the client from team B. I don’t know if it is clear but that is the problem.

The solution that I think would be the easiest (if possible implementing) will be changing the ownership of the NetworkView so that when it is the turn of team A his client controls (owns) all the balls, and in the turn of team B then its own all the balls. I don’t even know if this solution would be possible or if another think should be do.

Any advice in this?

In general, this is an impossible problem to solve such that everybody sees the right thing and nobody has any latency.

If you're OK with control latency, then the easiest way to do this is to use a server that resolves all the physics, and players send commands to this server, but display only the outcome of commands/simulations that come back from the server. For low-latency network connections, this is often totally acceptable; for high-latency connections, it becomes very laggy.

If you're OK with players seeing "warping" effect in the case of interference, then use this same mechanism, but display player-local data "optimistically simulated forward" and display remote-received data "forward extrapolated" using a mechanism such as EPIC. ( http://www.mindcontrol.org/~hplus/epic/ ) When the optimistically forward-simulated data is wrong compared to what comes back from the server, the player will see snapping/corrections.

It's possible to adjust the time frame of reference for each object such that it runs on "received server time" when it's far away, and runs at "client local time" when it's close; however, there are some patents issued around this area. Also, it still doesn't solve the problem of two players being really close to each other; one will "win" and one will "lose" in this case no matter what.

enum Bool { True, False, FileNotFound };
Advertisement

The problem is that when you use a non-shared physic engine.

You have two solutions :

First where you don't allow the resolution of the collision with the unity physic engine and you do it yourself (elastic shock or simple shock).

(It's easy for a air hockey game.)

To do it you have to

  1. Each player synchronize the position of his own footballers
  2. Make a function wich calculate the vector of force to apply on the shock on each footballers when they are in collision
  3. Send this to the owner of the player by RPC which will apply it to his own footballers.

Second iwhere you select a "delegate player" (i think about the server) which syncrhonyze all and the other only give his inputs by RPC which the delegate server apply.

This script should do the trick for you.

Save is as NetworkRigidbody and attach to your ball.

This kinda solves the latency issues. And all you need to do about the ownership of the ball is:

Each player has its own ball, on an event, say the player kicks the ball.

You send an RPC across the network and update the position of the ball in all clients.

Check this out:

http://docs.unity3d.com/Documentation/Components/net-RPCDetails.html

http://docs.unity3d.com/Documentation/ScriptReference/NetworkView.RPC.html

Although using too much networkviews can be desastrous.

I suggest you to look into photon networking, it's an api from http://www.exitgames.com/


//Note: Example code from the unity networking examples

using UnityEngine;
using System.Collections;

public class NetworkRigidbody : MonoBehaviour
{
	
	public double m_InterpolationBackTime = 0.1;
	public double m_ExtrapolationLimit = 0.5;
	
	internal struct  State
	{
		internal double timestamp;
		internal Vector3 pos;
		internal Vector3 velocity;
		internal Quaternion rot;
		internal Vector3 angularVelocity;
	}
	
	// We store twenty states with "playback" information
	State[] m_BufferedState = new State[20];
	// Keep track of what slots are used
	int m_TimestampCount;
	
	
	
	void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
	{
	
		// Send data to server
		if (stream.isWriting)
		{
			Vector3 pos = transform.position;
			Quaternion rot = transform.rotation;
			//Vector3 velocity = Vector3.zero;  //rigidbody.velocity;
			//Vector3 angularVelocity = Vector3.zero; // rigidbody.angularVelocity;

			stream.Serialize(ref pos);
			//stream.Serialize(ref velocity);
			stream.Serialize(ref rot);
			//stream.Serialize(ref angularVelocity);
		}
		// Read data from remote client
		else
		{
			Vector3 pos = Vector3.zero;
			Vector3 velocity = Vector3.zero;
			Quaternion rot = Quaternion.identity;
			Vector3 angularVelocity = Vector3.zero;
			stream.Serialize(ref pos);
			//stream.Serialize(ref velocity);
			stream.Serialize(ref rot);
			//stream.Serialize(ref angularVelocity);
			
			// Shift the buffer sideways, deleting state 20
			for (int i=m_BufferedState.Length-1;i>=1;i--)
			{
				m_BufferedState[i] = m_BufferedState[i-1];
			}
			
			// Record current state in slot 0
			State state;
			state.timestamp = info.timestamp;
			state.pos = pos;
			state.velocity = velocity;
			state.rot = rot;
			state.angularVelocity = angularVelocity;
			m_BufferedState[0] = state;
			
			// Update used slot count, however never exceed the buffer size
			// Slots aren't actually freed so this just makes sure the buffer is
			// filled up and that uninitalized slots aren't used.
			m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);

			// Check if states are in order, if it is inconsistent you could reshuffel or 
			// drop the out-of-order state. Nothing is done here
			for (int i=0;i<m_TimestampCount-1;i++)
			{
				if (m_BufferedState[i].timestamp < m_BufferedState[i+1].timestamp)
					Debug.Log("State inconsistent");
			}	
		}
	}
	
	// We have a window of interpolationBackTime where we basically play 
	// By having interpolationBackTime the average ping, you will usually use interpolation.
	// And only if no more data arrives we will use extra polation
	void Update () {
		// This is the target playback time of the rigid body
		double interpolationTime = Network.time - m_InterpolationBackTime;
		
		// Use interpolation if the target playback time is present in the buffer
		if (m_BufferedState[0].timestamp > interpolationTime)
		{
			// Go through buffer and find correct state to play back
			for (int i=0;i<m_TimestampCount;i++)
			{
				if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1)
				{
					// The state one slot newer (<100ms) than the best playback state
					State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
					// The best playback state (closest to 100 ms old (default time))
					State lhs = m_BufferedState[i];
					
					// Use the time between the two slots to determine if interpolation is necessary
					double length = rhs.timestamp - lhs.timestamp;
					float t = 0.0F;
					// As the time difference gets closer to 100 ms t gets closer to 1 in 
					// which case rhs is only used
					// Example:
					// Time is 10.000, so sampleTime is 9.900 
					// lhs.time is 9.910 rhs.time is 9.980 length is 0.070
					// t is 9.900 - 9.910 / 0.070 = 0.14. So it uses 14% of rhs, 86% of lhs
					if (length > 0.0001){
						t = (float)((interpolationTime - lhs.timestamp) / length);
					}
					//	Debug.Log(t);
					// if t=0 => lhs is used directly
					transform.localPosition = Vector3.Lerp(lhs.pos, rhs.pos, t);
					transform.localRotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
					return;
				}
			}
		}
		// Use extrapolation
		else
		{
			State latest = m_BufferedState[0];
			
			float extrapolationLength = (float)(interpolationTime - latest.timestamp);
			// Don't extrapolation for more than 500 ms, you would need to do that carefully
			if (extrapolationLength < m_ExtrapolationLimit)
			{
				float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
				Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);
				
				
				//this is some code from a character wich only rotated on the y axis
				/*transform.position = latest.pos + latest.velocity * extrapolationLength;
				transform.rotation = angularRotation * latest.rot;*/
			
				rigidbody.velocity = latest.velocity;
				rigidbody.angularVelocity = latest.angularVelocity;
			}
		}
	}
}

This topic is closed to new replies.

Advertisement