【问题标题】:Why is my player's movement speed changing? (using Fishnet with Unity)为什么我的玩家的移动速度会发生变化? (在 Unity 中使用渔网)
【发布时间】:2022-06-17 19:54:44
【问题描述】:

我是 Unity 的新手,我已经开始学习如何使用 Fishnet 网络。我创建了一个基本的玩家移动脚本,它可以比网络转换更快地同步玩家位置。但是我遇到了一个奇怪的问题,我不知道如何解决。

在我的场景中,我有一个网络管理器,它在连接后会生成我的播放器预制件——一个带有播放器脚本和网络对象的简单精灵。我没有添加网络转换,因为我将手动同步每个玩家的位置以减少客户端之间的延迟。这是播放器脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FishNet.Object;

public class Player : NetworkBehaviour
{
    private void Update()
    {
        if (IsOwner) //only the client that owns this object will run this code
        {
            //get input, send it to server
            float horizontalInput = Input.GetAxisRaw("Horizontal");
            float verticalInput = Input.GetAxisRaw("Vertical");
            RpcMoveCharacter(horizontalInput, verticalInput);
        }

        //since this is an observers rpc, only the server will call it
        RpcSendCharacterPosition(transform.position.x, transform.position.y);
    }

    [ServerRpc]
    public void RpcMoveCharacter(float x, float y)
    {
        //change the position of the server's instance of the player
        transform.Translate(x * 10f * Time.deltaTime * Vector3.right);
        transform.Translate(y * 10f * Time.deltaTime * Vector3.up);
    }

    [ObserversRpc]
    public void RpcSendCharacterPosition(float x, float y)
    {
        if (IsClientOnly)
        {
            //ensure clients' instance of the player match the server's' position
            transform.position = new Vector2(x, y);
        }
    }
}

脚本运行良好...除了一个问题:两个玩家的玩家移动速度不一致。只有当我构建并运行我的游戏,然后连接两个版本的游戏时才会出现这些问题。

当任一玩家是主机(服务器 + 客户端)时,他们的玩家对象在两个屏幕上均以中等速度移动。这是预期的速度。

当从我的统一编辑器窗口运行的游戏版本只是一个客户端时,玩家在两个屏幕上的移动速度都很快——比预期快很多倍。

当我使用“构建并运行”创建的游戏版本只是一个客户端时,玩家在两个屏幕上的移动速度都很慢——比预期慢很多倍。

我已经测试了我能想到的一切。我做的一项测试是防止网络管理器生成播放器预制件,提前将播放器对象放置在场景中,然后进行转换:

    private void Update()
    {
        if (IsOwner)
        {
            float horizontalInput = Input.GetAxisRaw("Horizontal");
            float verticalInput = Input.GetAxisRaw("Vertical");
            RpcMoveCharacter(horizontalInput, verticalInput);
        }

        RpcSendCharacterPosition(transform.position.x, transform.position.y);
    }

    [ServerRpc]

到这里:

    private void Update()
    {
        //now anyone can control the player object
        float horizontalInput = Input.GetAxisRaw("Horizontal");
        float verticalInput = Input.GetAxisRaw("Vertical");
        RpcMoveCharacter(horizontalInput, verticalInput);

        RpcSendCharacterPosition(transform.position.x, transform.position.y);
    }
    //same effect as note above
    [ServerRpc (RequireOwnership = false)]

为了查看是否存在有关玩家生成功能的问题。我的更改效果为零——根本没有任何改变。 如果我的 editor 是一个客户端,它仍然移动播放器太快,如果我的 build 是一个客户端,它仍然移动播放器太慢。强>

我尝试的另一件事是制作一个全新的项目,以防我在上一个项目中奇怪地切换了设置或其他东西。一旦我创建了一个新项目,我所做的就是导入鱼网,将鱼网的默认 NetworkManager 对象添加到我的场景中,创建一个名为 player 的简单预制件,将一个网络对象和原始播放器脚本添加到播放器预制件中,将网络管理器设置为生成播放器预制件,然后再试一次。不走运——一切都完全一样。

有什么想法吗?我被困在这里——我不知道还能尝试什么,因为代码/场景中的一切似乎都运行良好。我无法弄清楚为什么我的构建会与我的编辑器的播放模式表现不同,无论哪个是服务器(或主机),哪个只是客户端。

谢谢!

【问题讨论】:

  • 一般注意你总结了垂直和水平移动 -> 移动对角线时你会移动得更快(使用Vector3.ClampMaginutde来避免这种情况)
  • 一般来说,调用网络方法每一帧都不是一个好主意,你不能保证它们以相同的时间间隔到达服务器,因为所有设备都有不同的每秒帧数 => 所以Time.deltaTime 在服务器端将无效,因为一个玩家可能仍然比另一个更频繁地调用它。您应该以特定的时间间隔调用它,并确保服务器正确补偿这个固定的时间间隔(例如,存储每个玩家最后收到的时间戳并乘以差异)或在客户端计算它并发送最终动作
  • 我很想在客户端计算它并发送最终动作,但我不知道该怎么做。我已经尝试过 transform.Translate 和 transform.position 无济于事 - 我该怎么做?我应该使用水平输入和垂直输入变量来确定 x 和 y 变量(然后我可以将它们传递给服务器)吗?我怎么做?以及如何在不使用更新每帧发送 rpc 的情况下发送最终动作?我找不到这方面的任何指南(除了一些使用过时的网络系统的十年前的论坛帖子)
  • var movement = transform.rotation * Vector2.ClampMagnitude(new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")), 1f) * 10f * speed * Time.deltaTime; 这是您在客户端执行的操作,然后您在 transform.position += movement; 中使用此操作,或者计算每个客户端的位置,然后只同步最终位置(这就是我更喜欢的)
  • 知道了!这与我尝试做的非常相似,我只是缺少一些基本的东西,谢谢!不过,再一次,在不使用更新方法的情况下同步最终位置的最佳方法是什么? (据我所知,SyncVars 只与服务器>客户端通信,除非我遗漏了什么。这意味着我需要一个 rpc,如果我要使用 ServerRpc,如果不在更新函数中,我应该在哪里调用它?太棒了!

标签: c# unity3d network-programming


【解决方案1】:

所以我真的不知道这个Fishnet 究竟是如何工作的。

但正如在任何网络中一般所说的那样,您永远不能依赖

  • 您的所有设备都以相同的 FPS(每秒帧数)运行
  • 您的联网消息立即到达服务器/其他客户端
  • 您的网络消息到达服务器/其他客户端的时间间隔与您结束它们的时间完全相同

所以我更愿意做的是

  • 首先不要每帧发送网络消息,而是一些固定的时间间隔(例如,通常每 0.2 秒使用一次)

  • 而是立即在本地处理所有本地移动

    如果您将用户输入发送到服务器并且必须等到它通过接收返回的结果位置来应用,这对 UX 来说将是非常糟糕的。这会导致 2 倍的网络延迟,这对于本地用户来说是极其不可思议的。

  • 而不是增量,而是同步结果位置值。

    通过这种方式,您可以确保所有玩家都与实际产生的位置同步,并且对于稍后加入会话或由于网络延迟而可能错过一些输入消息的玩家也可以立即生效。

所以我会做类似的事情

public class Player : NetworkBehaviour
{
    // Interval in seconds how often to send your position to the server/clients
    [SerializeField] private float sendInterval = 0.2f;
    
    // How fast you can move in units per second
    [SerializeField] private float moveSpeed = 10f;

    // Use this to adjust your input sensitivities 
    [SerializeField] [Min(0)] private float inputSensitivityX = 1f;
    [SerializeField] [Min(0)] private float inputSensitivityY = 1f;

    // Might have to play a bit with this value to make smooth interpolation faster or slower
    // 5 is an arbitrary value but works quite good from experience
    // depends on your sendInterval and movespeed as well
    [SerializeField] privte float interpolation = 5f;

    // keeps track of passed time
    private float sendTimer;

    private Vector2 receivedTargetPosition;

    private void Start()
    {
        if(!IsOwner)
        {
            receivedTargetPosition = transform.position;
        }
    }

    private void Update()
    {
        //only the client that owns this object will run this code
        if (IsOwner) 
        {
            //get input
            var horizontalInput = Input.GetAxisRaw("Horizontal");
            var verticalInput = Input.GetAxisRaw("Vertical");

            var input = new Vector2(horizontalInput * inputSensitivityX, verticalInput * inputSensitivityY);
            // Makes sure that you always have maximum 1 magnitude for the input
            input = Vector2.ClampMagnitude(input, 1f);

            // use the rotation to already rotate this vector from local into world space
            input = trasform.rotation * input;

            // Here you want the deltaTime of THIS DEVICE
            var movement = moveSpeed * Time.deltaTime * input;

            // Move your player LOCALLY
            transform.position += (Vector3)movement;
        }
        // If you are not the owner you rather apply the received position
        else
        {
            // I would e.g. smoothly interpolate somewhat like
            transform.position = Vector3.Lerp(transform.position, receivedTargetPosition, interpolation * Time.deltaTime);
        }

        // Check if next send time interval has passed
        sendTimer += Time.deltaTime;
        if(sendTimer >= sendInterval)
        {
            sendTimer = 0;

            if(IsServer)
            {
                RpcSendPositionToClients(transform.position.x, transform.position.y);
            }
            else
            {
                RpcSendPositionToServer(transform.position.x, transform.position.y);
            }
        } 
    }

    [ServerRpc]
    public void RpcSendPositionToServer(float x, float y)
    {
        // just in case
        // the owner already gets its position in Update so nothing to do
        if(IsOwner) return;

        //change the position of the server's instance of the player
        receivedTargetPosition = new Vector2(x, y);
    }

    [ClientRpc]
    public void RpcSendPositionToClients(float x, float y)
    {
        // Owner and server already know the positions
        if(IsOwner || IsServer) return;
        
        receivedTargetPosition = new Vector2(x, y);
    }
}

【讨论】:

  • @azeTrom 更新了代码;)现在应该这样做
  • 现在很好用,谢谢!! ...除了两个客户端之间存在相当大的延迟。当然,如果我删除与 Lerping 和 SendIntervals 相关的代码,延迟就会消失——但正如你所提到的,这并不理想......有什么建议可以减少延迟而不在每一帧上调用 rpcs?
【解决方案2】:

FishNet 的创建者在这里。比内置 NetworkTransform 更快地更新转换数据的唯一可能方法是发送 RPC 并在每次数据到达时捕捉转换。

否则在 NetworkTransform 上将插值和间隔设置为 1 将发送每个刻度,并且仅平滑 1 个刻度。这实际上是在不捕捉它们的情况下更新变换的最快方法。

如果您希望整个网络移动得更快,那么将 TimeManager 组件添加到您的网络管理器预制件中并提高滴答率。

【讨论】:

    猜你喜欢
    • 2020-09-13
    • 2018-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多