Night Racer​

Info:

Gemaakt met: Unity
Codeertaal: C#
Mijn rol: Programmer
Tijdspan: 4 weken

Team:

Developers:
Jaiden Bruijn
Matthijs van den Bovenkamp

Artist:
Wan Ting Hu
Jamie de Geus

Mijn bijdrage in Night Racer

Lan Multiplayer met behulp van unity netcode

Ik heb met de, op dat moment, nieuwe unity netcode een LAN multiplayer gemaakt en toegevoegd aan de game.

Dit is de lobby manager. Die zorgt ervoor dat als je de game start je in een lobby komt te zitten. Daar kan je kiezen of je een game wil hosten of een game wilt joinen.

Dit is de eerst versie van het lobby systeem.

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using Unity.Netcode.Transports.UTP;
using TMPro;

public class LobbyManager : NetworkBehaviour
{
    [SerializeField] private Button startGameButton;
    [SerializeField] private Button hostGameButton;
    [SerializeField] private Button clientGameButton;
    [SerializeField] private Button ipGameButton;
    [SerializeField] private TMP_InputField HostIp;
    [SerializeField] private TMP_InputField HostPort;
    public static LobbyManager Instance;
    private void Awake()
    {
        Instance = this;
        startGameButton.onClick.AddListener(StartGame);
        ipGameButton.onClick.AddListener(SetIp);
    }

    private void Update()
    {
        if (IsServer)
        {
            if (IsEveryoneReady() == true)
            {
                startGameButton.interactable = true;
            }
        }
    }

    public override void OnNetworkSpawn()
    {
        if (IsServer)
        {
            startGameButton.gameObject.SetActive(true);
        }
    }
    private bool IsEveryoneReady()
    {
        if (NetworkManager.ConnectedClients.Count < 2)
        {
            return false;
        }

        return true;
    }

    public void StartGame()
    {
        NetworkManager.SceneManager.LoadScene("Level1", LoadSceneMode.Single);
    }

    public void SetIp()
    {
        NetworkManager.GetComponent<UnityTransport>().ConnectionData.Address = HostIp.text;
        NetworkManager.GetComponent<UnityTransport>().ConnectionData.Port = ushort.Parse(HostPort.text);
        ipGameButton.interactable = false;
        hostGameButton.interactable = true;
        clientGameButton.interactable = true;
    }
}
C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class MultiplayerUI : MonoBehaviour
{
    [SerializeField] private Button multiplayer;
    [SerializeField] private Button singlePlayer;

    private void Awake()
    {
        multiplayer.onClick.AddListener(PlayGameInMultiplayer);
        singlePlayer.onClick.AddListener(PlayGameInSinglePlayer);
    }

    public void PlayGameInSinglePlayer()
    {
        SceneManager.LoadScene("Level1");
    }

    public void PlayGameInMultiplayer()
    {
        SceneManager.LoadScene("Lobby");
    }
}

Dit is de Multiplayer UI. Die zorgt ervoor dat als je het spel start dat alle juiste UI worden ingeladen voor SinglePlayer en voor Multiplayer.

Dit is de ClientNetworkTransform. Het is een script van netcode voor als je wilt dat clients een request kunnen maken naar de server.

C#
using Unity.Netcode.Components;
using UnityEngine;

namespace Unity.Multiplayer.Samples.Utilities.ClientAuthority
{
    /// <summary>
    /// Used for syncing a transform with client side changes. This includes host. Pure server as owner isn't supported by this. Please use NetworkTransform
    /// for transforms that'll always be owned by the server.
    /// </summary>
    [DisallowMultipleComponent]
    public class ClientNetworkTransform : NetworkTransform
    {
        /// <summary>
        /// Used to determine who can write to this transform. Owner client only.
        /// This imposes state to the server. This is putting trust on your clients. Make sure no security-sensitive features use this transform.
        /// </summary>
        protected override bool OnIsServerAuthoritative()
        {
            return false;
        }
    }
}
C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;

public class playerSpawner : NetworkBehaviour
{
    private ulong clientId;

    public override void OnNetworkSpawn()
    {
        clientId = NetworkManager.Singleton.LocalClientId;
        GameManager.instance.SpawenPlayerServerRpc(clientId);
    }
}
    

Dit is de playerSpawner. Die zorgt ervoor dat de players spawnen als ze verbinding maken met de server.

Dit is de CheckOwnership. Hier wordt gekeken of de camera van jou is. Indien dit zo is, dan laat hij hem aan staan. Zo niet dan zet hij hem uit.

Dit zorgt er voor dat je altijd de goede camera hebt.

C#
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using System.Linq;

public class CheckOwnership : NetworkBehaviour
{
    public override void OnNetworkSpawn()
    {
        Camera[] cameras = GetComponents<Camera>();

        for (int i = 0; i < cameras.Length; i++)
        {
            if (cameras[i] != IsOwner)
            {
                cameras[i].gameObject.SetActive(false);
            }
        }
    }
}
C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Unity.Netcode;

public class NetworkManagerUI : MonoBehaviour
{
    //[SerializeField] private Button ServerButton;
    [SerializeField] private Button hostButton;
    [SerializeField] private Button ClientButton;

    private void Awake()
    {
        /*ServerButton.onClick.AddListener(() =>
        {
            NetworkManager.Singleton.StartServer();
        });*/
        hostButton.onClick.AddListener(() =>
        {
            NetworkManager.Singleton.StartHost();
            MultiplayerStart();
        });
        ClientButton.onClick.AddListener(() =>
        {
            NetworkManager.Singleton.StartClient();
            MultiplayerStart();
        });
    }

    public void MultiplayerStart()
    {
        hostButton.gameObject.SetActive(false);
        ClientButton.gameObject.SetActive(false);
    }
}

Dit is de NetworkManagerUI. Deze zorgt er voor dat iedereen een UI krijgt voor het joinen van een server.

Dit is de PlayerNetwork. Deze zorgt er voor dat alle inputs van de player naar de server gaan.

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;

public class PlayerNetwork : NetworkBehaviour
{
    private NetworkVariable<int> randomNumber = new NetworkVariable<int>(1, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);

    public override void OnNetworkSpawn()
    {
        randomNumber.OnValueChanged += (int previousValue, int newValue) => { Debug.Log(OwnerClientId + "; randomNumber" + randomNumber.Value); };
    }

    private void Update()
    {
        if (!IsOwner) return;

        if (Input.GetKeyDown(KeyCode.T))
        {
            randomNumber.Value = Random.Range(0, 100);
        }

        Vector3 moveDir = new Vector3(0, 0, 0);

        if (Input.GetKey(KeyCode.W)) moveDir.z = +1f;
        if (Input.GetKey(KeyCode.S)) moveDir.z = -1f;
        if (Input.GetKey(KeyCode.A)) moveDir.x = -1f;
        if (Input.GetKey(KeyCode.D)) moveDir.x = +1f;

        float moveSpeed = 3f;
        transform.position += moveDir * moveSpeed * Time.deltaTime;
    }
}
C#
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using Unity.Netcode;

public class Controller : NetworkBehaviour
{
    private const string HORIZONTAL = "Horizontal";
    private const string VERTICAL = "Vertical";

    public Rigidbody rb;

    private float horizontalInput;
    private float verticalInput;
    private float currentSteerAngle;
    private float currentbreakForce;
    private bool isBreaking;

    public float currentSpeed;
    public float speedFillAmt;

    [SerializeField] private float motorForce;
    [SerializeField] private float breakForce;
    [SerializeField] private float maxSteerAngle;

    [SerializeField] private WheelCollider frontLeftWheelCollider;
    [SerializeField] private WheelCollider frontRightWheelCollider;
    [SerializeField] private WheelCollider rearLeftWheelCollider;
    [SerializeField] private WheelCollider rearRightWheelCollider;

    [SerializeField] private float MPH;
    [SerializeField] private float KMPH;

    [SerializeField] private bool Units = true;

    [SerializeField] GameObject SpeedText;
    [SerializeField] GameObject UnitText;

    [SerializeField] GameObject SpeedoMeterImage;

    public ulong playerId;
    public int lap = 1;
    public int currentCheckpointIndex = 0;

    Checkpoint latistCheckpointTrigger;

    private void Start()
    {
        playerId = NetworkManager.Singleton.LocalClientId;
        rb = GetComponent<Rigidbody>();
        rb.centerOfMass = new Vector3(0, -1, 0);
        if (IsOwner)
        {
            SpeedText = GameObject.Find("Speedometer/Speed");
            UnitText = GameObject.Find("Speedometer/Units");
            SpeedoMeterImage = GameObject.Find("Speedometer");
        }
    }

    private void FixedUpdate()
    {
        if (IsOwner && GameManager.instance.gameStart)
        {
            GetInput();
            HandleMotor();
            HandleSteering();
        }
    }

    private void Update()
    {
        if (IsOwner)
        {
            SpeedoMeter();
        }
        if (this.gameObject.transform.position.y <= -10)
        {
            TrackCheckpoints.instance.cheackCurrentCheckpoint(this, out latistCheckpointTrigger);
            latistCheckpointTrigger.gameObject.transform.position = this.gameObject.transform.position;
            latistCheckpointTrigger.gameObject.transform.rotation = this.gameObject.transform.rotation;
        }
    }

    private void GetInput()
    {
        if (!IsOwner) return;
        horizontalInput = Input.GetAxis(HORIZONTAL);
        verticalInput = Input.GetAxis(VERTICAL);    
        isBreaking = Input.GetKey(KeyCode.Space);
    }

    private void HandleMotor()
    {
        frontLeftWheelCollider.motorTorque = verticalInput * motorForce;
        frontRightWheelCollider.motorTorque = verticalInput * motorForce;
        currentbreakForce = isBreaking ? breakForce : 0f;
        ApplyBreaking();
    }

    private void ApplyBreaking()
    {
        frontRightWheelCollider.brakeTorque = currentbreakForce;
        frontLeftWheelCollider.brakeTorque = currentbreakForce;
        rearLeftWheelCollider.brakeTorque = currentbreakForce;
        rearRightWheelCollider.brakeTorque = currentbreakForce;
    }

    private void HandleSteering()
    {
        currentSteerAngle = maxSteerAngle * horizontalInput;
        frontLeftWheelCollider.steerAngle = currentSteerAngle;
        frontRightWheelCollider.steerAngle = currentSteerAngle;
    }

    private void SpeedoMeter()
    {
        CalculateSpeed();
        CalculateSpeedoMeterFill();

        if (!Units)
        {
            SpeedText.GetComponent<TextMeshProUGUI>().text = MPH.ToString("0");
            UnitText.GetComponent<TextMeshProUGUI>().text = "M/H";
        }
        else
        {
            SpeedText.GetComponent<TextMeshProUGUI>().text = KMPH.ToString("0");
            UnitText.GetComponent<TextMeshProUGUI>().text = "KM/H";
        }
    }

    private void CalculateSpeedoMeterFill()
    {
        speedFillAmt = currentSpeed;

        SpeedoMeterImage.GetComponent<Image>().fillAmount = speedFillAmt * 1.1f;

    }

    private void CalculateSpeed()
    {
        MPH = rb.velocity.magnitude * 2.23693629f;
        KMPH = (rb.velocity.magnitude * 2.23693629f) * 1.609344f;

        currentSpeed = (MPH) / 100;
    }

}

De besturing van het voertuig

Ik heb de besturing van de auto gemaakt met behulp van WheelCollider voor de wielen.

Ik kijk eerst of je een knop indrukt daarna voeg ik snelheid toe aan het voertuig. En als je op spatie drukt dan rem je af.

We kijken ook hoe hard je gaat en zetten dat om in kmh en mph. We laten dat dan zien op de snelheidsmeter.

Game Manger

De game manger bepaalt van wie elke voertuig is. En waar hij komt te staan.

Als de game is afgelopen wordt de snelste tijd opgeslagen

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;

public class GameManager : NetworkBehaviour
{
    public static GameManager instance;
    public bool isInMultiplayer = false;
    [SerializeField] private GameObject networkManager;
    [SerializeField] private GameObject player;
    [SerializeField] private GameObject rift;
    public float bestTimeLevel1;
    [SerializeField] private Vector3[] posSpwanPlayer = new Vector3[8];
    private int numbber = 0;
    public bool gameStart = false;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else if (instance != null)
        {
            Destroy(gameObject);
        }
        networkManager = FindObjectOfType<NetworkManager>().gameObject;
        SaveGameData data = SaveSystem.LoadGame();
        bestTimeLevel1 = data.bestTimeLevel1;
        
    }

    public void SetBestTime(float time)
    {
        if (bestTimeLevel1 < time && time != 0)
        {
            time = bestTimeLevel1;
        }
        else if (time == 0)
        {
            time = bestTimeLevel1;
        }
        Debug.LogError("save");
        SaveSystem.SaveGame(this);
    }


    [ServerRpc(RequireOwnership = false)]
    public void SpawenPlayerServerRpc(ulong clientId)
    {
        if (networkManager == null) return;
        Vector3 pos = new Vector3(35.0f, 2.7f, 18.8f);
        GameObject vehicle = Instantiate(player, posSpwanPlayer[numbber], transform.rotation);
        vehicle.GetComponent<NetworkObject>().SpawnWithOwnership(clientId);
        numbber++;
    }

    public void RiftActived()
    {
        rift.SetActive(true);
    }
}