The client is falling through the mesh (*Solved*, but not very well, I'll be glad if you suggest so)

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:
9886596--1426746--upload_2024-6-12_14-55-13.png
Mesh’s collider:


player collider:
9886596--1426755--upload_2024-6-12_14-58-3.png

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

NetworkManager:

I fixed it by modifying the code in PlayerMovement(modified code below), but I don’t think it’s a good solution. if anyone has a better idea, I’d be happy to hear from you.

    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.

        rb = GetComponent<Rigidbody>();

        if (IsHost)
        {
            Vector3 spawnPos = new Vector3(DataStorage.x, DataStorage.y, DataStorage.z);

            transform.position = spawnPos;

            rb.constraints = RigidbodyConstraints.None;
            rb.freezeRotation = true;
        }
        else
        {
            Invoke("DelayClient", .5F);
        }

    }

    void DelayClient()
    {
        rb.constraints = RigidbodyConstraints.None;
        rb.freezeRotation = true;
    }