I managed to get an authoratative server going fr om a tutorial I found here: http://www.playblack.net/zee-code/unity-3d-authoritative-server-networking-pt1
I ran into a road block though and I can’t quite wrap my head around it. The client’s that connect request a “spawn” from the server, the server then instantiates a “player” for the client and assigns it to the player. The tutorial then goes onto the movement logic for the client side, and each frame (from what I can tell) an RPC is called to the server to “updateClientMotion”.
So the client does the movement logic:
Client Side Movement Handling:
void Update ()
{
if (Network.isServer) {
return; //get lost, this is the client side!
}
float motionH, motionY;
if ((owner != null) (Network.player == owner)) {
grounded = IsGrounded ();
if (grounded) {
// Calculate how fast we should be moving
jumping = false;
targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
motionH = Input.GetAxis ("Horizontal");
motionY = Input.GetAxis ("Vertical");
networkView.RPC ("updateClientMotion", RPCMode.Server, motionH, motionY);
targetVelocity = transform.TransformDirection (targetVelocity);
targetVelocity *= speed;
Vector3 velocity = rigidbody.velocity;
Vector3 velocityChange = (targetVelocity - velocity) + groundVelocity;
velocityChange.x = Mathf.Clamp (velocityChange.x, -maxVelocityChange, maxVelocityChange);
velocityChange.z = Mathf.Clamp (velocityChange.z, -maxVelocityChange, maxVelocityChange);
velocityChange.y = 0;
rigidbody.AddForce (velocityChange, ForceMode.VelocityChange);
// Jump
if (canJump Input.GetButton ("Jump")) {
//no idea how to handle this, do I call another RPC?
grounded = false;
canJump = false;
}
}else{
targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
motionH = Input.GetAxis ("Horizontal");
motionY = Input.GetAxis ("Vertical");
networkView.RPC ("updateClientMotion", RPCMode.Server, motionH, motionY);
targetVelocity = transform.TransformDirection (targetVelocity) * inAirControl;
rigidbody.AddForce (targetVelocity, ForceMode.VelocityChange);
}
if (Input.GetButtonUp ("Jump")) {
canJump = true;
}
rigidbody.AddForce (new Vector3 (0, -gravity * 1, 0));
networkView.RPC ("updateClientMotion", RPCMode.Server, motionH, motionY, transform.eulerAngles, 0.0f);
}
}
First question is, am I supposed to be calling the RPC function each frame?
On the server, this is my “updateClientMotion” RPC function:
RPC that the client is supposed to call when the player moves:
[RPC]
void updateClientMotion (float client_motionH, float client_motionY)
{
motionH = client_motionH;
motionY = client_motionY;
}
Where I get lost now is, how am I supposed to do the physics correctly on the server, what I have now it makes my “players” go haywire. I know it’s supposed to handle the physics the same way as the client side, but it doesn’t seem to work… then I get lost on how to calculate a “jump”. Here is the server way of handling movement:
The server side code for handling player movement:
public void Update ()
{
if (Network.isClient) {
return; //Get lost, this is the server-side!
}
transform.eulerAngles = rotation;
grounded = IsGrounded ();
if (grounded) {
//Debug.Log("Processing clients movement commands on server");
targetVelocity = new Vector3 (motionH, 0, motionY);
targetVelocity = transform.TransformDirection (targetVelocity);
targetVelocity *= speed;
// Apply a force that attempts to reach our target velocity
Vector3 velocity = rigidbody.velocity;
Vector3 velocityChange = (targetVelocity - velocity) + groundVelocity;
velocityChange.x = Mathf.Clamp (velocityChange.x, -maxVelocityChange, maxVelocityChange);
velocityChange.z = Mathf.Clamp (velocityChange.z, -maxVelocityChange, maxVelocityChange);
velocityChange.y = 0;
rigidbody.AddForce (velocityChange, ForceMode.VelocityChange);
if (jumpVerticalSpeed > 0.0f) {
rigidbody.velocity = new Vector3 (velocity.x, CalculateJumpVerticalSpeed (), velocity.z);
grounded = false;
}
}else{
// Add in air
//targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
targetVelocity = new Vector3 (motionH, 0, motionY);
targetVelocity = transform.TransformDirection (targetVelocity) * inAirControl;
rigidbody.AddForce (targetVelocity, ForceMode.VelocityChange);
}
rigidbody.AddForce (new Vector3 (0, -gravity * 1, 0));
}
And finally, here is my “Predictor” which is not entirely the same as the tutorials because that one seemed to jitter around alot and it wasn’t even predicting my movement correctly, so I found a script called “NetworkRigidBody” which I had tested much earlier and it seems to be pretty much the same as the one shown in the tutorial, but I sort of combined the two:
The “Prediction” script for movement (interpolation/extrapolation):
using UnityEngine;
using System.Collections;
internal struct State
{
internal double timestamp;
internal Vector3 pos;
internal Vector3 velocity;
internal Quaternion rot;
internal Vector3 angularVelocity;
}
public class Predictor : MonoBehaviour {
public Transform observedTransform;
public C_PlayerManager receiver; //Guy who is receiving data
public float pingMargin = 0.5f; //ping top-margin
public double m_InterpolationBackTime = 0.1;
public double m_ExtrapolationLimit = 0.5;
float extrapolationLength;
// We store twenty states with "playback" information
State[] m_BufferedState = new State[20];
// Keep track of what slots are used
int m_TimestampCount;
private float clientPing;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if ((Network.player == receiver.getOwner()) || Network.isServer) {
return; //This is only for remote peers, get off
}
double interpolationTime = Network.time - m_InterpolationBackTime;
//client side has !!only the server connected!!
//clientPing = (Network.GetAveragePing(Network.connections[0]) / 100) + pingMargin;
//Try interpolation if possible.
//If the latest serverStateBuffer timestamp is smaller than the latency
//we're not slow enough to really lag out and just extrapolate.
if (m_BufferedState[0].timestamp > interpolationTime){
for (int i=0;i<m_TimestampCount;i++){
if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1){
// Find the state which matches the interp. time or use last state
State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
State lhs = m_BufferedState[i];
double length = rhs.timestamp - lhs.timestamp;
float t = 0.0F;
if (length > 0.0001)
t = (float)((interpolationTime - lhs.timestamp) / length);
transform.position = Vector3.Lerp(lhs.pos, rhs.pos, t);
transform.rotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
//Okay found our way through to lerp the positions, lets return here
return;
}
}
}
//so it appears there is no lag through latency.
else {
State latest = m_BufferedState[0];
extrapolationLength = (float)(interpolationTime - latest.timestamp);
if (extrapolationLength < m_ExtrapolationLimit)
{
float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);
observedTransform.rigidbody.position = latest.pos + latest.velocity * extrapolationLength;
observedTransform.rigidbody.rotation = angularRotation * latest.rot;
observedTransform.rigidbody.velocity = latest.velocity;
observedTransform.rigidbody.angularVelocity = latest.angularVelocity;
}
}
}
public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) {
if (stream.isWriting)
{
Vector3 pos = observedTransform.rigidbody.position;
Quaternion rot = observedTransform.rigidbody.rotation;
Vector3 velocity = observedTransform.rigidbody.velocity;
Vector3 angularVelocity = observedTransform.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);
}
}
}
As of now, my server side “prediction” code is commented out. The “isWriting” portion on the “OnSerializeNetworkView” function never seems to get called.
I can host a game, players can join, but their positions are not being sent to other players. I’m hoping someone can help me that has any idea of what I’m supposed to do and how to do it correctly.