377 lines
13 KiB
C#
377 lines
13 KiB
C#
using Steamworks;
|
|
using UnityEngine;
|
|
|
|
namespace MegaKoop.Game.Networking
|
|
{
|
|
[DisallowMultipleComponent]
|
|
public class SteamCharacterNetworkBridge : MonoBehaviour
|
|
{
|
|
[Header("References")]
|
|
[SerializeField] private ThirdPersonCharacterController characterController;
|
|
[SerializeField] private Animator animator;
|
|
[SerializeField] private NetworkIdentity identity;
|
|
[SerializeField] private Transform rootTransform;
|
|
[SerializeField] private NetworkCharacterInputProxy networkInputProxy;
|
|
|
|
[Header("Settings")]
|
|
[SerializeField] private float transformBroadcastInterval = 0.05f;
|
|
[SerializeField] private float remoteLerpSpeed = 12f;
|
|
[SerializeField] private ulong ownerSteamId;
|
|
[SerializeField] private bool autoAssignOwnerToLocalPlayer = true;
|
|
|
|
private SteamCoopNetworkManager networkManager;
|
|
private float broadcastTimer;
|
|
private bool isRegistered;
|
|
private bool isLocalPlayer;
|
|
private bool isAuthority;
|
|
|
|
private Vector3 remoteTargetPosition;
|
|
private Quaternion remoteTargetRotation;
|
|
private Vector3 remoteTargetVelocity;
|
|
private bool haveRemoteState;
|
|
private bool localOverrideSet;
|
|
|
|
public void AssignOwner(ulong steamId, bool localPlayer)
|
|
{
|
|
ownerSteamId = steamId;
|
|
isLocalPlayer = localPlayer;
|
|
localOverrideSet = true;
|
|
UpdateAuthority();
|
|
ConfigureController();
|
|
}
|
|
|
|
public bool IsLocalPlayer => isLocalPlayer;
|
|
public bool IsAuthority => isAuthority;
|
|
public ulong OwnerSteamId => ownerSteamId;
|
|
|
|
private void Awake()
|
|
{
|
|
if (characterController == null)
|
|
{
|
|
characterController = GetComponent<ThirdPersonCharacterController>();
|
|
}
|
|
|
|
if (identity == null)
|
|
{
|
|
identity = GetComponent<NetworkIdentity>();
|
|
}
|
|
|
|
if (rootTransform == null)
|
|
{
|
|
rootTransform = transform;
|
|
}
|
|
|
|
if (networkInputProxy == null)
|
|
{
|
|
networkInputProxy = GetComponent<NetworkCharacterInputProxy>();
|
|
if (networkInputProxy == null)
|
|
{
|
|
networkInputProxy = gameObject.AddComponent<NetworkCharacterInputProxy>();
|
|
networkInputProxy.hideFlags = HideFlags.HideInInspector;
|
|
}
|
|
}
|
|
|
|
if (animator == null)
|
|
{
|
|
animator = GetComponent<Animator>();
|
|
if (animator != null)
|
|
{
|
|
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
|
}
|
|
}
|
|
|
|
remoteTargetPosition = rootTransform.position;
|
|
remoteTargetRotation = rootTransform.rotation;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
networkManager = SteamCoopNetworkManager.Instance;
|
|
UpdateAuthority();
|
|
ConfigureController();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
networkManager = SteamCoopNetworkManager.Instance;
|
|
RegisterHandlers();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
UnregisterHandlers();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (networkManager == null)
|
|
{
|
|
networkManager = SteamCoopNetworkManager.Instance;
|
|
if (networkManager != null)
|
|
{
|
|
RegisterHandlers();
|
|
}
|
|
}
|
|
|
|
if (networkManager == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isAuthority)
|
|
{
|
|
broadcastTimer -= Time.deltaTime;
|
|
if (broadcastTimer <= 0f)
|
|
{
|
|
BroadcastTransform();
|
|
broadcastTimer = transformBroadcastInterval;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (haveRemoteState)
|
|
{
|
|
rootTransform.position = Vector3.Lerp(rootTransform.position, remoteTargetPosition, remoteLerpSpeed * Time.deltaTime);
|
|
rootTransform.rotation = Quaternion.Slerp(rootTransform.rotation, remoteTargetRotation, remoteLerpSpeed * Time.deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RegisterHandlers()
|
|
{
|
|
if (networkManager == null || isRegistered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkManager.RegisterHandler(NetworkMessageType.PlayerInput, HandlePlayerInputMessage);
|
|
networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage);
|
|
networkManager.RegisterHandler(NetworkMessageType.CharacterAnim, HandleCharacterAnimMessage);
|
|
isRegistered = true;
|
|
}
|
|
|
|
private void UnregisterHandlers()
|
|
{
|
|
if (networkManager == null || !isRegistered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkManager.UnregisterHandler(NetworkMessageType.PlayerInput, HandlePlayerInputMessage);
|
|
networkManager.UnregisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage);
|
|
networkManager.UnregisterHandler(NetworkMessageType.CharacterAnim, HandleCharacterAnimMessage);
|
|
isRegistered = false;
|
|
}
|
|
|
|
private void UpdateAuthority()
|
|
{
|
|
if (networkManager == null)
|
|
{
|
|
networkManager = SteamCoopNetworkManager.Instance;
|
|
}
|
|
|
|
ulong localSteamId = SteamBootstrap.IsInitialized ? SteamUser.GetSteamID().m_SteamID : 0UL;
|
|
|
|
if (!localOverrideSet && autoAssignOwnerToLocalPlayer && ownerSteamId == 0 && localSteamId != 0)
|
|
{
|
|
ownerSteamId = localSteamId;
|
|
}
|
|
|
|
if (!localOverrideSet)
|
|
{
|
|
isLocalPlayer = ownerSteamId != 0 && ownerSteamId == localSteamId;
|
|
}
|
|
|
|
isAuthority = isLocalPlayer; // Each player has authority over their own character.
|
|
}
|
|
|
|
private void ConfigureController()
|
|
{
|
|
if (characterController == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isAuthority)
|
|
{
|
|
characterController.enabled = true;
|
|
characterController.SetInputSource(isLocalPlayer ? null : networkInputProxy);
|
|
var unityController = characterController.GetComponent<UnityEngine.CharacterController>();
|
|
if (unityController != null)
|
|
{
|
|
unityController.enabled = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
characterController.enabled = false;
|
|
characterController.SetInputSource(null);
|
|
var unityController = characterController.GetComponent<UnityEngine.CharacterController>();
|
|
if (unityController != null)
|
|
{
|
|
unityController.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void BroadcastTransform()
|
|
{
|
|
if (identity == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var unityController = GetComponent<CharacterController>();
|
|
Vector3 velocity = unityController != null ? unityController.velocity : Vector3.zero;
|
|
SteamCharacterStateCache.ReportLocalState(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity);
|
|
var message = new CharacterTransformMessage(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity);
|
|
byte[] payload = CharacterTransformMessage.Serialize(message);
|
|
networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);
|
|
|
|
BroadcastAnimatorParameters();
|
|
}
|
|
|
|
private void BroadcastAnimatorParameters()
|
|
{
|
|
if (networkManager == null || identity == null || animator == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float moveX = animator.GetFloat("MoveX");
|
|
float moveZ = animator.GetFloat("MoveZ");
|
|
float speed = animator.GetFloat("Speed");
|
|
float moveSpeedNorm = animator.GetFloat("MoveSpeedNormalized");
|
|
bool isGround = animator.GetBool("IsGrounded");
|
|
bool isCrouch = animator.GetBool("IsCrouching");
|
|
bool isDeadFlag = animator.GetBool("IsDead");
|
|
bool isJump = animator.GetBool("IsJumping");
|
|
|
|
var animMsg = new CharacterAnimMessage(
|
|
identity.NetworkId,
|
|
moveX,
|
|
moveZ,
|
|
speed,
|
|
moveSpeedNorm,
|
|
isGround,
|
|
isCrouch,
|
|
isDeadFlag,
|
|
isJump);
|
|
byte[] animPayload = CharacterAnimMessage.Serialize(animMsg);
|
|
networkManager.SendToAll(NetworkMessageType.CharacterAnim, animPayload, EP2PSend.k_EP2PSendUnreliableNoDelay);
|
|
}
|
|
|
|
private void HandlePlayerInputMessage(NetworkMessage message)
|
|
{
|
|
if (isAuthority)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PlayerInputMessage inputMessage = PlayerInputMessage.Deserialize(message.Payload);
|
|
if (inputMessage.NetworkId != identity.NetworkId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkInputProxy.SetInput(inputMessage.MoveInput, inputMessage.JumpPressed);
|
|
}
|
|
|
|
private void HandleCharacterTransformMessage(NetworkMessage message)
|
|
{
|
|
if (isAuthority)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CharacterTransformMessage transformMessage = CharacterTransformMessage.Deserialize(message.Payload);
|
|
if (identity != null && transformMessage.NetworkId != identity.NetworkId)
|
|
{
|
|
if (ownerSteamId != 0 && message.Sender != ownerSteamId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var existing = NetworkIdRegistry.GetById(transformMessage.NetworkId);
|
|
if (existing != null && existing != identity)
|
|
{
|
|
return;
|
|
}
|
|
|
|
identity.SetNetworkId(transformMessage.NetworkId);
|
|
if (identity.NetworkId != transformMessage.NetworkId)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
remoteTargetPosition = transformMessage.Position;
|
|
remoteTargetRotation = transformMessage.Rotation;
|
|
remoteTargetVelocity = transformMessage.Velocity;
|
|
haveRemoteState = true;
|
|
}
|
|
|
|
private void HandleCharacterAnimMessage(NetworkMessage message)
|
|
{
|
|
if (isAuthority)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CharacterAnimMessage anim = CharacterAnimMessage.Deserialize(message.Payload);
|
|
if (identity != null && anim.NetworkId != identity.NetworkId)
|
|
{
|
|
if (ownerSteamId != 0 && message.Sender != ownerSteamId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var existing = NetworkIdRegistry.GetById(anim.NetworkId);
|
|
if (existing != null && existing != identity)
|
|
{
|
|
return;
|
|
}
|
|
|
|
identity.SetNetworkId(anim.NetworkId);
|
|
if (identity.NetworkId != anim.NetworkId)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (animator == null)
|
|
{
|
|
animator = GetComponent<Animator>();
|
|
if (animator == null)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
animator.SetFloat("MoveX", anim.MoveX);
|
|
animator.SetFloat("MoveZ", anim.MoveZ);
|
|
animator.SetFloat("Speed", anim.Speed);
|
|
animator.SetFloat("MoveSpeedNormalized", anim.MoveSpeedNormalized);
|
|
animator.SetBool("IsGrounded", anim.IsGrounded);
|
|
animator.SetBool("IsCrouching", anim.IsCrouching);
|
|
animator.SetBool("IsDead", anim.IsDead);
|
|
animator.SetBool("IsJumping", anim.IsJumping);
|
|
}
|
|
|
|
public void SendLocalInput(Vector2 moveInput, bool jump)
|
|
{
|
|
if (networkManager == null || identity == null || !isAuthority)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!networkManager.IsConnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkManager.SendToAll(NetworkMessageType.PlayerInput, PlayerInputMessage.Serialize(new PlayerInputMessage(identity.NetworkId, moveInput, jump)), EP2PSend.k_EP2PSendUnreliableNoDelay);
|
|
}
|
|
}
|
|
}
|