Files
sbox-public/engine/Sandbox.Engine/Scene/Components/Render/SkinnedModelRenderer.Parameters.cs
Garry Newman ca96c2a945 Make Inventory, BaseWeapon, AmmoResource, Screenshake first party (#5245)
https://sbox.game/dev/doc/gameplay/inventory-weapons/

Added

InventoryComponent - a slot based inventory, hotbar or shared bucket slots, with a starting loadout
BaseInventoryItem - base class for inventory items, with hooks to veto switching, holstering and dropping
BaseWeapon - a GMod style base weapon - fire modes, magazines and reserve ammo, reloading, dry fire, ballistics, spread, crosshair and HUD
BaseWeaponModel - view/world model base with muzzle flash, brass ejection, tracers and animation hooks
Ammo types are resources (.ammo) - weapons share reserve pools by type, clamped to a max reserve
BaseWeapon.ShootBullets / ShootBullet - bullet tracing with damage, impulse and hitbox tags
Weapon hold types editor
Animgraph enum parameters can be set by option name - Renderer.Set( "holdtype", "pistol" )
NpcUsage on BaseWeapon - engagement range and burst behaviour so NPCs can use any weapon
ICameraModifier - cameras compose their view through an ordered modifier chain, read the result from CameraComponent.View
CameraEffectSystem - screen shake, directional punches and tilts, with world epicenter falloff - camera.AddShake / AddPunch / AddTilt
EnvShake component - shakes the view, rumbles controllers, kicks physics
TracerEffect - an animated tracer line, ITargetedEffect for effects that fly at a target
SceneAnchor - a world position that can follow an object or bone
SkinnedModelRenderer.GetClosestBone( worldPosition )
Vector3.WithAimCone can take a custom Random for reproducible spread
Improve/Fix

PlayerController searches parents for IPressable when pressing
PlayerController camera runs through the modifier chain - IEvents.PostCameraSetup is obsolete
SoundPlayer preview uses shortcuts - Space toggles playback, Ctrl+Space restarts
Fixed Radius( 0 ) traces missing everything
Fixed NRE in AssetPreviewWidget
Removed

Removed CameraComponent.ISceneCameraSetup (obsolete since October, nothing uses)
2026-07-04 12:46:59 +01:00

423 lines
12 KiB
C#

using System.Text.Json;
using System.Text.Json.Nodes;
namespace Sandbox;
public sealed partial class SkinnedModelRenderer
{
/// <summary>
/// If something sets parameters before the model is spawned, then we store them
/// and apply them when it does spawn. This isn't ideal, but it is what it is.
/// Stored per-type so setting a value type every frame doesn't box.
/// </summary>
Dictionary<string, float> _floatParams;
Dictionary<string, int> _intParams;
Dictionary<string, bool> _boolParams;
Dictionary<string, Vector3> _vectorParams;
Dictionary<string, Rotation> _rotationParams;
Dictionary<string, string> _enumParams;
// A parameter can be reached through more than one overload - an enum by index or by option
// name, a bool by float. Every write drops the other stores' entry for the name, so the last
// write wins when the stored parameters reapply.
public void Set( string v, Vector3 value )
{
ClearParameter( v );
(_vectorParams ??= new( StringComparer.OrdinalIgnoreCase ))[v] = value;
SceneModel?.SetAnimParameter( v, value );
}
public void Set( string v, int value )
{
ClearParameter( v );
(_intParams ??= new( StringComparer.OrdinalIgnoreCase ))[v] = value;
SceneModel?.SetAnimParameter( v, value );
}
public void Set( string v, float value )
{
ClearParameter( v );
(_floatParams ??= new( StringComparer.OrdinalIgnoreCase ))[v] = value;
SceneModel?.SetAnimParameter( v, value );
}
public void Set( string v, bool value )
{
ClearParameter( v );
(_boolParams ??= new( StringComparer.OrdinalIgnoreCase ))[v] = value;
SceneModel?.SetAnimParameter( v, value );
}
public void Set( string v, Rotation value )
{
ClearParameter( v );
(_rotationParams ??= new( StringComparer.OrdinalIgnoreCase ))[v] = value;
SceneModel?.SetAnimParameter( v, value );
}
/// <summary>
/// Set an enum parameter by option name (e.g. Set( "holdtype", "pistol" )).
/// </summary>
public void Set( string v, string option )
{
ClearParameter( v );
(_enumParams ??= new( StringComparer.OrdinalIgnoreCase ))[v] = option;
SceneModel?.SetAnimParameter( v, option );
}
void ApplyStoredAnimParameters()
{
if ( _vectorParams is not null ) foreach ( var p in _vectorParams ) SceneModel.SetAnimParameter( p.Key, p.Value );
if ( _floatParams is not null ) foreach ( var p in _floatParams ) SceneModel.SetAnimParameter( p.Key, p.Value );
if ( _intParams is not null ) foreach ( var p in _intParams ) SceneModel.SetAnimParameter( p.Key, p.Value );
if ( _boolParams is not null ) foreach ( var p in _boolParams ) SceneModel.SetAnimParameter( p.Key, p.Value );
if ( _rotationParams is not null ) foreach ( var p in _rotationParams ) SceneModel.SetAnimParameter( p.Key, p.Value );
if ( _enumParams is not null ) foreach ( var p in _enumParams ) SceneModel.SetAnimParameter( p.Key, p.Value );
// Tick the animation by a frame so we're fully up to date on the first frame.
if ( Scene.IsEditor && !CanUpdateInEditor() )
{
SceneModel.UpdateToBindPose( ReadBonesFromGameObjects );
}
else
{
SceneModel.Update( Time.Delta, ReadBonesFromGameObjects );
}
}
/// <summary>
/// Remove any stored parameters
/// </summary>
public void ClearParameters()
{
_floatParams?.Clear();
_intParams?.Clear();
_boolParams?.Clear();
_vectorParams?.Clear();
_rotationParams?.Clear();
_enumParams?.Clear();
if ( SceneModel.IsValid() )
{
SceneModel.ResetAnimParameters();
}
}
internal void ClearParameter( string name )
{
_floatParams?.Remove( name );
_intParams?.Remove( name );
_boolParams?.Remove( name );
_vectorParams?.Remove( name );
_rotationParams?.Remove( name );
_enumParams?.Remove( name );
}
internal bool ContainsParameter( string name )
{
return (_floatParams?.ContainsKey( name ) ?? false)
|| (_intParams?.ContainsKey( name ) ?? false)
|| (_boolParams?.ContainsKey( name ) ?? false)
|| (_vectorParams?.ContainsKey( name ) ?? false)
|| (_rotationParams?.ContainsKey( name ) ?? false)
|| (_enumParams?.ContainsKey( name ) ?? false);
}
/// <summary>Total number of stored (modified) anim-graph parameters across all types.</summary>
internal int StoredParameterCount => (_floatParams?.Count ?? 0) + (_intParams?.Count ?? 0) + (_boolParams?.Count ?? 0) + (_vectorParams?.Count ?? 0) + (_rotationParams?.Count ?? 0) + (_enumParams?.Count ?? 0);
// public void Set( string v, Enum value ) => _sceneObject.SetAnimParameter( v, value );
public bool GetBool( string v ) => SceneModel?.GetBool( v ) ?? false;
public int GetInt( string v ) => SceneModel?.GetInt( v ) ?? 0;
public float GetFloat( string v ) => SceneModel?.GetFloat( v ) ?? 0.0f;
public Vector3 GetVector( string v ) => SceneModel?.GetVector3( v ) ?? Vector3.Zero;
public Rotation GetRotation( string v ) => SceneModel?.GetRotation( v ) ?? Rotation.Identity;
/// <summary>
/// Converts value to vector local to this entity's eyepos and passes it to SetAnimVector
/// </summary>
public void SetLookDirection( string name, Vector3 eyeDirectionWorld )
{
var delta = eyeDirectionWorld * WorldRotation.Inverse;
Set( name, delta );
}
/// <summary>
/// Converts value to vector local to this entity's eyepos and passes it to SetAnimVector.
/// This also sets {name}_weight to the weight value.
/// </summary>
public void SetLookDirection( string name, Vector3 eyeDirectionWorld, float weight )
{
var delta = eyeDirectionWorld * WorldRotation.Inverse;
Set( name, delta );
Set( $"{name}_weight", weight );
}
/// <summary>
/// Sets an IK parameter. This sets 3 variables that should be set in the animgraph:
/// 1. ik.{name}.enabled
/// 2. ik.{name}.position
/// 3. ik.{name}.rotation
/// </summary>
public void SetIk( string name, Transform tx )
{
// convert local to model
tx = WorldTransform.ToLocal( tx );
Set( $"ik.{name}.enabled", true );
Set( $"ik.{name}.position", tx.Position );
Set( $"ik.{name}.rotation", tx.Rotation );
}
/// <summary>
/// This sets ik.{name}.enabled to false.
/// </summary>
public void ClearIk( string name )
{
Set( $"ik.{name}.enabled", false );
}
ParameterAccessor _parameters;
/// <summary>
/// Access to the animgraph parameters for this model
/// </summary>
[Property, Group( "Parameters", StartFolded = true ), ShowIf( nameof( ShouldShowParametersEditor ), true )]
public ParameterAccessor Parameters
{
get
{
_parameters ??= new( this );
return _parameters;
}
}
public bool ShouldShowParametersEditor
{
get
{
if ( !UseAnimGraph ) return false;
if ( !SceneModel.IsValid() ) return false;
var graph = SceneModel.AnimationGraph;
if ( graph is null ) return false;
if ( graph.ParamCount <= 0 ) return false;
return true;
}
}
public sealed class ParameterAccessor : IJsonPopulator
{
public AnimationGraph Graph => _renderer.IsValid() && _renderer.SceneModel.IsValid() ?
_renderer.SceneModel.AnimationGraph : null;
readonly SkinnedModelRenderer _renderer;
readonly Dictionary<string, bool> _bools = new( StringComparer.OrdinalIgnoreCase );
readonly Dictionary<string, int> _ints = new( StringComparer.OrdinalIgnoreCase );
readonly Dictionary<string, float> _floats = new( StringComparer.OrdinalIgnoreCase );
readonly Dictionary<string, Vector3> _vectors = new( StringComparer.OrdinalIgnoreCase );
readonly Dictionary<string, Rotation> _rotations = new( StringComparer.OrdinalIgnoreCase );
internal ParameterAccessor( SkinnedModelRenderer renderer )
{
_renderer = renderer;
}
public void Clear()
{
_bools.Clear();
_ints.Clear();
_floats.Clear();
_vectors.Clear();
_rotations.Clear();
_renderer.ClearParameters();
}
public void Reset( string name )
{
var parameter = Graph.GetParameterFromList( name );
if ( parameter.IsNull )
return;
var defaultValue = parameter.GetDefaultValue();
switch ( parameter.GetParameterType() )
{
case NativeEngine.AnimParamType.Float:
Set( name, defaultValue.GetValue<float>() );
break;
case NativeEngine.AnimParamType.Int:
Set( name, defaultValue.GetValue<int>() );
break;
case NativeEngine.AnimParamType.Enum:
Set( name, defaultValue.GetValue<byte>() );
break;
case NativeEngine.AnimParamType.Bool:
Set( name, defaultValue.GetValue<bool>() );
break;
case NativeEngine.AnimParamType.Vector:
Set( name, defaultValue.GetValue<Vector3>() );
break;
case NativeEngine.AnimParamType.Rotation:
Set( name, defaultValue.GetValue<Rotation>() );
break;
default:
throw new NotSupportedException( $"Unsupported parameter type: {parameter.GetParameterType()}" );
}
}
public void Clear( string name )
{
Reset( name );
_bools.Remove( name );
_ints.Remove( name );
_floats.Remove( name );
_vectors.Remove( name );
_rotations.Remove( name );
_renderer.ClearParameter( name );
}
public bool Contains( string name )
{
return _renderer.ContainsParameter( name );
}
public bool GetBool( string v ) => _renderer.GetBool( v );
public int GetInt( string v ) => _renderer.GetInt( v );
public float GetFloat( string v ) => _renderer.GetFloat( v );
public Vector3 GetVector( string v ) => _renderer.GetVector( v );
public Rotation GetRotation( string v ) => _renderer.GetRotation( v );
public void Set( string v, Vector3 value )
{
_vectors[v] = value;
_renderer.Set( v, value );
}
public void Set( string v, int value )
{
_ints[v] = value;
_renderer.Set( v, value );
}
public void Set( string v, float value )
{
_floats[v] = value;
_renderer.Set( v, value );
}
public void Set( string v, bool value )
{
_bools[v] = value;
_renderer.Set( v, value );
}
public void Set( string v, Rotation value )
{
_rotations[v] = value;
_renderer.Set( v, value );
}
JsonNode IJsonPopulator.Serialize()
{
var obj = new JsonObject();
var boolsObj = new JsonObject();
var intsObj = new JsonObject();
var floatsObj = new JsonObject();
var vectorsObj = new JsonObject();
var rotationsObj = new JsonObject();
foreach ( var value in _bools )
{
boolsObj.Add( value.Key, value.Value );
}
foreach ( var value in _ints )
{
intsObj.Add( value.Key, value.Value );
}
foreach ( var value in _floats )
{
floatsObj.Add( value.Key, value.Value );
}
foreach ( var value in _vectors )
{
vectorsObj.Add( value.Key, JsonSerializer.SerializeToNode( value.Value ) );
}
foreach ( var value in _rotations )
{
rotationsObj.Add( value.Key, JsonSerializer.SerializeToNode( value.Value ) );
}
obj.Add( "bools", boolsObj );
obj.Add( "ints", intsObj );
obj.Add( "floats", floatsObj );
obj.Add( "vectors", vectorsObj );
obj.Add( "rotations", rotationsObj );
return obj;
}
void IJsonPopulator.Deserialize( JsonNode e )
{
if ( e is not JsonObject jso )
return;
_bools.Clear();
_ints.Clear();
_floats.Clear();
_vectors.Clear();
_rotations.Clear();
if ( jso.TryGetPropertyValue( "bools", out var boolsNode ) && boolsNode is JsonObject boolsObj )
{
foreach ( var o in boolsObj )
{
Set( o.Key, o.Value.GetValue<bool>() );
}
}
if ( jso.TryGetPropertyValue( "ints", out var intsNode ) && intsNode is JsonObject intsObj )
{
foreach ( var o in intsObj )
{
Set( o.Key, o.Value.GetValue<int>() );
}
}
if ( jso.TryGetPropertyValue( "floats", out var floatsNode ) && floatsNode is JsonObject floatsObj )
{
foreach ( var o in floatsObj )
{
Set( o.Key, o.Value.GetValue<float>() );
}
}
if ( jso.TryGetPropertyValue( "vectors", out var vectorsNode ) && vectorsNode is JsonObject vectorsObj )
{
foreach ( var o in vectorsObj )
{
Set( o.Key, JsonSerializer.Deserialize<Vector3>( o.Value ) );
}
}
if ( jso.TryGetPropertyValue( "rotations", out var rotationsNode ) && rotationsNode is JsonObject rotationsObj )
{
foreach ( var o in rotationsObj )
{
Set( o.Key, JsonSerializer.Deserialize<Rotation>( o.Value ) );
}
}
}
}
}