- Add SceneNode and EntityNode classes for editor-only metadata storage - Implement SceneGraph view-model with O(1) entity lookup via internal caching - Create IdRemapTable for file-local to global entity ID remapping on load - Implement SceneSerializationContext for load/save operation tracking - Add JSON-serializable SceneAssetData, EntityData, and ComponentData models - Implement SceneSerializer for save/load with validation and reference remapping - Add comprehensive documentation: README.md, IMPLEMENTATION_GUIDE.md, SYSTEM_SUMMARY.md - Update Ghost.Editor.Core.csproj to reference Ghost.Entities assembly - Support parent-child relationships via Hierarchy component - Enforce no cross-scene entity references - Keep runtime minimal: only SceneID, Hierarchy, LocalToWorld components - All editor metadata (names, UI state) stored in editor-only SceneNode/EntityNode classes This implements the architecture from SceneGraph Plan.md with clean separation of concerns, minimal runtime footprint, and AOT compatibility.
4.2 KiB
Architecture Plan: Scene Graph and Scene Representation
The Scene Graph is a hierarchical structure that represents all the objects and entities within a 3D scene in the Ghost Editor.
Scene Graph (Editor representation of runtime data)
There should be two main types of nodes in the Scene Graph:
- Entity Node: Represents an individual entity within a scene. Name stored here, not runtime component.
- Scene Node: Represents a Scene object, which can contain multiple entities. Name stored here not runtime data.
Editor World
Editor contains a different world compares to the runtime world. When user click the Play button, we will create a runtime world and load the scene data from the editor world to the runtime world. This allows us to
- Unload the runtime only systems like physics, rendering, etc when user stop playing.
- Load editor only systems like gizmos, debug, etc when user stop playing.
- Allow editor only entities like editor camera, editor lights, etc to exist in the editor world without affecting the runtime world.
Editor Hierarchy
The Scene Graph should be represented as a tree structure in the editor (TreeView in WinUI 3), where:
- The top level nodes represents the loaded Scenes in the editor world.
- Levels below the Scene nodes represents the Entity nodes that belong to that scene.
- Each Entity node can have child Entity nodes representing parent-child relationships between entities.
An example hierarchy could look like this:
- Scene 1
- Entity A
- Entity B
- Entity C
- Scene 2
- Entity D
Scene (The runtime representation)
A Scene is a collection of entities with SceneID component from a world that are grouped together. There can be multiple scenes in a world.
Save a Scene
When save a scene, all entities with the SceneID component matching the scene's ID should be included in the saved data. When an Entity references another Entity in the same scene, we should store the file local id instead of the global entity id. For example, if Entity A (id: 10, 5th in scene) references Entity B (id: 20, 50th in scene) in the same scene, in the saved data for Entity A, we should store 50 (the file local id) as the reference to Entity B instead of 20 (the global entity id).
We does not allow cross-scene references for now because ideally it's not a good practice to have cross-scene references. We can use query or singleton pattern to access entities from other scenes if needed because they are in the same world.
Load a Scene
When loading a scene, we need to reconstruct the entities and their relationships based on the saved data.
- We allocate the entities in the world and assign them new global entity IDs.
- We remap the file local IDs to the new global entity IDs and change the references accordingly. For example if Entity A (file local id: 5) references Entity B (file local id: 50) in the saved data, we need to find the new global entity IDs for both entities after loading and update the reference in Entity A to point to the new global entity ID of Entity B.
Data format
The scene data should be stored in a structured format (e.g., JSON or binary) that includes:
- Scene metadata (e.g., name, ID. Note that name of entity and scene are editor only data, should be included inside SceneNode and EntityNode, not runtime data)
- List of entities with their components and properties (Entities must in the order that file local id directly maps to the index in the list)
- References between entities using file local IDs
JSON should only be used in the editor and JSON serialization/deserialization logic should also only exist in the editor codebase (Ghost.Editor.Core). Reflection is allowed here. Binary format should be used in the runtime for better performance. The runtime codebase (Ghost.Engine) must be aot compatible.
Currently we strict the IComponent to must be unmanaged and blittable types. However, we also support ManagedEntity and ManagedEntityRef with ScriptComponent to allow OOP like logic for common gameplay logic that DOD pattern is not suitable for. Serializing/deserializing with those components will be tricky. We can use MemoryPack for binary serialization/deserialization because it supports both unmanaged and managed types.