Components/MorphState.cs
/// <summary>
/// Persists morph values on a GameObject so they survive over the network and dupes.
/// </summary>
[Title( "Morph State" )]
[Category( "Rendering" )]
public sealed class MorphState : Component
{
/// <summary>
/// Serialized morphs. Persisted for dupes/saves — not synced over the network.
/// </summary>
[Property]
public string SerializedMorphs { get; set; }
protected override void OnStart()
{
Apply();
}
/// <summary>
/// Apply a partial morph batch on the host, then broadcast to all clients.
/// </summary>
public void ApplyBatch( string morphsJson )
{
var smr = GameObject.GetComponentInChildren<SkinnedModelRenderer>();
if ( !smr.IsValid() ) return;
var morphs = Json.Deserialize<Dictionary<string, float>>( morphsJson );
if ( morphs is null ) return;
foreach ( var (name, val) in morphs )
smr.SceneModel.Morphs.Set( name, val );
Capture( smr );
BroadcastBatch( morphsJson );
}
/// <summary>
/// Apply a full morph preset on the host (resets all first), then broadcast to all clients.
/// </summary>
public void ApplyPreset( string morphsJson )
{
var smr = GameObject.GetComponentInChildren<SkinnedModelRenderer>();
if ( !smr.IsValid() ) return;
var morphs = Json.Deserialize<Dictionary<string, float>>( morphsJson );
if ( morphs is null ) return;
foreach ( var name in smr.Morphs.Names )
smr.SceneModel.Morphs.Reset( name );
foreach ( var (name, val) in morphs )
smr.SceneModel.Morphs.Set( name, val );
Capture( smr );
BroadcastPreset( morphsJson );
}
[Rpc.Broadcast]
private void BroadcastBatch( string morphsJson )
{
if ( Networking.IsHost ) return;
var smr = GameObject.GetComponentInChildren<SkinnedModelRenderer>();
if ( !smr.IsValid() ) return;
var morphs = Json.Deserialize<Dictionary<string, float>>( morphsJson );
if ( morphs is null ) return;
foreach ( var (name, val) in morphs )
smr.SceneModel.Morphs.Set( name, val );
}
[Rpc.Broadcast]
private void BroadcastPreset( string morphsJson )
{
if ( Networking.IsHost ) return;
var smr = GameObject.GetComponentInChildren<SkinnedModelRenderer>();
if ( !smr.IsValid() ) return;
var morphs = Json.Deserialize<Dictionary<string, float>>( morphsJson );
if ( morphs is null ) return;
foreach ( var name in smr.Morphs.Names )
smr.SceneModel.Morphs.Reset( name );
foreach ( var (name, val) in morphs )
smr.SceneModel.Morphs.Set( name, val );
}
/// <summary>
/// Snapshot all current morph values from <paramref name="smr"/> into <see cref="SerializedMorphs"/>.
/// </summary>
public void Capture( SkinnedModelRenderer smr )
{
SerializedMorphs = Json.Serialize( smr.Morphs.Names.ToDictionary( n => n, n => smr.SceneModel?.Morphs.Get( n ) ?? 0f ) );
}
/// <summary>
/// Apply the stored <see cref="SerializedMorphs"/> to the first <see cref="SkinnedModelRenderer"/> we find.
/// Called on spawn/dupe restore.
/// </summary>
public void Apply()
{
if ( string.IsNullOrEmpty( SerializedMorphs ) ) return;
var smr = GameObject.GetComponentInChildren<SkinnedModelRenderer>();
if ( !smr.IsValid() ) return;
var morphs = Json.Deserialize<Dictionary<string, float>>( SerializedMorphs );
if ( morphs is null ) return;
foreach ( var name in smr.Morphs.Names )
smr.SceneModel.Morphs.Reset( name );
foreach ( var (name, val) in morphs )
smr.SceneModel.Morphs.Set( name, val );
}
}