【问题标题】:Unity - Networking. Host can't see client changes团结 - 网络。主机看不到客户端更改
【发布时间】:2017-11-30 15:55:46
【问题描述】:

好吧,这让我很沮丧,因为我以前从未做过这种事情,我需要一个解决方案。

所以我的游戏是 2D 的,需要你与其他玩家对抗,以便在时间限制内尽可能多地收集地图中的资源。世界是通过在地图中的每个图块上用一个随机块实例化一个精灵来生成的,玩家通过挖掘他们的方式并根据他们挖掘的资源获得积分。这些块也被保存到一个公共的二维数组中。

我有一个名为 LevelGenerator 的脚本,它在玩家开始游戏时运行。在脚本中,块是随机选择的,然后通过实例化生成,然后由 NetworkServer.Spawn 生成。整个世代被称为[Command]

拥有关卡生成器脚本的游戏对象具有网络身份,既不是服务器,也不是本地玩家权限。

在播放器脚本中,我有一些代码可以将射线从播放器投射到鼠标位置,它工作正常,但是游戏对象的破坏被破坏了。当玩家按下鼠标左键并且光线投射击中一个块时,我销毁命中的对象,在块的二维数组中添加与块的点值相同数量的点,然后将该数组中的块设置为空。当主机破坏一个块时,客户端可以看到这种情况发生,但是当客户端破坏一个块时,客户端显然可以看到它正在破坏,但主机看不到来自该客户端的更改。这可能不相关,但是会抛出一个错误,说块数组为空,即使我是通过 findobjectoftype 直接找到 levelgenerator 的。然而,这不会发生在主机上。地图是相同的,所以 networkserver.spawn 正在工作,但我在同步块以及它们是否损坏时遇到问题。

这些块是预制的,并且都有一个未选中任何框的网络标识,以及一个网络发送速率为 0 的网络转换,因为它们在生成时没有移动。

所以是的,我想不通。任何帮助表示赞赏。谢谢。

关卡生成器:

using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Networking;

public class LevelGenerator : NetworkBehaviour
{
    [SerializeField] private List<Block> blocks; // Blocks are pre-defined in the Unity Editor.
    [SerializeField] private int mapWidth; // How wide the map is.
    [SerializeField] private int mapHeight; // How deep the map is.
    public const int blockSize = 1;
    public Block[,] blockMatrix; // 2D array of all blocks.
    private List<Block> resourceBlocks = new List<Block>();

    private List<GameObject> blockObjects = new List<GameObject>();

    void Start()
    {
        CmdSpawnMap();
    }

    [Command]
    public void CmdSpawnMap()
    {
        blockMatrix = new Block[mapWidth, mapHeight];

        resourceBlocks = blocks.Where(z => z.blockType == "Resource").ToList(); // List of all the blocks that are tagged as resources.
        resourceBlocks = resourceBlocks.OrderBy(z => z.rarity).ToList();

        for (int r = 0; r < resourceBlocks.Count; r++)
        {
            resourceBlocks[r].spawnPercentagesAtLevels = new float[mapHeight];
            for (int s = 1; s < resourceBlocks[r].spawnPercentagesAtLevels.Length; s++)
            {
                resourceBlocks[r].spawnPercentagesAtLevels[s] = resourceBlocks[r].basePercentage * Mathf.Pow(resourceBlocks[r].percentageMultiplier, resourceBlocks[r].spawnPercentagesAtLevels.Length - s);
            }
        }

        // For every block in the map.
        System.Random random = new System.Random();
        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                if (y == mapHeight - 1)
                {
                    Block grass = blocks.Where(z => z.blockName == "Grass").FirstOrDefault();
                    blockMatrix[x, y] = grass;
                    GameObject blockInstance = Instantiate(grass.blockPrefabs[random.Next(0, grass.blockPrefabs.Count - 1)]);
                    blockInstance.transform.position = new Vector3(x * blockSize, y * blockSize, 0);
                    blockInstance.transform.SetParent(transform, false);
                    blockInstance.name = blockMatrix[x, y].blockName + " => " + x + ", " + y;
                    blockObjects.Add(blockInstance);
                    NetworkServer.Spawn(blockInstance);
                    continue;
                }

                bool resourceSpawned = false;
                for (int i = resourceBlocks.Count - 1; i >= 0; i--)
                {
                    float roll = Random.Range(0.0f, 100.0f);
                    if (roll <= resourceBlocks[i].spawnPercentagesAtLevels[y])
                    {
                        blockMatrix[x, y] = resourceBlocks[i];
                        GameObject blockInstance = Instantiate(resourceBlocks[i].blockPrefabs[random.Next(0, resourceBlocks[i].blockPrefabs.Count - 1)]);
                        blockInstance.transform.position = new Vector3(x * blockSize, y * blockSize, 0);
                        blockInstance.transform.SetParent(transform, false);
                        blockInstance.name = blockMatrix[x, y].blockName + " => " + x + ", " + y;
                        blockObjects.Add(blockInstance);
                        resourceSpawned = true;
                        NetworkServer.Spawn(blockInstance);
                        break;
                    }
                }

                if (!resourceSpawned)
                {
                    Block ground = blocks.Where(z => z.blockName == "Ground").FirstOrDefault();
                    blockMatrix[x, y] = ground;
                    GameObject blockInstance = Instantiate(ground.blockPrefabs[random.Next(0, ground.blockPrefabs.Count - 1)]);
                    blockInstance.transform.position = new Vector3(x * blockSize, y * blockSize, 0);
                    blockInstance.transform.SetParent(transform, false);
                    blockInstance.name = blockMatrix[x, y].blockName + " => " + x + ", " + y;
                    blockObjects.Add(blockInstance);
                    NetworkServer.Spawn(blockInstance);
                }

                resourceSpawned = false;
            }
        }
    }
}

玩家:

using UnityEngine;
using UnityEngine.Networking;

[RequireComponent(typeof(Rigidbody2D))]
public class Player : NetworkBehaviour
{
    private Rigidbody2D rb;

    [Header("Player Movement Settings")]
    [SerializeField] private float moveSpeed;

    private bool facingRight;
    [HideInInspector] public int points;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        facingRight = true; // Start facing right
    }

    void FixedUpdate()
    {
        float horizontalSpeed = Input.GetAxis("Horizontal") * moveSpeed;
        float jumpSpeed = 0;

        if (horizontalSpeed > 0 && !facingRight || horizontalSpeed < 0 && facingRight) // If you were moving left and now have a right velocity
        { // or were moving right and now have a left velocity,
            facingRight = !facingRight; // change your direction
            Vector3 s = transform.localScale;
            transform.localScale = new Vector3(s.x * -1, s.y, s.z); // Flip the player when the direction has been switched
        }

        rb.velocity = new Vector2(horizontalSpeed, jumpSpeed);
    }

    void Update()
    {
        GameManager gm = FindObjectOfType<GameManager>();
        LevelGenerator levelGen = FindObjectOfType<LevelGenerator>();
        if (gm.playing)
        {
            Camera playerCam = GetComponentInChildren<Camera>();
            Vector3 direction = playerCam.ScreenToWorldPoint(Input.mousePosition) - transform.position;
            direction.z = 0;
            RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, 1);
            Debug.DrawRay(transform.position, direction, Color.red);
            if (hit)
            {
                if (Input.GetButtonDown("BreakBlock"))
                {
                    Destroy(hit.transform.gameObject);
                    points += levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)].blockPointsValue;
                    levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)] = null;
                }
            }
        }
    }
}

【问题讨论】:

  • 您应该考虑在您的问题中添加代码。正如所写,这是模糊且难以消化的。
  • 好的,我已经添加了代码,如果还有其他内容请告诉我

标签: c# unity3d


【解决方案1】:

很简单,您永远不会从客户端发送网络消息来告诉服务器销毁块。当您销毁服务器上的块时,它会自动告诉所有客户端也删除该块(尽管您应该使用NetworkServer.Destroy()Destroy() 也足以做到这一点)。另一方面,客户端没有权限或方式告诉其他客户端一个块已被破坏。

您需要做的是从客户端向服务器发送一条消息以销毁该块,然后服务器可以告诉所有客户端该块应该被销毁。

这可以通过从客户端到服务器的Command call 来完成。与其销毁块,不如从客户端向服务器发送一条带有您要销毁的块的消息(因为块 GameObject 具有网络标识,您可以将 GameObjects 作为参数传递)。

if (hit)
{
    if (Input.GetButtonDown("BreakBlock"))
    {
        CmdBreakBlock(hit.transform.gameObject);
        points += levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)].blockPointsValue;
        levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)] = null;
    }
}

然后添加您的 Cmd 函数(将在服务器上运行)。

[Command]
void CmdBreakBlock(GameObject block)
{
    NetworkServer.Destroy(block);
}

虽然您应该立即注意到一个问题。现在在客户端想要破坏一个块和它实际发生之间有一点延迟。如果客户端在服务器销毁它之前再次命中该块并将消息发送回客户端,我们将遇到很多问题(客户端将发送另一条消息来销毁现在为空的块)。

那么为什么不直接销毁客户端上的块并告诉服务器也销毁它的版本呢?那么当服务器销毁它并向该客户端发送消息时,客户端将不知道应该销毁什么,因为他们已经销毁了它。

不幸的是,我所知道的使用高级 API 并没有太多干净的解决方案。最好的办法是停用客户端上试图销毁它的对象,而实际上它在服务器上已被销毁,但这是一个有点笨拙的解决方案。

不幸的是,即使这样也不足以解决这个问题。就像您提到的那样,块矩阵对于客户来说是空的。这是有道理的,因为客户端版本的LevelGenerator 从未运行过,所以让我解释一下为什么会这样。

当您拥有一个具有网络身份但没有权限的对象时,将会发生的情况是 Cmd 调用对任何客户端都不起作用。没有权限的命令只能从服务器发送,所以只有LevelGenerator脚本在服务器上运行。

这可能是一件好事,虽然好像每个客户端和服务器的所有 LevelGenerators 都运行了,但它们所要做的就是告诉服务器在服务器上运行地图生成脚本很多次;您的客户仍然不会发生任何事情(除了会在彼此之上生成更多块游戏对象)。

此时我会说你有三个选择:

  • 尝试将块矩阵同步到客户端。将此矩阵同步到客户端的理想方法是使用自动更新的[SyncVar] 属性,但最后我检查了这与自定义类型的多维数组有问题(尽管可能值得一试)。实际上,我希望您必须编写一个不好玩的自定义 OnSerializeOnDeserialize 函数,所以我不确定我是否推荐它。
  • 同步用于生成关卡的种子,然后在客户端上运行关卡生成代码,而无需生成对象。如果客户端在块被销毁之前连接,这可能会起作用,但您仍然需要在客户端和服务器上维护块矩阵,所以我不确定我是否推荐它
  • 不要尝试同步矩阵,只需使用服务器上的矩阵。目前,客户关心矩阵的唯一时间是获取点值,但可以通过在销毁块时添加ClientRPC call 轻松更改。

我推荐的最后一个,你可以试试这样:

  if (Input.GetButtonDown("BreakBlock")) // Ran on client
  {
      CmdBreakBlock(hit.transform.gameObject);
      hit.transform.gameObject.SetActive(false); // Mimic that it was destroyed on the client so they don't try to mine it again.
  }

//...

[Command]
void CmdBreakBlock(GameObject hit) // Ran on server, arguments from client
{
    NetworkServer.Destroy(hit);
    RpcAddPoints(levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)].blockPointsValue);
    levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)] = null;
}

[ClientRpc]
void RpcAddPoints(int p) // Ran on client, arguments from server
{
    points += p;
}

对不起,如果我不能提供太多帮助,我更习惯低级 API,但如果您有任何问题,我很乐意尝试回答!

【讨论】:

  • 哇,多么精彩的回应。我真的很感激,它实际上解决了这个问题!非常感谢您抽出时间来提供帮助。你说的很有道理,我知道问题出在哪里,但没有足够的知识来解决它,所以谢谢!
  • 没问题,总是愿意帮助托马斯的同胞。
  • 嘿,很抱歉回到这个问题,但我只是想知道如何从服务器上的 levelgenerator 获取块?我想为块添加一个名为“outlineTexture”的属性,这样当您查看块时它会突出显示,为此我需要访问块矩阵。我怎样才能做到这一点?谢谢。
  • 嗯,你可以使用相同的结构,通过命令将块游戏对象发送到服务器,然后服务器为你想要的纹理发回某种整数 ID(不能发回完整的纹理)并且您必须为纹理编写某种查找表。不过,将所有块数据放在块预制本身的一个组件上可能是值得的,因此您不需要不断地向服务器询问点值、纹理或其他类似的东西。这样您就不必经常维护您的 blockMatrix,这会让您的生活更轻松。
猜你喜欢
  • 2015-04-20
  • 1970-01-01
  • 2019-03-18
  • 1970-01-01
  • 2013-05-07
  • 1970-01-01
  • 1970-01-01
  • 2021-05-27
  • 2011-03-30
相关资源
最近更新 更多