using UnityEngine;
using System.Collections;
using TNet;
public abstract class NetworkTransform : TNBehaviour
{
private int m_currentOwner = 0;
[Range(0.0f, 30.0f)]
public int m_updatesPerSecond = 20;
[Range(0.0f, 10.0f)]
public double m_InterpolationBackTime = 0.1;
[Range(0.0f, 20.0f)]
public double m_ExtrapolationLimit = 0.5;
[HideInInspector]
public CharacterController m_characterController;
[HideInInspector]
public PlayerController m_playerController;
private Rigidbody m_rigidBody;
internal struct State
{
internal double timestamp;
internal Vector3 pos;
internal Vector3 velocity;
internal Vector3 angularVelocity;
internal Quaternion rot;
internal Vector3 moveInput;
internal Quaternion mouseInput;
}
// We store twenty states with "playback" information
State
[] m_BufferedState
= new State
[20]; // Keep track of what slots are used
int m_TimestampCount;
public virtual void Awake()
{
if (GetComponent<Rigidbody>() != null)
{
m_rigidBody = GetComponent<Rigidbody>();
}
if (TNManager.isHosting)
m_currentOwner = TNManager.playerID;
}
public virtual void Start()
{
}
public virtual void Update ()
{
if (!tno.isMine)
InterpolateTransform();
}
public IEnumerator PeriodicSync()
{
for (;;)
{
if (TNManager.IsInChannel(tno.channelID))
{
if (IsOwnerOfThisObject)
{
Sync();
}
else if (HostOwnsThisObject)
{
Sync();
}
}
yield return new WaitForSeconds
(1f
/ m_updatesPerSecond
); }
}
void Sync()
{
if (m_rigidBody != null)
{
tno.SendQuickly("SetTransform", Target.OthersSaved, m_rigidBody.position, m_rigidBody.rotation, m_rigidBody.velocity, m_rigidBody.angularVelocity, Vector3.zero, Quaternion.identity);
}
else
{
tno.SendQuickly("SetTransform", Target.OthersSaved, transform.position, transform.rotation, Vector3.zero, Vector3.zero, Vector3.zero, Quaternion.identity);
}
}
void InterpolateTransform()
{
// 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
// This is the target playback time of the rigid body
double interpolationTime = Time.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);
// 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);
if(m_playerController != null)
{
m_playerController.MoveInput = Vector3.Lerp(lhs.moveInput, rhs.moveInput, t);
m_playerController.MouseInput = Quaternion.Slerp(lhs.mouseInput, rhs.mouseInput, 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.velocity);
if (m_rigidBody != null)
{
m_rigidBody.position = latest.pos + latest.angularVelocity * extrapolationLength;
m_rigidBody.rotation = angularRotation * latest.rot;
m_rigidBody.velocity = latest.velocity;
m_rigidBody.angularVelocity = latest.angularVelocity;
}
else
{
transform.position = latest.pos + latest.angularVelocity * extrapolationLength;
transform.rotation = angularRotation * latest.rot;
if (m_playerController != null)
{
m_playerController.MoveInput = latest.moveInput;
m_playerController.MouseInput = latest.mouseInput;
}
if (m_characterController != null)
{
m_characterController.SimpleMove(latest.velocity);
}
}
}
}
}
[RFC]
protected void SetTransform(Vector3 pos, Quaternion rot, Vector3 vel, Vector3 angularVelocity, Vector3 moveInput, Quaternion mouseInput)
{
// 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 = Time.time;
state.pos = pos;
state.velocity = vel;
state.rot = rot;
state.angularVelocity = angularVelocity;
state.moveInput = moveInput;
state.mouseInput = mouseInput;
//state.input = input;
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)
Log.Info("State inconsistent");
}
}
public void SetLocalWantControl(bool _set)
{
tno.Send("OnPlayerWantControls", Target.Host, TNManager.playerID, _set);
}
[RFC]
void OnPlayerWantControls(int _playerID, bool _set)
{
if (_set) //PLAYER WANT CONTROL
{
if (IsOwnerOfThisObject) //HOSTER CONTROLS THIS OBJ
{
OwnerOfThisObject = _playerID;
}
}
else //PLAYER DONT WANT CONTROL - LET HOSTER CONTROL
{
OwnerOfThisObject = TNManager.playerID;
}
tno.Send("OnPlayerGetControls", Target.Others, m_currentOwner);
}
[RFC]
void OnPlayerGetControls(int _newOwner)
{
OwnerOfThisObject = _newOwner;
}
public bool IsOwnerOfThisObject
{
get
{
return OwnerOfThisObject == TNManager.playerID;
}
}
public bool HostOwnsThisObject
{
get
{
return IsOwnerOfThisObject && TNManager.isHosting;
}
}
public int OwnerOfThisObject
{
get
{
return m_currentOwner;
}
set
{
m_currentOwner = value;
}
}
}