Hi, I’m making a multiplayer game, but I’m having a problem with the client falls through the mesh when connecting to a host. I find that if I put a plane under the mesh it collides with it, so I assume it’s just that the collider doesn’t load in time to catch the client. I don’t know how to solve this problem though. Does anyone have any suggestions?
(the guest does not fall through the mesh)
Thank you for all the replies
MeshGeneration script:
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);
}
}*/
}
Player Script:
// Some stupid rigidbody based movement by Dani
using System;
using UnityEngine;
using Unity.Netcode;
using System.Transactions;
public class PlayerMovement : NetworkBehaviour
{
//Assingables
public Transform playerCam;
public Transform orientation;
//Other
private Rigidbody rb;
//Rotation and look
private float xRotation;
private float sensitivity = 50f;
private float sensMultiplier = 1f;
//Movement
public float moveSpeed = 4500;
public float maxSpeed = 20;
public bool grounded;
public LayerMask whatIsGround;
public float counterMovement = 0.175f;
private float threshold = 0.01f;
public float maxSlopeAngle = 35f;
//Crouch & Slide
private Vector3 crouchScale = new Vector3(1, 0.5f, 1);
private Vector3 playerScale;
public float slideForce = 400;
public float slideCounterMovement = 0.2f;
//Jumping
private bool readyToJump = true;
private float jumpCooldown = 0.25f;
public float jumpForce = 550f;
//Input
float x, y;
bool jumping, sprinting, crouching;
//Sliding
private Vector3 normalVector = Vector3.up;
private Vector3 wallNormalVector;
public GameObject cameraHolder;
/* public override void OnNetworkSpawn()
{
if (!IsOwner)
{
enabled = false;
return;
}
}*/
public override void OnNetworkSpawn()
{ // This is basically a Start method
cameraHolder.SetActive(IsOwner);
base.OnNetworkSpawn(); // Not sure if this is needed though, but good to have it.
transform.position = new Vector3 (DataStorage.x, DataStorage.y + 2, DataStorage.z);
}
void Awake()
{
rb = GetComponent<Rigidbody>();
}
void Start()
{
playerScale = transform.localScale;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
private void FixedUpdate()
{
Movement();
}
private void Update()
{
MyInput();
Look();
}
/// <summary>
/// Find user input. Should put this in its own class but im lazy
/// </summary>
private void MyInput()
{
x = Input.GetAxisRaw("Horizontal");
y = Input.GetAxisRaw("Vertical");
jumping = Input.GetButton("Jump");
crouching = Input.GetKey(KeyCode.LeftControl);
//Crouching
if (Input.GetKeyDown(KeyCode.LeftControl))
StartCrouch();
if (Input.GetKeyUp(KeyCode.LeftControl))
StopCrouch();
}
private void StartCrouch()
{
transform.localScale = crouchScale;
transform.position = new Vector3(transform.position.x, transform.position.y - 0.5f, transform.position.z);
if (rb.velocity.magnitude > 0.5f)
{
if (grounded)
{
rb.AddForce(orientation.transform.forward * slideForce);
}
}
}
private void StopCrouch()
{
transform.localScale = playerScale;
transform.position = new Vector3(transform.position.x, transform.position.y + 0.5f, transform.position.z);
}
private void Movement()
{
//Extra gravity
rb.AddForce(Vector3.down * Time.deltaTime * 10);
//Find actual velocity relative to where player is looking
Vector2 mag = FindVelRelativeToLook();
float xMag = mag.x, yMag = mag.y;
//Counteract sliding and sloppy movement
CounterMovement(x, y, mag);
//If holding jump && ready to jump, then jump
if (readyToJump && jumping) Jump();
//Set max speed
float maxSpeed = this.maxSpeed;
//If sliding down a ramp, add force down so player stays grounded and also builds speed
if (crouching && grounded && readyToJump)
{
rb.AddForce(Vector3.down * Time.deltaTime * 3000);
return;
}
//If speed is larger than maxspeed, cancel out the input so you don't go over max speed
if (x > 0 && xMag > maxSpeed) x = 0;
if (x < 0 && xMag < -maxSpeed) x = 0;
if (y > 0 && yMag > maxSpeed) y = 0;
if (y < 0 && yMag < -maxSpeed) y = 0;
//Some multipliers
float multiplier = 1f, multiplierV = 1f;
// Movement in air
if (!grounded)
{
multiplier = 0.5f;
multiplierV = 0.5f;
}
// Movement while sliding
if (grounded && crouching) multiplierV = 0f;
//Apply forces to move player
rb.AddForce(orientation.transform.forward * y * moveSpeed * Time.deltaTime * multiplier * multiplierV);
rb.AddForce(orientation.transform.right * x * moveSpeed * Time.deltaTime * multiplier);
}
private void Jump()
{
if (grounded && readyToJump)
{
readyToJump = false;
//Add jump forces
rb.AddForce(Vector2.up * jumpForce * 1.5f);
rb.AddForce(normalVector * jumpForce * 0.5f);
//If jumping while falling, reset y velocity.
Vector3 vel = rb.velocity;
if (rb.velocity.y < 0.5f)
rb.velocity = new Vector3(vel.x, 0, vel.z);
else if (rb.velocity.y > 0)
rb.velocity = new Vector3(vel.x, vel.y / 2, vel.z);
Invoke(nameof(ResetJump), jumpCooldown);
}
}
private void ResetJump()
{
readyToJump = true;
}
private float desiredX;
private void Look()
{
float mouseX = Input.GetAxis("Mouse X") * sensitivity * Time.fixedDeltaTime * sensMultiplier;
float mouseY = Input.GetAxis("Mouse Y") * sensitivity * Time.fixedDeltaTime * sensMultiplier;
//Find current look rotation
Vector3 rot = playerCam.transform.localRotation.eulerAngles;
desiredX = rot.y + mouseX;
//Rotate, and also make sure we dont over- or under-rotate.
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, -90f, 90f);
//Perform the rotations
playerCam.transform.localRotation = Quaternion.Euler(xRotation, desiredX, 0);
orientation.transform.localRotation = Quaternion.Euler(0, desiredX, 0);
}
private void CounterMovement(float x, float y, Vector2 mag)
{
if (!grounded || jumping) return;
//Slow down sliding
if (crouching)
{
rb.AddForce(moveSpeed * Time.deltaTime * -rb.velocity.normalized * slideCounterMovement);
return;
}
//Counter movement
if (Math.Abs(mag.x) > threshold && Math.Abs(x) < 0.05f || (mag.x < -threshold && x > 0) || (mag.x > threshold && x < 0))
{
rb.AddForce(moveSpeed * orientation.transform.right * Time.deltaTime * -mag.x * counterMovement);
}
if (Math.Abs(mag.y) > threshold && Math.Abs(y) < 0.05f || (mag.y < -threshold && y > 0) || (mag.y > threshold && y < 0))
{
rb.AddForce(moveSpeed * orientation.transform.forward * Time.deltaTime * -mag.y * counterMovement);
}
//Limit diagonal running. This will also cause a full stop if sliding fast and un-crouching, so not optimal.
if (Mathf.Sqrt((Mathf.Pow(rb.velocity.x, 2) + Mathf.Pow(rb.velocity.z, 2))) > maxSpeed)
{
float fallspeed = rb.velocity.y;
Vector3 n = rb.velocity.normalized * maxSpeed;
rb.velocity = new Vector3(n.x, fallspeed, n.z);
}
}
/// <summary>
/// Find the velocity relative to where the player is looking
/// Useful for vectors calculations regarding movement and limiting movement
/// </summary>
/// <returns></returns>
public Vector2 FindVelRelativeToLook()
{
float lookAngle = orientation.transform.eulerAngles.y;
float moveAngle = Mathf.Atan2(rb.velocity.x, rb.velocity.z) * Mathf.Rad2Deg;
float u = Mathf.DeltaAngle(lookAngle, moveAngle);
float v = 90 - u;
float magnitue = rb.velocity.magnitude;
float yMag = magnitue * Mathf.Cos(u * Mathf.Deg2Rad);
float xMag = magnitue * Mathf.Cos(v * Mathf.Deg2Rad);
return new Vector2(xMag, yMag);
}
private bool IsFloor(Vector3 v)
{
float angle = Vector3.Angle(Vector3.up, v);
return angle < maxSlopeAngle;
}
private bool cancellingGrounded;
/// <summary>
/// Handle ground detection
/// </summary>
private void OnCollisionStay(Collision other)
{
//Make sure we are only checking for walkable layers
int layer = other.gameObject.layer;
if (whatIsGround != (whatIsGround | (1 << layer))) return;
//Iterate through every collision in a physics update
for (int i = 0; i < other.contactCount; i++)
{
Vector3 normal = other.contacts[i].normal;
//FLOOR
if (IsFloor(normal))
{
grounded = true;
cancellingGrounded = false;
normalVector = normal;
CancelInvoke(nameof(StopGrounded));
}
}
//Invoke ground/wall cancel, since we can't check normals with CollisionExit
float delay = 3f;
if (!cancellingGrounded)
{
cancellingGrounded = true;
Invoke(nameof(StopGrounded), Time.deltaTime * delay);
}
}
private void StopGrounded()
{
grounded = false;
}
}
DataStorage Script:
using System.Numerics;
[System.Serializable]
public class DataStorage
{
public static int seed;
public static bool multiplayer = false;
public static bool client = false;
public static bool solo = false;
public static float x;
public static float y;
public static float z;
}
player prefab:

Mesh’s collider:
player collider:
Player’s rigidbody (These components are attached to a player that has a playerModel in it):
NetworkManager:


