using ProjectM.Simulation; using Unity.Entities; using Unity.Mathematics; using Unity.NetCode; namespace ProjectM.Client { /// /// Client-only twin-stick input gather. Samples the new Input System action map (the generated /// ProjectMInput wrapper over Assets/Settings/Project M Input.inputactions) once per /// frame and writes on the locally-owned player ghost (filtered to /// ). Runs in — NOT the /// prediction loop — so devices are read once per frame, never re-read during rollback. /// /// Implemented as a managed (not a Burst ISystem) because it holds /// and reads the managed Input System wrapper. Fire is an : the event field /// is reset each frame and raised via Set() on the press edge, so a single click fires /// exactly once; netcode accumulates the absolute Count into the command buffer across the /// frame→tick boundary (read back in AbilityFireSystem as the predicted-spawn key). /// /// /// NOTE: Input System types are fully qualified (e.g. UnityEngine.Vector2) and /// using UnityEngine.InputSystem; is intentionally omitted — that namespace defines a /// PlayerInput type that collides with and /// makes the Entities generator bind RefRW<PlayerInput> to the managed class (a /// spurious CS8377). The generated ProjectMInput wrapper lives in this assembly. /// /// [UpdateInGroup(typeof(GhostInputSystemGroup))] [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)] public partial class PlayerInputGatherSystem : SystemBase { private ProjectMInput _controls; protected override void OnCreate() { RequireForUpdate(); _controls = new ProjectMInput(); _controls.Gameplay.Enable(); } protected override void OnDestroy() { if (_controls != null) { _controls.Gameplay.Disable(); _controls.Dispose(); _controls = null; } } protected override void OnUpdate() { var gameplay = _controls.Gameplay; float2 move = (float2)gameplay.Move.ReadValue(); float2 aim = (float2)gameplay.Aim.ReadValue(); // Right-stick deadzone: a resting stick yields zero Aim so PlayerAimSystem falls back to the // movement heading (controller-first directional aim). if (math.lengthsq(aim) < 0.04f) aim = float2.zero; bool firePressed = gameplay.Fire.WasPressedThisFrame(); foreach (var input in SystemAPI.Query>().WithAll()) { input.ValueRW.Move = move; input.ValueRW.Aim = aim; // Reset the per-frame event, then raise it on the press edge. Netcode latches the // absolute Count into the command buffer; AbilityFireSystem reads it as the SpawnId key. input.ValueRW.Fire = default; if (firePressed) input.ValueRW.Fire.Set(); } } } }