First, convert ThirdPersonPlayerInputs component to a IInputComponentData, and replace the FixedInputEvent fields with Netcode's InputEvent:
[Serializable]
public struct ThirdPersonPlayerInputs : IInputComponentData
{
public float2 MoveInput;
public float2 CameraLookInput;
public float CameraZoomInput;
public InputEvent JumpPressed; // This is now an InputEvent
}Then, add this component to your project, and modify your ThirdPersonPlayerAuthoring so that it adds this component to the player entity:
[Serializable]
[GhostComponent(SendTypeOptimization = GhostSendType.OnlyPredictedClients)]
public struct ThirdPersonPlayerNetworkInput : IComponentData
{
[GhostField()]
public float2 LastProcessedCameraLookInput;
[GhostField()]
public float LastProcessedCameraZoomInput;
}Then, the player "input" and "control" systems need to be modified in order to use commands properly. Because there are many changes, the full sources for the new systems are provided below. But here's an overview of the changes:
ThirdPersonPlayerInputsSystemnow updates in theGhostInputSystemGroupand only on clients.- Inputs are only gathered for entities with
WithAll<GhostOwnerIsLocal>(). - For "event" inputs, we set them using
Set()and get them usingIsSet. - For "delta" inputs, we set them using
NetworkInputUtilities.AddInputDeltaand get them usingNetworkInputUtilities.GetInputDelta.- In order to use
NetworkInputUtilities.GetInputDelta, some systems need to get the current and previous tick inputs based on the inputs buffer, usingNetworkInputUtilities.GetCurrentAndPreviousTickandNetworkInputUtilities.GetCurrentAndPreviousTickInputs.
- In order to use
ThirdPersonPlayerVariableStepControlSystemandThirdPersonPlayerFixedStepControlSystemnow update in prediction system groups.
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.CharacterController;
using Unity.NetCode;
[UpdateInGroup(typeof(GhostInputSystemGroup))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial class ThirdPersonPlayerInputsSystem : SystemBase
{
protected override void OnCreate()
{
RequireForUpdate<NetworkTime>();
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ThirdPersonPlayer, ThirdPersonPlayerInputs>().Build());
}
protected override void OnUpdate()
{
foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<ThirdPersonPlayerInputs>, ThirdPersonPlayer>().WithAll<GhostOwnerIsLocal>())
{
playerInputs.ValueRW.MoveInput = new float2
{
x = (Keyboard.current.dKey.isPressed ? 1f : 0f) + (Keyboard.current.aKey.isPressed ? -1f : 0f),
y = (Keyboard.current.wKey.isPressed ? 1f : 0f) + (Keyboard.current.sKey.isPressed ? -1f : 0f),
};
InputDeltaUtilities.AddInputDelta(ref playerInputs.ValueRW.CameraLookInput, Mouse.current.delta.ReadValue());
InputDeltaUtilities.AddInputDelta(ref playerInputs.ValueRW.CameraZoomInput, -Mouse.current.scroll.ReadValue());
playerInputs.ValueRW.JumpPressed = default;
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
playerInputs.ValueRW.JumpPressed.Set();
}
}
}
}
/// <summary>
/// Apply inputs that need to be read at a variable rate
/// </summary>
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[UpdateAfter(typeof(PredictedFixedStepSimulationSystemGroup))]
[BurstCompile]
public partial struct ThirdPersonPlayerVariableStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ThirdPersonPlayer, ThirdPersonPlayerInputs>().Build());
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, playerNetworkInput, player) in SystemAPI.Query<ThirdPersonPlayerInputs, RefRW<ThirdPersonPlayerNetworkInput>, ThirdPersonPlayer>().WithAll<Simulate>())
{
// Compute input deltas, compared to last known values
float2 lookInputDelta = InputDeltaUtilities.GetInputDelta(
playerInputs.CameraLookInput,
playerNetworkInput.ValueRO.LastProcessedCameraLookInput);
float zoomInputDelta = InputDeltaUtilities.GetInputDelta(
playerInputs.CameraZoomInput,
playerNetworkInput.ValueRO.LastProcessedCameraZoomInput);
playerNetworkInput.ValueRW.LastProcessedCameraLookInput = playerInputs.CameraLookInput;
playerNetworkInput.ValueRW.LastProcessedCameraZoomInput = playerInputs.CameraZoomInput;
if (SystemAPI.HasComponent<OrbitCameraControl>(player.ControlledCamera))
{
OrbitCameraControl cameraControl = SystemAPI.GetComponent<OrbitCameraControl>(player.ControlledCamera);
cameraControl.FollowedCharacterEntity = player.ControlledCharacter;
cameraControl.LookDegreesDelta = lookInputDelta;
cameraControl.ZoomDelta = zoomInputDelta;
SystemAPI.SetComponent(player.ControlledCamera, cameraControl);
}
}
}
}
/// <summary>
/// Apply inputs that need to be read at a fixed rate.
/// It is necessary to handle this as part of the fixed step group, in case your framerate is lower than the fixed step rate.
/// </summary>
[UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup), OrderFirst = true)]
[BurstCompile]
public partial struct ThirdPersonPlayerFixedStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ThirdPersonPlayer, ThirdPersonPlayerInputs>().Build());
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, player) in SystemAPI.Query<ThirdPersonPlayerInputs, ThirdPersonPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<ThirdPersonCharacterControl>(player.ControlledCharacter))
{
ThirdPersonCharacterControl characterControl = SystemAPI.GetComponent<ThirdPersonCharacterControl>(player.ControlledCharacter);
float3 characterUp = MathUtilities.GetUpFromRotation(SystemAPI.GetComponent<LocalTransform>(player.ControlledCharacter).Rotation);
// Get camera rotation, since our movement is relative to it.
quaternion cameraRotation = quaternion.identity;
if (SystemAPI.HasComponent<OrbitCamera>(player.ControlledCamera))
{
// Camera rotation is calculated rather than gotten from transform, because this allows us to
// reduce the size of the camera ghost state in a netcode prediction context.
// If not using netcode prediction, we could simply get rotation from transform here instead.
OrbitCamera orbitCamera = SystemAPI.GetComponent<OrbitCamera>(player.ControlledCamera);
cameraRotation = OrbitCameraUtilities.CalculateCameraRotation(characterUp, orbitCamera.PlanarForward, orbitCamera.PitchAngle);
}
float3 cameraForwardOnUpPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(cameraRotation), characterUp));
float3 cameraRight = MathUtilities.GetRightFromRotation(cameraRotation);
// Move
characterControl.MoveVector = (playerInputs.MoveInput.y * cameraForwardOnUpPlane) + (playerInputs.MoveInput.x * cameraRight);
characterControl.MoveVector = MathUtilities.ClampToMaxLength(characterControl.MoveVector, 1f);
// Jump
characterControl.Jump = playerInputs.JumpPressed.IsSet;
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}Then, in ThirdPersonCharacterVariableUpdateSystem:
- Set all of the following update rules
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))] [UpdateAfter(typeof(PredictedFixedStepSimulationSystemGroup))] [UpdateAfter(typeof(ThirdPersonPlayerVariableStepControlSystem))] [BurstCompile] public partial struct ThirdPersonCharacterVariableUpdateSystem : ISystem { // ... }
Note: the ThirdPersonCharacterPhysicsUpdateSystem doesn't need to change, because it's already updating in a sub-group of the physics update group, and the physics update group is automatically added to the prediction group by Netcode.
Then, in OrbitCameraSimulationSystem:
- Set all of the following update rules
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))] [UpdateAfter(typeof(PredictedFixedStepSimulationSystemGroup))] [UpdateAfter(typeof(ThirdPersonPlayerVariableStepControlSystem))] [UpdateAfter(typeof(ThirdPersonCharacterVariableUpdateSystem))] [BurstCompile] public partial struct OrbitCameraSimulationSystem : ISystem { // ... }