3d 多人游戏中的 FPS 差异
FPS difference in 3d multiplayer games
我正在使用 Unity 开发 3d 手机游戏,我已经到了需要编写多人游戏部分的地步。我用 node.js/socket.io 编写了所有匹配等,但我在游戏部分遇到了问题。问题是;每当用户移动时,他都会将自己的位置传输给其他用户。但是尽管服务器非常强大(4 GHz cpu,16 GB 内存),但用户并没有像在单人游戏中那样移动(我的意思是 AI 移动)。它们似乎滑动了几帧,这使得它们的移动不如我预期的那么流畅。我对可能导致这种情况的原因有一些想法,例如 FPS 差异。由于其中一个用户的发射速度可能比其他用户慢,因为他们之间的 FPS 差异。有什么想法可以解决这个问题吗?
这有点取决于您在 Clients/Server 之间传输数据的准确程度。
(这里假设你只使用 Unity 的网络而不是框架)
您永远不应直接将接收方客户端上的新位置设置为
transform.position = X.Yf;
而是因为您已经注意到的滞后/时间因素而进行一些插值。
transform.position = Vector3.Lerp(actualPosition, receivedPosition, Time.deltaTime * interpolationRate);
我不太喜欢 Unity 的 NetworkTransform - 因为我没能让它顺利运行。所以我写了自己的同步脚本(当然做了很多谷歌搜索;))
我将在此处仅针对职位提供示例。您应该能够对其进行调整,然后自己编写旋转部分。它可能并不完美,但这就是我使用它的方式(到目前为止非常满意)。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class CustomNetworkTransform : NetworkBehaviour
{
/* If we are not the localPlayer we will save here
* the last received Position to which we are actually moving.
* (see below in the transmition part) */
private Vector3 _lastReceivedPosition = Vector3.zero;
/*#############################################*/
/* THE RECEIVING PART */
/* The interpolation rate. You have to tweak this maybe
* but 15 seemed a good value so far */
private float _interpolationRate = 15.0f;
private void Update()
{
/* I used hasAuthority here instead of isLocalPlayer
* so it works with a Host-Clients
* setup as well as with a Client-Server-Clients one.
*/
if (hasAuthority) return;
ReceiveData();
}
private void ReceiveData()
{
InterpolatePosition(_lastReceivedPosition, Time.deltaTime * _interpolationRate);
}
private void InterpolatePosition(Vector3 receivedPosition, float factor)
{
transform.position = Vector3.Lerp(transform.position, receivedPosition, factor);
}
/* That's already all for the receiving Part. Maybe that already
* Fits your need. Down here I anyway add the way how I transmit stuff. Maybe it helps you to understand better the way Networking works in Unity.
*/
/*#############################################*/
/* THE TRANSMITION PART */
/* To save a bit of Bandwidth we use this as a treshold
* If you whish you can set this also to 0 */
[Tooltip("The Accuracy of synchronized Positions")]
[SerializeField]
private float _positionAccuracy = 0.05f;
/* Make a forced Transmition every x seconds
* also if nothing was changed */
[SerializeField]
private float _forcedSyncTimeout = 2.0f;
/* Counter for the forced transmition
* I set it to 0 at the beginning to have an instant
* sync when connecting. */
private float _timeout = 0;
/* Wel'll compare the actual position to this one and only
* sync, if the distance is bigger than the _positionAccuracy */
private Vector3 _lastTransmittedPosition = Vector3.zero;
private void FixedUpdate()
{
/* again here I use hasAuthority instead of
* isLocalPlayer so it works in both connection designs */
if (!hasAuthority) return;
TransmitChangedData();
_timeout-= Time.deltaTime;
if (_timeout > 0) return;
TransmitData();
_timeout = _forcedSyncTimeout;
}
/* This is the forced transmition
* which doesn't check for changes */
private void TransmitData()
{
TransmitPositionToServer(transform.position)
}
/* This is the normal transmition which checks
* if changes are big enough before transmitting */
private void TransmitChangedData()
{
if(Vector3.Distance(transform.position, _lastTransmittedPosition) > _positionAccuracy)
{
TransmitPositionToServer(transform.position);
_lastTransmittedPosition = transform.position;
}
}
/* NOTE: for the transmition I don't use SyncVar
* but rather a 3-Step Syncronization:
*
* 1. Client transmits data to Server
* 2. Server transmits data to all Clients
* 3. All other Clients receive and interpolate the position
*
* I felt more comfortable doing this to have
* more freedom and better debug options.
*
* This will also do all the interpolations on the server as well
* so it can be used e.g. to observe. */
//STEP 1.
/*
* Only performed on clients
*/
[Client]
private void TransmitPositionToServer(Vector3 value)
{
CmdPushPositionToServer(value);
}
//STEP 2.
/*
* Invoced from the client but only performed on the server
*/
[Command]
private void CmdPushPositionToServer(Vector3 value)
{
_lastReceivedPosition = value;
RpcProvidePositionToClients(value);
}
//STEP 3.
/*
* Invoced from the server but only performed on all the client
*/
[ClientRpc]
private void RpcProvidePositionToClients(Vector3 value)
{
/* This value is also set on the originally sending client
* but it doesn't matter since we don't move this client
* because he has the local authority (see Update() )*/
_lastReceivedPosition = value;
}
}
希望对您有所帮助。
我什至曾经看到另一种解决方案使用某种管道插值将所有接收到的数据包存储在一个数组中并通过它进行插值,但在我看来它并没有更好地工作。
我正在使用 Unity 开发 3d 手机游戏,我已经到了需要编写多人游戏部分的地步。我用 node.js/socket.io 编写了所有匹配等,但我在游戏部分遇到了问题。问题是;每当用户移动时,他都会将自己的位置传输给其他用户。但是尽管服务器非常强大(4 GHz cpu,16 GB 内存),但用户并没有像在单人游戏中那样移动(我的意思是 AI 移动)。它们似乎滑动了几帧,这使得它们的移动不如我预期的那么流畅。我对可能导致这种情况的原因有一些想法,例如 FPS 差异。由于其中一个用户的发射速度可能比其他用户慢,因为他们之间的 FPS 差异。有什么想法可以解决这个问题吗?
这有点取决于您在 Clients/Server 之间传输数据的准确程度。
(这里假设你只使用 Unity 的网络而不是框架)
您永远不应直接将接收方客户端上的新位置设置为
transform.position = X.Yf;
而是因为您已经注意到的滞后/时间因素而进行一些插值。
transform.position = Vector3.Lerp(actualPosition, receivedPosition, Time.deltaTime * interpolationRate);
我不太喜欢 Unity 的 NetworkTransform - 因为我没能让它顺利运行。所以我写了自己的同步脚本(当然做了很多谷歌搜索;))
我将在此处仅针对职位提供示例。您应该能够对其进行调整,然后自己编写旋转部分。它可能并不完美,但这就是我使用它的方式(到目前为止非常满意)。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class CustomNetworkTransform : NetworkBehaviour
{
/* If we are not the localPlayer we will save here
* the last received Position to which we are actually moving.
* (see below in the transmition part) */
private Vector3 _lastReceivedPosition = Vector3.zero;
/*#############################################*/
/* THE RECEIVING PART */
/* The interpolation rate. You have to tweak this maybe
* but 15 seemed a good value so far */
private float _interpolationRate = 15.0f;
private void Update()
{
/* I used hasAuthority here instead of isLocalPlayer
* so it works with a Host-Clients
* setup as well as with a Client-Server-Clients one.
*/
if (hasAuthority) return;
ReceiveData();
}
private void ReceiveData()
{
InterpolatePosition(_lastReceivedPosition, Time.deltaTime * _interpolationRate);
}
private void InterpolatePosition(Vector3 receivedPosition, float factor)
{
transform.position = Vector3.Lerp(transform.position, receivedPosition, factor);
}
/* That's already all for the receiving Part. Maybe that already
* Fits your need. Down here I anyway add the way how I transmit stuff. Maybe it helps you to understand better the way Networking works in Unity.
*/
/*#############################################*/
/* THE TRANSMITION PART */
/* To save a bit of Bandwidth we use this as a treshold
* If you whish you can set this also to 0 */
[Tooltip("The Accuracy of synchronized Positions")]
[SerializeField]
private float _positionAccuracy = 0.05f;
/* Make a forced Transmition every x seconds
* also if nothing was changed */
[SerializeField]
private float _forcedSyncTimeout = 2.0f;
/* Counter for the forced transmition
* I set it to 0 at the beginning to have an instant
* sync when connecting. */
private float _timeout = 0;
/* Wel'll compare the actual position to this one and only
* sync, if the distance is bigger than the _positionAccuracy */
private Vector3 _lastTransmittedPosition = Vector3.zero;
private void FixedUpdate()
{
/* again here I use hasAuthority instead of
* isLocalPlayer so it works in both connection designs */
if (!hasAuthority) return;
TransmitChangedData();
_timeout-= Time.deltaTime;
if (_timeout > 0) return;
TransmitData();
_timeout = _forcedSyncTimeout;
}
/* This is the forced transmition
* which doesn't check for changes */
private void TransmitData()
{
TransmitPositionToServer(transform.position)
}
/* This is the normal transmition which checks
* if changes are big enough before transmitting */
private void TransmitChangedData()
{
if(Vector3.Distance(transform.position, _lastTransmittedPosition) > _positionAccuracy)
{
TransmitPositionToServer(transform.position);
_lastTransmittedPosition = transform.position;
}
}
/* NOTE: for the transmition I don't use SyncVar
* but rather a 3-Step Syncronization:
*
* 1. Client transmits data to Server
* 2. Server transmits data to all Clients
* 3. All other Clients receive and interpolate the position
*
* I felt more comfortable doing this to have
* more freedom and better debug options.
*
* This will also do all the interpolations on the server as well
* so it can be used e.g. to observe. */
//STEP 1.
/*
* Only performed on clients
*/
[Client]
private void TransmitPositionToServer(Vector3 value)
{
CmdPushPositionToServer(value);
}
//STEP 2.
/*
* Invoced from the client but only performed on the server
*/
[Command]
private void CmdPushPositionToServer(Vector3 value)
{
_lastReceivedPosition = value;
RpcProvidePositionToClients(value);
}
//STEP 3.
/*
* Invoced from the server but only performed on all the client
*/
[ClientRpc]
private void RpcProvidePositionToClients(Vector3 value)
{
/* This value is also set on the originally sending client
* but it doesn't matter since we don't move this client
* because he has the local authority (see Update() )*/
_lastReceivedPosition = value;
}
}
希望对您有所帮助。
我什至曾经看到另一种解决方案使用某种管道插值将所有接收到的数据包存储在一个数组中并通过它进行插值,但在我看来它并没有更好地工作。