Comunity Project: Simple multiplayer networked game (2D Space shooter)

Project Package: http://www.mediafire.com/?vct9mwne79zi3iu

We put together 3dBuzz’s simple 2d space shooter and implemented networked multiplayer using M2H Networking tutorial (Example 4)…

In multiplayer, the networked movement lacks quality, skips badly and is not smooth.

It has basic interpolation/extrapolation with the NetworkRigidbody script. It’s not enough.

How can we properly add Prediction, Dead reckoning, interpolation/extrapolation, so that the networked movement looks smooth ?

best regards.

We could achieve smooth movement by increasing the network send rate from 15 to 30, OR by adding an RPC call to the Player so that as soon as movement is detected, it is sent over the network, from M2H example:

if (Vector3.Distance(transform.position, lastPosition) > 0.05f)
{
lastPosition = transform.position;
networkView.RPC(“SetPosition”, RPCMode.Others, transform.position);
}
[RPC]
void SetPosition(Vector3 newPos)
{
transform.position = newPos;
}

Still, this is done without using interpolation/extrapolation, and especially increasing the network send rate seems like a big compromise.

Are you sure you’re using NetworkRigidbody right? I don’t get any lag or skipping with it. Are you disabling it on the owner?

Thanks legend, that was it.

Using NetworkRigidbody the movement is smooth.

Ran into some new problems with the player screen wrapping + NetworkRigidbody = trail across the screen. Improvised a fix by disabling render for a shot time.

How can the NetworkRigidbody buffer be cleared?

NetworkRigidbody code:

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 = 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*.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.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;*

* // 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;
}
}
}
}*_