Когда два или более игрока подключаются друг к другу, деревья генерируются для каждого игрока немного по-разному.
Я пытался добавить преобразование 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");
}
}
}
Ну, местность та же самая, только не деревья, но это правда, что я не отправляю семя, поэтому деревья могут быть созданы из других семени, но в этом случае местность тоже должна быть другой, верно?
@Jonda_MC мы не можем сказать, не видя твоего кода, если честно
Позже я могу добавить скрипт, запускающий «сеанс», но сейчас меня нет дома.
@derHugo, я добавил все сценарии, которые могли вызвать это плохое поколение.
Это C# или UnityScript?
@TylerH Это C#
Вы делаете
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), поэтому мне просто нужно как-то передать начальное значение клиенту, вы знаете как это сделать?
когда я устанавливаю в скрипте начальное значение 0 для хоста, оно выглядит одинаково, но я думаю, что есть разница между одиночной и многопользовательской игрой, но я бы не беспокоился об этом
ну кто генерирует деревья? если каждый из них генерирует свои собственные деревья, почему они должны быть одинаковыми?