Files
Project-M/Assets/_Project/Scripts/Simulation/Player/PlayerAimSystem.cs
T
2026-06-03 18:35:05 -07:00

70 lines
3.0 KiB
C#

using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
namespace ProjectM.Simulation
{
/// <summary>
/// Predicted aim/facing: writes <see cref="PlayerFacing"/> from twin-stick Aim, falling back to
/// the movement direction when Aim is zero (controller-first directional aim). Also turns the
/// ghost transform toward the facing direction for top-down presentation. When there is no input
/// this tick the previous facing is held. Deterministic (pure math); filtered to
/// <see cref="Simulate"/> so it runs only for predicted ghosts.
/// </summary>
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[BurstCompile]
public partial struct PlayerAimSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float dt = SystemAPI.Time.DeltaTime;
foreach (var (facing, transform, input, stats) in
SystemAPI.Query<RefRW<PlayerFacing>, RefRW<LocalTransform>, RefRO<PlayerInput>, RefRO<EffectiveCharacterStats>>()
.WithAll<Simulate>().WithDisabled<Dead>())
{
float2 aim = input.ValueRO.Aim;
if (math.lengthsq(aim) < 1e-6f)
aim = input.ValueRO.Move; // fall back to movement heading
if (math.lengthsq(aim) < 1e-6f)
continue; // no input this tick: keep last facing
aim = math.normalize(aim);
// Rate-limited turn: rotate the current facing toward the aim target by at most
// TurnRateRadiansPerSec * dt this tick. Deterministic (pure planar math, fixed-step dt)
// so it replays correctly on rollback; the first tick (uninitialized facing) snaps.
float2 cur = facing.ValueRO.Direction;
float2 dir;
if (math.lengthsq(cur) < 1e-6f)
{
dir = aim; // uninitialized facing -> snap to target
}
else
{
cur = math.normalize(cur);
float maxStep = stats.ValueRO.TurnRateRadiansPerSec * dt;
float angle = math.acos(math.clamp(math.dot(cur, aim), -1f, 1f));
if (angle <= maxStep)
{
dir = aim; // within reach this tick
}
else
{
float sign = (cur.x * aim.y - cur.y * aim.x) >= 0f ? 1f : -1f;
math.sincos(maxStep * sign, out float sn, out float cs);
dir = math.normalize(new float2(cur.x * cs - cur.y * sn, cur.x * sn + cur.y * cs));
}
}
facing.ValueRW.Direction = dir;
float3 forward = new float3(dir.x, 0f, dir.y);
transform.ValueRW.Rotation = quaternion.LookRotationSafe(forward, math.up());
}
}
}
}