fighter/CameraMovementModifier.cs
using System;
using Sandbox;
public sealed class CameraMovementModifier : Component
{
[Property, Group( "Références" )] public CameraComponent Camera { get; set; }
[Property, Group( "Références" )] public SkinnedModelRenderer ArmsRenderer1PP { get; set; }
[Property, Group( "Références" )] public FighterMovement Movement { get; set; }
// =========================================================================
// STRAFE ROLL / TILT PROCÉDURAL
// =========================================================================
[Property, Group( "1. Strafe Roll (Inclinaison)" )]
[Description( "Angle maximal de roll en degrés (ex: 1.5° à 3.0°)." )]
public float MaxRollAngle { get; set; } = 2.0f;
[Property, Group( "1. Strafe Roll (Inclinaison)" )]
[Description( "Vitesse de déplacement latéral requise pour atteindre le roll maximal (en u/s)." )]
public float ReferenceStrafeSpeed { get; set; } = 200f;
[Property, Group( "1. Strafe Roll (Inclinaison)" )]
[Description( "Vitesse de lissage de l'inclinaison de la caméra (plus élevé = plus réactif)." )]
public float RollSmoothSpeed { get; set; } = 8.0f;
// =========================================================================
// POIDS D'ANIMATION DE MARCHE (AnimGraph Blend Add)
// =========================================================================
[Property, Group( "2. AnimGraph Cam Walk" )]
[Description( "Nom du paramètre float dans l'AnimGraph pour doser le blend add de l'animation de marche." )]
public string CamWalkParamName { get; set; } = "cam_walk_weight";
[Property, Group( "2. AnimGraph Cam Walk" )]
[Description( "Vitesse horizontale minimale pour commencer à injecter l'animation de foulée." )]
public float MinWalkThreshold { get; set; } = 15f;
[Property, Group( "2. AnimGraph Cam Walk" )]
[Description( "Vitesse de référence pour atteindre 1.0 de poids d'animation (ex: vitesse de sprint, 280 u/s)." )]
public float MaxSpeedReference { get; set; } = 280f;
[Property, Group( "2. AnimGraph Cam Walk" )]
[Description( "Multiplicateur global du poids d'animation envoyé à l'AnimGraph." )]
public float WalkWeightMultiplier { get; set; } = 1.0f;
[Property, Group( "2. AnimGraph Cam Walk" )]
[Description( "Vitesse de transition du poids d'animation (fondu à l'arrêt ou en saut)." )]
public float WeightSmoothSpeed { get; set; } = 10.0f;
private float _currentRoll = 0f;
private float _currentWalkWeight = 0f;
protected override void OnAwake()
{
Camera ??= Components.GetInChildren<CameraComponent>();
Movement ??= Components.Get<FighterMovement>();
if ( ArmsRenderer1PP == null )
{
var pc = Components.Get<PlayerController>();
if ( pc != null )
ArmsRenderer1PP = pc.ArmsRenderer1PP;
}
}
protected override void OnUpdate()
{
UpdateAnimGraphCamWalk();
}
protected override void OnPreRender()
{
ApplyStrafeRoll();
}
/// <summary>
/// Calcule la vitesse latérale locale (gauche/droite) et applique un roll additif sur la caméra.
/// </summary>
private void ApplyStrafeRoll()
{
if ( Camera == null || Movement == null ) return;
float targetRoll = 0f;
// On ne penche pas si le joueur est en l'air
if ( Movement.IsGrounded )
{
// Récupère la vitesse horizontale
Vector3 vel = Movement.Controller != null && Movement.Controller.Enabled
? Movement.Controller.Velocity
: Movement.WishVelocity;
var horizontalVel = vel.WithZ( 0 );
// Projection dans le repère local du joueur (axe Y = droite/gauche)
var localVel = WorldRotation.Inverse * horizontalVel;
float strafeSpeed = -localVel.y; // > 0 quand strafe à gauche, < 0 quand strafe à droite
float strafeRatio = Math.Clamp( strafeSpeed / MathF.Max( 10f, ReferenceStrafeSpeed ), -1f, 1f );
targetRoll = strafeRatio * MaxRollAngle;
}
// Lissage fluide du retour à zéro
_currentRoll = MathX.Lerp( _currentRoll, targetRoll, RollSmoothSpeed * Time.Delta );
// On compose le roll par-dessus la rotation déjà déterminée par PlayerController (bone motion)
Camera.LocalRotation *= Rotation.FromRoll( _currentRoll );
}
/// <summary>
/// Calcule le poids du blend add d'animation selon la vitesse au sol et l'envoie à l'AnimGraph.
/// </summary>
private void UpdateAnimGraphCamWalk()
{
if ( ArmsRenderer1PP == null || !ArmsRenderer1PP.IsValid || Movement == null ) return;
float targetWeight = 0f;
// L'animation de pas ne doit jouer que si on touche le sol et qu'on avance
if ( Movement.IsGrounded )
{
Vector3 vel = Movement.Controller != null && Movement.Controller.Enabled
? Movement.Controller.Velocity
: Vector3.Zero;
float horizontalSpeed = vel.WithZ( 0 ).Length;
if ( horizontalSpeed > MinWalkThreshold )
{
float speedRatio = Math.Clamp( (horizontalSpeed - MinWalkThreshold) / MathF.Max( 1f, MaxSpeedReference - MinWalkThreshold ), 0f, 1f );
targetWeight = speedRatio * WalkWeightMultiplier;
}
}
// Fondu progressif du poids pour éviter un pop net des bras à l'arrêt ou lors d'un saut
_currentWalkWeight = MathX.Lerp( _currentWalkWeight, targetWeight, WeightSmoothSpeed * Time.Delta );
if ( !string.IsNullOrEmpty( CamWalkParamName ) )
{
ArmsRenderer1PP.Set( CamWalkParamName, _currentWalkWeight );
}
}
}