Multiplayer Updated

This commit is contained in:
kingjuulian06
2023-12-22 14:41:12 +01:00
parent 2b2abe5eaa
commit f042410dab
1778 changed files with 165193 additions and 0 deletions
@@ -0,0 +1,197 @@
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Mirror.Examples.AdditiveLevels
{
[AddComponentMenu("")]
public class AdditiveLevelsNetworkManager : NetworkManager
{
public static new AdditiveLevelsNetworkManager singleton { get; private set; }
/// <summary>
/// Runs on both Server and Client
/// Networking is NOT initialized when this fires
/// </summary>
public override void Awake()
{
base.Awake();
singleton = this;
}
[Header("Additive Scenes - First is start scene")]
[Scene, Tooltip("Add additive scenes here.\nFirst entry will be players' start scene")]
public string[] additiveScenes;
[Header("Fade Control - See child FadeCanvas")]
[Tooltip("Reference to FadeInOut script on child FadeCanvas")]
public FadeInOut fadeInOut;
// This is set true after server loads all subscene instances
bool subscenesLoaded;
// This is managed in LoadAdditive, UnloadAdditive, and checked in OnClientSceneChanged
bool isInTransition;
#region Scene Management
/// <summary>
/// Called on the server when a scene is completed loaded, when the scene load was initiated by the server with ServerChangeScene().
/// </summary>
/// <param name="sceneName">The name of the new scene.</param>
public override void OnServerSceneChanged(string sceneName)
{
// This fires after server fully changes scenes, e.g. offline to online
// If server has just loaded the Container (online) scene, load the subscenes on server
if (sceneName == onlineScene)
StartCoroutine(ServerLoadSubScenes());
}
IEnumerator ServerLoadSubScenes()
{
foreach (string additiveScene in additiveScenes)
yield return SceneManager.LoadSceneAsync(additiveScene, new LoadSceneParameters
{
loadSceneMode = LoadSceneMode.Additive,
localPhysicsMode = LocalPhysicsMode.Physics3D // change this to .Physics2D for a 2D game
});
subscenesLoaded = true;
}
/// <summary>
/// Called from ClientChangeScene immediately before SceneManager.LoadSceneAsync is executed
/// <para>This allows client to do work / cleanup / prep before the scene changes.</para>
/// </summary>
/// <param name="sceneName">Name of the scene that's about to be loaded</param>
/// <param name="sceneOperation">Scene operation that's about to happen</param>
/// <param name="customHandling">true to indicate that scene loading will be handled through overrides</param>
public override void OnClientChangeScene(string sceneName, SceneOperation sceneOperation, bool customHandling)
{
//Debug.Log($"{System.DateTime.Now:HH:mm:ss:fff} OnClientChangeScene {sceneName} {sceneOperation}");
if (sceneOperation == SceneOperation.UnloadAdditive)
StartCoroutine(UnloadAdditive(sceneName));
if (sceneOperation == SceneOperation.LoadAdditive)
StartCoroutine(LoadAdditive(sceneName));
}
IEnumerator LoadAdditive(string sceneName)
{
isInTransition = true;
// This will return immediately if already faded in
// e.g. by UnloadAdditive or by default startup state
yield return fadeInOut.FadeIn();
// host client is on server...don't load the additive scene again
if (mode == NetworkManagerMode.ClientOnly)
{
// Start loading the additive subscene
loadingSceneAsync = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
while (loadingSceneAsync != null && !loadingSceneAsync.isDone)
yield return null;
}
// Reset these to false when ready to proceed
NetworkClient.isLoadingScene = false;
isInTransition = false;
OnClientSceneChanged();
// Reveal the new scene content.
yield return fadeInOut.FadeOut();
}
IEnumerator UnloadAdditive(string sceneName)
{
isInTransition = true;
// This will return immediately if already faded in
// e.g. by LoadAdditive above or by default startup state.
yield return fadeInOut.FadeIn();
// host client is on server...don't unload the additive scene here.
if (mode == NetworkManagerMode.ClientOnly)
{
yield return SceneManager.UnloadSceneAsync(sceneName);
yield return Resources.UnloadUnusedAssets();
}
// Reset these to false when ready to proceed
NetworkClient.isLoadingScene = false;
isInTransition = false;
OnClientSceneChanged();
// There is no call to FadeOut here on purpose.
// Expectation is that a LoadAdditive or full scene change
// will follow that will call FadeOut after that scene loads.
}
/// <summary>
/// Called on clients when a scene has completed loaded, when the scene load was initiated by the server.
/// <para>Scene changes can cause player objects to be destroyed. The default implementation of OnClientSceneChanged in the NetworkManager is to add a player object for the connection if no player object exists.</para>
/// </summary>
/// <param name="conn">The network connection that the scene change message arrived on.</param>
public override void OnClientSceneChanged()
{
// Only call the base method if not in a transition.
// This will be called from LoadAdditive / UnloadAdditive after setting isInTransition to false
// but will also be called first by Mirror when the scene loading finishes.
if (!isInTransition)
base.OnClientSceneChanged();
}
#endregion
#region Server System Callbacks
/// <summary>
/// Called on the server when a client is ready.
/// <para>The default implementation of this function calls NetworkServer.SetClientReady() to continue the network setup process.</para>
/// </summary>
/// <param name="conn">Connection from client.</param>
public override void OnServerReady(NetworkConnectionToClient conn)
{
// This fires from a Ready message client sends to server after loading the online scene
base.OnServerReady(conn);
if (conn.identity == null)
StartCoroutine(AddPlayerDelayed(conn));
}
// This delay is mostly for the host player that loads too fast for the
// server to have subscenes async loaded from OnServerSceneChanged ahead of it.
IEnumerator AddPlayerDelayed(NetworkConnectionToClient conn)
{
// Wait for server to async load all subscenes for game instances
while (!subscenesLoaded)
yield return null;
// Send Scene msg to client telling it to load the first additive scene
conn.Send(new SceneMessage { sceneName = additiveScenes[0], sceneOperation = SceneOperation.LoadAdditive, customHandling = true });
// We have Network Start Positions in first additive scene...pick one
Transform start = GetStartPosition();
// Instantiate player as child of start position - this will place it in the additive scene
// This also lets player object "inherit" pos and rot from start position transform
GameObject player = Instantiate(playerPrefab, start);
// now set parent null to get it out from under the Start Position object
player.transform.SetParent(null);
// Wait for end of frame before adding the player to ensure Scene Message goes first
yield return new WaitForEndOfFrame();
// Finally spawn the player object for this connection
NetworkServer.AddPlayerForConnection(conn, player);
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01eafa8309a0894479f0f87ae1a9c30f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,71 @@
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
namespace Mirror.Examples.AdditiveLevels
{
public class FadeInOut : MonoBehaviour
{
// set these in the inspector
[Tooltip("Reference to Image component on child panel")]
public Image fadeImage;
[Tooltip("Color to use during scene transition")]
public Color fadeColor = Color.black;
[Range(1, 100), Tooltip("Rate of fade in / out: higher is faster")]
public byte stepRate = 2;
float step;
void OnValidate()
{
if (fadeImage == null)
fadeImage = GetComponentInChildren<Image>();
}
void Start()
{
// Convert user-friendly setting value to working value
step = stepRate * 0.001f;
}
/// <summary>
/// Calculates FadeIn / FadeOut time.
/// </summary>
/// <returns>Duration in seconds</returns>
public float GetDuration()
{
float frames = 1 / step;
float frameRate = Time.deltaTime;
float duration = frames * frameRate * 0.1f;
return duration;
}
public IEnumerator FadeIn()
{
float alpha = fadeImage.color.a;
while (alpha < 1)
{
yield return null;
alpha += step;
fadeColor.a = alpha;
fadeImage.color = fadeColor;
}
}
public IEnumerator FadeOut()
{
float alpha = fadeImage.color.a;
while (alpha > 0)
{
yield return null;
alpha -= step;
fadeColor.a = alpha;
fadeImage.color = fadeColor;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 363a8867bb9c7b845a73233566df8c1e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
using UnityEngine;
namespace Mirror.Examples.AdditiveLevels
{
// This script is attached to portal labels to keep them facing the camera
public class LookAtMainCamera : MonoBehaviour
{
// This will be enabled by Portal script in OnStartClient
void OnValidate()
{
this.enabled = false;
}
// LateUpdate so that all camera updates are finished.
[ClientCallback]
void LateUpdate()
{
transform.forward = Camera.main.transform.forward;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc58300ee45438a418d9e32957fdc0c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,44 @@
using UnityEngine;
using UnityEngine.SceneManagement;
// This sets up the scene camera for the local player
namespace Mirror.Examples.AdditiveLevels
{
public class PlayerCamera : NetworkBehaviour
{
Camera mainCam;
void Awake()
{
mainCam = Camera.main;
}
public override void OnStartLocalPlayer()
{
if (mainCam != null)
{
// configure and make camera a child of player with 3rd person offset
mainCam.orthographic = false;
mainCam.transform.SetParent(transform);
mainCam.transform.localPosition = new Vector3(0f, 3f, -8f);
mainCam.transform.localEulerAngles = new Vector3(10f, 0f, 0f);
}
else
Debug.LogWarning("PlayerCamera: Could not find a camera in scene with 'MainCamera' tag.");
}
public override void OnStopLocalPlayer()
{
if (mainCam != null)
{
mainCam.transform.SetParent(null);
SceneManager.MoveGameObjectToScene(mainCam.gameObject, SceneManager.GetActiveScene());
mainCam.orthographic = true;
mainCam.orthographicSize = 15f;
mainCam.transform.localPosition = new Vector3(0f, 70f, 0f);
mainCam.transform.localEulerAngles = new Vector3(90f, 0f, 0f);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 22976424f775a0f4a8531e6713ff6de2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,186 @@
using UnityEngine;
namespace Mirror.Examples.AdditiveLevels
{
[RequireComponent(typeof(CapsuleCollider))]
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(NetworkTransformReliable))]
[RequireComponent(typeof(Rigidbody))]
public class PlayerController : NetworkBehaviour
{
public enum GroundState : byte { Jumping, Falling, Grounded }
[Header("Avatar Components")]
public CharacterController characterController;
[Header("Movement")]
[Range(1, 20)]
public float moveSpeedMultiplier = 8f;
[Header("Turning")]
[Range(1f, 200f)]
public float maxTurnSpeed = 100f;
[Range(.5f, 5f)]
public float turnDelta = 3f;
[Header("Jumping")]
[Range(0.1f, 1f)]
public float initialJumpSpeed = 0.2f;
[Range(1f, 10f)]
public float maxJumpSpeed = 5f;
[Range(0.1f, 1f)]
public float jumpDelta = 0.2f;
[Header("Diagnostics - Do Not Modify")]
public GroundState groundState = GroundState.Grounded;
[Range(-1f, 1f)]
public float horizontal;
[Range(-1f, 1f)]
public float vertical;
[Range(-200f, 200f)]
public float turnSpeed;
[Range(-10f, 10f)]
public float jumpSpeed;
[Range(-1.5f, 1.5f)]
public float animVelocity;
[Range(-1.5f, 1.5f)]
public float animRotation;
public Vector3Int velocity;
public Vector3 direction;
protected override void OnValidate()
{
base.OnValidate();
if (characterController == null)
characterController = GetComponent<CharacterController>();
// Override CharacterController default values
characterController.enabled = false;
characterController.skinWidth = 0.02f;
characterController.minMoveDistance = 0f;
GetComponent<Rigidbody>().isKinematic = true;
this.enabled = false;
}
public override void OnStartAuthority()
{
characterController.enabled = true;
this.enabled = true;
}
public override void OnStopAuthority()
{
this.enabled = false;
characterController.enabled = false;
}
void Update()
{
if (!characterController.enabled)
return;
HandleTurning();
HandleJumping();
HandleMove();
// Reset ground state
if (characterController.isGrounded)
groundState = GroundState.Grounded;
else if (groundState != GroundState.Jumping)
groundState = GroundState.Falling;
// Diagnostic velocity...FloorToInt for display purposes
velocity = Vector3Int.FloorToInt(characterController.velocity);
}
// TODO: Turning works while airborne...feature?
void HandleTurning()
{
// Q and E cancel each other out, reducing the turn to zero.
if (Input.GetKey(KeyCode.Q))
turnSpeed = Mathf.MoveTowards(turnSpeed, -maxTurnSpeed, turnDelta);
if (Input.GetKey(KeyCode.E))
turnSpeed = Mathf.MoveTowards(turnSpeed, maxTurnSpeed, turnDelta);
// If both pressed, reduce turning speed toward zero.
if (Input.GetKey(KeyCode.Q) && Input.GetKey(KeyCode.E))
turnSpeed = Mathf.MoveTowards(turnSpeed, 0, turnDelta);
// If neither pressed, reduce turning speed toward zero.
if (!Input.GetKey(KeyCode.Q) && !Input.GetKey(KeyCode.E))
turnSpeed = Mathf.MoveTowards(turnSpeed, 0, turnDelta);
transform.Rotate(0f, turnSpeed * Time.deltaTime, 0f);
}
void HandleJumping()
{
// Handle variable force jumping.
// Jump starts with initial power on takeoff, and jumps higher / longer
// as player holds spacebar. Jump power is increased by a diminishing amout
// every frame until it reaches maxJumpSpeed, or player releases the spacebar,
// and then changes to the falling state until it gets grounded.
if (groundState != GroundState.Falling && Input.GetKey(KeyCode.Space))
{
if (groundState != GroundState.Jumping)
{
// Start jump at initial power.
groundState = GroundState.Jumping;
jumpSpeed = initialJumpSpeed;
}
else
// Jumping has already started...increase power toward maxJumpSpeed over time.
jumpSpeed = Mathf.MoveTowards(jumpSpeed, maxJumpSpeed, jumpDelta);
// If power has reached maxJumpSpeed, change to falling until grounded.
// This prevents over-applying jump power while already in the air.
if (jumpSpeed == maxJumpSpeed)
groundState = GroundState.Falling;
}
else if (groundState != GroundState.Grounded)
{
// handles running off a cliff and/or player released Spacebar.
groundState = GroundState.Falling;
jumpSpeed = Mathf.Min(jumpSpeed, maxJumpSpeed);
jumpSpeed += Physics.gravity.y * Time.deltaTime;
}
else
jumpSpeed = Physics.gravity.y * Time.deltaTime;
}
// TODO: Directional input works while airborne...feature?
void HandleMove()
{
// Capture inputs
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
// Create initial direction vector without jumpSpeed (y-axis).
direction = new Vector3(horizontal, 0f, vertical);
// Clamp so diagonal strafing isn't a speed advantage.
direction = Vector3.ClampMagnitude(direction, 1f);
// Transforms direction from local space to world space.
direction = transform.TransformDirection(direction);
// Multiply for desired ground speed.
direction *= moveSpeedMultiplier;
// Add jumpSpeed to direction as last step.
direction.y = jumpSpeed;
// Finally move the character.
characterController.Move(direction * Time.deltaTime);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05e10150710dde14b83d3c8f5aa853c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,98 @@
using System.Collections;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Mirror.Examples.AdditiveLevels
{
public class Portal : NetworkBehaviour
{
[Scene, Tooltip("Which scene to send player from here")]
public string destinationScene;
[Tooltip("Where to spawn player in Destination Scene")]
public Vector3 startPosition;
[Tooltip("Reference to child TextMesh label")]
public TextMesh label; // don't depend on TMPro. 2019 errors.
[SyncVar(hook = nameof(OnLabelTextChanged))]
public string labelText;
public void OnLabelTextChanged(string _, string newValue)
{
label.text = labelText;
}
public override void OnStartServer()
{
labelText = Path.GetFileNameWithoutExtension(destinationScene).Replace("MirrorAdditiveLevels","");
// Simple Regex to insert spaces before capitals, numbers
labelText = Regex.Replace(labelText, @"\B[A-Z0-9]+", " $0");
}
public override void OnStartClient()
{
if (label.TryGetComponent(out LookAtMainCamera lookAtMainCamera))
lookAtMainCamera.enabled = true;
}
// Note that I have created layers called Player(6) and Portal(7) and set them
// up in the Physics collision matrix so only Player collides with Portal.
void OnTriggerEnter(Collider other)
{
// tag check in case you didn't set up the layers and matrix as noted above
if (!other.CompareTag("Player")) return;
// applies to host client on server and remote clients
if (other.TryGetComponent(out PlayerController playerController))
playerController.enabled = false;
if (isServer)
StartCoroutine(SendPlayerToNewScene(other.gameObject));
}
[ServerCallback]
IEnumerator SendPlayerToNewScene(GameObject player)
{
if (player.TryGetComponent(out NetworkIdentity identity))
{
NetworkConnectionToClient conn = identity.connectionToClient;
if (conn == null) yield break;
// Tell client to unload previous subscene with custom handling (see NetworkManager::OnClientChangeScene).
conn.Send(new SceneMessage { sceneName = gameObject.scene.path, sceneOperation = SceneOperation.UnloadAdditive, customHandling = true });
// wait for fader to complete
yield return new WaitForSeconds(AdditiveLevelsNetworkManager.singleton.fadeInOut.GetDuration());
// Remove player after fader has completed
NetworkServer.RemovePlayerForConnection(conn, false);
// reposition player on server and client
player.transform.position = startPosition;
// Rotate player to face center of scene
// Player is 2m tall with pivot at 0,1,0 so we need to look at
// 1m height to not tilt the player down to look at origin
player.transform.LookAt(Vector3.up);
// Move player to new subscene.
SceneManager.MoveGameObjectToScene(player, SceneManager.GetSceneByPath(destinationScene));
// Tell client to load the new subscene with custom handling (see NetworkManager::OnClientChangeScene).
conn.Send(new SceneMessage { sceneName = destinationScene, sceneOperation = SceneOperation.LoadAdditive, customHandling = true });
// Player will be spawned after destination scene is loaded
NetworkServer.AddPlayerForConnection(conn, player);
// host client playerController would have been disabled by OnTriggerEnter above
// Remote client players are respawned with playerController already enabled
if (NetworkClient.localPlayer != null && NetworkClient.localPlayer.TryGetComponent(out PlayerController playerController))
playerController.enabled = true;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e680878006965146a8f9d85834c4d1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: