Skip to content

Latest commit

 

History

History
164 lines (146 loc) · 7.24 KB

File metadata and controls

164 lines (146 loc) · 7.24 KB

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:

  • FirstPersonPlayerInputsSystem now updates in the GhostInputSystemGroup and only on clients.
  • Inputs are only gathered for entities with WithAll<GhostOwnerIsLocal>().
  • For "event" inputs, we set them using Set() and get them using IsSet.
  • For "delta" inputs, we set them using NetworkInputUtilities.AddInputDelta and get them using NetworkInputUtilities.GetInputDelta.
    • In order to use NetworkInputUtilities.GetInputDelta, some systems need to get the current and previous tick inputs based on the inputs buffer, using NetworkInputUtilities.GetCurrentAndPreviousTick and NetworkInputUtilities.GetCurrentAndPreviousTickInputs.
  • FirstPersonPlayerVariableStepControlSystem and FirstPersonPlayerFixedStepControlSystem now 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.