using System.Text.Json.Nodes; using System.Text.Json.Serialization; namespace Sandbox; /// /// A GameObject which is saved to a file. /// [Expose] [AssetType( Name = "Prefab", Extension = "prefab", Category = "World", Flags = AssetTypeFlags.NoEmbedding )] public partial class PrefabFile : GameResource { /// /// Contains the original JSON read from File. /// public JsonObject RootObject { get; set; } public override int ResourceVersion => 2; /// /// This is used as a reference /// [JsonIgnore] internal PrefabCacheScene CachedScene { get; set; } /// /// Get the actual scene scene /// public PrefabScene GetScene() { if ( CachedScene is not null ) return CachedScene; CachedScene = new PrefabCacheScene() { Source = this, Name = ResourceName }; CachedScene.Load( this ); return CachedScene; } protected override void PostLoad() { PostReload(); } protected override void PostReload() { // Make sure our RootObjects name is consistent with the file name. // In case of renames or duplicated prefabs. if ( RootObject is not null && RootObject[GameObject.JsonKeys.Name]?.GetValue() != ResourceName ) { RootObject[GameObject.JsonKeys.Name] = ResourceName; } // Load the cached scene if ( CachedScene is PrefabCacheScene cachedScene ) { cachedScene.Refresh( this ); } Register(); } protected override void OnDestroy() { CachedScene?.DestroyImmediate(); CachedScene = null; Unregister(); } /// /// If true then we'll show this in the right click menu, so people can create it /// public bool ShowInMenu { get; set; } /// /// If ShowInMenu is true, this is the path in the menu for this prefab /// public string MenuPath { get; set; } /// /// Icon to show to the left of the option in the menu /// [IconName] public string MenuIcon { get; set; } /// /// If true then the prefab will not be broken when created as a template /// public bool DontBreakAsTemplate { get; set; } [Hide, JsonIgnore] protected override Type ActionGraphTargetType => null; [Hide, JsonIgnore] protected override object ActionGraphTarget => null; /// /// Read metadata saved using a ISceneMetadata based component, such as SceneInformation /// public string GetMetadata( string title, string defaultValue = null ) { if ( RootObject is null ) return defaultValue; if ( RootObject["__properties"] is not JsonObject properties ) return defaultValue; if ( properties["Metadata"] is not JsonObject metadata ) return defaultValue; return metadata.GetPropertyValue( title, defaultValue ); } protected override Bitmap CreateAssetTypeIcon( int width, int height ) { var svg = " "; return Bitmap.CreateFromSvgString( svg, width, height ); } #region ObjectsById /// /// If this instance is stored in , what's the key? /// private Guid? _objectDictKey; /// /// Add this instance to , indexed by Guid. If the Guid has changed, /// removes the old entry. /// private void Register() { // // Get the guid of the root object. We'll reference this over the network // to look up this prefab via GameObjectDirectory.FindByGuid. // if ( RootObject.GetPropertyValue( "__guid", null ) is not { } guid ) { Unregister(); return; } if ( ObjectsById.TryGetValue( guid, out var existing ) ) { if ( existing == this ) return; existing.Unregister(); } _objectDictKey = guid; ObjectsById[guid] = this; } /// /// Remove this instance from . /// private void Unregister() { if ( _objectDictKey is not { } guid ) return; _objectDictKey = null; if ( ObjectsById.TryGetValue( guid, out var registered ) && registered != this ) { Log.Warning( $"Another prefab was registered with this Guid: \"{ResourcePath}\" vs \"{registered.ResourcePath}\"" ); return; } ObjectsById.Remove( guid ); } /// /// We store each prefabfile in here indexed by their root object id, allowing /// us to discuss them over the network, because the net system will be able to /// look the GameObject up. /// private static Dictionary ObjectsById { get; } = new(); /// /// We can look up prefabfile by their object guid /// internal static PrefabFile FindByGuid( Guid guid ) { return ObjectsById.GetValueOrDefault( guid ); } #endregion ObjectsById } /// /// A prefab variable definition /// [Expose] [Obsolete] public class PrefabVariable { /// /// A unique id for this variable. This is what it will be referred to in code. /// [ReadOnly] public string Id { get; set; } /// /// A user friendly title for this variable /// public string Title { get; set; } /// /// A user friendly description for this variable /// [TextArea] public string Description { get; set; } /// /// An optional group for this variable to belong to /// public string Group { get; set; } /// /// Lower numbers appear first /// public int Order { get; set; } /// /// Component variables that are being targetted /// [Hide] public List Targets { get; set; } /// /// Add a target property /// public void AddTarget( Guid id, string propertyName ) { Targets.Add( new PrefabVariableTarget { Id = id, Property = propertyName } ); } /// /// Targets a property in a component or gameobject. /// /// The Id of the gameobject or component. /// The name of the parameter on the target. public record struct PrefabVariableTarget( Guid Id, string Property ); }