Last active
May 26, 2024 12:01
-
-
Save Kellojo/1f13002116cce6ebbc37a1f97d3db722 to your computer and use it in GitHub Desktop.
A PlayerNetworkTransform that allows syncing player position on moving platforms using netcode for gameobjects.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using KinematicCharacterController; | |
using Unity.Netcode; | |
using UnityEngine; | |
[RequireComponent(typeof(PhysicsMover))] | |
public abstract class PhysicsMoverNetworkTransform : NetworkBehaviour, IMoverController { | |
PhysicsMover PhysicsMover; | |
PhysicsMoverState PreviousState; | |
float LastUpdateTime; | |
[SerializeField] NetworkVariable<PhysicsMoverState> State = new NetworkVariable<PhysicsMoverState>(writePerm: NetworkVariableWritePermission.Owner); | |
float TickDuration { | |
get { | |
return 1f / (float)NetworkManager.Singleton.NetworkTickSystem.TickRate; | |
} | |
} | |
protected virtual void Awake() { | |
PhysicsMover = GetComponent<PhysicsMover>(); | |
PhysicsMover.MoverController = this; | |
} | |
private void OnEnable() { | |
State.OnValueChanged += OnStateChance; | |
} | |
private void OnDisable() { | |
State.OnValueChanged -= OnStateChance; | |
} | |
private void Update() { | |
if (!IsSpawned) return; | |
// collect state | |
if (IsOwner) { | |
State.Value = PhysicsMover.GetState(); | |
} | |
} | |
void OnStateChance(PhysicsMoverState oldState, PhysicsMoverState newState) { | |
LastUpdateTime = Time.time; | |
PreviousState = oldState; | |
} | |
public virtual void UpdateMovement(out Vector3 goalPosition, out Quaternion goalRotation, float deltaTime) { | |
if (IsOwner) { | |
// generate movement input, if we're the owner | |
UpdateMovementOwner(out goalPosition, out goalRotation, deltaTime); | |
} else { | |
// apply movement input, if we're not the owner | |
if (IsSpawned) { | |
var t = (Time.time - LastUpdateTime) / TickDuration; | |
var newState = State.Value; | |
goalPosition = Vector3.Lerp(PreviousState.Position, newState.Position, t); | |
goalRotation = Quaternion.Slerp(PreviousState.Rotation, newState.Rotation, t); | |
} else { | |
goalPosition = transform.position; | |
goalRotation = transform.rotation; | |
} | |
} | |
} | |
public abstract void UpdateMovementOwner(out Vector3 goalPosition, out Quaternion goalRotation, float deltaTime); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using KinematicCharacterController; | |
using Unity.Netcode; | |
using UnityEngine; | |
[RequireComponent(typeof(PlayerPhysicsSticker))] | |
public class PlayerNetworkTransform : NetworkBehaviour | |
{ | |
public NetworkVariable<PlayerNetworkTransformState> State = new NetworkVariable<PlayerNetworkTransformState>(writePerm: NetworkVariableWritePermission.Owner); | |
public KinematicCharacterMotor PlayerMotor; | |
PlayerPhysicsSticker PlayerPhysicsSticker; | |
PlayerNetworkTransformState PreviousState; | |
Rigidbody Rigidbody; | |
float LastUpdateTime; | |
float TickDuration { | |
get { | |
return 1f / (float) NetworkManager.Singleton.NetworkTickSystem.TickRate; | |
} | |
} | |
private void Awake() { | |
PlayerPhysicsSticker = GetComponent<PlayerPhysicsSticker>(); | |
Rigidbody = GetComponent<Rigidbody>(); | |
} | |
private void OnEnable() { | |
State.OnValueChanged += OnStateChange; | |
} | |
private void OnDisable() { | |
State.OnValueChanged -= OnStateChange; | |
} | |
public override void OnNetworkSpawn() { | |
base.OnNetworkSpawn(); | |
Rigidbody.isKinematic = !IsLocalPlayer; | |
} | |
private void LateUpdate() { | |
// update state, if we're the owner | |
if (IsLocalPlayer) { | |
var state = new PlayerNetworkTransformState(); | |
state.WorldPosition = transform.position; | |
state.WorldRotation = transform.rotation; | |
state.LocalPosition = Vector3.zero; | |
var physicsMover = PlayerPhysicsSticker.CurrentMover; | |
state.IsGroundedOnPhysicsMover = physicsMover != null; | |
state.IsGrounded = PlayerMotor.GroundingStatus.FoundAnyGround; | |
state.SetUsedPhysicsMover(physicsMover); | |
if (physicsMover != null) { | |
state.LocalPosition = physicsMover.transform.InverseTransformPoint(state.WorldPosition); | |
} | |
State.Value = state; | |
} | |
// apply state, if we're not the owner | |
if (!IsLocalPlayer) { | |
var t = (Time.time - LastUpdateTime) / TickDuration; | |
var playerTransform = transform; | |
var state = State.Value; | |
var newPos = Vector3.Lerp(PreviousState.WorldPosition, state.WorldPosition, t); // world pos | |
if (state.IsGroundedOnPhysicsMover) { | |
// standard moving case | |
var ilocalPos = Vector3.Lerp(PreviousState.LocalPosition, state.LocalPosition, t); | |
newPos = state.GetWorldPosOn(ilocalPos); | |
// jumping on a mover | |
// entering / exiting a mover | |
if (!PreviousState.IsGroundedOnPhysicsMover) { | |
newPos = Vector3.Lerp(PreviousState.WorldPosition, state.GetWorldPosOn(state.LocalPosition), t); | |
} | |
} | |
playerTransform.position = newPos; | |
playerTransform.rotation = Quaternion.Slerp(PreviousState.WorldRotation, state.WorldRotation, t); | |
} | |
} | |
void OnStateChange(PlayerNetworkTransformState oldState, PlayerNetworkTransformState newState) { | |
LastUpdateTime = Time.time; | |
PreviousState = oldState; | |
//Debug.Log(newState); | |
} | |
[System.Serializable] | |
public struct PlayerNetworkTransformState : INetworkSerializable { | |
public Vector3 WorldPosition; | |
public Quaternion WorldRotation; | |
public Vector3 LocalPosition; | |
public bool IsGroundedOnPhysicsMover; | |
public bool IsGrounded; | |
public NetworkObjectReference UsedPhysicsMoverReference; | |
public void SetUsedPhysicsMover(PhysicsMover physicsMover) { | |
if (physicsMover == null) return; | |
UsedPhysicsMoverReference = new NetworkObjectReference(physicsMover.NetworkObject); | |
} | |
public PhysicsMover GetUsedPhysicsMover() { | |
if (!IsGroundedOnPhysicsMover) return null; | |
UsedPhysicsMoverReference.TryGet(out NetworkObject physicsMoverObj); | |
if (physicsMoverObj == null) return null; | |
return physicsMoverObj.GetComponent<PhysicsMover>(); | |
} | |
public Vector3 GetWorldPosOn(Vector3 localPos) { | |
var mover = GetUsedPhysicsMover(); | |
return mover.transform.TransformPoint(localPos); | |
} | |
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { | |
serializer.SerializeValue(ref WorldPosition); | |
serializer.SerializeValue(ref WorldRotation); | |
serializer.SerializeValue(ref LocalPosition); | |
serializer.SerializeValue(ref IsGroundedOnPhysicsMover); | |
serializer.SerializeValue(ref IsGrounded); | |
serializer.SerializeValue(ref UsedPhysicsMoverReference); | |
} | |
public override string ToString() { | |
return $"{WorldPosition}, ${WorldRotation.eulerAngles}, {IsGroundedOnPhysicsMover} on {GetUsedPhysicsMover()} with {LocalPosition}"; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment