ProjectObject

August 28, 2026 ยท View on GitHub

Composed world objects for ALIS - characters, creatures, buildings, furniture, and interactive elements.

This README describes the code-side composition model. Some folder examples refer to the full project checkout; the public mirror excludes Unreal content payloads and other non-code assets.

Purpose

  • ACTORS ONLY - this plugin contains AActor classes that compose components
  • Placeable C++ actors for world objects
  • Composes capability components from ProjectObjectCapabilities
  • Composes motion components from ProjectMotionSystem
  • Thin wrappers - behavior lives in reusable components
  • Pattern: Actor = Mesh + Capability Components

Actors vs Components (IMPORTANT)

ProjectObject (this)                 ProjectObjectCapabilities (Gameplay/)
--------------------                 ------------------------------------
ACTORS that compose                  COMPONENTS ONLY (reusable capabilities)

AInteractableActor (base)            ULockableComponent
AOpenableActor (Mesh + Lock)         UHealthComponent (future)
AHingedOpenable (+ Rotator)          UPowerableComponent (future)
ASlidingOpenable (+ Slider)

Rule: If you're creating a placeable world object, put it HERE. If you're creating a reusable capability (lock, health, power), put it in ProjectObjectCapabilities.

Architecture

Plugins/Resources/ProjectObject/
|-- Content/
|   |-- Human/                       <- Humanoid characters (player, NPCs)
|   |-- Animal/                      <- Animal creatures
|   |-- Template/                    <- Man-made object templates
|   |   `-- Fenestration/            <- Doors, windows (Content)
|   `-- Nature/                      <- Natural objects (rocks, plants)
`-- Source/ProjectObject/
    |-- Public/
    |   `-- Template/                <- Base actor templates (C++)
    |       |-- Interactable/
    |       |   `-- InteractableActor.h    <- Base: interaction dispatch
    |       `-- Openable/
    |           |-- OpenableActor.h        <- Base: Mesh + LockComponent
    |           |-- HingedOpenable.h       <- Adds SpringRotatorComponent
    |           `-- SlidingOpenable.h      <- Adds SpringSliderComponent
    `-- Private/
        `-- Template/
            |-- Interactable/
            |   `-- InteractableActor.cpp
            `-- Openable/
                `-- *.cpp

Cross-Reference Consumers (check before renaming)

Object definition IDs, dialogue tree IDs, loot profile IDs, and audio preset IDs are referenced across multiple plugins. Renaming any of these breaks downstream consumers silently at runtime.

Consumers:

  • seedEntries objectId -- loot containers and object defs reference object IDs
  • lootProfileId -- object defs reference loot profile profileId
  • DialogueTreeAsset -- object capabilities reference DLG_*.json asset paths
  • AudioPresetAsset -- object capabilities reference AUDIO_*.json asset paths
  • Dialogue actions -- DLG_*.json actions reference object IDs and other DLG trees
  • Dialogue conditions -- DLG_*.json options reference object IDs for inventory checks
  • ProjectMind signal_tags -- thought mappings reference dialogue tree/node IDs

Validate after any rename:

python scripts/ue/check/data/validate_all.py
python scripts/ue/check/gameplay/projectmind/validate_data.py

Pre-commit hook (.githooks/pre-commit) runs these automatically when relevant JSON files are staged.

Content Categories

FolderPurposeExamples
Human/Humanoid characters with skeletal meshesPlayer, NPCs, zombies
Animal/Animal creatures with skeletal meshesDogs, birds, mutants
Template/Man-made static/interactive objectsBuildings, furniture, doors
Nature/Natural world objectsRocks, trees (non-animated)

NOTE: Characters in Human/ and Animal/ REFERENCE assets from universal asset libraries:

  • ProjectAnimation (skeletal templates, animations, blendspaces)
  • ProjectMesh (static mesh parts)
  • ProjectMaterial (materials)
  • ProjectTexture (textures)

They compose these universal assets into complete characters/objects.

Material ownership follows the same universal/concrete split. A reusable material family, graph, or compiler archetype belongs to ProjectMaterial. A genuinely ObjectId-specific material or texture may remain beside that concrete entity and be selected by its mesh override. If the resource becomes reused, generalize and promote it to ProjectMaterial instead of teaching ProjectMaterial about the concrete object. ProjectObject never owns material compiler or archetype logic.

Dependency Flow

ProjectInventory (Features)
    |
    v depends on
ProjectObject (Resources) <- this plugin
    |
    v depends on
ProjectAnimation (skeletal templates, animations - for Human/Animal)
ProjectMotionSystem (USpringRotatorComponent, procedural motion)
ProjectObjectCapabilities (ULockableComponent)
ProjectWorld (AProjectWorldActor base)
ProjectCore

Door Actor

AProjectDoorActor - Interactive door composing motion and lock capabilities.

Components

ComponentSourcePurpose
UStaticMeshComponentEngineDoor visual mesh
USpringRotatorComponentProjectMotionSystemSpring-damper rotation
ULockableComponentProjectObjectCapabilitiesKey/lock access control

Placement

Place via Place Actors -> PROJECT_Template -> Project Door Actor

(Registered by ProjectPlacementEditor)

Configuration

Configure via components in Details panel:

RotatorComponent (Motion):

PropertyDefaultDescription
OpenAngle90Rotation angle when open (degrees)
Stiffness100Spring stiffness (higher = faster)
Damping10Damping ratio (higher = less oscillation)

LockComponent (Access):

PropertyDefaultDescription
LockTagEmptyRequired key tag (empty = unlocked)
bConsumeKeyOnUnlockfalseWhether key is consumed on use

Actor:

PropertyDescription
DoorMeshAssetDoor visual mesh (soft reference)

Lock/Key System

Doors can be locked via LockComponent.LockTag (gameplay tag). The lock component blocks interaction and provides a UI prompt via GetInteractionPrompt. Key checks and inventory consumption are not wired here yet. That integration belongs to gameplay/interaction logic, not ProjectObject.

Interaction Flow (Single Selection)

When multiple capabilities are eligible for the same interaction, AInteractableActor selects exactly one capability. Selection follows the same rule as focus/HUD label resolution.

Player presses E on "door" mesh
    |
    v
AInteractableActor::OnInteract
    |
    Select best capability:
      1) highest-priority mesh-scoped capability matching hit hierarchy
      2) else highest-priority actor-scoped capability
    |
    [100] LockableComponent::OnComponentInteract()
            -> executes once (no multi-trigger fan-out)

Selection rules:

  • Mesh-scoped: highest-priority matching capability wins.
  • Actor-scope fallback: if no mesh match, highest-priority actor-scoped capability wins.
  • No hit component: highest-priority cached capability executes.

API

// Convenience (delegates to RotatorComponent)
void ToggleDoor();

// Access components directly for queries
RotatorComponent->bIsOpen;
RotatorComponent->bIsAnimating;
LockComponent->IsLocked();
LockComponent->GetLockTag();

Data-Driven Objects

UObjectDefinition enables fully data-driven object creation from JSON.

Pattern: JSON describes complete object (meshes + capabilities + optional item data), factory builds dynamically on spawnClass (or falls back to AInteractableActor).

Note: Do not add a "$schema" field to object JSON files under Plugins/Resources/ProjectObject/Content/. The generator does not use it, and the schema does not allow it.

Architecture: See Layer Contract for the 3-layer separation (Capabilities / Item Data / GAS Profiles).

Structure

PropertyTypeDescription
ObjectIdFNameStable ID (decoupled from asset path)
SpawnClassTSoftClassPtrOptional parent actor class for spawn (spawnClass JSON field). Accepted input forms: /Game/.../BP_Name or /Script/Module.ClassName
AttachToComponentTagFNameOptional default attach root tag for generated meshes
ActorTagsTArrayOptional actor tags applied to spawned actor instance (actorTags JSON field)
MeshesTArrayMesh ID + soft object ref + optional materials
CapabilitiesTArrayType + scope + properties

ID Resolution: ObjectId is stable for gameplay/runtime references (survives folder moves). Folder path is for editor browsing only. Cross-plugin lookup via AssetManager->GetPrimaryAssetObject().

Mesh Entry Fields

Each mesh entry in Meshes array supports:

FieldTypeRequiredDescription
idstring[v]Mesh identifier for capability scoping
assetstring[v]Path to StaticMesh (auto-normalized)
parentstringParent mesh ID for hierarchical attachment
transformobjectRelative location/rotation/scale
physicsobjectMass, damping, collision settings
materialsarrayMaterial overrides (index = slot)

Material Overrides:

  • Array index maps to material slot index (0 = slot 0, etc.)
  • Supports short paths: /ProjectObject/Materials/M_Wood
  • Auto-normalized to full soft paths: /ProjectObject/Materials/M_Wood.M_Wood
  • Null entries use mesh default for that slot
  • Empty array or omitted = use all mesh defaults

Example:

{
  "id": "frame",
  "asset": "/ProjectObject/.../SM_DoorFrame",
  "materials": [
    "/ProjectObject/Materials/M_Oak",
    null,
    "/Game/Materials/M_Handle"
  ]
}

Capabilities

Capabilities are C++ components with stable IDs via GetPrimaryAssetId():

IDComponentScope
LockableULockableComponentactor
HingedUSpringRotatorComponentmesh
SlidingUSpringSliderComponentmesh

AssetRegistry Tags

UObjectDefinition exports capability tags to AssetRegistry for UI filtering without asset loads. See GetAssetRegistryTags.

Tags exported:

TagExample ValuePurpose
ALIS.Cap.<Name>"true"Boolean per capability (Hinged, Lockable, Pickup, etc.)
ALIS.Section.<Name>"true"Boolean per section (Item, Storage, etc.)
ALIS.ItemTag.<Tag>"true"Boolean per item tag (e.g., ALIS.ItemTag.Item.Type.Consumable)
DisplayName"Water Bottle"Tooltip display (from Item section)
Weight"0.5"Tooltip display (from Item section)

Usage: UI reads tags via FAssetData::GetTagValue() - no asset load required. Enables filtering thousands of objects instantly.

Scope behavior:

  • ["actor"] - single component on actor root
  • ["panel"] - component bound to that mesh via interaction target interface
  • ["left", "right"] - spawns component per mesh (same config)

Placement

Window > Project Placement > Objects section:

  • Double-click: opens asset editor
  • Drag-drop to viewport: spawns actor

Placement Guardrails (Fail Fast)

Spawn now fails fast instead of silently placing partially-invalid actors.

Error signatures to watch in Output Log:

  • ObjectSpawn: Failed to persist definition host state ...
  • ObjectSpawn: Capability property apply failed ...

Meaning:

  • Host state error: actor was spawned but definition metadata did not round-trip correctly (ObjectDefinitionId and hashes).
  • Capability apply error: one or more capability properties from JSON could not be applied (for example wrong property name, bad gameplay tag, unsupported value format).

Expected response:

  1. Regenerate the definition from source JSON.
  2. Fix the reported failing property in JSON or tag registration.
  3. Re-place actor from Project Placement (do not keep partially-configured old instance).

Architecture: See Flexible Path Pattern

Definition Update System (Auto-Update When JSON Changes)

When JSON definitions change, placed actors are automatically updated without manual intervention.

See: Data Pipeline Architecture for complete sync/generation/propagation flow.

Update behavior:

Two-tier approach:

Change TypeActionWhat's Preserved
Property value (OpenPosition, Stiffness...)ReapplyEverything - references, sequencer, attachments
Mesh transformReapplyEverything
Mesh asset pathReapplyEverything
Mesh material overridesReapplyEverything
Mesh added/removedReplaceTransform, label, folder
Capability added/removedReplaceTransform, label, folder

Implementation details:

  • ObjectDefinitionId on actor links it to source definition
  • DefinitionStructureHash detects structural changes
  • Mesh components tagged with DefMeshId=<id> for reliable matching
  • Actor update flow is handled by ProjectPlacementEditor and the definition propagation pipeline described in ../../../docs/data/README.md

Logging: Full cycle logging under LogInteractableActor and LogDefinitionGeneratorEditor categories


Adding New Object Types (C++ Templates)

Follow the pattern:

  1. Create header in Public/Template/<Category>/
  2. Create implementation in Private/Template/<Category>/
  3. Inherit from AProjectWorldActor (provides DataId GUID)
  4. Compose capability components as needed
  5. Register in ProjectPlacementEditor
  6. Add interaction handling in appropriate Features plugin

Legacy Paths

Code marker format:

  • // LEGACY_OBJECT_PARENT_GENERALIZATION(L###): <reason>. Remove when <condition>.
Legacy IDLocationWhy It ExistsRemove Trigger
L001Source/ProjectObject/Private/Spawning/ObjectSpawnUtility.cpp (SpawnObjectFromDefinitionInternal)Keep fallback spawn behavior for definitions without spawnClassRemove when all targeted definitions use explicit parent/host path and fallback-free smoke tests pass

References