Add scene graph draft

This commit is contained in:
2026-01-25 22:06:58 +09:00
parent fdf831630b
commit 49f54c6b43
18 changed files with 1632 additions and 1553 deletions

View File

@@ -1,50 +1,154 @@
using System.Collections.ObjectModel;
using Ghost.Engine.Core;
using Ghost.Entities;
using System.Collections.ObjectModel;
namespace Ghost.Editor.Core.SceneGraph;
/// <summary>
/// Represents a Scene node in the editor hierarchy.
/// Contains editor-only metadata like name and display state.
/// The actual scene data (entities, components) is stored as SceneID in the runtime ECS world.
/// Represents a scene node in the editor scene graph hierarchy.
/// Contains editor-only metadata like display name and root entities.
/// </summary>
public class SceneNode
{
public string Name { get; set; }
public short SceneId { get; private set; }
public Guid SceneGuid { get; private set; }
/// <summary>
/// Child entity nodes belonging to this scene.
/// </summary>
public ObservableCollection<EntityNode> Children { get; }
private string _name;
private readonly ObservableCollection<EntityNode> _rootEntities;
public SceneNode(string name, short sceneId, Guid? sceneGuid = null)
/// <summary>
/// Gets or sets the runtime scene this node represents.
/// </summary>
public Scene Scene { get; set; }
/// <summary>
/// Gets or sets the display name for this scene in the editor.
/// This is NOT stored in runtime data.
/// </summary>
public string Name
{
Name = name;
SceneId = sceneId;
SceneGuid = sceneGuid ?? Guid.NewGuid();
Children = new ObservableCollection<EntityNode>();
get => _name;
set
{
_name = value;
OnNameChanged?.Invoke(this);
}
}
/// <summary>
/// Finds an entity node by its global entity ID.
/// Searches recursively through the hierarchy.
/// Gets or sets the file path where this scene is saved.
/// </summary>
public EntityNode? FindEntityNode(Entity entityId)
{
foreach (var child in Children)
{
if (child.EntityId == entityId)
return child;
public string? FilePath { get; set; }
var found = child.FindRecursive(entityId);
/// <summary>
/// Gets or sets whether this scene is currently loaded in the editor.
/// </summary>
public bool IsLoaded { get; set; }
/// <summary>
/// Gets or sets whether this scene has unsaved changes.
/// </summary>
public bool IsDirty { get; set; }
/// <summary>
/// Gets the collection of root entity nodes in this scene.
/// </summary>
public ObservableCollection<EntityNode> RootEntities => _rootEntities;
/// <summary>
/// Event raised when the name property changes.
/// </summary>
public event Action<SceneNode>? OnNameChanged;
/// <summary>
/// Event raised when root entities collection changes.
/// </summary>
public event Action<SceneNode>? OnRootEntitiesChanged;
public SceneNode(Scene scene, string name = "New Scene")
{
Scene = scene;
_name = name;
_rootEntities = [];
_rootEntities.CollectionChanged += (s, e) => OnRootEntitiesChanged?.Invoke(this);
}
/// <summary>
/// Adds a root entity node to this scene.
/// </summary>
/// <param name="entityNode">The entity node to add.</param>
public void AddRootEntity(EntityNode entityNode)
{
// Remove from previous parent if any
if (entityNode.Parent != null)
{
entityNode.Parent.RemoveChild(entityNode);
}
entityNode.Parent = null;
entityNode.OwnerScene = this;
_rootEntities.Add(entityNode);
}
/// <summary>
/// Removes a root entity node from this scene.
/// </summary>
/// <param name="entityNode">The entity node to remove.</param>
/// <returns>True if the entity was removed, false otherwise.</returns>
public bool RemoveRootEntity(EntityNode entityNode)
{
if (_rootEntities.Remove(entityNode))
{
entityNode.OwnerScene = null;
return true;
}
return false;
}
/// <summary>
/// Gets all entity nodes in this scene (root and descendants) in depth-first order.
/// </summary>
/// <returns>An enumerable of all entity nodes in the scene.</returns>
public IEnumerable<EntityNode> GetAllEntities()
{
foreach (var root in _rootEntities)
{
yield return root;
foreach (var descendant in root.GetAllDescendants())
{
yield return descendant;
}
}
}
/// <summary>
/// Finds an entity node by its entity reference.
/// </summary>
/// <param name="entity">The entity to search for.</param>
/// <returns>The entity node if found, null otherwise.</returns>
public EntityNode? FindNode(Entity entity)
{
foreach (var root in _rootEntities)
{
var found = root.FindNode(entity);
if (found != null)
{
return found;
}
}
return null;
}
public override string ToString() => $"Scene: {Name} (ID: {SceneId})";
/// <summary>
/// Marks this scene as dirty (has unsaved changes).
/// </summary>
public void MarkDirty()
{
IsDirty = true;
}
public override string ToString()
{
return $"{Name} (Scene ID: {Scene.ID}, Entities: {_rootEntities.Count})";
}
}