General Networking Question

Hey all, I am rather new to working with Unity networking and I have a question I am sure you all have heard before and know about.

I have 2 projects, a “server” and a “client”. The server has 1 scene, which just contains a GUI to manage the server. It also connects back to a database to store information about clients.

The clients are on a seperate project. So far I have basic RPC calls working, the clients can register and log into accounts, which then opens up the “Game” scene.

There are two problems Iam having. The first is syncing everything up. Is it generally a better idea to use RPC calls to sync object locations, with the server controlling everything? If so do I need the server to have a scene that is pretty much exactly the same as what the clients have?

Secondly, can one serialize object positions to everyone on the network without having to do anything with the dedicated server? If I call a serialize on a players position does it get delivered to everyone that is connected or just to the server?

Thanks for any hints in the right direction :slight_smile:

Well, the rule of serializing datas is that the owner sends data, and all other read it, so there is no problem.

Personally, I would go for OnSerializeNetworkView, it is a question of choice.
The obligation of having a scene identical depends on if you are running an authoritative server, or a non authoritative. In the first case, since the server manage everything (position players, physics) and transmits the result of his calculs, yes, you will need an identical environment.
In the case of a non authoritative server, it may not be required.

Thank you for your reply :slight_smile:

Ok so in that case I am gonna try the Serialize method because I dont feel like an authoritative server is needed.

I am encountering another problem with Network times though.

I have the following code:

// Send data to server
if (stream.isWriting){
Debug.Log("writing");
}
// Read data from remote client
else
{
Debug.Log("reading");
state.timestamp = info.timestamp;
m_BufferedState[0] = state;
}

And yet I never see “reading”. Is this normal? All the stream ever seems to do is write.

Also…

Debug.Log(Network.time + " - " + m_InterpolationBackTime + " = " + interpolationTime + " compared to " + m_BufferedState[0].timestamp);

Returns 60915.802 - 0.1 = 60915.702 compared to 0

And I think this is because its not reading. Any idea why?

Also, is NetworkConnection lost upon Application.LoadLevel ?

It must be my dedicated server breaking things for some reason.

I had a little test project that had interpolation working. It was just one project, but you could run a server or client. Before I started working on the dedicated server I would just load up 2 of these clients, throw one into server mode and the other into client mode and all would be well.

However, when I start the dedicated server, and then connect with 2 clients both can move around fine, but the other client is never updated on the opponents screen. Any idea what could cause this?

Could it be that I have a Server, and then 2 clients connect apart and after eachother while the scene is already in progress?

Not a problem. As I said, the owner of a game Object with Network View send data. Others (non owner) receive it. For your information, the owner of a game object is the player who called the function Network.Instantiate to make it on every user (server and clients).

…Eh ? What is a timeStamp ? What does this code do ?_? (I haven’t studied seriously yet THIS section of Network… :slight_smile:

I asked myself the question some times ago. I think the answer is “no”. You lose connection only if well, Internet go nuts, your ISP blow a bomb in their building, or the server kicks you, or you stop the connection, or the server stop the connection…but not when you change the current level.

Go to Edit > Project Settings > Player > Run In Background. Is it On ?

Anyway, I am currently a little busy, I haven’t understood. Can you explain again, please ? ^^’

Sorry for any confusion.

Here is basically the setup:

1 project called Server
1 project called Client

The server runs, I hit the start button and thats it.

Now I boot up 2 clients, both of which load the Login scene.

The login screen asks for a username and a password, and when the login button is hit it sends an RPC over to the server.

The server checks the incoming username and password, and if correct sends an RPC back saying all is well.

Here is where things get weird, and maybe something here will tip you off as to what the problem might be.

If both clients are up, they both will log in. Both will load the “Game” scene. There will be 2 ships on the screen, both spawned with Network.Instantiate on the Spawnpoint. Each client will be able to move 1 ship, and the other ship will just sit there.

Obviously this isnt how things are supposed to work. Both clients should not be logging in at the same time, and each ship should be able to be controlled and the point updated on the other persons screen.

Also if I boot the server up, and then load in one client, when I boot the second client up it will show an image of the first ship under the login GUI code. I am not sure why this is, but I assume its because the Main Camera gets attached to the players ship when they spawn in. Why a separate client would be seeing the camera from the first client thats on a different scene is beyond me.

I am not sure about the user of Network.AllocateViewID, could that be something I need to get this working?

Also thank you for your continued help.

Something funny, but you never say somewhere that the clients CONNECTS to the server, you directly say : they send a RPC (therefore they connected).

Or by “boot up” you meant connect ? If that’s the case, so be it.

Normal.

Two questions :

  • What is the expected result ?
  • What is the current result ?

By any chances, are the cameras in the login scene and the game scene at the same position and orientation ?

http://forum.unity3d.com/viewtopic.php?t=61016

Edit : this too http://forum.unity3d.com/viewtopic.php?t=60938

Ok so I solved the problem of both clients connecting when only one presses the login button. It was an incorrect if check on the owner.

The second client will still see the ship, but it is not the same camera orientation. Nor does the ship seem to change when I move around with the other client, I assume its because they arent synching.

In regards to players synching up:

Current Result: Both ships appear on both clien screens, the clients each can control one ship, move it around, but it never looks like it on the other client. Player A moves but Player B never sees Player As ship move.

Expected Result: Both ships appear on both client screens. Each client can control one ship and move it around. So when Player A moves to the left, Player B sees Player A’s ship move.

The real problem now is lack of ship synching.
Prepare for code dump, this is all from M2H’s tutorial:

//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);
				
				transform.position = latest.pos + latest.velocity * extrapolationLength;
				transform.rotation = angularRotation * latest.rot;
				//rigidbody.velocity = latest.velocity;
				//rigidbody.angularVelocity = latest.angularVelocity;
			}
		}
	}
}

And like I said in one of my earlier posts, if I debug the variables I get this:

Debug.Log(Network.time + " - " + m_InterpolationBackTime + " = " + interpolationTime + " compared to " + m_BufferedState[0].timestamp);

Returns 60915.802 - 0.1 = 60915.702 compared to 0

Obviously the BufferedState timestamp isnt being set, and its only set inside the Serialized reader, which means I assume I am not getting any data.

Oh also I think it might bear mentioning I did this:

public Vector3 recieved;

And inside the reader:

stream.Serialize(ref pos);
recieved =  pos;

When I run one built client, and one client in the editor I dont see either of the received variables change in the editor on either ship.

C# ? Farewell.

Joke aside, the script is so long, and I haven’t read yet something on C# syntax…it is a real pain.

// VARIABLES
private var screenWidth : float;
private var screenHeight : float;

private var showText : boolean = false;

// EVENTS
function Start ()
{
	screenWidth = Screen.width;
	screenHeight = Screen.height;
}

function OnGUI ()
{
	if (showText)
	{
		var mousePos : Vector3;
		mousePos = Input.mousePosition;
		
		var textToDisplay : String;
		textToDisplay = "Object:" + gameObject.name + " // NetworkPlayer:" + networkView.owner + " // Network View ID:" + networkView.viewID;
		
		GUI.Label (Rect (mousePos.x + 15, screenHeight - mousePos.y, 300, 100), textToDisplay);
	}
}

function OnMouseEnter ()
{
	showText = true;
}

function OnMouseExit ()
{
	showText = false;
}

Make a javascript file (I called it WhatIsMyNVID), put it on your ships. Put the mouse over them (they must have a collider). Does the value match ? (NetworkViewID and NetworkPlayer)

Player A sees his ship as NetworkPlayer:59 and his NetworkViewID as 6500
He sees Player B’s ship as NetworkPlayer:0 and his NetworkViewID as 6300

Player B sees his ship as NetworkPlayer57 and his NetworkViewID as 6300
He sees Player A’s ship as NetworkPlayer:0 and his NetworkViewID as 6500

So they seem to loose eachothers Network Player, but keep the ViewID intact.

What would that indicate? Sorry I am at a loss.

// VARIABLES
private var screenWidth : float;
private var screenHeight : float;

private var showText : boolean = false;

// EVENTS
function Start ()
{
	screenWidth = Screen.width;
	screenHeight = Screen.height;
}

function OnGUI ()
{
	if (showText)
	{
		var mousePos : Vector3;
		mousePos = Input.mousePosition;
		
		var textToDisplay : String;
		textToDisplay = "Object:" + gameObject.name + "\nNetworkPlayer Owner:" + networkView.owner + "\nNetwork View ID:" + networkView.viewID + "\nAm I the owner:" + networkView.isMine;
		
		GUI.Label (Rect (mousePos.x + 15, screenHeight - mousePos.y, 300, 100), textToDisplay);
	}
}

function OnMouseEnter ()
{
	showText = true;
}

function OnMouseExit ()
{
	showText = false;
}

I changed it a little. Try again, please :?

By the way, you have three programs running, right ? The server, and two clients.

Same result, different numbers. Each of the ships sees the NetworkViewID of the other, but not the NetworkPlayer.

Player A is the owner of his ship
Player B is the owner of his ship

And yes 3 programs, 1 server 2 clients.

There is a problem.

The NetworkPlayer value (0, 57, 59) refers to the ID value of a player : the player owning the game Object (since it retrieve the value Network.owner). This value is unique for each player. The value 0 usually refers to the server.

The Network View ID value refers to the ID value of a Network View. Each user has his game objects with Network Views. In order to synchronize them, so they can communicate between themselves (the Network View), those must have the same Network View ID.

To be shorter, if player A has a game Object of NVID 50, this game Object will try to communicate with game Object of NVID 50 at others user.

If I understand, each client sees his own ships, but the second ship they see is not the one of the other client, but of the server.

Because Player A Network Player is 59, and Player B Network Player is 57…so 0 is the server.

What would cause something like that? The ships are created from a script attached to the Main Camera on the Game scene. Here is the script(I know its C# but its simple mainly Unity API calls anyway):

using UnityEngine;
using System.Collections;

public class Intiate : MonoBehaviour {
	
	public GameObject ship;
	public Transform SpawnPoint;
	void Start(){
		ConnectToServer();
	}
	
	void ConnectToServer(){
		Network.Connect("10.0.0.3", 31337);
	}
	
	void OnFailedToConnect(NetworkConnectionError error)
	{
		Debug.Log("Could not connect to server: "+ error);
	}
	
	void OnConnectedToServer() {
		Network.Instantiate(ship, SpawnPoint.position, SpawnPoint.rotation, 0);
		Debug.Log("Connected to server");
	}
}

I thought Network.Instantiate gave ownership to the person that called it? Do I need to send updated information to all clients when its instantiated to breathe some life into the ship?

The person who call N.Instantiate is indeed the owner.

Are you loading any level ?

Sounds like a pretty weird set up to me. The way I understand Unity’s networking, it’s really not (edit :wink: ) designed for networking between different projects. All scene objects with NetworkViews attached have NetworkViewIDs - and those are stored with the scene. So when you have two projects, a lot will break. In fact, I’ve seen networking even break between different builds because sometimes Unity re-assigns those scene-object NetworkViewIDs … you can probably get it working with separate projects if you’re super-careful … but to be honest, I don’t see any use case for such a setup because you’d most likely be better off using something like Photon if you don’t have the actual game scenes on the server (which will almost certainly only work if it’s the same project … or you do some magical synchronization of all NetworkViewIDs which basically means you’d have to have your own IDs and maintain those which doesn’t sound like fun :wink: ).

The main reason why you’d keep Unity networking is because you want to use all the scene/editor features that Unity provides for the server (like Physics, for instance). If you’re not using any of that … a solution like Photon might turn out to be more robust (e.g. I had quite a bit of trouble with my game server and long-term connection to the database which is most likely due to issues in the Mono 1.2.5 version used in Unity 2.6 which is super-outdated). Even when Unity 3 comes with a more recent Mono version, for some stuff it’s really nice to be able to use the most current .NET version :wink:

In case you’re using Unity 3 beta 5 (or any of the earlier betas), this could be a bug which is said to be fixed, so I’m personally waiting for beta 6. However, another explanation is the one from above: Having two projects might just give you lots and lots of trouble :wink:

Btw, you can use conditional compilation to make sure the clients don’t get any of the secret server sauce. It’s not really conveniently implemented in Unity because you can’t set the symbols for compilation in the editor, but there’s workarounds (basically editor scripts that do code replacements and comment in/out the #defines).

If that’s relevant for you, you might consider voting up Editor: Interface for listing preprocessor macros (#define) in build settings

Oh, and of course you’d disable all cameras and rendering stuff on the server. Basically, what I do is have a marker-script that, if I’m running the server, destroys everything that’s just fancy eye-candy. So what remains on my server is just a bunch of colliders :wink: … more or less …

Have I gone nuts or did you write a mistake ?

Yup, there’s a “not” missing :wink: … will fix in a second :wink:

Well thats disheartening to hear :(.

So at this juncture I suppose I should scrap the 2 project idea and just build in server support into the 1 project?

Is there a way to have a 32 person game + 1 observer that just does the server functionality?