Multiplayer 3D Game Unity, C#

👤 Sharing: AI
```csharp
using UnityEngine;
using Unity.Netcode;

public class PlayerController : NetworkBehaviour
{
    public float moveSpeed = 5f;
    public float rotationSpeed = 500f;
    public GameObject bulletPrefab;
    public Transform bulletSpawnPoint; // Attach an empty GameObject as a child for precise spawn location

    private NetworkVariable<Color> playerColor = new NetworkVariable<Color>(); //Example of a simple synced var


    //Called when this object is spawned over the network.  This will not be called for prefabs.
    public override void OnNetworkSpawn()
    {
        //This is called on all clients, so only change the color if this is our player
        if (IsOwner)
        {
            RequestColorChange(); //Initial color request
        }

        //This is called on all clients and the server, so subscribe to value changes here
        playerColor.OnValueChanged += OnPlayerColorChanged;
    }

    private void Update()
    {
        // Only control the player if this is the local player
        if (!IsOwner) return;

        HandleMovement();
        HandleRotation();
        HandleShooting();
    }

    //Use ClientRpcParams when you want to pass data for a specific client
    [ClientRpc]
    public void ApplyColorClientRpc(Color newColor, ClientRpcParams clientRpcParams = default)
    {
        GetComponent<Renderer>().material.color = newColor;
    }


    //Server RPCs are called on the client, and run on the server
    [ServerRpc]
    public void RequestColorChangeServerRpc(Color newColor, ServerRpcParams serverRpcParams = default)
    {
        Debug.Log("Color change request from " + serverRpcParams.ClientId);

        playerColor.Value = newColor; //This will automatically sync the value and trigger the callback on all clients
    }


    private void OnPlayerColorChanged(Color previousValue, Color newValue)
    {
        //This is called on all clients when the value changes, so apply the color to the material
        GetComponent<Renderer>().material.color = newValue;
    }

    private void RequestColorChange()
    {
        RequestColorChangeServerRpc(new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)));
    }

    private void HandleMovement()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(horizontalInput, 0, verticalInput).normalized;
        transform.Translate(movement * moveSpeed * Time.deltaTime);
    }

    private void HandleRotation()
    {
        float mouseX = Input.GetAxis("Mouse X");
        transform.Rotate(Vector3.up, mouseX * rotationSpeed * Time.deltaTime);
    }

    private void HandleShooting()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            ShootServerRpc();
        }
    }

    [ServerRpc]
    private void ShootServerRpc()
    {
        GameObject bullet = Instantiate(bulletPrefab, bulletSpawnPoint.position, bulletSpawnPoint.rotation);

        //  Important:  Make the bullet a NetworkObject BEFORE trying to spawn it!
        NetworkObject bulletNetworkObject = bullet.GetComponent<NetworkObject>();
        if (bulletNetworkObject != null)
        {
             bulletNetworkObject.Spawn(true); //  "true" to destroy when parent destroyed
        }
        else
        {
            Debug.LogError("Bullet prefab is missing the NetworkObject component.  Can't spawn it across the network.");
            Destroy(bullet);
            return; //Important!  Exit early so we don't continue with code meant for a spawned bullet.
        }


        //Apply force to the bullet *after* spawning it, otherwise physics may not work correctly on clients.
        Rigidbody rb = bullet.GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.AddForce(bullet.transform.forward * 500f);
        }
        else
        {
            Debug.LogError("Bullet prefab is missing a Rigidbody component.");
        }



        Destroy(bullet, 3f); // Destroy after 3 seconds (on the server, will propagate to clients)
    }
}
```

**Explanation:**

1.  **`PlayerController : NetworkBehaviour`:**
    *   The script inherits from `NetworkBehaviour`. This is crucial because it gives the script the ability to use Netcode for GameObjects features, making it network-aware. Only `NetworkBehaviour` can send server and client RPCs, spawn networked objects, and more.

2.  **Variables:**
    *   `moveSpeed`:  Determines how fast the player moves.
    *   `rotationSpeed`:  Determines how fast the player rotates.
    *   `bulletPrefab`:  A reference to the bullet prefab.  Drag your bullet prefab into this field in the Unity editor.
    *   `bulletSpawnPoint`: A `Transform` representing the point from which bullets are fired.  Create an empty game object as a child of your player prefab and position it at the barrel of your weapon.
    *   `playerColor`:  A `NetworkVariable<Color>` that stores the player's color and automatically synchronizes it across the network.

3.  **`OnNetworkSpawn()`:**
    *   This method is automatically called when the network object associated with this script is spawned on the network (both on the server and all connected clients).
    *   `IsOwner`:  Checks if this instance of the script is running on the client that owns the player object.
    *   `RequestColorChange()`: Calls a function (explained later) to request a random color for the player.
    *   `playerColor.OnValueChanged += OnPlayerColorChanged;`: Subscribes to the `OnValueChanged` event of the `playerColor` network variable. This means that whenever the `playerColor`'s value changes, the `OnPlayerColorChanged` method will be called on all clients.

4.  **`Update()`:**
    *   This method is called every frame.
    *   `if (!IsOwner) return;`:  This is a vital check. It ensures that only the client that *owns* this player object executes the movement and shooting logic.  This prevents multiple players from controlling the same character, which would result in strange behavior.
    *   `HandleMovement()`, `HandleRotation()`, `HandleShooting()`:  These methods handle the player's movement, rotation, and shooting, respectively.

5.  **`HandleMovement()`:**
    *   Gets input from the "Horizontal" (A/D or Left/Right arrow keys) and "Vertical" (W/S or Up/Down arrow keys) axes.
    *   Creates a movement vector based on the input.
    *   Normalizes the movement vector to ensure consistent speed in all directions.
    *   Moves the player using `transform.Translate()`.

6.  **`HandleRotation()`:**
    *   Gets input from the "Mouse X" axis (horizontal mouse movement).
    *   Rotates the player around the Y-axis based on the mouse movement.

7.  **`HandleShooting()`:**
    *   Checks if the "Fire1" button (usually the left mouse button) is pressed.
    *   If the button is pressed, calls the `ShootServerRpc()` method.

8. **RPCs (Remote Procedure Calls):**

    *   RPCs are the mechanism to call functions between client and server.
    *   **`[ServerRpc]`:** This attribute marks a method that is called by a client, but executed on the server.  The server then performs the action on behalf of the client.
    *   **`[ClientRpc]`:** This attribute marks a method that is called by the server, but executed on one or more clients.

9. **`RequestColorChangeServerRpc`**
    *   This function is called from the client using `RequestColorChange()`.
    *   The `[ServerRpc]` attribute indicates that this function executes on the server.  Any client can call this function to request a color change.
    *   `ServerRpcParams` are used to get information about the client that called the Server RPC.  In this case, we're using it to log the client ID.
    *   The `playerColor.Value = newColor` assignment triggers the `OnPlayerColorChanged` callback on all clients (including the server) because `playerColor` is a `NetworkVariable`.

10. **`OnPlayerColorChanged`**
    *   This function is automatically called on every client (and the server) whenever the `playerColor` changes.  This happens because we subscribed to `playerColor.OnValueChanged` in `OnNetworkSpawn`.
    *   This function updates the player's material color using the new value of `playerColor`.

11. **`ShootServerRpc()`:**
    *   This function is called when the player presses the "Fire1" button.
    *   The `[ServerRpc]` attribute indicates that this function executes on the server.  The server creates the bullet and handles its physics.
    *   `Instantiate()`: Creates a new instance of the `bulletPrefab` at the specified position and rotation.
    *   `NetworkObject bulletNetworkObject = bullet.GetComponent<NetworkObject>();`: Gets the `NetworkObject` component from the instantiated bullet.  This component *must* exist on your bullet prefab for network spawning to work.  Add a NetworkObject component to the root GameObject of your bullet prefab.
    *   `bulletNetworkObject.Spawn(true)`:  Spawns the bullet on the network, making it visible to all connected clients.  The `true` argument means the bullet will be despawned when this NetworkObject is destroyed.  Make sure `NetworkObject` is on the root object.
    *   `Rigidbody rb = bullet.GetComponent<Rigidbody>();`: Gets the Rigidbody component from the bullet for physics simulation.  Add a Rigidbody component to your bullet prefab.
    *   `rb.AddForce(bullet.transform.forward * 500f);`: Applies a force to the bullet to propel it forward.  Adjust the force multiplier (500f) to control the bullet's speed.
    *   `Destroy(bullet, 3f);`: Destroys the bullet after 3 seconds. Because the bullet is spawned across the network, destroying it on the server will destroy it on all clients.

**How to Use:**

1.  **Create a new Unity project.**
2.  **Install Netcode for GameObjects:**
    *   Go to `Window -> Package Manager`.
    *   Search for "Netcode for GameObjects" and install it.  You might also need to enable "Show preview packages".
3.  **Create Player Prefab:**
    *   Create a new 3D object (e.g., a Cube or Capsule) in your scene. This will be your player.
    *   Add a `CharacterController` component to the player object.  A CharacterController is often preferred to a Rigidbody for player movement because it provides collision detection and basic movement without requiring you to manually manage physics forces as heavily.  You can use a Rigidbody if you prefer, but you'll need to write the movement logic to use `Rigidbody.MovePosition` and avoid using `transform.Translate` or setting `transform.position` directly.
    *   Add a `NetworkObject` component to the player object.
    *   Add the `PlayerController` script to the player object.
    *   Create an empty GameObject as a child of the player and name it "BulletSpawnPoint".  Position this at the end of the player's weapon (e.g., if the player is a capsule, put it at the top end).
    *   Drag the `BulletSpawnPoint` object into the `Bullet Spawn Point` field of the `PlayerController` script in the Inspector.
    *   Create a prefab from the player object by dragging it from the Hierarchy window into your Project window.  Delete the player object from the scene after creating the prefab.
4.  **Create Bullet Prefab:**
    *   Create a new 3D object (e.g., a Sphere or Cube) in your scene. This will be your bullet.
    *   Add a `Rigidbody` component to the bullet object.  Set `Use Gravity` to `false`.
    *   Add a `NetworkObject` component to the bullet object.
    *   Create a prefab from the bullet object.  Delete the bullet object from the scene.
    *   Drag the `Bullet` prefab into the `Bullet Prefab` field of the `PlayerController` script in the Inspector of your player prefab.
5.  **Create a `NetworkManager` GameObject:**
    *   Create an empty GameObject in your scene and name it "NetworkManager".
    *   Add a `NetworkManager` component to the "NetworkManager" object.
    *   Add a `UnityTransport` component to the "NetworkManager" object.  The default settings for the transport are usually sufficient for testing locally.
    *   In the `NetworkManager` inspector, drag your player prefab into the "Player Prefab" field.
6.  **Set up Input:**
    *   Go to `Edit -> Project Settings -> Input Manager`.
    *   Make sure you have "Horizontal", "Vertical", and "Fire1" input axes defined.  The defaults should work.
7.  **Testing:**
    *   In the Unity editor, click the "Play" button. The game will start in server mode.  You should see the "Server" scene running.
    *   Start another instance of the game (e.g., by building the project and running the executable).  This will be your client.  You should see the "Client" scene running.
    *   The client will automatically connect to the server.
    *   You should now be able to control both players independently.
    *   If running the executable does not connect, you can change the `NetworkManager` "Start in" option to client for one editor window and server for the other.
    *   You can also open multiple editor windows and test with multiple clients.

**Important Considerations:**

*   **Server Authority:**  This example uses server authority for spawning the bullets.  This is a good practice for preventing cheating and ensuring consistency across the network. The server decides when and where the bullets are created.
*   **Input Handling:** The `Input.GetAxis()` calls in `Update()` read input every frame. This is simple but can be inconsistent across different framerates.  For more advanced input handling, consider using the new Input System.
*   **Error Handling:** The script includes some basic error handling (e.g., checking if the `NetworkObject` and `Rigidbody` components exist on the bullet prefab).  In a real game, you would want to add more robust error handling.
*   **Scalability:**  This example is a basic demonstration.  For a large multiplayer game, you would need to consider scalability issues, such as using a more efficient network transport, optimizing the code, and implementing techniques like object pooling.
*   **Security:**  In a real game, you would need to implement security measures to prevent cheating and protect against malicious attacks.
*   **Netcode versions:** Ensure that the version of Netcode for GameObjects you are using is compatible with your Unity version.
*   **Network Transform:** Use a `NetworkTransform` component on the player (or a child object) to sync the player's position and rotation across the network. This can be easier than manually syncing the transform yourself, but you might need to customize it for smoother movement.
*   **Client Prediction/Reconciliation:** For better responsiveness in online games, especially with higher latencies, research and implement client-side prediction and server reconciliation techniques.  Client prediction allows the client to anticipate the results of their actions immediately, while server reconciliation corrects the client's prediction if it deviates from the server's actual state.  This is a more advanced topic.

This comprehensive example provides a solid foundation for building a basic multiplayer game in Unity using Netcode for GameObjects and C#.  Remember to adapt and expand upon it based on your specific game requirements.
👁️ Viewed: 6

Comments