Skip to content

Instantly share code, notes, and snippets.

@Kellojo
Last active May 26, 2024 12:01
Show Gist options
  • Save Kellojo/1f13002116cce6ebbc37a1f97d3db722 to your computer and use it in GitHub Desktop.
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.
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);
}
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