First, convert FirstPersonPlayerInputs component to a IInputComponentData, and replace the FixedInputEvent fields with Netcode's InputEvent:
[Serializable]
public struct FirstPersonPlayerInputs : IInputComponentData
{
public float2 MoveInput;
public float2 LookInput;
public InputEvent JumpPressed; // This is now an InputEvent
}Then, add this component to your project, and modify your FirstPersonPlayerAuthoring so that it adds this component to the player entity:
[Serializable]
[GhostComponent(SendTypeOptimization = GhostSendType.OnlyPredictedClients)]
public struct FirstPersonPlayerNetworkInput : IComponentData
{
[GhostField()]
public float2 LastProcessedLookInput;
}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:
FirstPersonPlayerInputsSystemnow 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
FirstPersonPlayerVariableStepControlSystemandFirstPersonPlayerFixedStepControlSystemnow 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 FirstPersonPlayerInputsSystem : SystemBase
{
protected override void OnCreate()
{
RequireForUpdate<NetworkTime>();
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<FirstPersonPlayer, FirstPersonPlayerInputs>().Build());
}
protected override void OnUpdate()
{
foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<FirstPersonPlayerInputs>, FirstPersonPlayer>().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.LookInput, Mouse.current.delta.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 FirstPersonPlayerVariableStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<FirstPersonPlayer, FirstPersonPlayerInputs>().Build());
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, playerNetworkInput, player) in SystemAPI.Query<FirstPersonPlayerInputs, RefRW<FirstPersonPlayerNetworkInput>, FirstPersonPlayer>().WithAll<Simulate>())
{
// Compute input deltas, compared to last known values
float2 lookInputDelta = InputDeltaUtilities.GetInputDelta(
playerInputs.LookInput,
playerNetworkInput.ValueRO.LastProcessedLookInput);
playerNetworkInput.ValueRW.LastProcessedLookInput = playerInputs.LookInput;
if (SystemAPI.HasComponent<FirstPersonCharacterControl>(player.ControlledCharacter))
{
FirstPersonCharacterControl characterControl = SystemAPI.GetComponent<FirstPersonCharacterControl>(player.ControlledCharacter);
characterControl.LookDegreesDelta = lookInputDelta;
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}
/// <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 FirstPersonPlayerFixedStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<FirstPersonPlayer, FirstPersonPlayerInputs>().Build());
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, player) in SystemAPI.Query<FirstPersonPlayerInputs, FirstPersonPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<FirstPersonCharacterControl>(player.ControlledCharacter))
{
FirstPersonCharacterControl characterControl = SystemAPI.GetComponent<FirstPersonCharacterControl>(player.ControlledCharacter);
quaternion characterRotation = SystemAPI.GetComponent<LocalTransform>(player.ControlledCharacter).Rotation;
// Move
float3 characterForward = MathUtilities.GetForwardFromRotation(characterRotation);
float3 characterRight = MathUtilities.GetRightFromRotation(characterRotation);
characterControl.MoveVector = (playerInputs.MoveInput.y * characterForward) + (playerInputs.MoveInput.x * characterRight);
characterControl.MoveVector = MathUtilities.ClampToMaxLength(characterControl.MoveVector, 1f);
// Jump
characterControl.Jump = playerInputs.JumpPressed.IsSet;
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}Then, in FirstPersonCharacterVariableUpdateSystem:
- Set all of the following update rules
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))] [UpdateAfter(typeof(PredictedFixedStepSimulationSystemGroup))] [UpdateAfter(typeof(FirstPersonPlayerVariableStepControlSystem))] [BurstCompile] public partial struct FirstPersonCharacterVariableUpdateSystem : ISystem { // ... }
Note: the FirstPersonCharacterPhysicsUpdateSystem 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.