Разное поколение деревьев для каждого игрока. мультиплеер (сетевой код для GameObjects)

Когда два или более игрока подключаются друг к другу, деревья генерируются для каждого игрока немного по-разному.

Я пытался добавить преобразование Network в дерево префаба, но это не помогло, и оно все равно другое.

Точка зрения ведущего:

Точка зрения клиента:

Это скрипт, который генерирует ландшафт (сетку) и деревья (DataStorage — это класс, который хранит данные между сценами):

using UnityEngine;
using System.Linq;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using Unity.Netcode;

public class MeshGenerator : MonoBehaviour
{
    Mesh mesh;

    Vector3[] vertices;
    int[] triangles;

    [Header("Map settings")]
    public int Size = 20;

    [Space]

    public float heightMultiplier = 20f;

    public AnimationCurve curve;  

    [Header("Noise map")]
    public float scale = 0.3f;

    public int octaves;
    public float presistance;
    public float lacunarity;

    [Space]

    public int seed;
    public Vector2 offset;

    float[,] heightMap;

    [Header("Trees")]
    public GameObject treePrefab;

    [Space]

    public int PosibleTrees;
    public int trees;

    float[,] treeHeightMap;



    [Header("Texture")]
    public Material mat;

    [Space]

    public Layer[] layers;

    const int textureSize = 512;
    const TextureFormat textureFormat = TextureFormat.RGB565;

    Vector2[] uvs;

    [Header("Player")]

    public GameObject playerPrefab;

    bool multiplayer;
    bool client;

    System.Random seedGen;

    async void Awake()
    {
        mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;
        mesh.name = "Mesh";

        heightMap = GenerateNoiseMap(Size, scale, seed, octaves, presistance, lacunarity, offset);
        treeHeightMap = GenerateNoiseMap(Size, scale, seed + 3, octaves, presistance, lacunarity, offset);
        seed = DataStorage.seed;
        multiplayer = DataStorage.multiplayer;
        client = DataStorage.client;

        seedGen = new System.Random(seed);

        await Task.Delay(1000);

        CreateShape();
        UpdateMesh();
     
        LevelManager.instance._loaderConvas.SetActive(false);

    }

    void CreateShape()
    {

        vertices = new Vector3[(Size + 1) * (Size + 1)];


        for (int i = 0, z = 0; z <= Size; z++)
        {
            for (int x = 0; x <= Size; x++)
            {

                float currentHeight = (float)curve.Evaluate((float)heightMap[x, z]) * (float)heightMultiplier;

                GenerateTree(currentHeight, x, z, seedGen);

                vertices[i] = new Vector3(x, currentHeight, z);
                i++;
            }
        }

        triangles = new int[Size * Size * 6];

        int vert = 0;
        int tris = 0;

        for (int z = 0; z < Size; z++)
        {
            for (int x = 0; x < Size; x++)
            {
                triangles[tris + 0] = vert + 0;
                triangles[tris + 1] = vert + Size + 1;
                triangles[tris + 2] = vert + 1;
                triangles[tris + 3] = vert + 1;
                triangles[tris + 4] = vert + Size + 1;
                triangles[tris + 5] = vert + Size + 2;

                vert++;
                tris += 6;
            }
            vert++;
        }

        GenerateUVs();

        SetShaderVariable();
    }

    void GenerateUVs()
    {
        uvs = new Vector2[vertices.Length];
        for (int i = 0, z = 0; z <= Size; z++)
        {
            for (int x = 0; x <= Size; x++)
            {
                uvs[i] = new Vector2((float)x / Size, (float)z / Size);
                i++;
            }
        }
    }

    void SetShaderVariable()
    {
        mat.SetFloat("minHeight", heightMultiplier * curve.Evaluate(0));
        mat.SetFloat("maxHeight", heightMultiplier * curve.Evaluate(1));

        mat.SetInt("layerCount", layers.Length);
        mat.SetColorArray("baseColours", layers.Select(x => x.tint).ToArray());
        mat.SetFloatArray("baseStartHeights", layers.Select(x => x.startHeigth).ToArray());
        mat.SetFloatArray("baseBlends", layers.Select(x => x.blendStrength).ToArray());
        mat.SetFloatArray("baseColourStrength", layers.Select(x => x.tintStrength).ToArray());
        mat.SetFloatArray("baseTextureScales", layers.Select(x => x.textureScale).ToArray());

        Texture2DArray textureArray = GenerateTextureArray(layers.Select(x => x.texture).ToArray());

        mat.SetTexture("baseTextures", textureArray);
    }

    void SpawnPlayer(float currentHeight, Vector3 pos)
    {
        GameObject player = Instantiate(playerPrefab);

        player.transform.position = pos;
    }

    Texture2DArray GenerateTextureArray(Texture2D[] textures)
    {
        Texture2DArray textureArray = new Texture2DArray(textureSize, textureSize, textures.Length, textureFormat, true);

        for(int i = 0; i < textures.Length; i++)
        {
            textureArray.SetPixels(textures[i].GetPixels(), i);
        }

        textureArray.Apply();

        return textureArray;
    }
    void UpdateMesh()
    {
        mesh.Clear();

        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.uv = uvs;

        mesh.RecalculateNormals();

        GetComponent<MeshCollider>().sharedMesh = mesh;

        float currentHeight;
        Vector3 pos;

        while (true)
        {
            int x = seedGen.Next(0, Size);
            int z = seedGen.Next(0, Size);

            currentHeight = (float)curve.Evaluate((float)heightMap[x, z]) * (float)heightMultiplier;

            if (currentHeight > 1.8)
            {
                pos = new Vector3(x, (float)currentHeight + 2f, z);
                break;
            }
        }



     

        if (multiplayer && !client)
        {
            DataStorage.x = (float)pos.x;
            DataStorage.y = (float)pos.y;
            DataStorage.z = (float)pos.z;

            if (NetworkManager.Singleton.StartHost())
            {
                Debug.Log("host");
            }
            else
            {
                Debug.Log("host failed");
            }
        }
        else if (DataStorage.solo == true)
        {
            SpawnPlayer(currentHeight, pos);
            Debug.Log("player");
        }
    }

    float[,] GenerateNoiseMap(int size, float scale, int seed, int octaves, float presistance, float lacunarity, Vector2 offset)
    {
        float[,] noiseMap = new float[size + 1, size + 1];

        System.Random prng = new System.Random(seed);
        Vector2[] octaveOffsets = new Vector2[octaves + 1];

        for (int i = 0; i <= octaves; i++)
        {
            float offsetX = prng.Next(-100000, 100000) + offset.x;
            float offsetZ = prng.Next(-100000, 100000) + offset.y;

            octaveOffsets[i] = new Vector2(offsetX, offsetZ);
        }

        if (scale <= 0)
        {
            scale = 0.0001f;
        }

        float minNoiseHeight = float.MaxValue;
        float maxNoiseHeight = float.MinValue;

        float halfSize = size / 2;

        for (int z = 0; z <= size; z++)
        {
            for (int x = 0; x <= size; x++)
            {

                float amplitude = 1;
                float frequency = 1;
                float noiseHeight = 0;

                for (int i = 0; i <= octaves; i++)
                {
                    float sampleX = (x - halfSize) / scale * frequency + octaveOffsets[i].x;
                    float sampleY = (z - halfSize) / scale * frequency + octaveOffsets[i].y;

                    float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
                    noiseHeight += perlinValue * amplitude;

                    amplitude *= presistance;
                    frequency *= lacunarity;
                }

                if (noiseHeight < minNoiseHeight)
                {
                    minNoiseHeight = noiseHeight;
                }
                else if (noiseHeight > maxNoiseHeight)
                {
                    maxNoiseHeight = noiseHeight;
                }

                noiseMap[x, z] = noiseHeight;
            }
        }

        for (int z = 0; z <= size; z++)
        {
            for (int x = 0; x <= size; x++)
            {
                noiseMap[x, z] = Mathf.InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x, z]);

                float fallOffX = z / (float)size * 2 - 1;
                float fallOffZ = x / (float)size * 2 - 1;

                float value = Mathf.Max(Mathf.Abs(fallOffX), Mathf.Abs(fallOffZ));

                noiseMap[x, z] = Mathf.Clamp01(noiseMap[x, z] - Evaluate(value));
            }
        }

        return noiseMap;
    }

    void GenerateTree(float currentHeight, int x, int z, System.Random prng)
    {

        PosibleTrees++;

        int d = prng.Next(0, 10);
        int c = prng.Next(0, 10);

        double X = prng.NextDouble();
        double Z = prng.NextDouble();
        double Y = prng.NextDouble();

        if (Y < .5f)
        {
            Y = .5f;
        }

        if (treeHeightMap[x, z] >= Y && currentHeight > 1.5 && currentHeight < 10 && d >= 8)
        {
            GameObject tree = Instantiate(treePrefab);

            tree.transform.position = new Vector3((float)(x + X), currentHeight, (float)(z + Z));

            trees++;
        }
    }

    float Evaluate(float value)
    {
        float a = 3;
        float b = 2.2f;

        return Mathf.Pow(value, a) / (Mathf.Pow(value, a) + Mathf.Pow(b - b * value, a));
    }


    [System.Serializable]
    public class Layer
    {
        public Texture2D texture;
        public Color tint;

        [Range(0,1)]
        public float tintStrength;

        [Range(0, 1)]
        public float startHeigth;

        [Range(0, 1)]
        public float blendStrength;

        public float textureScale;
    }

    /*private void OnDrawGizmos()
    {
        Gizmos.color = Color.black;
        for (int i = 0; i < vertices.Length; i++)
        {
            Gizmos.DrawSphere(vertices[i], 0.1f);
        }
    }*/
}

Это скрипт, который прикрепляется к дереву, чтобы деревья не летали в воздухе (возможна проблема со столкновением, поэтому они будут уничтожены, но если это так, я не знаю, как это исправить.) :

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting.Antlr3.Runtime.Tree;
using UnityEngine;

public class TreeMnager : MonoBehaviour
{

    public GameObject groundCheck;
    public GameObject iTree;


    // Start is called before the first frame update
    void Start()
    {
        if (!Physics.CheckSphere(groundCheck.transform.position, .1f, LayerMask.GetMask("Mesh")))
        {
            Destroy(iTree);
        }

        Collider[] hitColliders = Physics.OverlapSphere(iTree.transform.position, 1);
        foreach (var hitCollider in hitColliders)
        {
            if (hitCollider.gameObject != iTree &&
                hitCollider.gameObject.CompareTag("Tree"))
            {
                Destroy(iTree);
            }
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Этот скрипт управляет типом игры (многопользовательская/одиночная):

using System.Threading.Tasks;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class LevelManager : MonoBehaviour
{
    public static LevelManager instance;

    public GameObject _loaderConvas;
    [SerializeField] private Slider _loaderSlider;
    [SerializeField] private InputField input;

    // Start is called before the first frame update
    void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public async void LoadScene(string sceneName)
    {
        int seed = 0;
        int.TryParse(input.text, out seed);

        if (seed == 0)
        {
            seed = Random.Range(-2147483647, 2147483647);
        }

        DataStorage.seed = seed;
        DataStorage.multiplayer = false;
        DataStorage.client = false;
        DataStorage.solo = true;

        var scene = SceneManager.LoadSceneAsync(sceneName);
        scene.allowSceneActivation = false;

       _loaderConvas.SetActive(true);

        do
        {
            await Task.Delay(500);
            _loaderSlider.value = scene.progress;
        } while (scene.progress < 0.9f);

        scene.allowSceneActivation = true;
    }

    public async void LoadSceneHost(string sceneName)
    {
        int seed = 0;
        int.TryParse(input.text, out seed);

        if (seed == 0)
        {
            seed = Random.Range(-2147483647, 2147483647);
        }

        DataStorage.seed = seed;
        DataStorage.multiplayer = true;
        DataStorage.client = false;
        DataStorage.solo = false;

        var scene = SceneManager.LoadSceneAsync(sceneName);
        scene.allowSceneActivation = false;

        _loaderConvas.SetActive(true);

        do
        {
            await Task.Delay(500);
            _loaderSlider.value = scene.progress;
        } while (scene.progress < 0.9f);

        scene.allowSceneActivation = true;
    }

    public void JoinWorld()
    {

        DataStorage.multiplayer = true;
        DataStorage.client = true;
        DataStorage.solo = false;

        if (NetworkManager.Singleton.StartClient())
        {
            Debug.Log("succes");
        }
        else
        {
            Debug.Log("World not found");
        }
    }
}

ну кто генерирует деревья? если каждый из них генерирует свои собственные деревья, почему они должны быть одинаковыми?

BugFinder 13.06.2024 00:26

Ну, местность та же самая, только не деревья, но это правда, что я не отправляю семя, поэтому деревья могут быть созданы из других семени, но в этом случае местность тоже должна быть другой, верно?

Jonda_MC 13.06.2024 06:49

@Jonda_MC мы не можем сказать, не видя твоего кода, если честно

derHugo 13.06.2024 10:53

Позже я могу добавить скрипт, запускающий «сеанс», но сейчас меня нет дома.

Jonda_MC 13.06.2024 14:06

@derHugo, я добавил все сценарии, которые могли вызвать это плохое поколение.

Jonda_MC 13.06.2024 16:35

Это C# или UnityScript?

TylerH 13.06.2024 21:25

@TylerH Это C#

Jonda_MC 13.06.2024 21:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы делаете

heightMap = GenerateNoiseMap(Size, scale, seed, octaves, presistance, lacunarity, offset);
treeHeightMap = GenerateNoiseMap(Size, scale, seed + 3, octaves, presistance, lacunarity, offset);

который оба уже используют seed прямо перед тем, как вы его назначите

seed = DataStorage.seed;

Поэтому позже CreateShape и, наконец, GenerateTree и т. д. обязательно используйте другой

seedGen = new System.Random(seed);

чем предыдущие поколения карт высот.

=> Вероятно, вы захотите переместить это наверх, чтобы все фактически использовало одно и то же seed.


Я знаю: это все еще не объясняет, почему они различаются между хостом и клиентом. Но исправление этой первой проблемы может пролить больше света на эту проблему.

Спасибо, я вообще этого не заметил. Но теперь это имеет смысл, потому что на клиенте я сейчас генерирую мир с начальным значением 0 (поскольку DataStorage.seed не установлен и поэтому равен 0), поэтому мне просто нужно как-то передать начальное значение клиенту, вы знаете как это сделать?

Jonda_MC 13.06.2024 21:32

когда я устанавливаю в скрипте начальное значение 0 для хоста, оно выглядит одинаково, но я думаю, что есть разница между одиночной и многопользовательской игрой, но я бы не беспокоился об этом

Jonda_MC 13.06.2024 22:08

Другие вопросы по теме