diff --git a/Gems/O3DEReflect/CMakeLists.txt b/Gems/O3DEReflect/CMakeLists.txt
new file mode 100644
index 0000000000..9a1f55cffe
--- /dev/null
+++ b/Gems/O3DEReflect/CMakeLists.txt
@@ -0,0 +1,18 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+
+
+o3de_gem_setup("O3DEReflect")
+
+set(gem_path ${CMAKE_CURRENT_LIST_DIR})
+set(gem_json ${gem_path}/gem.json)
+o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
+
+o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_parent_relative_path}")
+
+ly_add_external_target_path(${CMAKE_CURRENT_LIST_DIR}/3rdParty)
+
+add_subdirectory(Code)
+
diff --git a/Gems/O3DEReflect/Code/CMakeLists.txt b/Gems/O3DEReflect/Code/CMakeLists.txt
new file mode 100644
index 0000000000..180ddb0f21
--- /dev/null
+++ b/Gems/O3DEReflect/Code/CMakeLists.txt
@@ -0,0 +1,55 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+
+# O3DEReflect gem - no platform-specific code needed
+
+ly_add_target(
+ NAME O3DEReflect.Static STATIC
+ NAMESPACE Gem
+ FILES_CMAKE
+ o3dereflect_files.cmake
+ INCLUDE_DIRECTORIES
+ PUBLIC
+ Include
+ Source
+ BUILD_DEPENDENCIES
+ PUBLIC
+ AZ::AzCore
+ AZ::AzFramework
+)
+
+ly_add_target(
+ NAME O3DEReflect ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+ NAMESPACE Gem
+ FILES_CMAKE
+ o3dereflect_shared_files.cmake
+ INCLUDE_DIRECTORIES
+ PUBLIC
+ Include
+ PRIVATE
+ Source
+ BUILD_DEPENDENCIES
+ PRIVATE
+ Gem::O3DEReflect.Static
+)
+
+# Inject the gem name into the Module source file
+ly_add_source_properties(
+ SOURCES
+ Source/O3DEReflectModule.cpp
+ PROPERTY COMPILE_DEFINITIONS
+ VALUES
+ O3DE_GEM_NAME=O3DEReflect
+ O3DE_GEM_VERSION=1.0.0
+)
+
+# Load the "Gem::O3DEReflect" module in all types of applications
+ly_create_alias(NAME O3DEReflect.Servers NAMESPACE Gem TARGETS Gem::O3DEReflect)
+ly_create_alias(NAME O3DEReflect.Unified NAMESPACE Gem TARGETS Gem::O3DEReflect)
+ly_create_alias(NAME O3DEReflect.Clients NAMESPACE Gem TARGETS Gem::O3DEReflect)
+ly_create_alias(NAME O3DEReflect.Tools NAMESPACE Gem TARGETS Gem::O3DEReflect)
+ly_create_alias(NAME O3DEReflect.Builders NAMESPACE Gem TARGETS Gem::O3DEReflect)
+
+
diff --git a/Gems/O3DEReflect/Code/Examples/AIState.O3DEReflect.xml b/Gems/O3DEReflect/Code/Examples/AIState.O3DEReflect.xml
new file mode 100644
index 0000000000..3d572e05f6
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/AIState.O3DEReflect.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gems/O3DEReflect/Code/Examples/BeforeAfterComparison.h b/Gems/O3DEReflect/Code/Examples/BeforeAfterComparison.h
new file mode 100644
index 0000000000..6495ac0d6a
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/BeforeAfterComparison.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace TraditionalExample
+{
+ // HEADER FILE - PlayerMovementComponent.h
+ // ========================================
+
+ class PlayerMovementComponent
+ : public AZ::Component
+ {
+ public:
+ AZ_COMPONENT(PlayerMovementComponent, "{A1B2C3D4-E5F6-7890-ABCD-123456789ABC}");
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+ static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+ static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+ static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+ PlayerMovementComponent() = default;
+ ~PlayerMovementComponent() override = default;
+
+ // Getters and Setters - MUST WRITE MANUALLY
+ float GetMoveSpeed() const { return m_moveSpeed; }
+ void SetMoveSpeed(float value) { m_moveSpeed = value; }
+
+ float GetSprintMultiplier() const { return m_sprintMultiplier; }
+ void SetSprintMultiplier(float value) { m_sprintMultiplier = value; }
+
+ float GetJumpForce() const { return m_jumpForce; }
+ void SetJumpForce(float value) { m_jumpForce = value; }
+
+ int GetMaxJumps() const { return m_maxJumps; }
+ void SetMaxJumps(int value) { m_maxJumps = value; }
+
+ bool GetIsGrounded() const { return m_isGrounded; }
+ const AZ::Vector3& GetCurrentVelocity() const { return m_currentVelocity; }
+
+ void Jump();
+ void Move(const AZ::Vector3& direction, float deltaTime);
+ void SetSprinting(bool sprinting);
+ float GetCurrentSpeed() const;
+ void Teleport(const AZ::Vector3& position);
+
+ protected:
+ void Init() override;
+ void Activate() override;
+ void Deactivate() override;
+
+ private:
+ // MUST MANUALLY DECLARE ALL MEMBERS
+ float m_moveSpeed = 5.0f;
+ float m_sprintMultiplier = 2.0f;
+ float m_jumpForce = 400.0f;
+ int m_maxJumps = 2;
+ float m_gravityMultiplier = 1.0f;
+ bool m_isGrounded = false;
+ AZ::Vector3 m_currentVelocity = AZ::Vector3::CreateZero();
+ };
+
+} // namespace TraditionalExample
+
+
diff --git a/Gems/O3DEReflect/Code/Examples/DamageFlags.O3DEReflect.xml b/Gems/O3DEReflect/Code/Examples/DamageFlags.O3DEReflect.xml
new file mode 100644
index 0000000000..70a9b161f7
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/DamageFlags.O3DEReflect.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gems/O3DEReflect/Code/Examples/DamageInfo.O3DEReflect.xml b/Gems/O3DEReflect/Code/Examples/DamageInfo.O3DEReflect.xml
new file mode 100644
index 0000000000..5dbe3932d0
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/DamageInfo.O3DEReflect.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gems/O3DEReflect/Code/Examples/PlayerMovementComponent.O3DEReflect.xml b/Gems/O3DEReflect/Code/Examples/PlayerMovementComponent.O3DEReflect.xml
new file mode 100644
index 0000000000..fbdd2b7cc6
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/PlayerMovementComponent.O3DEReflect.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gems/O3DEReflect/Code/Examples/V2/AIStateComponent.h b/Gems/O3DEReflect/Code/Examples/V2/AIStateComponent.h
new file mode 100644
index 0000000000..137dc67f1d
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/V2/AIStateComponent.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * V2 Example: AI State Component using header-macro parsing
+ *
+ * Demonstrates a more complex component with enums and state management.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+// ============================================================================
+// AI State Enum
+// ============================================================================
+
+O3DE_ENUM(ScriptType,
+ Category = "AI",
+ Description = "Possible states for AI behavior"
+)
+enum class AIState : uint8_t
+{
+ O3DE_ENUM_VALUE(Idle, DisplayName = "Idle", Description = "AI is standing still, not engaged")
+ Idle = 0,
+
+ O3DE_ENUM_VALUE(Patrol, DisplayName = "Patrolling", Description = "AI is following a patrol route")
+ Patrol,
+
+ O3DE_ENUM_VALUE(Alert, DisplayName = "Alert", Description = "AI has detected something suspicious")
+ Alert,
+
+ O3DE_ENUM_VALUE(Chase, DisplayName = "Chasing", Description = "AI is pursuing a target")
+ Chase,
+
+ O3DE_ENUM_VALUE(Combat, DisplayName = "In Combat", Description = "AI is actively fighting")
+ Combat,
+
+ O3DE_ENUM_VALUE(Flee, DisplayName = "Fleeing", Description = "AI is running away")
+ Flee,
+
+ O3DE_ENUM_VALUE(Dead, DisplayName = "Dead", Description = "AI is dead/disabled")
+ Dead
+};
+
+// ============================================================================
+// AI Behavior Flags
+// ============================================================================
+
+O3DE_ENUM(ScriptType, Flags,
+ Category = "AI",
+ Description = "Behavior modifiers for AI"
+)
+enum class AIBehaviorFlags : uint32_t
+{
+ O3DE_ENUM_VALUE(None, DisplayName = "None")
+ None = 0,
+
+ O3DE_ENUM_VALUE(CanPatrol, DisplayName = "Can Patrol")
+ CanPatrol = 1 << 0,
+
+ O3DE_ENUM_VALUE(CanChase, DisplayName = "Can Chase")
+ CanChase = 1 << 1,
+
+ O3DE_ENUM_VALUE(CanFlee, DisplayName = "Can Flee")
+ CanFlee = 1 << 2,
+
+ O3DE_ENUM_VALUE(CanCallForHelp, DisplayName = "Can Call For Help")
+ CanCallForHelp = 1 << 3,
+
+ O3DE_ENUM_VALUE(Aggressive, DisplayName = "Aggressive (attacks on sight)")
+ Aggressive = 1 << 4,
+
+ O3DE_ENUM_VALUE(Cowardly, DisplayName = "Cowardly (flees when hurt)")
+ Cowardly = 1 << 5
+};
+
+// ============================================================================
+// Patrol Point Struct
+// ============================================================================
+
+O3DE_STRUCT(ScriptType,
+ Category = "AI",
+ Description = "A waypoint in a patrol route"
+)
+struct PatrolPoint
+{
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Patrol",
+ Tooltip = "World position of this patrol point"
+ )
+ AZ::Vector3 position = AZ::Vector3::CreateZero();
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Patrol",
+ Tooltip = "How long to wait at this point (seconds)",
+ Min = 0.0f, Max = 60.0f
+ )
+ float waitTime = 2.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Patrol",
+ Tooltip = "Should the AI look around at this point?"
+ )
+ bool lookAround = false;
+};
+
+// ============================================================================
+// AI State Component
+// ============================================================================
+
+O3DE_CLASS(
+ Category = "AI/Core",
+ Description = "Manages AI state machine and behavior",
+ Icon = "Icons/Components/AI.svg"
+)
+class AIStateComponent : public AZ::Component
+{
+ O3DE_GENERATED_BODY()
+
+ O3DE_COMPONENT(AIStateComponent, "{B2C3D4E5-F6A7-8901-BCDE-F23456789ABC}",
+ ProvidesServices = "AIStateService",
+ RequiresServices = "TransformService",
+ IncompatibleServices = "PlayerControllerService"
+ )
+
+public:
+ // ========================================================================
+ // State Properties
+ // ========================================================================
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "State",
+ Tooltip = "Current AI state"
+ )
+ AIState m_currentState = AIState::Idle;
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "State",
+ Tooltip = "Previous AI state (for transition logic)"
+ )
+ AIState m_previousState = AIState::Idle;
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "State",
+ Tooltip = "Time spent in current state (seconds)"
+ )
+ float m_timeInState = 0.0f;
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "State",
+ Tooltip = "Current target entity (if any)"
+ )
+ AZ::EntityId m_currentTarget;
+
+ // ========================================================================
+ // Configuration Properties
+ // ========================================================================
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Detection",
+ Tooltip = "How far the AI can see",
+ Min = 1.0f, Max = 100.0f, Suffix = "m"
+ )
+ float m_sightRange = 20.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Detection",
+ Tooltip = "Field of view angle (degrees)",
+ Min = 30.0f, Max = 360.0f, Suffix = "°"
+ )
+ float m_fieldOfView = 120.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Detection",
+ Tooltip = "How far the AI can hear",
+ Min = 1.0f, Max = 50.0f, Suffix = "m"
+ )
+ float m_hearingRange = 15.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Behavior",
+ Tooltip = "Behavior flags controlling AI capabilities"
+ )
+ AIBehaviorFlags m_behaviorFlags = AIBehaviorFlags::CanPatrol;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Combat",
+ Tooltip = "Preferred combat distance",
+ Min = 1.0f, Max = 30.0f, Suffix = "m"
+ )
+ float m_preferredCombatRange = 5.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Combat",
+ Tooltip = "Health threshold to trigger flee (0-1)",
+ Min = 0.0f, Max = 1.0f
+ )
+ float m_fleeHealthThreshold = 0.2f;
+
+ // ========================================================================
+ // Patrol Properties
+ // ========================================================================
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Patrol",
+ Tooltip = "Patrol waypoints"
+ )
+ AZStd::vector m_patrolPoints;
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "Patrol",
+ Tooltip = "Current patrol point index"
+ )
+ int m_currentPatrolIndex = 0;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Patrol",
+ Tooltip = "Should patrol loop back to start?"
+ )
+ bool m_loopPatrol = true;
+
+ // ========================================================================
+ // State Control Functions
+ // ========================================================================
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "State",
+ Tooltip = "Force transition to a new state"
+ )
+ void SetState(AIState newState);
+
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "State",
+ Tooltip = "Check if AI is in a specific state"
+ )
+ bool IsInState(AIState state) const;
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "State",
+ Tooltip = "Set the current target entity"
+ )
+ void SetTarget(AZ::EntityId target);
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "State",
+ Tooltip = "Clear the current target"
+ )
+ void ClearTarget();
+
+ // ========================================================================
+ // Detection Functions
+ // ========================================================================
+
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "Detection",
+ Tooltip = "Check if AI can see a specific entity"
+ )
+ bool CanSeeEntity(AZ::EntityId entity) const;
+
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "Detection",
+ Tooltip = "Check if AI can hear a position"
+ )
+ bool CanHearPosition(const AZ::Vector3& position, float noiseLevel) const;
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "Detection",
+ Tooltip = "Scan for nearby threats, returns closest threat"
+ )
+ AZ::EntityId ScanForThreats();
+
+ // ========================================================================
+ // Patrol Functions
+ // ========================================================================
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "Patrol",
+ Tooltip = "Add a patrol point at runtime"
+ )
+ void AddPatrolPoint(const PatrolPoint& point);
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "Patrol",
+ Tooltip = "Clear all patrol points"
+ )
+ void ClearPatrolPoints();
+
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "Patrol",
+ Tooltip = "Get the next patrol point position"
+ )
+ AZ::Vector3 GetNextPatrolPosition() const;
+
+ // ========================================================================
+ // Debug Functions
+ // ========================================================================
+
+ O3DE_FUNCTION(CallInEditor,
+ Category = "Debug",
+ Tooltip = "Draw detection ranges in editor"
+ )
+ void DrawDebugVisualization();
+
+ O3DE_FUNCTION(CallInEditor,
+ Category = "Debug",
+ Tooltip = "Reset AI to initial state"
+ )
+ void ResetAI();
+
+protected:
+ void Activate() override;
+ void Deactivate() override;
+
+private:
+ void UpdateStateMachine(float deltaTime);
+ void OnStateEnter(AIState state);
+ void OnStateExit(AIState state);
+};
diff --git a/Gems/O3DEReflect/Code/Examples/V2/DamageInfo.h b/Gems/O3DEReflect/Code/Examples/V2/DamageInfo.h
new file mode 100644
index 0000000000..a62ba458f1
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/V2/DamageInfo.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * V2 Example: DamageInfo struct using header-macro parsing
+ *
+ * Demonstrates O3DE_STRUCT for data-only structures.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+// Forward declare the enum
+enum class DamageType : uint8_t;
+
+// ============================================================================
+// DamageInfo Struct - Data container for damage events
+// ============================================================================
+
+O3DE_STRUCT(ScriptType,
+ Category = "Combat",
+ Description = "Contains all information about a damage event"
+)
+struct DamageInfo
+{
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Damage",
+ Tooltip = "Base damage amount before modifiers",
+ Min = 0.0f
+ )
+ float baseDamage = 0.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Damage",
+ Tooltip = "Damage multiplier (crits, weaknesses, etc.)",
+ Min = 0.0f, Max = 10.0f
+ )
+ float damageMultiplier = 1.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Source",
+ Tooltip = "Entity that caused the damage"
+ )
+ AZ::EntityId instigator;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Source",
+ Tooltip = "Entity that directly dealt the damage (e.g., projectile)"
+ )
+ AZ::EntityId damageCauser;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Location",
+ Tooltip = "World position where damage was applied"
+ )
+ AZ::Vector3 hitLocation = AZ::Vector3::CreateZero();
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Location",
+ Tooltip = "Direction the damage came from"
+ )
+ AZ::Vector3 hitDirection = AZ::Vector3::CreateZero();
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Location",
+ Tooltip = "Name of the bone/hitbox that was hit"
+ )
+ AZStd::string hitBoneName;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Damage",
+ Tooltip = "Type of damage (for resistance calculations)"
+ )
+ DamageType damageType;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Flags",
+ Tooltip = "Was this a critical hit?"
+ )
+ bool isCriticalHit = false;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Flags",
+ Tooltip = "Should this damage be displayed in UI?"
+ )
+ bool showDamageNumbers = true;
+
+ // Helper methods (exposed to scripting)
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "Damage",
+ Tooltip = "Calculate final damage after multipliers"
+ )
+ float GetFinalDamage() const
+ {
+ return baseDamage * damageMultiplier;
+ }
+
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "Damage",
+ Tooltip = "Check if damage is lethal (would reduce health to 0)"
+ )
+ bool IsLethal(float currentHealth) const
+ {
+ return GetFinalDamage() >= currentHealth;
+ }
+};
+
+// ============================================================================
+// DamageType Enum
+// ============================================================================
+
+O3DE_ENUM(ScriptType,
+ Category = "Combat",
+ Description = "Types of damage for resistance calculations"
+)
+enum class DamageType : uint8_t
+{
+ O3DE_ENUM_VALUE(Physical, DisplayName = "Physical Damage")
+ Physical = 0,
+
+ O3DE_ENUM_VALUE(Fire, DisplayName = "Fire Damage")
+ Fire,
+
+ O3DE_ENUM_VALUE(Ice, DisplayName = "Ice/Cold Damage")
+ Ice,
+
+ O3DE_ENUM_VALUE(Electric, DisplayName = "Electric/Lightning Damage")
+ Electric,
+
+ O3DE_ENUM_VALUE(Poison, DisplayName = "Poison/Toxic Damage")
+ Poison,
+
+ O3DE_ENUM_VALUE(Magic, DisplayName = "Magic/Arcane Damage")
+ Magic,
+
+ O3DE_ENUM_VALUE(True, DisplayName = "True Damage (ignores resistances)")
+ True
+};
+
+// ============================================================================
+// DamageFlags Enum (bitmask)
+// ============================================================================
+
+O3DE_ENUM(ScriptType, Flags,
+ Category = "Combat",
+ Description = "Flags that modify damage behavior"
+)
+enum class DamageFlags : uint32_t
+{
+ O3DE_ENUM_VALUE(None, DisplayName = "No Flags")
+ None = 0,
+
+ O3DE_ENUM_VALUE(IgnoreArmor, DisplayName = "Ignore Armor")
+ IgnoreArmor = 1 << 0,
+
+ O3DE_ENUM_VALUE(IgnoreShield, DisplayName = "Ignore Shield")
+ IgnoreShield = 1 << 1,
+
+ O3DE_ENUM_VALUE(CanCrit, DisplayName = "Can Critical Hit")
+ CanCrit = 1 << 2,
+
+ O3DE_ENUM_VALUE(CanBeBlocked, DisplayName = "Can Be Blocked")
+ CanBeBlocked = 1 << 3,
+
+ O3DE_ENUM_VALUE(AppliesKnockback, DisplayName = "Applies Knockback")
+ AppliesKnockback = 1 << 4,
+
+ O3DE_ENUM_VALUE(AppliesStagger, DisplayName = "Applies Stagger")
+ AppliesStagger = 1 << 5,
+
+ O3DE_ENUM_VALUE(OverTime, DisplayName = "Damage Over Time")
+ OverTime = 1 << 6,
+
+ O3DE_ENUM_VALUE(AreaOfEffect, DisplayName = "Area of Effect")
+ AreaOfEffect = 1 << 7
+};
+
+// Bitwise operators for DamageFlags
+inline DamageFlags operator|(DamageFlags a, DamageFlags b)
+{
+ return static_cast(static_cast(a) | static_cast(b));
+}
+
+inline DamageFlags operator&(DamageFlags a, DamageFlags b)
+{
+ return static_cast(static_cast(a) & static_cast(b));
+}
+
+inline bool HasFlag(DamageFlags flags, DamageFlags flag)
+{
+ return (flags & flag) == flag;
+}
diff --git a/Gems/O3DEReflect/Code/Examples/V2/PlayerMovementComponent.h b/Gems/O3DEReflect/Code/Examples/V2/PlayerMovementComponent.h
new file mode 100644
index 0000000000..c73860921e
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Examples/V2/PlayerMovementComponent.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * V2 Example: PlayerMovementComponent using header-macro parsing
+ *
+ * This file demonstrates the simplified reflection syntax.
+ * The O3DEReflect generator will parse these macros and generate:
+ * - PlayerMovementComponent.generated.h (included via O3DE_GENERATED_BODY)
+ * - PlayerMovementComponent.generated.cpp (reflection implementation)
+ *
+ * Compare this to the equivalent XML definition in:
+ * ../PlayerMovementComponent.O3DEReflect.xml
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+
+// ============================================================================
+
+O3DE_CLASS(
+ Category = "Gameplay/Movement",
+ Description = "Handles player movement with physics-based locomotion"
+)
+class PlayerMovementComponent : public AZ::Component
+{
+ // This macro includes the generated declarations and Reflect() signature
+ O3DE_GENERATED_BODY()
+
+ // Explicit UUID (optional - will auto-generate deterministic UUID if omitted)
+ O3DE_COMPONENT(PlayerMovementComponent, "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}",
+ ProvidesServices = "PlayerMovementService",
+ RequiresServices = "TransformService",
+ IncompatibleServices = "AIMovementService"
+ )
+
+public:
+ // ========================================================================
+ // Movement Properties
+ // ========================================================================
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Movement|Speed",
+ Tooltip = "Maximum movement speed in meters per second",
+ DisplayName = "Max Speed",
+ Min = 0.0f, Max = 100.0f, UIMax = 50.0f,
+ Suffix = "m/s"
+ )
+ float m_maxSpeed = 10.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Movement|Speed",
+ Tooltip = "Acceleration rate",
+ Min = 0.0f, Max = 500.0f
+ )
+ float m_acceleration = 50.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Movement|Speed",
+ Tooltip = "Deceleration rate when not moving",
+ Min = 0.0f, Max = 500.0f
+ )
+ float m_deceleration = 40.0f;
+
+ // ========================================================================
+ // Jump Properties
+ // ========================================================================
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Movement|Jump",
+ Tooltip = "Initial velocity when jumping",
+ Min = 0.0f, Max = 50.0f
+ )
+ float m_jumpVelocity = 8.0f;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Movement|Jump",
+ Tooltip = "Number of jumps allowed before landing",
+ Min = 1, Max = 5
+ )
+ int m_maxJumps = 2;
+
+ O3DE_PROPERTY(EditAnywhere, ScriptReadOnly,
+ Category = "Movement|Jump",
+ Tooltip = "Custom gravity multiplier"
+ )
+ float m_gravityMultiplier = 1.0f;
+
+ // ========================================================================
+ // State Properties (runtime, visible but not editable)
+ // ========================================================================
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "State",
+ Tooltip = "Current velocity vector"
+ )
+ AZ::Vector3 m_currentVelocity = AZ::Vector3::CreateZero();
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "State",
+ Tooltip = "Is the player currently on the ground?"
+ )
+ bool m_isGrounded = false;
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly,
+ Category = "State",
+ Tooltip = "Remaining jumps available"
+ )
+ int m_jumpsRemaining = 2;
+
+ // ========================================================================
+ // Exposed Functions
+ // ========================================================================
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "Movement",
+ Tooltip = "Apply movement input direction",
+ DisplayName = "Move"
+ )
+ void Move(const AZ::Vector3& direction);
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "Movement",
+ Tooltip = "Attempt to jump, returns true if successful"
+ )
+ bool Jump();
+
+ O3DE_FUNCTION(ScriptCallable,
+ Category = "Movement",
+ Tooltip = "Stop all movement immediately"
+ )
+ void StopMovement();
+
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "State",
+ Tooltip = "Get current speed (magnitude of velocity)"
+ )
+ float GetCurrentSpeed() const;
+
+ O3DE_FUNCTION(ScriptPure, ScriptCallable,
+ Category = "State",
+ Tooltip = "Check if player is moving"
+ )
+ bool IsMoving() const;
+
+ O3DE_FUNCTION(CallInEditor,
+ Category = "Debug",
+ Tooltip = "Reset player to spawn position"
+ )
+ void ResetToSpawn();
+
+protected:
+ // Component interface (these are manually implemented)
+ void Activate() override;
+ void Deactivate() override;
+
+private:
+ // Private implementation details (not reflected)
+ void UpdateMovement(float deltaTime);
+ void ApplyGravity(float deltaTime);
+ AZ::Vector3 m_pendingInput = AZ::Vector3::CreateZero();
+};
+
+// In PlayerMovementComponent.cpp, you would add:
+// O3DE_IMPLEMENT_REFLECT(PlayerMovementComponent)
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect.xsd b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect.xsd
new file mode 100644
index 0000000000..04f1f86cca
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect.xsd
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflectGen.py b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflectGen.py
new file mode 100644
index 0000000000..a6c06da8b5
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflectGen.py
@@ -0,0 +1,1203 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+
+"""
+O3DEReflect Code Generator
+
+This module extends the AzAutoGen system to provide simplified reflection
+code generation for O3DE components, structs, and enums.
+
+Usage:
+ python O3DEReflectGen.py --input MyComponent.O3DEReflect.xml --output-dir ./Generated
+"""
+
+import os
+import re
+import sys
+import json
+import argparse
+import hashlib
+import logging
+import glob
+from pathlib import Path
+from xml.etree import ElementTree as ET
+
+# Add cmake directory to path for accessing AzAutoGen utilities
+script_dir = os.path.dirname(os.path.abspath(__file__))
+cmake_dir = os.path.abspath(os.path.join(script_dir, '..', '..', '..', '..', '..', 'cmake'))
+if cmake_dir not in sys.path:
+ sys.path.insert(0, cmake_dir)
+
+# Try to find O3DE's Python venv site-packages for Jinja2
+def find_o3de_site_packages():
+ """Find O3DE's Python venv site-packages directory."""
+ # Common locations for O3DE Python venv
+ home = os.path.expanduser("~")
+ patterns = [
+ os.path.join(home, ".o3de", "Python", "venv", "*", "lib", "site-packages"),
+ os.path.join(home, ".o3de", "Python", "venv", "*", "Lib", "site-packages"), # Windows
+ ]
+ for pattern in patterns:
+ matches = glob.glob(pattern)
+ if matches:
+ return matches[0]
+ return None
+
+# Add O3DE site-packages to path if needed
+o3de_site_packages = find_o3de_site_packages()
+if o3de_site_packages and o3de_site_packages not in sys.path:
+ sys.path.insert(0, o3de_site_packages)
+
+try:
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
+ JINJA2_AVAILABLE = True
+except ImportError:
+ JINJA2_AVAILABLE = False
+ print("Warning: Jinja2 not available. Template rendering will fail.", file=sys.stderr)
+ if o3de_site_packages:
+ print(f" Tried O3DE site-packages at: {o3de_site_packages}", file=sys.stderr)
+
+logging.basicConfig(format='[%(levelname)s] %(name)s: %(message)s')
+logger = logging.getLogger('O3DEReflectGen')
+logger.setLevel(logging.INFO)
+
+
+def create_hash_guid(string: str) -> str:
+ """
+ Generate a deterministic UUID from a string using MD5 hash.
+ Matches the CreateHashGuid function in AzAutoGen.py.
+ """
+ hash_obj = hashlib.md5(string.encode('utf-8'))
+ hash_str = hash_obj.hexdigest()
+ return "{{{}-{}-{}-{}-{}}}".format(
+ hash_str[0:8].upper(),
+ hash_str[8:12].upper(),
+ hash_str[12:16].upper(),
+ hash_str[16:20].upper(),
+ hash_str[20:].upper()
+ )
+
+
+def boolean_true(value) -> bool:
+ """Check if a string value represents true."""
+ if isinstance(value, bool):
+ return value
+ if isinstance(value, str):
+ return value.lower().strip() in ('true', '1', 'yes')
+ return bool(value)
+
+
+def camel_to_human(string: str) -> str:
+ """Convert camelCase to Human Readable format."""
+ if not string:
+ return string
+ result = string[0].upper()
+ for char in string[1:]:
+ if char.isupper():
+ result += ' '
+ result += char
+ return result
+
+
+def sanitize_path(path: str) -> str:
+ """Normalize path separators."""
+ return path.replace('\\', '/')
+
+
+class O3DEReflectParser:
+ """Parser for O3DEReflect XML files."""
+
+ def __init__(self, input_file: str):
+ self.input_file = input_file
+ self.tree = None
+ self.root = None
+
+ def parse(self) -> dict:
+ """Parse the XML file and return a structured dictionary."""
+ self.tree = ET.parse(self.input_file)
+ self.root = self.tree.getroot()
+
+ result = {
+ 'components': [],
+ 'structs': [],
+ 'enums': [],
+ 'source_file': os.path.basename(self.input_file)
+ }
+
+ # Handle single element root (Component, Struct, or Enum)
+ if self.root.tag in ('Component', 'Struct', 'Enum'):
+ elements = [self.root]
+ else:
+ # Handle O3DEReflect wrapper with multiple elements
+ elements = list(self.root)
+
+ for element in elements:
+ if element.tag == 'Component':
+ result['components'].append(self._parse_component(element))
+ elif element.tag == 'Struct':
+ result['structs'].append(self._parse_struct(element))
+ elif element.tag == 'Enum':
+ result['enums'].append(self._parse_enum(element))
+
+ return result
+
+ def _parse_component(self, elem: ET.Element) -> dict:
+ """Parse a Component element."""
+ name = elem.attrib.get('Name', '')
+ namespace = elem.attrib.get('Namespace', '')
+ full_name = f"{namespace}::{name}" if namespace else name
+
+ return {
+ 'name': name,
+ 'namespace': namespace,
+ 'full_name': full_name,
+ 'uuid': elem.attrib.get('Uuid') or create_hash_guid(full_name),
+ 'category': elem.attrib.get('Category', 'General'),
+ 'description': elem.attrib.get('Description', f'{name} component'),
+ 'display_name': elem.attrib.get('DisplayName', camel_to_human(name)),
+ 'icon': elem.attrib.get('Icon', ''),
+ 'hide_in_editor': boolean_true(elem.attrib.get('HideInEditor', 'false')),
+ 'abstract': boolean_true(elem.attrib.get('Abstract', 'false')),
+ 'menu_category': elem.attrib.get('AppearsInAddComponentMenu', 'Game'),
+ 'includes': self._parse_includes(elem),
+ 'base_classes': self._parse_base_classes(elem),
+ 'services': self._parse_services(elem),
+ 'properties': self._parse_properties(elem),
+ 'functions': self._parse_functions(elem),
+ }
+
+ def _parse_struct(self, elem: ET.Element) -> dict:
+ """Parse a Struct element."""
+ name = elem.attrib.get('Name', '')
+ namespace = elem.attrib.get('Namespace', '')
+ full_name = f"{namespace}::{name}" if namespace else name
+
+ return {
+ 'name': name,
+ 'namespace': namespace,
+ 'full_name': full_name,
+ 'uuid': elem.attrib.get('Uuid') or create_hash_guid(full_name),
+ 'category': elem.attrib.get('Category', 'Data'),
+ 'description': elem.attrib.get('Description', f'{name} data structure'),
+ 'display_name': elem.attrib.get('DisplayName', camel_to_human(name)),
+ 'script_type': boolean_true(elem.attrib.get('ScriptType', 'true')),
+ 'atomic': boolean_true(elem.attrib.get('Atomic', 'false')),
+ 'includes': self._parse_includes(elem),
+ 'properties': self._parse_properties(elem),
+ }
+
+ def _parse_enum(self, elem: ET.Element) -> dict:
+ """Parse an Enum element."""
+ name = elem.attrib.get('Name', '')
+ namespace = elem.attrib.get('Namespace', '')
+ full_name = f"{namespace}::{name}" if namespace else name
+
+ return {
+ 'name': name,
+ 'namespace': namespace,
+ 'full_name': full_name,
+ 'uuid': elem.attrib.get('Uuid') or create_hash_guid(full_name),
+ 'category': elem.attrib.get('Category', 'Enums'),
+ 'description': elem.attrib.get('Description', f'{name} enumeration'),
+ 'display_name': elem.attrib.get('DisplayName', camel_to_human(name)),
+ 'script_type': boolean_true(elem.attrib.get('ScriptType', 'true')),
+ 'is_flags': boolean_true(elem.attrib.get('Flags', 'false')),
+ 'underlying_type': elem.attrib.get('UnderlyingType', 'int32_t'),
+ 'values': self._parse_enum_values(elem),
+ }
+
+ def _parse_includes(self, elem: ET.Element) -> list:
+ """Parse Include elements."""
+ return [inc.attrib.get('File', '') for inc in elem.findall('Include')]
+
+ def _parse_base_classes(self, elem: ET.Element) -> list:
+ """Parse BaseClass elements."""
+ result = []
+ for base in elem.findall('BaseClass'):
+ result.append({
+ 'name': base.attrib.get('Name', ''),
+ 'namespace': base.attrib.get('Namespace', ''),
+ 'include': base.attrib.get('Include', ''),
+ })
+ return result
+
+ def _parse_services(self, elem: ET.Element) -> dict:
+ """Parse Service elements grouped by type."""
+ result = {
+ 'provides': [],
+ 'requires': [],
+ 'incompatible': [],
+ 'dependent': [],
+ }
+ for service in elem.findall('Service'):
+ service_type = service.attrib.get('Type', '').lower()
+ service_name = service.attrib.get('Name', '')
+ if service_type in result:
+ result[service_type].append(service_name)
+ return result
+
+ def _parse_properties(self, elem: ET.Element) -> list:
+ """Parse Property elements."""
+ result = []
+ for prop in elem.findall('Property'):
+ prop_name = prop.attrib.get('Name', '')
+ result.append({
+ 'name': prop_name,
+ 'member_name': prop_name if prop_name.startswith('m_') else f'm_{prop_name}',
+ 'type': prop.attrib.get('Type', 'float'),
+ 'default': prop.attrib.get('Default', ''),
+ 'display_name': prop.attrib.get('DisplayName', camel_to_human(prop_name.lstrip('m_'))),
+ 'category': prop.attrib.get('Category', ''),
+ 'tooltip': prop.attrib.get('Tooltip', ''),
+ 'suffix': prop.attrib.get('Suffix', ''),
+ # Editor visibility
+ 'edit_anywhere': boolean_true(prop.attrib.get('EditAnywhere', 'true')),
+ 'edit_defaults_only': boolean_true(prop.attrib.get('EditDefaultsOnly', 'false')),
+ 'edit_instance_only': boolean_true(prop.attrib.get('EditInstanceOnly', 'false')),
+ 'visible_anywhere': boolean_true(prop.attrib.get('VisibleAnywhere', 'false')),
+ 'read_only': boolean_true(prop.attrib.get('ReadOnly', 'false')),
+ # Script visibility
+ 'script_read_write': boolean_true(prop.attrib.get('ScriptReadWrite', 'true')),
+ 'script_read_only': boolean_true(prop.attrib.get('ScriptReadOnly', 'false')),
+ 'expose_to_script': boolean_true(prop.attrib.get('ExposeToScript', 'true')),
+ # Numeric constraints
+ 'min': prop.attrib.get('Min'),
+ 'max': prop.attrib.get('Max'),
+ 'ui_min': prop.attrib.get('UIMin'),
+ 'ui_max': prop.attrib.get('UIMax'),
+ 'step': prop.attrib.get('Step'),
+ # Advanced
+ 'change_notify': prop.attrib.get('ChangeNotify', ''),
+ 'visibility': prop.attrib.get('Visibility', ''),
+ 'order': int(prop.attrib.get('Order', '0')),
+ })
+ return result
+
+ def _parse_functions(self, elem: ET.Element) -> list:
+ """Parse Function elements."""
+ result = []
+ for func in elem.findall('Function'):
+ func_name = func.attrib.get('Name', '')
+ return_elem = func.find('Return')
+ params = []
+ for param in func.findall('Param'):
+ params.append({
+ 'name': param.attrib.get('Name', ''),
+ 'type': param.attrib.get('Type', ''),
+ 'default': param.attrib.get('Default', ''),
+ 'display_name': param.attrib.get('DisplayName', ''),
+ 'tooltip': param.attrib.get('Tooltip', ''),
+ })
+
+ result.append({
+ 'name': func_name,
+ 'display_name': func.attrib.get('DisplayName', camel_to_human(func_name)),
+ 'category': func.attrib.get('Category', ''),
+ 'tooltip': func.attrib.get('Tooltip', ''),
+ 'return_type': return_elem.attrib.get('Type', 'void') if return_elem is not None else 'void',
+ 'parameters': params,
+ # Flags
+ 'script_callable': boolean_true(func.attrib.get('ScriptCallable', 'true')),
+ 'script_pure': boolean_true(func.attrib.get('ScriptPure', 'false')),
+ 'call_in_editor': boolean_true(func.attrib.get('CallInEditor', 'false')),
+ 'server': boolean_true(func.attrib.get('Server', 'false')),
+ 'client': boolean_true(func.attrib.get('Client', 'false')),
+ 'net_multicast': boolean_true(func.attrib.get('NetMulticast', 'false')),
+ })
+ return result
+
+ def _parse_enum_values(self, elem: ET.Element) -> list:
+ """Parse EnumValue elements."""
+ result = []
+ for val in elem.findall('EnumValue'):
+ val_name = val.attrib.get('Name', '')
+ result.append({
+ 'name': val_name,
+ 'value': val.attrib.get('Value'),
+ 'display_name': val.attrib.get('DisplayName', camel_to_human(val_name)),
+ 'tooltip': val.attrib.get('Tooltip', ''),
+ 'hidden': boolean_true(val.attrib.get('Hidden', 'false')),
+ })
+ return result
+
+
+class O3DEReflectGenerator:
+ """Code generator using Jinja2 templates."""
+
+ def __init__(self, template_dir: str, output_dir: str):
+ self.template_dir = template_dir
+ self.output_dir = output_dir
+
+ if not JINJA2_AVAILABLE:
+ raise RuntimeError("Jinja2 is required for code generation")
+
+ self.env = Environment(
+ loader=FileSystemLoader(template_dir),
+ autoescape=select_autoescape(['html', 'xml']),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ )
+
+ # Add custom filters
+ self.env.filters['booleanTrue'] = boolean_true
+ self.env.globals['CreateHashGuid'] = create_hash_guid
+ self.env.globals['CamelToHuman'] = camel_to_human
+
+ def generate(self, parsed_data: dict, source_file: str):
+ """Generate code from parsed data."""
+ os.makedirs(self.output_dir, exist_ok=True)
+
+ file_prefix = os.path.splitext(os.path.basename(source_file))[0]
+ if file_prefix.endswith('.O3DEReflect'):
+ file_prefix = file_prefix[:-12]
+
+ # Get user code if available
+ user_code = parsed_data.get('user_code', {})
+
+ generated_files = []
+
+ # Generate component files
+ for component in parsed_data.get('components', []):
+ header_file = self._generate_component_header(component, file_prefix, source_file)
+ source_file_out = self._generate_component_source(component, file_prefix, source_file, user_code)
+ generated_files.extend([header_file, source_file_out])
+
+ # Generate struct files
+ for struct in parsed_data.get('structs', []):
+ header_file = self._generate_struct_header(struct, file_prefix, source_file)
+ source_file_out = self._generate_struct_source(struct, file_prefix, source_file)
+ generated_files.extend([header_file, source_file_out])
+
+ # Generate enum files
+ for enum in parsed_data.get('enums', []):
+ header_file = self._generate_enum_header(enum, file_prefix, source_file)
+ source_file_out = self._generate_enum_source(enum, file_prefix, source_file)
+ generated_files.extend([header_file, source_file_out])
+
+ return generated_files
+
+ def _generate_component_header(self, component: dict, file_prefix: str, source_file: str) -> str:
+ """Generate component header file."""
+ output_file = os.path.join(self.output_dir, f"{component['name']}.AutoReflect.h")
+ content = self._render_component_header(component, file_prefix, source_file)
+ self._write_file(output_file, content)
+ return output_file
+
+ def _generate_component_source(self, component: dict, file_prefix: str, source_file: str, user_code: dict = None) -> str:
+ """Generate component source file."""
+ output_file = os.path.join(self.output_dir, f"{component['name']}.AutoReflect.cpp")
+ content = self._render_component_source(component, file_prefix, source_file, user_code or {})
+ self._write_file(output_file, content)
+ return output_file
+
+ def _generate_struct_header(self, struct: dict, file_prefix: str, source_file: str) -> str:
+ """Generate struct header file."""
+ output_file = os.path.join(self.output_dir, f"{struct['name']}.AutoReflect.h")
+ content = self._render_struct_header(struct, file_prefix, source_file)
+ self._write_file(output_file, content)
+ return output_file
+
+ def _generate_struct_source(self, struct: dict, file_prefix: str, source_file: str) -> str:
+ """Generate struct source file."""
+ output_file = os.path.join(self.output_dir, f"{struct['name']}.AutoReflect.cpp")
+ content = self._render_struct_source(struct, file_prefix, source_file)
+ self._write_file(output_file, content)
+ return output_file
+
+ def _generate_enum_header(self, enum: dict, file_prefix: str, source_file: str) -> str:
+ """Generate enum header file."""
+ output_file = os.path.join(self.output_dir, f"{enum['name']}.AutoReflect.h")
+ content = self._render_enum_header(enum, file_prefix, source_file)
+ self._write_file(output_file, content)
+ return output_file
+
+ def _generate_enum_source(self, enum: dict, file_prefix: str, source_file: str) -> str:
+ """Generate enum source file."""
+ output_file = os.path.join(self.output_dir, f"{enum['name']}.AutoReflect.cpp")
+ content = self._render_enum_source(enum, file_prefix, source_file)
+ self._write_file(output_file, content)
+ return output_file
+
+ def _render_component_header(self, component: dict, file_prefix: str, source_file: str) -> str:
+ """Render component header using inline template."""
+ return f'''/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {os.path.basename(source_file)}
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+{self._render_includes(component.get('includes', []))}
+{self._render_base_class_includes(component.get('base_classes', []))}
+
+{self._render_namespace_begin(component.get('namespace', ''))}
+ //! {component.get('description', '')}
+ class {component['name']}
+{self._render_base_class_inheritance(component.get('base_classes', []))}
+ {{
+ public:
+ AZ_COMPONENT_DECL({component['name']});
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+ static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+ static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+ static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+ {component['name']}() = default;
+ ~{component['name']}() override = default;
+
+{self._render_property_accessors(component['name'], component.get('properties', []))}
+ protected:
+ // AZ::Component interface
+ void Init() override;
+ void Activate() override;
+ void Deactivate() override;
+
+{self._render_function_declarations(component.get('functions', []))}
+ private:
+ // Member variables
+{self._render_property_members(component.get('properties', []))}
+ }};
+{self._render_namespace_end(component.get('namespace', ''))}
+'''
+
+ def _render_component_source(self, component: dict, file_prefix: str, source_file: str, user_code: dict = None) -> str:
+ """Render component source using inline template, merging user implementations."""
+ user_code = user_code or {}
+ user_functions = user_code.get('functions', {})
+
+ # Build base class string, filtering out bus handlers (they shouldn't be in serializeContext)
+ base_class_parts = []
+ for b in component.get('base_classes', []):
+ full_name = f"{b.get('namespace', '')}::{b['name']}" if b.get('namespace') else b['name']
+ # Filter out bus handlers - they aren't serializable base classes
+ if full_name.endswith('::Handler') or 'BusHandler' in full_name or 'Bus::Handler' in full_name:
+ continue
+ base_class_parts.append(full_name)
+ base_classes_str = ', '.join(base_class_parts) if base_class_parts else 'AZ::Component'
+
+ # Get component name and namespace for function lookup
+ comp_name = component['name']
+ namespace = component.get('namespace', '')
+ full_class_name = f"{namespace}::{comp_name}" if namespace else comp_name
+
+ return f'''/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {os.path.basename(source_file)}
+ */
+
+#include "{component['name']}.AutoReflect.h"
+
+#include
+#include
+#include
+#include
+#include
+
+{self._render_namespace_begin(component.get('namespace', ''))}
+
+ AZ_COMPONENT_IMPL({component['name']}, "{component['name']}", "{component['uuid']}"{self._render_base_class_list(component.get('base_classes', []), exclude_bus_handlers=True)});
+
+ void {component['name']}::Reflect(AZ::ReflectContext* context)
+ {{
+ // SerializeContext
+ if (auto* serializeContext = azrtti_cast(context))
+ {{
+ serializeContext->Class<{component['name']}, {base_classes_str}>()
+ ->Version(1)
+{self._render_serialize_fields(component['name'], component.get('properties', []))}
+ ;
+
+ // EditContext
+ if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+ {{
+ editContext->Class<{component['name']}>("{component.get('display_name', component['name'])}", "{component.get('description', '')}")
+ ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+ ->Attribute(AZ::Edit::Attributes::Category, "{component.get('category', 'General')}")
+{self._render_edit_class_attributes(component)}
+{self._render_edit_data_elements(component['name'], component.get('properties', []))}
+ ;
+ }}
+ }}
+
+ // BehaviorContext
+ if (auto* behaviorContext = azrtti_cast(context))
+ {{
+ behaviorContext->Class<{component['name']}>("{component['name']}")
+ ->Attribute(AZ::Script::Attributes::Category, "{component.get('category', 'General')}")
+{self._render_behavior_properties(component['name'], component.get('properties', []))}
+{self._render_behavior_methods(component['name'], component.get('functions', []))}
+ ;
+ }}
+ }}
+
+{self._render_service_functions(component['name'], component.get('services', {}))}
+
+{self._render_user_implementations(comp_name, full_class_name, user_functions, component.get('functions', []))}
+
+{self._render_namespace_end(component.get('namespace', ''))}
+'''
+
+ def _render_struct_header(self, struct: dict, file_prefix: str, source_file: str) -> str:
+ """Render struct header."""
+ return f'''/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {os.path.basename(source_file)}
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+{self._render_includes(struct.get('includes', []))}
+
+{self._render_namespace_begin(struct.get('namespace', ''))}
+ //! {struct.get('description', '')}
+ struct {struct['name']}
+ {{
+ AZ_TYPE_INFO({struct['name']}, "{struct['uuid']}");
+ AZ_CLASS_ALLOCATOR({struct['name']}, AZ::SystemAllocator);
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ {struct['name']}() = default;
+ ~{struct['name']}() = default;
+
+ bool operator==(const {struct['name']}& rhs) const;
+ bool operator!=(const {struct['name']}& rhs) const {{ return !(*this == rhs); }}
+
+ // Member variables
+{self._render_struct_members(struct.get('properties', []))}
+ }};
+{self._render_namespace_end(struct.get('namespace', ''))}
+'''
+
+ def _render_struct_source(self, struct: dict, file_prefix: str, source_file: str) -> str:
+ """Render struct source."""
+ return f'''/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {os.path.basename(source_file)}
+ */
+
+#include "{struct['name']}.AutoReflect.h"
+
+#include
+#include
+#include
+#include
+
+{self._render_namespace_begin(struct.get('namespace', ''))}
+
+ void {struct['name']}::Reflect(AZ::ReflectContext* context)
+ {{
+ if (auto* serializeContext = azrtti_cast(context))
+ {{
+ serializeContext->Class<{struct['name']}>()
+ ->Version(1)
+{self._render_serialize_fields(struct['name'], struct.get('properties', []))}
+ ;
+
+ if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+ {{
+ editContext->Class<{struct['name']}>("{struct.get('display_name', struct['name'])}", "{struct.get('description', '')}")
+ ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+ ->Attribute(AZ::Edit::Attributes::Category, "{struct.get('category', 'Data')}")
+{self._render_edit_data_elements(struct['name'], struct.get('properties', []))}
+ ;
+ }}
+ }}
+
+{self._render_struct_behavior_context(struct)}
+ }}
+
+ bool {struct['name']}::operator==(const {struct['name']}& rhs) const
+ {{
+{self._render_struct_equality(struct.get('properties', []))}
+ }}
+
+{self._render_namespace_end(struct.get('namespace', ''))}
+'''
+
+ def _render_enum_header(self, enum: dict, file_prefix: str, source_file: str) -> str:
+ """Render enum header."""
+ return f'''/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {os.path.basename(source_file)}
+ */
+
+#pragma once
+
+#include
+#include
+
+{self._render_namespace_begin(enum.get('namespace', ''))}
+ //! {enum.get('description', '')}
+ enum class {enum['name']} : {enum.get('underlying_type', 'int32_t')}
+ {{
+{self._render_enum_values(enum)}
+ }};
+
+{self._render_enum_bitwise_operators(enum)}
+ // Enum reflection helper
+ class {enum['name']}Reflect
+ {{
+ public:
+ static void Reflect(AZ::ReflectContext* context);
+ }};
+{self._render_namespace_end(enum.get('namespace', ''))}
+'''
+
+ def _render_enum_source(self, enum: dict, file_prefix: str, source_file: str) -> str:
+ """Render enum source."""
+ return f'''/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {os.path.basename(source_file)}
+ */
+
+#include "{enum['name']}.AutoReflect.h"
+
+#include
+#include
+
+{self._render_namespace_begin(enum.get('namespace', ''))}
+
+ void {enum['name']}Reflect::Reflect(AZ::ReflectContext* context)
+ {{
+ if (auto* behaviorContext = azrtti_cast(context))
+ {{
+{self._render_enum_behavior_values(enum)}
+ }}
+ }}
+
+{self._render_namespace_end(enum.get('namespace', ''))}
+'''
+
+ # Helper methods for rendering
+ def _render_includes(self, includes: list) -> str:
+ return 'n'.join([f'#include <{inc}>' for inc in includes])
+
+ def _render_base_class_includes(self, base_classes: list) -> str:
+ includes = [b['include'] for b in base_classes if b.get('include')]
+ return '\\n'.join([f'#include <{inc}>' for inc in includes])
+
+ def _render_base_class_inheritance(self, base_classes: list) -> str:
+ if not base_classes:
+ return ' : public AZ::Component'
+ parts = []
+ for b in base_classes:
+ full_name = f"{b['namespace']}::{b['name']}" if b.get('namespace') else b['name']
+ parts.append(f'public {full_name}')
+ return ' : ' + ', '.join(parts)
+
+ def _render_base_class_list(self, base_classes: list, exclude_bus_handlers: bool = False) -> str:
+ """Render base class list for macros like AZ_COMPONENT_IMPL.
+
+ Args:
+ base_classes: List of base class dicts with 'name' and 'namespace' keys
+ exclude_bus_handlers: If True, filter out bus handlers (end with ::Handler or BusHandler)
+ """
+ if not base_classes:
+ return ''
+ parts = []
+ for b in base_classes:
+ full_name = f"{b['namespace']}::{b['name']}" if b.get('namespace') else b['name']
+ if exclude_bus_handlers:
+ # Filter out bus handlers - they typically end with ::Handler or BusHandler
+ if full_name.endswith('::Handler') or 'BusHandler' in full_name or 'Bus::Handler' in full_name:
+ continue
+ parts.append(full_name)
+ if not parts:
+ return ''
+ return ', ' + ', '.join(parts)
+
+ def _render_namespace_begin(self, namespace: str) -> str:
+ if not namespace:
+ return ''
+ return f'namespace {namespace}\\n{{'
+
+ def _render_namespace_end(self, namespace: str) -> str:
+ if not namespace:
+ return ''
+ return f'}} // namespace {namespace}'
+
+ def _render_property_accessors(self, class_name: str, properties: list) -> str:
+ lines = [' // Getters and Setters']
+ for prop in properties:
+ prop_name = prop['name']
+ member_name = prop['member_name']
+ prop_type = prop['type']
+ clean_name = prop_name[2:] if prop_name.startswith('m_') else prop_name
+ getter_name = f'Get{clean_name[0].upper()}{clean_name[1:]}'
+ setter_name = f'Set{clean_name[0].upper()}{clean_name[1:]}'
+
+ lines.append(f' //! Get {camel_to_human(clean_name)}')
+ lines.append(f' const {prop_type}& {getter_name}() const {{ return {member_name}; }}')
+ if not prop.get('read_only', False):
+ lines.append(f' //! Set {camel_to_human(clean_name)}')
+ lines.append(f' void {setter_name}(const {prop_type}& value) {{ {member_name} = value; }}')
+ lines.append('')
+ return '\\n'.join(lines)
+
+ def _render_property_members(self, properties: list) -> str:
+ lines = []
+ for prop in properties:
+ member_name = prop['member_name']
+ prop_type = prop['type']
+ default = prop.get('default', '')
+ if default:
+ lines.append(f' {prop_type} {member_name} = {default};')
+ else:
+ lines.append(f' {prop_type} {member_name}{{}};')
+ return '\\n'.join(lines)
+
+ def _render_struct_members(self, properties: list) -> str:
+ lines = []
+ for prop in properties:
+ member_name = prop['member_name']
+ prop_type = prop['type']
+ default = prop.get('default', '')
+ if default:
+ lines.append(f' {prop_type} {member_name} = {default};')
+ else:
+ lines.append(f' {prop_type} {member_name}{{}};')
+ return '\\n'.join(lines)
+
+ def _render_function_declarations(self, functions: list) -> str:
+ lines = []
+ for func in functions:
+ func_name = func['name']
+ return_type = func.get('return_type', 'void')
+ params = ', '.join([
+ f"{p['type']} {p['name']}" + (f" = {p['default']}" if p.get('default') else '')
+ for p in func.get('parameters', [])
+ ])
+ tooltip = func.get('tooltip', func_name)
+ lines.append(f' //! {tooltip}')
+ lines.append(f' {return_type} {func_name}({params});')
+ lines.append('')
+ return '\\n'.join(lines)
+
+ def _render_serialize_fields(self, class_name: str, properties: list) -> str:
+ lines = []
+ for prop in properties:
+ prop_name = prop['name']
+ member_name = prop['member_name']
+ lines.append(f' ->Field("{prop_name}", &{class_name}::{member_name})')
+ return '\\n'.join(lines)
+
+ def _render_edit_class_attributes(self, component: dict) -> str:
+ lines = []
+ if component.get('icon'):
+ lines.append(f' ->Attribute(AZ::Edit::Attributes::Icon, "{component["icon"]}")')
+ if not component.get('hide_in_editor', False):
+ menu_cat = component.get('menu_category', 'Game')
+ lines.append(f' ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("{menu_cat}"))')
+ lines.append(' ->Attribute(AZ::Edit::Attributes::AutoExpand, true)')
+ return '\\n'.join(lines)
+
+ def _render_edit_data_elements(self, class_name: str, properties: list) -> str:
+ lines = []
+ current_category = ''
+ for prop in properties:
+ prop_name = prop['name']
+ member_name = prop['member_name']
+ display_name = prop.get('display_name', camel_to_human(prop_name.lstrip('m_')))
+ tooltip = prop.get('tooltip', '')
+ category = prop.get('category', '')
+
+ # Add category group if changed
+ if category and category != current_category:
+ lines.append(f' ->ClassElement(AZ::Edit::ClassElements::Group, "{category}")')
+ lines.append(' ->Attribute(AZ::Edit::Attributes::AutoExpand, true)')
+ current_category = category
+
+ lines.append(f' ->DataElement(AZ::Edit::UIHandlers::Default, &{class_name}::{member_name}, "{display_name}", "{tooltip}")')
+
+ # Add property attributes
+ if prop.get('suffix'):
+ lines.append(f' ->Attribute(AZ::Edit::Attributes::Suffix, "{prop["suffix"]}")')
+ if prop.get('min') is not None:
+ lines.append(f' ->Attribute(AZ::Edit::Attributes::Min, {prop["min"]})')
+ if prop.get('max') is not None:
+ lines.append(f' ->Attribute(AZ::Edit::Attributes::Max, {prop["max"]})')
+ if prop.get('read_only'):
+ lines.append(' ->Attribute(AZ::Edit::Attributes::ReadOnly, true)')
+
+ return '\\n'.join(lines)
+
+ def _render_behavior_properties(self, class_name: str, properties: list) -> str:
+ lines = []
+ for prop in properties:
+ if not prop.get('expose_to_script', True) and not prop.get('script_read_write', False):
+ continue
+
+ prop_name = prop['name']
+ member_name = prop['member_name']
+ prop_type = prop['type']
+ clean_name = prop_name[2:] if prop_name.startswith('m_') else prop_name
+ getter_name = f'Get{clean_name[0].upper()}{clean_name[1:]}'
+ setter_name = f'Set{clean_name[0].upper()}{clean_name[1:]}'
+
+ if prop.get('script_read_only', False):
+ lines.append(f' ->Property("{clean_name}",')
+ lines.append(f' []({class_name}* self) {{ return self->{getter_name}(); }},')
+ lines.append(' nullptr)')
+ else:
+ lines.append(f' ->Property("{clean_name}",')
+ lines.append(f' []({class_name}* self) {{ return self->{getter_name}(); }},')
+ lines.append(f' []({class_name}* self, const {prop_type}& value) {{ self->{setter_name}(value); }})')
+
+ return '\\n'.join(lines)
+
+ def _render_behavior_methods(self, class_name: str, functions: list) -> str:
+ lines = []
+ for func in functions:
+ if not func.get('script_callable', True):
+ continue
+
+ func_name = func['name']
+ category = func.get('category', '')
+ lines.append(f' ->Method("{func_name}", &{class_name}::{func_name})')
+ if category:
+ lines.append(f' ->Attribute(AZ::Script::Attributes::Category, "{category}")')
+
+ return '\\n'.join(lines)
+
+ def _render_service_functions(self, class_name: str, services: dict) -> str:
+ provides = services.get('provides', [])
+ incompatible = services.get('incompatible', [])
+ requires = services.get('requires', [])
+ dependent = services.get('dependent', [])
+
+ return f'''
+ void {class_name}::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+ {{
+{self._render_service_list('provided', provides)}
+ }}
+
+ void {class_name}::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+ {{
+{self._render_service_list('incompatible', incompatible)}
+ }}
+
+ void {class_name}::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
+ {{
+{self._render_service_list('required', requires)}
+ }}
+
+ void {class_name}::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
+ {{
+{self._render_service_list('dependent', dependent)}
+ }}
+'''
+
+ def _render_service_list(self, var_name: str, services: list) -> str:
+ if not services:
+ return f' (void){var_name}; // No services'
+ lines = []
+ for service in services:
+ lines.append(f' {var_name}.push_back(AZ_CRC_CE("{service}"));')
+ return '\\n'.join(lines)
+
+ def _render_function_implementations(self, class_name: str, functions: list) -> str:
+ lines = []
+ for func in functions:
+ func_name = func['name']
+ return_type = func.get('return_type', 'void')
+ params = ', '.join([f"{p['type']} {p['name']}" for p in func.get('parameters', [])])
+
+ lines.append(f' {return_type} {class_name}::{func_name}({params})')
+ lines.append(' {')
+ lines.append(f' // TODO: Implement {func_name}')
+ if return_type != 'void':
+ lines.append(' return {};')
+ lines.append(' }')
+ lines.append('')
+
+ return '\\n'.join(lines)
+
+ def _render_default_function_implementations(self, class_name: str, functions: list) -> str:
+ """Render default empty implementations for functions (used when no _Impl.inl exists)."""
+ lines = []
+ for func in functions:
+ func_name = func['name']
+ return_type = func.get('return_type', 'void')
+ params = ', '.join([f"{p['type']} /*{p['name']}*/" for p in func.get('parameters', [])])
+
+ lines.append(f' {return_type} {class_name}::{func_name}({params})')
+ lines.append(' {')
+ if return_type != 'void':
+ lines.append(' return {};')
+ lines.append(' }')
+
+ return '\\n'.join(lines)
+
+ def _render_user_implementations(self, class_name: str, full_class_name: str,
+ user_functions: dict, declared_functions: list) -> str:
+ """
+ Render function implementations, using user code where available.
+
+ Args:
+ class_name: Short class name (e.g., "TestReflectionComponent")
+ full_class_name: Full class name with namespace (e.g., "NewProject::TestReflectionComponent")
+ user_functions: Dict of user-provided function implementations from .cpp
+ declared_functions: List of O3DE_FUNCTION declared functions
+ """
+ lines = []
+ lines.append(' // ========================================================================')
+ lines.append(' // Component Lifecycle Functions')
+ lines.append(' // ========================================================================')
+ lines.append('')
+
+ # List of standard component functions to look for
+ lifecycle_functions = ['Init', 'Activate', 'Deactivate']
+
+ for func_name in lifecycle_functions:
+ # Try to find user implementation
+ user_impl = None
+ for key in [f"{class_name}::{func_name}", f"{full_class_name}::{func_name}"]:
+ if key in user_functions:
+ user_impl = user_functions[key]
+ break
+
+ if user_impl:
+ # Use user's implementation
+ lines.append(f" void {class_name}::{func_name}()")
+ lines.append(' {')
+ # Indent the user's body
+ for line in user_impl['body'].split('\\n'):
+ lines.append(f" {line}" if line.strip() else '')
+ lines.append(' }')
+ else:
+ # Provide empty default
+ lines.append(f" void {class_name}::{func_name}()")
+ lines.append(' {')
+ lines.append(f' // TODO: Implement {func_name}')
+ lines.append(' }')
+ lines.append('')
+
+ # Now handle O3DE_FUNCTION declared functions
+ if declared_functions:
+ lines.append(' // ========================================================================')
+ lines.append(' // Custom Functions')
+ lines.append(' // ========================================================================')
+ lines.append('')
+
+ for func in declared_functions:
+ func_name = func['name']
+ return_type = func.get('return_type', 'void')
+ params = ', '.join([f"{p['type']} {p['name']}" for p in func.get('parameters', [])])
+
+ # Try to find user implementation
+ user_impl = None
+ for key in [f"{class_name}::{func_name}", f"{full_class_name}::{func_name}"]:
+ if key in user_functions:
+ user_impl = user_functions[key]
+ break
+
+ const_suffix = ' const' if func.get('is_const', False) else ''
+
+ if user_impl:
+ # Use user's implementation
+ lines.append(f" {return_type} {class_name}::{func_name}({params}){const_suffix}")
+ lines.append(' {')
+ for line in user_impl['body'].split('\\n'):
+ lines.append(f" {line}" if line.strip() else '')
+ lines.append(' }')
+ else:
+ # Provide stub default
+ lines.append(f" {return_type} {class_name}::{func_name}({params}){const_suffix}")
+ lines.append(' {')
+ lines.append(f' // TODO: Implement {func_name}')
+ if return_type != 'void':
+ lines.append(' return {};')
+ lines.append(' }')
+ lines.append('')
+
+ return '\\n'.join(lines)
+
+ def _render_struct_behavior_context(self, struct: dict) -> str:
+ if not struct.get('script_type', True):
+ return ''
+
+ struct_name = struct['name']
+ category = struct.get('category', 'Data')
+ properties = struct.get('properties', [])
+
+ lines = [
+ ' if (auto* behaviorContext = azrtti_cast(context))',
+ ' {',
+ f' behaviorContext->Class<{struct_name}>("{struct_name}")',
+ f' ->Attribute(AZ::Script::Attributes::Category, "{category}")',
+ ' ->Constructor()',
+ ]
+
+ for prop in properties:
+ if not prop.get('expose_to_script', True):
+ continue
+ member_name = prop['member_name']
+ clean_name = prop['name'][2:] if prop['name'].startswith('m_') else prop['name']
+ lines.append(f' ->Property("{clean_name}", BehaviorValueProperty(&{struct_name}::{member_name}))')
+
+ lines.append(' ;')
+ lines.append(' }')
+
+ return '\\n'.join(lines)
+
+ def _render_struct_equality(self, properties: list) -> str:
+ if not properties:
+ return ' return true;'
+
+ lines = [' return']
+ for i, prop in enumerate(properties):
+ member_name = prop['member_name']
+ if i == 0:
+ lines.append(f' {member_name} == rhs.{member_name}')
+ else:
+ lines.append(f' && {member_name} == rhs.{member_name}')
+ lines.append(' ;')
+
+ return '\\n'.join(lines)
+
+ def _render_enum_values(self, enum: dict) -> str:
+ lines = []
+ is_flags = enum.get('is_flags', False)
+ counter = 0
+
+ for val in enum.get('values', []):
+ val_name = val['name']
+ val_num = val.get('value')
+
+ if val_num is not None:
+ lines.append(f' {val_name} = {val_num},')
+ elif is_flags:
+ lines.append(f' {val_name} = 1 << {counter},')
+ counter += 1
+ else:
+ lines.append(f' {val_name},')
+
+ return '\\n'.join(lines)
+
+ def _render_enum_bitwise_operators(self, enum: dict) -> str:
+ if not enum.get('is_flags', False):
+ return ''
+ return f' AZ_DEFINE_ENUM_BITWISE_OPERATORS({enum["name"]});\\n'
+
+ def _render_enum_behavior_values(self, enum: dict) -> str:
+ enum_name = enum['name']
+ category = enum.get('category', 'Enums')
+ values = enum.get('values', [])
+
+ if not values:
+ return ' // No enum values defined'
+
+ # Build the Enum template arguments
+ value_casts = ',\\n'.join([
+ f' static_cast({enum_name}::{v["name"]})'
+ for v in values
+ ])
+
+ lines = [
+ ' behaviorContext->Enum<',
+ value_casts,
+ f' >("{enum_name}")',
+ ]
+
+ for val in values:
+ display_name = val.get('display_name', camel_to_human(val['name']))
+ lines.append(f' ->Value("{display_name}", static_cast({enum_name}::{val["name"]}))')
+
+ lines.append(f' ->Attribute(AZ::Script::Attributes::Category, "{category}")')
+ lines.append(' ;')
+
+ return '\\n'.join(lines)
+
+ def _write_file(self, path: str, content: str):
+ """Write content to file, creating directories as needed."""
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, 'w', encoding='utf-8', newline='\\n') as f:
+ f.write(content)
+ logger.info(f"Generated: {path}")
+
+
+def main():
+ parser = argparse.ArgumentParser(description='O3DEReflect Code Generator')
+ parser.add_argument('--input', '-i', required=True, help='Input XML file')
+ parser.add_argument('--output-dir', '-o', required=True, help='Output directory')
+ parser.add_argument('--template-dir', '-t', help='Template directory (for Jinja2 templates)')
+ parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
+ parser.add_argument('--dry-run', '-n', action='store_true', help='Parse only, do not generate')
+
+ args = parser.parse_args()
+
+ if args.verbose:
+ logger.setLevel(logging.DEBUG)
+
+ # Parse input file
+ logger.info(f"Parsing: {args.input}")
+ xml_parser = O3DEReflectParser(args.input)
+ parsed_data = xml_parser.parse()
+
+ if args.verbose:
+ logger.debug(f"Parsed data: {json.dumps(parsed_data, indent=2, default=str)}")
+
+ if args.dry_run:
+ logger.info("Dry run - no files generated")
+ return 0
+
+ # Generate code
+ template_dir = args.template_dir or os.path.dirname(os.path.abspath(__file__))
+ generator = O3DEReflectGenerator(template_dir, args.output_dir)
+ generated_files = generator.generate(parsed_data, args.input)
+
+ logger.info(f"Generated {len(generated_files)} files")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflectHeaderParser.py b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflectHeaderParser.py
new file mode 100644
index 0000000000..c7f66a7c39
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflectHeaderParser.py
@@ -0,0 +1,911 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+
+"""
+O3DEReflect Header Parser (V2)
+
+This module parses C++ header files for O3DE_* macros and generates reflection code automatically.
+
+Supports:
+ O3DE_CLASS(...) - Mark a class for reflection
+ O3DE_COMPONENT(...) - Component-specific metadata
+ O3DE_PROPERTY(...) - Mark members for serialization/editor
+ O3DE_FUNCTION(...) - Expose methods to scripting
+ O3DE_STRUCT(...) - Mark structs for reflection
+ O3DE_ENUM(...) - Mark enums for reflection
+ O3DE_ENUM_VALUE(...) - Enum value metadata
+
+Usage:
+ python O3DEReflectHeaderParser.py --input MyComponent.h --output-dir ./Generated
+"""
+
+import os
+import re
+import sys
+import json
+import glob
+import argparse
+import hashlib
+import logging
+from pathlib import Path
+from dataclasses import dataclass, field
+from typing import List, Dict, Optional, Any, Tuple
+
+# Try to find O3DE's Python venv site-packages for Jinja2
+def find_o3de_site_packages():
+ """Find O3DE's Python venv site-packages directory."""
+ home = os.path.expanduser("~")
+ patterns = [
+ os.path.join(home, ".o3de", "Python", "venv", "*", "lib", "site-packages"),
+ os.path.join(home, ".o3de", "Python", "venv", "*", "Lib", "site-packages"), # Windows
+ ]
+ for pattern in patterns:
+ matches = glob.glob(pattern)
+ if matches:
+ return matches[0]
+ return None
+
+# Add O3DE site-packages to path if needed
+o3de_site_packages = find_o3de_site_packages()
+if o3de_site_packages and o3de_site_packages not in sys.path:
+ sys.path.insert(0, o3de_site_packages)
+
+logging.basicConfig(format='[%(levelname)s] %(name)s: %(message)s')
+logger = logging.getLogger('O3DEReflectHeaderParser')
+logger.setLevel(logging.INFO)
+
+
+def create_hash_guid(string: str) -> str:
+ """Generate a deterministic UUID from a string using MD5 hash."""
+ hash_obj = hashlib.md5(string.encode('utf-8'))
+ hash_str = hash_obj.hexdigest()
+ return "{{{}-{}-{}-{}-{}}}".format(
+ hash_str[0:8].upper(),
+ hash_str[8:12].upper(),
+ hash_str[12:16].upper(),
+ hash_str[16:20].upper(),
+ hash_str[20:].upper()
+ )
+
+
+def camel_to_human(string: str) -> str:
+ """Convert camelCase to Human Readable format."""
+ if not string:
+ return string
+ result = string[0].upper()
+ for char in string[1:]:
+ if char.isupper():
+ result += ' '
+ result += char
+ return result
+
+
+@dataclass
+class MacroAttribute:
+ """Represents a parsed macro attribute (e.g., Category="Movement")"""
+ name: str
+ value: Any = None
+ is_flag: bool = False # True for bare flags like "EditAnywhere"
+
+
+@dataclass
+class PropertyInfo:
+ """Parsed property information"""
+ name: str
+ cpp_type: str
+ default_value: str = ""
+ attributes: Dict[str, Any] = field(default_factory=dict)
+ line_number: int = 0
+
+
+@dataclass
+class FunctionInfo:
+ """Parsed function information"""
+ name: str
+ return_type: str = "void"
+ parameters: List[Dict[str, str]] = field(default_factory=list)
+ attributes: Dict[str, Any] = field(default_factory=dict)
+ line_number: int = 0
+
+
+@dataclass
+class EnumValueInfo:
+ """Parsed enum value information"""
+ name: str
+ value: Optional[int] = None
+ attributes: Dict[str, Any] = field(default_factory=dict)
+
+
+@dataclass
+class ClassInfo:
+ """Parsed class/struct information"""
+ name: str
+ namespace: str = ""
+ base_classes: List[str] = field(default_factory=list)
+ is_component: bool = False
+ is_struct: bool = False
+ is_enum: bool = False
+ uuid: str = ""
+ attributes: Dict[str, Any] = field(default_factory=dict)
+ properties: List[PropertyInfo] = field(default_factory=list)
+ functions: List[FunctionInfo] = field(default_factory=list)
+ enum_values: List[EnumValueInfo] = field(default_factory=list)
+ services: Dict[str, List[str]] = field(default_factory=dict)
+ includes: List[str] = field(default_factory=list)
+ line_number: int = 0
+
+
+class O3DEHeaderParser:
+ """
+ Parser for C++ headers containing O3DE_* reflection macros.
+
+ This parser uses regex-based tokenization to extract macro annotations
+ and their associated C++ declarations.
+ """
+
+ # Regex patterns for macro parsing
+ MACRO_PATTERN = re.compile(
+ r'O3DE_(CLASS|COMPONENT|PROPERTY|FUNCTION|STRUCT|ENUM|ENUM_VALUE|GENERATED_BODY)\s*\(([^)]*)\)',
+ re.MULTILINE
+ )
+
+ # Pattern to match class/struct declaration after O3DE_CLASS/O3DE_STRUCT
+ CLASS_DECL_PATTERN = re.compile(
+ r'(?:class|struct)\s+(?:\[\[.*?\]\]\s*)?(\w+)\s*(?:final\s*)?(?::\s*(.+?))?\s*\{',
+ re.MULTILINE | re.DOTALL
+ )
+
+ # Pattern to match enum declaration after O3DE_ENUM
+ ENUM_DECL_PATTERN = re.compile(
+ r'enum\s+(?:class\s+)?(\w+)\s*(?::\s*(\w+))?\s*\{([^}]*)\}',
+ re.MULTILINE | re.DOTALL
+ )
+
+ # Pattern to match member variable declaration after O3DE_PROPERTY
+ MEMBER_PATTERN = re.compile(
+ r'^\s*((?:const\s+)?(?:[\w:]+(?:<[^>]+>)?(?:\s*[*&])?)+)\s+(\w+)\s*(?:=\s*([^;]+))?\s*;',
+ re.MULTILINE
+ )
+
+ # Pattern to match function declaration after O3DE_FUNCTION
+ FUNCTION_PATTERN = re.compile(
+ r'^\s*((?:virtual\s+)?(?:static\s+)?(?:const\s+)?(?:[\w:]+(?:<[^>]+>)?(?:\s*[*&])?)+)\s+(\w+)\s*\(([^)]*)\)\s*(?:const)?\s*(?:override)?\s*(?:=\s*0)?\s*;',
+ re.MULTILINE
+ )
+
+ # Pattern to parse macro attributes
+ ATTR_PATTERN = re.compile(
+ r'(\w+)\s*(?:=\s*(?:"([^"]*)"|([^,\s]+)))?',
+ re.MULTILINE
+ )
+
+ # Pattern to match namespace
+ NAMESPACE_PATTERN = re.compile(
+ r'namespace\s+(\w+)\s*\{',
+ re.MULTILINE
+ )
+
+ # Pattern to match includes
+ INCLUDE_PATTERN = re.compile(
+ r'#include\s*[<"]([^>"]+)[>"]',
+ re.MULTILINE
+ )
+
+ def __init__(self, file_path: str):
+ self.file_path = file_path
+ self.content = ""
+ self.lines = []
+ self.classes: List[ClassInfo] = []
+ self.current_namespace = ""
+
+ def parse(self) -> Dict:
+ """Parse the header file and return structured data."""
+ with open(self.file_path, 'r', encoding='utf-8') as f:
+ self.content = f.read()
+ self.lines = self.content.split('\n')
+
+ # Extract includes
+ includes = self.INCLUDE_PATTERN.findall(self.content)
+
+ # Find all namespaces
+ namespaces = list(self.NAMESPACE_PATTERN.finditer(self.content))
+
+ # Find all O3DE_* macros
+ macros = list(self.MACRO_PATTERN.finditer(self.content))
+
+ # Process macros in order
+ current_class: Optional[ClassInfo] = None
+ pending_macro: Optional[Tuple[str, Dict, int]] = None
+
+ for macro in macros:
+ macro_type = macro.group(1)
+ macro_args = macro.group(2)
+ macro_pos = macro.start()
+ macro_line = self.content[:macro_pos].count('\n') + 1
+
+ # Determine current namespace
+ current_ns = ""
+ for ns_match in namespaces:
+ if ns_match.start() < macro_pos:
+ current_ns = ns_match.group(1)
+
+ attributes = self._parse_macro_attributes(macro_args)
+
+ if macro_type == 'CLASS':
+ # Look for class declaration after macro
+ remaining = self.content[macro.end():]
+ class_match = self.CLASS_DECL_PATTERN.search(remaining)
+ if class_match:
+ class_name = class_match.group(1)
+ base_classes_str = class_match.group(2) or ""
+ base_classes = self._parse_base_classes(base_classes_str)
+
+ full_name = f"{current_ns}::{class_name}" if current_ns else class_name
+
+ current_class = ClassInfo(
+ name=class_name,
+ namespace=current_ns,
+ base_classes=base_classes,
+ is_component=any('Component' in bc for bc in base_classes),
+ uuid=attributes.get('Uuid', create_hash_guid(full_name)),
+ attributes=attributes,
+ includes=includes.copy(),
+ line_number=macro_line,
+ )
+ current_class.services = {
+ 'provides': [],
+ 'requires': [],
+ 'incompatible': [],
+ 'dependent': [],
+ }
+ self.classes.append(current_class)
+
+ elif macro_type == 'STRUCT':
+ # Look for struct declaration after macro
+ remaining = self.content[macro.end():]
+ class_match = self.CLASS_DECL_PATTERN.search(remaining)
+ if class_match:
+ struct_name = class_match.group(1)
+ full_name = f"{current_ns}::{struct_name}" if current_ns else struct_name
+
+ current_class = ClassInfo(
+ name=struct_name,
+ namespace=current_ns,
+ is_struct=True,
+ uuid=attributes.get('Uuid', create_hash_guid(full_name)),
+ attributes=attributes,
+ includes=includes.copy(),
+ line_number=macro_line,
+ )
+ self.classes.append(current_class)
+
+ elif macro_type == 'ENUM':
+ # Look for enum declaration after macro
+ remaining = self.content[macro.end():]
+ enum_match = self.ENUM_DECL_PATTERN.search(remaining)
+ if enum_match:
+ enum_name = enum_match.group(1)
+ underlying_type = enum_match.group(2) or "int32_t"
+ enum_body = enum_match.group(3)
+
+ full_name = f"{current_ns}::{enum_name}" if current_ns else enum_name
+
+ enum_class = ClassInfo(
+ name=enum_name,
+ namespace=current_ns,
+ is_enum=True,
+ uuid=attributes.get('Uuid', create_hash_guid(full_name)),
+ attributes=attributes,
+ line_number=macro_line,
+ )
+ enum_class.attributes['UnderlyingType'] = underlying_type
+
+ # Parse enum values
+ enum_class.enum_values = self._parse_enum_values(enum_body)
+ self.classes.append(enum_class)
+
+ elif macro_type == 'COMPONENT':
+ # O3DE_COMPONENT adds component-specific info to current class
+ if current_class and current_class.is_component:
+ current_class.is_component = True
+ # Parse services from attributes
+ if 'ProvidesServices' in attributes:
+ current_class.services['provides'] = self._parse_service_list(attributes['ProvidesServices'])
+ if 'RequiresServices' in attributes:
+ current_class.services['requires'] = self._parse_service_list(attributes['RequiresServices'])
+ if 'IncompatibleServices' in attributes:
+ current_class.services['incompatible'] = self._parse_service_list(attributes['IncompatibleServices'])
+ if 'DependentServices' in attributes:
+ current_class.services['dependent'] = self._parse_service_list(attributes['DependentServices'])
+ # Merge other attributes
+ current_class.attributes.update(attributes)
+
+ elif macro_type == 'PROPERTY':
+ # Store pending property macro, look for member declaration
+ pending_macro = ('PROPERTY', attributes, macro.end())
+
+ elif macro_type == 'FUNCTION':
+ # Store pending function macro, look for function declaration
+ pending_macro = ('FUNCTION', attributes, macro.end())
+
+ elif macro_type == 'GENERATED_BODY':
+ # Marker for where generated code should be included
+ if current_class:
+ current_class.attributes['_generated_body_line'] = macro_line
+
+ # Now do a second pass to find member and function declarations
+ self._parse_members_and_functions()
+
+ return self._to_dict()
+
+ def _parse_macro_attributes(self, args_str: str) -> Dict[str, Any]:
+ """Parse macro attributes like Category="Movement", EditAnywhere, Min=0.0"""
+ attributes = {}
+ if not args_str.strip():
+ return attributes
+
+ # Split by comma, but be careful with nested content
+ parts = self._split_macro_args(args_str)
+
+ for part in parts:
+ part = part.strip()
+ if not part:
+ continue
+
+ # Check for key=value pattern
+ if '=' in part:
+ key, value = part.split('=', 1)
+ key = key.strip()
+ value = value.strip().strip('"')
+
+ # Try to convert to appropriate type
+ if value.lower() in ('true', 'false'):
+ value = value.lower() == 'true'
+ else:
+ try:
+ if '.' in value:
+ value = float(value.rstrip('f'))
+ else:
+ value = int(value)
+ except ValueError:
+ pass # Keep as string
+
+ attributes[key] = value
+ else:
+ # Bare flag like "EditAnywhere"
+ attributes[part] = True
+
+ return attributes
+
+ def _split_macro_args(self, args_str: str) -> List[str]:
+ """Split macro arguments by comma, respecting nested parentheses and strings."""
+ parts = []
+ current = ""
+ depth = 0
+ in_string = False
+
+ for char in args_str:
+ if char == '"' and (not current or current[-1] != '\\'):
+ in_string = not in_string
+ elif not in_string:
+ if char in '([{':
+ depth += 1
+ elif char in ')]}':
+ depth -= 1
+ elif char == ',' and depth == 0:
+ parts.append(current)
+ current = ""
+ continue
+ current += char
+
+ if current:
+ parts.append(current)
+
+ return parts
+
+ def _parse_base_classes(self, base_str: str) -> List[str]:
+ """Parse base class list from inheritance declaration."""
+ if not base_str:
+ return []
+
+ bases = []
+ for part in base_str.split(','):
+ part = part.strip()
+ # Remove access specifiers
+ for spec in ['public', 'protected', 'private', 'virtual']:
+ part = part.replace(spec, '').strip()
+ if part:
+ bases.append(part)
+
+ return bases
+
+ def _parse_service_list(self, services_str: str) -> List[str]:
+ """Parse comma-separated service list."""
+ if isinstance(services_str, list):
+ return services_str
+ return [s.strip().strip('"') for s in services_str.split(',') if s.strip()]
+
+ def _parse_enum_values(self, enum_body: str) -> List[EnumValueInfo]:
+ """Parse enum values from enum body."""
+ values = []
+
+ # Remove comments
+ enum_body = re.sub(r'//.*$', '', enum_body, flags=re.MULTILINE)
+ enum_body = re.sub(r'/\*.*?\*/', '', enum_body, flags=re.DOTALL)
+
+ # Split by comma
+ parts = [p.strip() for p in enum_body.split(',') if p.strip()]
+
+ for part in parts:
+ # Check for O3DE_ENUM_VALUE macro
+ ev_match = re.search(r'O3DE_ENUM_VALUE\s*\(([^)]*)\)\s*(\w+)', part)
+ if ev_match:
+ attrs = self._parse_macro_attributes(ev_match.group(1))
+ name = ev_match.group(2)
+ else:
+ # Parse regular enum value
+ if '=' in part:
+ name, val = part.split('=', 1)
+ name = name.strip()
+ try:
+ attrs = {'Value': int(val.strip())}
+ except ValueError:
+ attrs = {'Value': val.strip()}
+ else:
+ name = part.strip()
+ attrs = {}
+
+ if name:
+ values.append(EnumValueInfo(
+ name=name,
+ value=attrs.get('Value'),
+ attributes=attrs
+ ))
+
+ return values
+
+ def _parse_members_and_functions(self):
+ """Second pass: match O3DE_PROPERTY/FUNCTION macros to declarations."""
+ for cls in self.classes:
+ if cls.is_enum:
+ continue
+
+ # Find the class body
+ class_start = self._find_class_body_start(cls.name, cls.line_number)
+ if class_start < 0:
+ continue
+
+ class_body = self.content[class_start:]
+ # Find matching closing brace
+ brace_depth = 1
+ class_end = 0
+ for i, char in enumerate(class_body[1:], 1):
+ if char == '{':
+ brace_depth += 1
+ elif char == '}':
+ brace_depth -= 1
+ if brace_depth == 0:
+ class_end = i
+ break
+
+ class_body = class_body[:class_end]
+
+ # Find all O3DE_PROPERTY macros in class body
+ # Format: O3DE_PROPERTY(member_name, Type = "...", ...)
+ # Match multiline macros with parenthesis counting
+ property_pattern = re.compile(r'O3DE_PROPERTY\s*\(', re.MULTILINE)
+ for match in property_pattern.finditer(class_body):
+ start = match.end()
+ depth = 1
+ end = start
+ while end < len(class_body) and depth > 0:
+ if class_body[end] == '(':
+ depth += 1
+ elif class_body[end] == ')':
+ depth -= 1
+ end += 1
+
+ if depth == 0:
+ macro_content = class_body[start:end-1]
+ # Parse: member_name, attr1 = val1, attr2 = val2
+ args = self._split_macro_args(macro_content)
+ if args:
+ member_name = args[0].strip()
+ attr_str = ', '.join(args[1:]) if len(args) > 1 else ''
+ attrs = self._parse_macro_attributes(attr_str)
+
+ # Get type and default from attributes
+ prop_type = attrs.get('type', attrs.get('Type', 'AZ::s32'))
+ default_val = attrs.get('default', attrs.get('Default', ''))
+
+ prop = PropertyInfo(
+ name=member_name,
+ cpp_type=prop_type,
+ default_value=default_val,
+ attributes=attrs,
+ )
+ cls.properties.append(prop)
+
+ # Find all O3DE_FUNCTION macros in class body
+ for match in re.finditer(r'O3DE_FUNCTION\s*\(([^)]*)\)\s*\n\s*(.+?);', class_body, re.MULTILINE):
+ attrs = self._parse_macro_attributes(match.group(1))
+ decl = match.group(2).strip()
+
+ # Parse function declaration
+ func_match = re.match(r'([\w:<>,\s*&]+?)\s+(\w+)\s*\(([^)]*)\)', decl)
+ if func_match:
+ params = self._parse_function_params(func_match.group(3))
+ func = FunctionInfo(
+ name=func_match.group(2),
+ return_type=func_match.group(1).strip(),
+ parameters=params,
+ attributes=attrs,
+ )
+ cls.functions.append(func)
+
+ def _find_class_body_start(self, class_name: str, start_line: int) -> int:
+ """Find the position of the opening brace of a class."""
+ # Search from the given line
+ start_pos = sum(len(line) + 1 for line in self.lines[:start_line-1])
+ remaining = self.content[start_pos:]
+
+ # Find class declaration
+ pattern = re.compile(rf'(?:class|struct)\s+{re.escape(class_name)}\s*(?:final\s*)?(?::[^{{]+)?\s*\{{')
+ match = pattern.search(remaining)
+ if match:
+ return start_pos + match.end() - 1
+ return -1
+
+ def _parse_function_params(self, params_str: str) -> List[Dict[str, str]]:
+ """Parse function parameters."""
+ params = []
+ if not params_str.strip():
+ return params
+
+ for part in self._split_macro_args(params_str):
+ part = part.strip()
+ if not part:
+ continue
+
+ # Parse "Type name = default" or "Type name"
+ match = re.match(r'([\w:<>,\s*&]+?)\s+(\w+)\s*(?:=\s*(.+))?$', part)
+ if match:
+ params.append({
+ 'type': match.group(1).strip(),
+ 'name': match.group(2),
+ 'default': match.group(3).strip() if match.group(3) else "",
+ })
+
+ return params
+
+ def _to_dict(self) -> Dict:
+ """Convert parsed data to dictionary format compatible with generator."""
+ result = {
+ 'components': [],
+ 'structs': [],
+ 'enums': [],
+ 'source_file': os.path.basename(self.file_path),
+ }
+
+ for cls in self.classes:
+ if cls.is_enum:
+ enum_data = self._enum_to_dict(cls)
+ result['enums'].append(enum_data)
+ elif cls.is_struct:
+ struct_data = self._struct_to_dict(cls)
+ result['structs'].append(struct_data)
+ else:
+ comp_data = self._component_to_dict(cls)
+ result['components'].append(comp_data)
+
+ return result
+
+ def _component_to_dict(self, cls: ClassInfo) -> Dict:
+ """Convert ClassInfo to component dictionary."""
+ full_name = f"{cls.namespace}::{cls.name}" if cls.namespace else cls.name
+
+ return {
+ 'name': cls.name,
+ 'namespace': cls.namespace,
+ 'full_name': full_name,
+ 'uuid': cls.uuid,
+ 'category': cls.attributes.get('Category', 'General'),
+ 'description': cls.attributes.get('Description', f'{cls.name} component'),
+ 'display_name': cls.attributes.get('DisplayName', camel_to_human(cls.name)),
+ 'icon': cls.attributes.get('Icon', ''),
+ 'hide_in_editor': cls.attributes.get('HideInEditor', False),
+ 'abstract': cls.attributes.get('Abstract', False),
+ 'menu_category': cls.attributes.get('AppearsInAddComponentMenu', 'Game'),
+ 'includes': cls.includes,
+ 'base_classes': [{'name': bc.split('::')[-1], 'namespace': '::'.join(bc.split('::')[:-1]) if '::' in bc else '', 'include': ''} for bc in cls.base_classes],
+ 'services': cls.services,
+ 'properties': [self._property_to_dict(p) for p in cls.properties],
+ 'functions': [self._function_to_dict(f) for f in cls.functions],
+ }
+
+ def _struct_to_dict(self, cls: ClassInfo) -> Dict:
+ """Convert ClassInfo to struct dictionary."""
+ full_name = f"{cls.namespace}::{cls.name}" if cls.namespace else cls.name
+
+ return {
+ 'name': cls.name,
+ 'namespace': cls.namespace,
+ 'full_name': full_name,
+ 'uuid': cls.uuid,
+ 'category': cls.attributes.get('Category', 'Data'),
+ 'description': cls.attributes.get('Description', f'{cls.name} data structure'),
+ 'display_name': cls.attributes.get('DisplayName', camel_to_human(cls.name)),
+ 'script_type': cls.attributes.get('ScriptType', True),
+ 'atomic': cls.attributes.get('Atomic', False),
+ 'includes': cls.includes,
+ 'properties': [self._property_to_dict(p) for p in cls.properties],
+ }
+
+ def _enum_to_dict(self, cls: ClassInfo) -> Dict:
+ """Convert ClassInfo to enum dictionary."""
+ full_name = f"{cls.namespace}::{cls.name}" if cls.namespace else cls.name
+
+ return {
+ 'name': cls.name,
+ 'namespace': cls.namespace,
+ 'full_name': full_name,
+ 'uuid': cls.uuid,
+ 'category': cls.attributes.get('Category', 'Enums'),
+ 'description': cls.attributes.get('Description', f'{cls.name} enumeration'),
+ 'display_name': cls.attributes.get('DisplayName', camel_to_human(cls.name)),
+ 'script_type': cls.attributes.get('ScriptType', True),
+ 'is_flags': cls.attributes.get('Flags', False),
+ 'underlying_type': cls.attributes.get('UnderlyingType', 'int32_t'),
+ 'values': [self._enum_value_to_dict(v) for v in cls.enum_values],
+ }
+
+ def _property_to_dict(self, prop: PropertyInfo) -> Dict:
+ """Convert PropertyInfo to dictionary."""
+ prop_name = prop.name
+ return {
+ 'name': prop_name,
+ 'member_name': prop_name if prop_name.startswith('m_') else f'm_{prop_name}',
+ 'type': prop.cpp_type,
+ 'default': prop.default_value,
+ 'display_name': prop.attributes.get('DisplayName', camel_to_human(prop_name.lstrip('m_'))),
+ 'category': prop.attributes.get('Category', ''),
+ 'tooltip': prop.attributes.get('Tooltip', ''),
+ 'suffix': prop.attributes.get('Suffix', ''),
+ 'edit_anywhere': prop.attributes.get('EditAnywhere', False),
+ 'edit_defaults_only': prop.attributes.get('EditDefaultsOnly', False),
+ 'edit_instance_only': prop.attributes.get('EditInstanceOnly', False),
+ 'visible_anywhere': prop.attributes.get('VisibleAnywhere', False),
+ 'read_only': prop.attributes.get('ReadOnly', False),
+ 'script_read_write': prop.attributes.get('ScriptReadWrite', False),
+ 'script_read_only': prop.attributes.get('ScriptReadOnly', False),
+ 'expose_to_script': prop.attributes.get('ExposeToScript', True),
+ 'min': prop.attributes.get('Min'),
+ 'max': prop.attributes.get('Max'),
+ 'ui_min': prop.attributes.get('UIMin'),
+ 'ui_max': prop.attributes.get('UIMax'),
+ 'step': prop.attributes.get('Step'),
+ 'change_notify': prop.attributes.get('ChangeNotify', ''),
+ 'visibility': prop.attributes.get('Visibility', ''),
+ 'order': prop.attributes.get('Order', 0),
+ }
+
+ def _function_to_dict(self, func: FunctionInfo) -> Dict:
+ """Convert FunctionInfo to dictionary."""
+ return {
+ 'name': func.name,
+ 'display_name': func.attributes.get('DisplayName', camel_to_human(func.name)),
+ 'category': func.attributes.get('Category', ''),
+ 'tooltip': func.attributes.get('Tooltip', ''),
+ 'return_type': func.return_type,
+ 'parameters': func.parameters,
+ 'script_callable': func.attributes.get('ScriptCallable', True),
+ 'script_pure': func.attributes.get('ScriptPure', False),
+ 'call_in_editor': func.attributes.get('CallInEditor', False),
+ 'server': func.attributes.get('Server', False),
+ 'client': func.attributes.get('Client', False),
+ 'net_multicast': func.attributes.get('NetMulticast', False),
+ }
+
+ def _enum_value_to_dict(self, val: EnumValueInfo) -> Dict:
+ """Convert EnumValueInfo to dictionary."""
+ return {
+ 'name': val.name,
+ 'value': val.value,
+ 'display_name': val.attributes.get('DisplayName', camel_to_human(val.name)),
+ 'tooltip': val.attributes.get('Tooltip', ''),
+ 'hidden': val.attributes.get('Hidden', False),
+ }
+
+
+class CppSourceParser:
+ """
+ Parser for C++ source files to extract user function implementations.
+ Used to merge user code with generated reflection boilerplate.
+ """
+
+ def __init__(self, file_path: str):
+ self.file_path = file_path
+ self.content = ""
+ self.functions = {} # Dict of function_signature -> function_body
+ self.includes = []
+ self.extra_code = [] # Any code not recognized as a function implementation
+
+ def parse(self) -> Dict:
+ """Parse the source file and extract function implementations."""
+ if not os.path.exists(self.file_path):
+ logger.debug(f"Source file not found: {self.file_path}")
+ return {'functions': {}, 'includes': [], 'extra_code': []}
+
+ with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f:
+ self.content = f.read()
+
+ self._extract_includes()
+ self._extract_functions()
+
+ return {
+ 'functions': self.functions,
+ 'includes': self.includes,
+ 'extra_code': self.extra_code,
+ 'raw_content': self.content
+ }
+
+ def _extract_includes(self):
+ """Extract #include statements."""
+ include_pattern = re.compile(r'^\s*#include\s*[<"]([^>"]+)[>"]', re.MULTILINE)
+ self.includes = include_pattern.findall(self.content)
+
+ def _extract_functions(self):
+ """Extract function implementations with their bodies."""
+ # Pattern to match function definitions: ReturnType ClassName::FunctionName(params) { body }
+ # This handles multi-line function bodies by tracking brace depth
+
+ # First, remove comments to avoid matching code inside comments
+ content_no_comments = self._remove_comments(self.content)
+
+ # Pattern for function signature (simplified - handles common cases)
+ func_pattern = re.compile(
+ r'(?:^|\n)\s*' # Start of line
+ r'((?:[\w:*&<>,\s]+)?)' # Return type (optional, can be complex like const Type&)
+ r'\s+([\w:]+)::(\w+)' # ClassName::FunctionName
+ r'\s*\(([^)]*)\)' # Parameters
+ r'\s*(const)?' # Optional const
+ r'\s*(override)?' # Optional override
+ r'\s*\{', # Opening brace
+ re.MULTILINE
+ )
+
+ for match in func_pattern.finditer(content_no_comments):
+ return_type = match.group(1).strip() if match.group(1) else 'void'
+ class_name = match.group(2)
+ func_name = match.group(3)
+ params = match.group(4).strip()
+ is_const = bool(match.group(5))
+
+ # Find the matching closing brace
+ start_pos = match.end() - 1 # Position of opening brace
+ body_start = match.end()
+ body_end = self._find_matching_brace(content_no_comments, start_pos)
+
+ if body_end > body_start:
+ body = content_no_comments[body_start:body_end].strip()
+
+ # Create a key that identifies this function
+ key = f"{class_name}::{func_name}"
+
+ self.functions[key] = {
+ 'return_type': return_type,
+ 'class_name': class_name,
+ 'func_name': func_name,
+ 'params': params,
+ 'is_const': is_const,
+ 'body': body,
+ 'full_signature': f"{return_type} {class_name}::{func_name}({params})" + (" const" if is_const else "")
+ }
+
+ def _remove_comments(self, code: str) -> str:
+ """Remove C and C++ style comments from code."""
+ # Remove single-line comments
+ code = re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
+ # Remove multi-line comments
+ code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
+ return code
+
+ def _find_matching_brace(self, code: str, start_pos: int) -> int:
+ """Find the position of the closing brace that matches the opening brace at start_pos."""
+ if start_pos >= len(code) or code[start_pos] != '{':
+ return -1
+
+ depth = 1
+ pos = start_pos + 1
+ in_string = False
+ string_char = None
+
+ while pos < len(code) and depth > 0:
+ char = code[pos]
+ prev_char = code[pos - 1] if pos > 0 else ''
+
+ # Handle string literals
+ if char in '"\'':
+ if not in_string:
+ in_string = True
+ string_char = char
+ elif char == string_char and prev_char != '\\':
+ in_string = False
+ elif not in_string:
+ if char == '{':
+ depth += 1
+ elif char == '}':
+ depth -= 1
+
+ pos += 1
+
+ return pos - 1 if depth == 0 else -1
+
+
+def parse_source(file_path: str) -> Dict:
+ """Parse a C++ source file and return extracted function implementations."""
+ parser = CppSourceParser(file_path)
+ return parser.parse()
+
+
+def parse_header(file_path: str) -> Dict:
+ """Parse a header file and return structured data."""
+ parser = O3DEHeaderParser(file_path)
+ return parser.parse()
+
+
+def main():
+ parser = argparse.ArgumentParser(description='O3DEReflect Header Parser')
+ parser.add_argument('--input', '-i', required=True, help='Input header file')
+ parser.add_argument('--output-dir', '-o', help='Output directory for generated files')
+ parser.add_argument('--output-json', '-j', help='Output JSON file with parsed data')
+ parser.add_argument('--template-dir', '-t', help='Template directory (defaults to script directory)')
+ parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
+
+ args = parser.parse_args()
+
+ if args.verbose:
+ logger.setLevel(logging.DEBUG)
+
+ # Parse header
+ logger.info(f"Parsing: {args.input}")
+ parsed_data = parse_header(args.input)
+
+ # Also parse the corresponding .cpp file if it exists
+ cpp_file = args.input.replace('.h', '.cpp')
+ user_code = parse_source(cpp_file)
+ if user_code['functions']:
+ logger.info(f"Found user implementations in: {cpp_file}")
+ parsed_data['user_code'] = user_code
+
+ if args.verbose:
+ logger.debug(f"Parsed data: {json.dumps(parsed_data, indent=2, default=str)}")
+
+ # Output JSON if requested
+ if args.output_json:
+ with open(args.output_json, 'w', encoding='utf-8') as f:
+ json.dump(parsed_data, f, indent=2, default=str)
+ logger.info(f"Wrote JSON: {args.output_json}")
+
+ # Generate code if output directory specified
+ if args.output_dir:
+ # Import the generator from O3DEReflectGen
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ template_dir = args.template_dir if args.template_dir else script_dir
+ sys.path.insert(0, script_dir)
+ from O3DEReflectGen import O3DEReflectGenerator
+
+ generator = O3DEReflectGenerator(template_dir, args.output_dir)
+ generated_files = generator.generate(parsed_data, args.input)
+ logger.info(f"Generated {len(generated_files)} files")
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Common.jinja b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Common.jinja
new file mode 100644
index 0000000000..c13445992f
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Common.jinja
@@ -0,0 +1,98 @@
+{#
+ Copyright (c) Contributors to the Open 3D Engine Project.
+ For complete copyright and license terms please see the LICENSE at the root of this distribution.
+
+ SPDX-License-Identifier: Apache-2.0 OR MIT
+
+ O3DEReflect Common Macros - Shared Jinja2 macros for code generation
+#}
+
+{# Convert text to have uppercase first letter #}
+{% macro UpperFirst(text) %}{{ text[0] | upper}}{{ text[1:] }}{% endmacro %}
+
+{# Convert text to have lowercase first letter #}
+{% macro LowerFirst(text) %}{{ text[0] | lower}}{{ text[1:] }}{% endmacro %}
+
+{# Convert camelCase to Human Readable #}
+{% macro CamelToHuman(text) %}{{ text[0] | upper }}{% for char in text[1:] %}{% if char.isupper() %} {% endif %}{{ char }}{% endfor %}{% endmacro %}
+
+{# Generate display name from variable name (strip m_ prefix, convert to human readable) #}
+{% macro GenerateDisplayName(name) %}{% if name.startswith('m_') %}{{ CamelToHuman(name[2:]) }}{% else %}{{ CamelToHuman(name) }}{% endif %}{% endmacro %}
+
+{# Get fully qualified name with namespace #}
+{% macro FullyQualifiedName(element) %}{% if element.attrib.get('Namespace') %}{{ element.attrib['Namespace'] }}::{% endif %}{{ element.attrib['Name'] }}{% endmacro %}
+
+{# Generate member variable name from property name #}
+{% macro MemberName(name) %}{% if name.startswith('m_') %}{{ name }}{% else %}m_{{ name }}{% endif %}{% endmacro %}
+
+{# Generate getter name from property name #}
+{% macro GetterName(name) %}{% if name.startswith('m_') %}Get{{ UpperFirst(name[2:]) }}{% else %}Get{{ UpperFirst(name) }}{% endif %}{% endmacro %}
+
+{# Generate setter name from property name #}
+{% macro SetterName(name) %}{% if name.startswith('m_') %}Set{{ UpperFirst(name[2:]) }}{% else %}Set{{ UpperFirst(name) }}{% endif %}{% endmacro %}
+
+{# Check if property is editable in editor #}
+{% macro IsEditable(prop) %}{{ prop.attrib.get('EditAnywhere', 'true')|booleanTrue or prop.attrib.get('EditDefaultsOnly', 'false')|booleanTrue or prop.attrib.get('EditInstanceOnly', 'false')|booleanTrue }}{% endmacro %}
+
+{# Check if property is visible in editor #}
+{% macro IsVisible(prop) %}{{ IsEditable(prop) or prop.attrib.get('VisibleAnywhere', 'false')|booleanTrue }}{% endmacro %}
+
+{# Check if property is exposed to script #}
+{% macro IsScriptExposed(prop) %}{{ prop.attrib.get('ExposeToScript', 'true')|booleanTrue or prop.attrib.get('BlueprintReadWrite', 'false')|booleanTrue or prop.attrib.get('BlueprintReadOnly', 'false')|booleanTrue }}{% endmacro %}
+
+{# Generate edit context attributes for a property #}
+{% macro GenerateEditAttributes(prop) %}
+{% set displayName = prop.attrib.get('DisplayName', '') %}
+{% set tooltip = prop.attrib.get('Tooltip', '') %}
+{% set suffix = prop.attrib.get('Suffix', '') %}
+{% if displayName %}
+ ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "{{ displayName }}")
+{% endif %}
+{% if tooltip %}
+ ->Attribute(AZ::Edit::Attributes::DescriptionTextOverride, "{{ tooltip }}")
+{% endif %}
+{% if suffix %}
+ ->Attribute(AZ::Edit::Attributes::Suffix, "{{ suffix }}")
+{% endif %}
+{% if prop.attrib.get('Min') %}
+ ->Attribute(AZ::Edit::Attributes::Min, {{ prop.attrib['Min'] }})
+{% endif %}
+{% if prop.attrib.get('Max') %}
+ ->Attribute(AZ::Edit::Attributes::Max, {{ prop.attrib['Max'] }})
+{% endif %}
+{% if prop.attrib.get('ReadOnly', 'false')|booleanTrue %}
+ ->Attribute(AZ::Edit::Attributes::ReadOnly, true)
+{% endif %}
+{% if prop.attrib.get('ChangeNotify') %}
+ ->Attribute(AZ::Edit::Attributes::ChangeNotify, &{{ prop.attrib['ChangeNotify'] }})
+{% endif %}
+{% endmacro %}
+
+{# Generate property category path #}
+{% macro PropertyCategory(prop, defaultCategory) %}{% if prop.attrib.get('Category') %}"{{ prop.attrib['Category'] }}"{% else %}"{{ defaultCategory }}"{% endif %}{% endmacro %}
+
+{# Iterate over all properties in a component/struct #}
+{% macro ParseProperties(element) %}
+{% for prop in element.findall('Property') %}
+{{ caller(prop) }}
+{% endfor %}
+{% endmacro %}
+
+{# Iterate over all functions in a component #}
+{% macro ParseFunctions(element) %}
+{% for func in element.findall('Function') %}
+{{ caller(func) }}
+{% endfor %}
+{% endmacro %}
+
+{# Iterate over services of a specific type #}
+{% macro ParseServices(element, serviceType) %}
+{% for service in element.findall('Service') %}
+{% if service.attrib.get('Type') == serviceType %}
+{{ caller(service) }}
+{% endif %}
+{% endfor %}
+{% endmacro %}
+
+{# Generate service CRC #}
+{% macro ServiceCRC(serviceName) %}AZ_CRC_CE("{{ serviceName }}"){% endmacro %}
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Header.jinja b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Header.jinja
new file mode 100644
index 0000000000..433c2d3e7d
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Header.jinja
@@ -0,0 +1,192 @@
+{#
+ Copyright (c) Contributors to the Open 3D Engine Project.
+ For complete copyright and license terms please see the LICENSE at the root of this distribution.
+
+ SPDX-License-Identifier: Apache-2.0 OR MIT
+
+ O3DEReflect Header Template - Generates component/struct/enum header files
+#}
+{% import 'O3DEReflect_Common.jinja' as Common %}
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {{ dataFiles[0] }}
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+{% for include in root.findall('Include') %}
+#include <{{ include.attrib['File'] }}>
+{% endfor %}
+{% for baseClass in root.findall('BaseClass') %}
+{% if baseClass.attrib.get('Include') %}
+#include <{{ baseClass.attrib['Include'] }}>
+{% endif %}
+{% endfor %}
+
+{% if root.attrib.get('Namespace') %}
+namespace {{ root.attrib['Namespace'] }}
+{
+{% endif %}
+{% if root.tag == 'Component' %}
+{{ ComponentHeader(root) }}
+{% elif root.tag == 'Struct' %}
+{{ StructHeader(root) }}
+{% elif root.tag == 'Enum' %}
+{{ EnumHeader(root) }}
+{% endif %}
+{% if root.attrib.get('Namespace') %}
+} // namespace {{ root.attrib['Namespace'] }}
+{% endif %}
+
+{# ============================================================================
+ Component Header Generation
+ ============================================================================ #}
+{% macro ComponentHeader(comp) %}
+{% set className = comp.attrib['Name'] %}
+{% set uuid = comp.attrib.get('Uuid', CreateHashGuid(Common.FullyQualifiedName(comp))) %}
+{% set category = comp.attrib.get('Category', 'General') %}
+{% set description = comp.attrib.get('Description', className + ' component') %}
+ //! {{ description }}
+ class {{ className }}
+{% set baseClasses = comp.findall('BaseClass') %}
+{% if baseClasses %}
+ : {% for base in baseClasses %}{% if loop.index > 1 %}, {% endif %}public {% if base.attrib.get('Namespace') %}{{ base.attrib['Namespace'] }}::{% endif %}{{ base.attrib['Name'] }}{% endfor %}
+
+{% else %}
+ : public AZ::Component
+{% endif %}
+ {
+ public:
+ AZ_COMPONENT_DECL({{ className }});
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+ static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+ static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+ static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+ {{ className }}() = default;
+ ~{{ className }}() override = default;
+
+ // Getters and Setters
+{% for prop in comp.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set propType = prop.attrib['Type'] %}
+{% set memberName = Common.MemberName(propName) %}
+ //! Get {{ Common.GenerateDisplayName(propName) }}
+ const {{ propType }}& {{ Common.GetterName(propName) }}() const { return {{ memberName }}; }
+{% if not prop.attrib.get('ReadOnly', 'false')|booleanTrue %}
+ //! Set {{ Common.GenerateDisplayName(propName) }}
+ void {{ Common.SetterName(propName) }}(const {{ propType }}& value) { {{ memberName }} = value; }
+{% endif %}
+
+{% endfor %}
+ protected:
+ // AZ::Component interface
+ void Init() override;
+ void Activate() override;
+ void Deactivate() override;
+
+{% for func in comp.findall('Function') %}
+{% set funcName = func.attrib['Name'] %}
+{% set returnElem = func.find('Return') %}
+{% set returnType = returnElem.attrib['Type'] if returnElem is not none else 'void' %}
+{% set params = func.findall('Param') %}
+ //! {{ func.attrib.get('Tooltip', funcName) }}
+ {{ returnType }} {{ funcName }}({% for param in params %}{% if loop.index > 1 %}, {% endif %}{{ param.attrib['Type'] }} {{ param.attrib['Name'] }}{% if param.attrib.get('Default') %} = {{ param.attrib['Default'] }}{% endif %}{% endfor %});
+
+{% endfor %}
+ private:
+ // Member variables
+{% for prop in comp.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set propType = prop.attrib['Type'] %}
+{% set propDefault = prop.attrib.get('Default', '') %}
+{% set memberName = Common.MemberName(propName) %}
+ {{ propType }} {{ memberName }}{% if propDefault %} = {{ propDefault }}{% endif %};
+{% endfor %}
+ };
+{% endmacro %}
+
+{# ============================================================================
+ Struct Header Generation
+ ============================================================================ #}
+{% macro StructHeader(struct) %}
+{% set structName = struct.attrib['Name'] %}
+{% set uuid = struct.attrib.get('Uuid', CreateHashGuid(Common.FullyQualifiedName(struct))) %}
+{% set description = struct.attrib.get('Description', structName + ' data structure') %}
+ //! {{ description }}
+ struct {{ structName }}
+ {
+ AZ_TYPE_INFO({{ structName }}, "{{ uuid }}");
+ AZ_CLASS_ALLOCATOR({{ structName }}, AZ::SystemAllocator);
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ {{ structName }}() = default;
+ ~{{ structName }}() = default;
+
+ bool operator==(const {{ structName }}& rhs) const;
+ bool operator!=(const {{ structName }}& rhs) const { return !(*this == rhs); }
+
+ // Member variables
+{% for prop in struct.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set propType = prop.attrib['Type'] %}
+{% set propDefault = prop.attrib.get('Default', '') %}
+{% set memberName = Common.MemberName(propName) %}
+ {{ propType }} {{ memberName }}{% if propDefault %} = {{ propDefault }}{% endif %};
+{% endfor %}
+ };
+{% endmacro %}
+
+{# ============================================================================
+ Enum Header Generation
+ ============================================================================ #}
+{% macro EnumHeader(enum) %}
+{% set enumName = enum.attrib['Name'] %}
+{% set uuid = enum.attrib.get('Uuid', CreateHashGuid(Common.FullyQualifiedName(enum))) %}
+{% set underlyingType = enum.attrib.get('UnderlyingType', 'int32_t') %}
+{% set description = enum.attrib.get('Description', enumName + ' enumeration') %}
+{% set isFlags = enum.attrib.get('Flags', 'false')|booleanTrue %}
+ //! {{ description }}
+ enum class {{ enumName }} : {{ underlyingType }}
+ {
+{% set valueCounter = {'count': 0} %}
+{% for val in enum.findall('EnumValue') %}
+{% set valueName = val.attrib['Name'] %}
+{% set valueNum = val.attrib.get('Value') %}
+{% if valueNum %}
+ {{ valueName }} = {{ valueNum }},
+{% elif isFlags %}
+ {{ valueName }} = 1 << {{ valueCounter['count'] }},
+{% set _ = valueCounter.update({'count': valueCounter['count'] + 1}) %}
+{% else %}
+ {{ valueName }},
+{% endif %}
+{% endfor %}
+ };
+
+{% if isFlags %}
+ AZ_DEFINE_ENUM_BITWISE_OPERATORS({{ enumName }});
+{% endif %}
+
+ // Enum reflection helper
+ class {{ enumName }}Reflect
+ {
+ public:
+ static void Reflect(AZ::ReflectContext* context);
+ };
+{% endmacro %}
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Source.jinja b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Source.jinja
new file mode 100644
index 0000000000..e9952178ac
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/AutoGen/O3DEReflect_Source.jinja
@@ -0,0 +1,318 @@
+{#
+ Copyright (c) Contributors to the Open 3D Engine Project.
+ For complete copyright and license terms please see the LICENSE at the root of this distribution.
+
+ SPDX-License-Identifier: Apache-2.0 OR MIT
+
+ O3DEReflect Source Template - Generates reflection implementation files
+#}
+{% import 'O3DEReflect_Common.jinja' as Common %}
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * AUTO-GENERATED FILE - DO NOT EDIT
+ * Generated by O3DEReflect from {{ dataFiles[0] }}
+ */
+
+#include "{{ fileprefix }}.AutoReflect.h"
+
+#include
+#include
+#include
+#include
+#include
+
+{% if root.attrib.get('Namespace') %}
+namespace {{ root.attrib['Namespace'] }}
+{
+{% endif %}
+{% if root.tag == 'Component' %}
+{{ ComponentSource(root) }}
+{% elif root.tag == 'Struct' %}
+{{ StructSource(root) }}
+{% elif root.tag == 'Enum' %}
+{{ EnumSource(root) }}
+{% endif %}
+{% if root.attrib.get('Namespace') %}
+} // namespace {{ root.attrib['Namespace'] }}
+{% endif %}
+
+{# ============================================================================
+ Component Source Generation
+ ============================================================================ #}
+{% macro ComponentSource(comp) %}
+{% set className = comp.attrib['Name'] %}
+{% set uuid = comp.attrib.get('Uuid', CreateHashGuid(Common.FullyQualifiedName(comp))) %}
+{% set category = comp.attrib.get('Category', 'General') %}
+{% set description = comp.attrib.get('Description', className + ' component') %}
+{% set displayName = comp.attrib.get('DisplayName', Common.CamelToHuman(className)) %}
+{% set icon = comp.attrib.get('Icon', '') %}
+{% set hideInEditor = comp.attrib.get('HideInEditor', 'false')|booleanTrue %}
+{% set menuCategory = comp.attrib.get('AppearsInAddComponentMenu', 'Game') %}
+
+ AZ_COMPONENT_IMPL({{ className }}, "{{ className }}", "{{ uuid }}"{% for base in comp.findall('BaseClass') %}, {% if base.attrib.get('Namespace') %}{{ base.attrib['Namespace'] }}::{% endif %}{{ base.attrib['Name'] }}{% endfor %});
+
+ void {{ className }}::Reflect(AZ::ReflectContext* context)
+ {
+ // SerializeContext
+ if (auto* serializeContext = azrtti_cast(context))
+ {
+ serializeContext->Class<{{ className }}{% for base in comp.findall('BaseClass') %}, {% if base.attrib.get('Namespace') %}{{ base.attrib['Namespace'] }}::{% endif %}{{ base.attrib['Name'] }}{% endfor %}>()
+ ->Version(1)
+{% for prop in comp.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set memberName = Common.MemberName(propName) %}
+ ->Field("{{ propName }}", &{{ className }}::{{ memberName }})
+{% endfor %}
+ ;
+
+ // EditContext
+ if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+ {
+ editContext->Class<{{ className }}>("{{ displayName }}", "{{ description }}")
+ ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+ ->Attribute(AZ::Edit::Attributes::Category, "{{ category }}")
+{% if icon %}
+ ->Attribute(AZ::Edit::Attributes::Icon, "{{ icon }}")
+{% endif %}
+{% if not hideInEditor %}
+ ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("{{ menuCategory }}"))
+{% endif %}
+ ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+{% for prop in comp.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set propType = prop.attrib['Type'] %}
+{% set memberName = Common.MemberName(propName) %}
+{% set propDisplayName = prop.attrib.get('DisplayName', Common.GenerateDisplayName(propName)) %}
+{% set propTooltip = prop.attrib.get('Tooltip', '') %}
+{% set propCategory = prop.attrib.get('Category', '') %}
+{% if propCategory %}
+ ->ClassElement(AZ::Edit::ClassElements::Group, "{{ propCategory }}")
+ ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+{% endif %}
+ ->DataElement(AZ::Edit::UIHandlers::Default, &{{ className }}::{{ memberName }}, "{{ propDisplayName }}", "{{ propTooltip }}")
+{{ Common.GenerateEditAttributes(prop) }}
+{% endfor %}
+ ;
+ }
+ }
+
+ // BehaviorContext
+ if (auto* behaviorContext = azrtti_cast(context))
+ {
+ behaviorContext->Class<{{ className }}>("{{ className }}")
+ ->Attribute(AZ::Script::Attributes::Category, "{{ category }}")
+{% for prop in comp.findall('Property') %}
+{% if prop.attrib.get('ExposeToScript', 'true')|booleanTrue or prop.attrib.get('BlueprintReadWrite', 'false')|booleanTrue %}
+{% set propName = prop.attrib['Name'] %}
+{% set cleanName = propName[2:] if propName.startswith('m_') else propName %}
+{% if prop.attrib.get('BlueprintReadOnly', 'false')|booleanTrue %}
+ ->Property("{{ cleanName }}",
+ []({{ className }}* self) { return self->{{ Common.GetterName(propName) }}(); },
+ nullptr)
+{% else %}
+ ->Property("{{ cleanName }}",
+ []({{ className }}* self) { return self->{{ Common.GetterName(propName) }}(); },
+ []({{ className }}* self, const {{ prop.attrib['Type'] }}& value) { self->{{ Common.SetterName(propName) }}(value); })
+{% endif %}
+{% endif %}
+{% endfor %}
+{% for func in comp.findall('Function') %}
+{% if func.attrib.get('BlueprintCallable', 'true')|booleanTrue %}
+{% set funcName = func.attrib['Name'] %}
+{% set funcDisplayName = func.attrib.get('DisplayName', funcName) %}
+{% set funcCategory = func.attrib.get('Category', category) %}
+ ->Method("{{ funcName }}", &{{ className }}::{{ funcName }})
+ ->Attribute(AZ::Script::Attributes::Category, "{{ funcCategory }}")
+{% endif %}
+{% endfor %}
+ ;
+ }
+ }
+
+ void {{ className }}::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+ {
+{% for service in comp.findall('Service') %}
+{% if service.attrib.get('Type') == 'Provides' %}
+ provided.push_back({{ Common.ServiceCRC(service.attrib['Name']) }});
+{% endif %}
+{% endfor %}
+ }
+
+ void {{ className }}::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+ {
+{% for service in comp.findall('Service') %}
+{% if service.attrib.get('Type') == 'Incompatible' %}
+ incompatible.push_back({{ Common.ServiceCRC(service.attrib['Name']) }});
+{% endif %}
+{% endfor %}
+ }
+
+ void {{ className }}::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
+ {
+{% for service in comp.findall('Service') %}
+{% if service.attrib.get('Type') == 'Requires' %}
+ required.push_back({{ Common.ServiceCRC(service.attrib['Name']) }});
+{% endif %}
+{% endfor %}
+ }
+
+ void {{ className }}::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
+ {
+{% for service in comp.findall('Service') %}
+{% if service.attrib.get('Type') == 'Dependent' %}
+ dependent.push_back({{ Common.ServiceCRC(service.attrib['Name']) }});
+{% endif %}
+{% endfor %}
+ }
+
+ void {{ className }}::Init()
+ {
+ // TODO: Initialize member variables if needed
+ }
+
+ void {{ className }}::Activate()
+ {
+ // TODO: Subscribe to buses, start systems
+ }
+
+ void {{ className }}::Deactivate()
+ {
+ // TODO: Unsubscribe from buses, stop systems
+ }
+
+{% for func in comp.findall('Function') %}
+{% set funcName = func.attrib['Name'] %}
+{% set returnElem = func.find('Return') %}
+{% set returnType = returnElem.attrib['Type'] if returnElem is not none else 'void' %}
+{% set params = func.findall('Param') %}
+ {{ returnType }} {{ className }}::{{ funcName }}({% for param in params %}{% if loop.index > 1 %}, {% endif %}{{ param.attrib['Type'] }} {{ param.attrib['Name'] }}{% endfor %})
+ {
+ // TODO: Implement {{ funcName }}
+{% if returnType != 'void' %}
+ return {};
+{% endif %}
+ }
+
+{% endfor %}
+{% endmacro %}
+
+{# ============================================================================
+ Struct Source Generation
+ ============================================================================ #}
+{% macro StructSource(struct) %}
+{% set structName = struct.attrib['Name'] %}
+{% set uuid = struct.attrib.get('Uuid', CreateHashGuid(Common.FullyQualifiedName(struct))) %}
+{% set category = struct.attrib.get('Category', 'Data') %}
+{% set description = struct.attrib.get('Description', structName + ' data structure') %}
+{% set displayName = struct.attrib.get('DisplayName', Common.CamelToHuman(structName)) %}
+{% set blueprintType = struct.attrib.get('BlueprintType', 'true')|booleanTrue %}
+
+ void {{ structName }}::Reflect(AZ::ReflectContext* context)
+ {
+ // SerializeContext
+ if (auto* serializeContext = azrtti_cast(context))
+ {
+ serializeContext->Class<{{ structName }}>()
+ ->Version(1)
+{% for prop in struct.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set memberName = Common.MemberName(propName) %}
+ ->Field("{{ propName }}", &{{ structName }}::{{ memberName }})
+{% endfor %}
+ ;
+
+ // EditContext
+ if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+ {
+ editContext->Class<{{ structName }}>("{{ displayName }}", "{{ description }}")
+ ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+ ->Attribute(AZ::Edit::Attributes::Category, "{{ category }}")
+{% for prop in struct.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set memberName = Common.MemberName(propName) %}
+{% set propDisplayName = prop.attrib.get('DisplayName', Common.GenerateDisplayName(propName)) %}
+{% set propTooltip = prop.attrib.get('Tooltip', '') %}
+ ->DataElement(AZ::Edit::UIHandlers::Default, &{{ structName }}::{{ memberName }}, "{{ propDisplayName }}", "{{ propTooltip }}")
+{{ Common.GenerateEditAttributes(prop) }}
+{% endfor %}
+ ;
+ }
+ }
+
+{% if blueprintType %}
+ // BehaviorContext
+ if (auto* behaviorContext = azrtti_cast(context))
+ {
+ behaviorContext->Class<{{ structName }}>("{{ structName }}")
+ ->Attribute(AZ::Script::Attributes::Category, "{{ category }}")
+ ->Constructor()
+{% for prop in struct.findall('Property') %}
+{% if prop.attrib.get('ExposeToScript', 'true')|booleanTrue %}
+{% set propName = prop.attrib['Name'] %}
+{% set memberName = Common.MemberName(propName) %}
+{% set cleanName = propName[2:] if propName.startswith('m_') else propName %}
+ ->Property("{{ cleanName }}", BehaviorValueProperty(&{{ structName }}::{{ memberName }}))
+{% endif %}
+{% endfor %}
+ ;
+ }
+{% endif %}
+ }
+
+ bool {{ structName }}::operator==(const {{ structName }}& rhs) const
+ {
+ return
+{% for prop in struct.findall('Property') %}
+{% set propName = prop.attrib['Name'] %}
+{% set memberName = Common.MemberName(propName) %}
+{% if loop.first %}
+ {{ memberName }} == rhs.{{ memberName }}
+{% else %}
+ && {{ memberName }} == rhs.{{ memberName }}
+{% endif %}
+{% endfor %}
+ ;
+ }
+
+{% endmacro %}
+
+{# ============================================================================
+ Enum Source Generation
+ ============================================================================ #}
+{% macro EnumSource(enum) %}
+{% set enumName = enum.attrib['Name'] %}
+{% set uuid = enum.attrib.get('Uuid', CreateHashGuid(Common.FullyQualifiedName(enum))) %}
+{% set category = enum.attrib.get('Category', 'Enums') %}
+{% set description = enum.attrib.get('Description', enumName + ' enumeration') %}
+{% set blueprintType = enum.attrib.get('BlueprintType', 'true')|booleanTrue %}
+
+ void {{ enumName }}Reflect::Reflect(AZ::ReflectContext* context)
+ {
+ // BehaviorContext
+ if (auto* behaviorContext = azrtti_cast(context))
+ {
+ behaviorContext->Enum<
+{% for val in enum.findall('EnumValue') %}
+{% set valueName = val.attrib['Name'] %}
+{% if not loop.last %}
+ static_cast({{ enumName }}::{{ valueName }}),
+{% else %}
+ static_cast({{ enumName }}::{{ valueName }})
+{% endif %}
+{% endfor %}
+ >("{{ enumName }}")
+{% for val in enum.findall('EnumValue') %}
+{% set valueName = val.attrib['Name'] %}
+{% set displayName = val.attrib.get('DisplayName', Common.CamelToHuman(valueName)) %}
+ ->Value("{{ displayName }}", static_cast({{ enumName }}::{{ valueName }}))
+{% endfor %}
+ ->Attribute(AZ::Script::Attributes::Category, "{{ category }}")
+ ;
+ }
+ }
+
+{% endmacro %}
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/O3DEReflectMacros.h b/Gems/O3DEReflect/Code/Include/O3DEReflect/O3DEReflectMacros.h
new file mode 100644
index 0000000000..5a336c1fba
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/O3DEReflectMacros.h
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * O3DE Reflect System - Reflection macros for simplified component/struct/enum creation
+ *
+ * Header-Macro Parsing
+ * ========================
+ * These macros are designed to be parsed by the O3DEReflect code generator.
+ * They expand to empty or minimal code at compile time, while providing
+ * metadata that the generator uses to create reflection code.
+ *
+ * Usage:
+ * O3DE_CLASS(Category="Gameplay", Description="My awesome component")
+ * class MyComponent : public AZ::Component {
+ * O3DE_GENERATED_BODY()
+ *
+ * O3DE_PROPERTY(EditAnywhere, Category="Movement", Tooltip="Speed in m/s", Min=0.0f, Max=100.0f)
+ * float m_speed = 10.0f;
+ *
+ * O3DE_FUNCTION(ScriptCallable, Category="Actions")
+ * void DoSomething();
+ * };
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+// ============================================================================
+// MACRO IMPLEMENTATION STRATEGY
+// ============================================================================
+//
+// These macros serve dual purposes:
+// 1. At compile time: They expand to minimal/empty code
+// 2. At parse time: The O3DEReflect generator extracts metadata from them
+//
+// The generator parses the source file and looks for these macro patterns,
+// then generates the appropriate reflection code (.generated.h/.generated.cpp)
+//
+// ============================================================================
+
+// ============================================================================
+// Property Visibility Specifiers
+// ============================================================================
+// These are used as bare tokens within O3DE_PROPERTY()
+//
+// Editor visibility - controls how property appears in Entity Inspector:
+// EditAnywhere - Editable in editor on instances and archetypes
+// EditDefaultsOnly - Editable only on archetypes/prefabs
+// EditInstanceOnly - Editable only on instances
+// VisibleAnywhere - Visible but not editable
+// VisibleDefaultsOnly - Visible only on defaults
+// VisibleInstanceOnly - Visible only on instances
+//
+// Script visibility - controls script access:
+// ScriptReadWrite - Readable and writable from Lua/ScriptCanvas
+// ScriptReadOnly - Readable only from Lua/ScriptCanvas
+//
+// Note: These are parsed as string tokens, not as macro values
+
+// ============================================================================
+// O3DE_CLASS - Mark a class for reflection
+// ============================================================================
+//
+// Attributes:
+// Category - Category in Add Component menu (e.g., "Gameplay/Movement")
+// Description - Tooltip description for the class
+// Icon - Path to icon file (e.g., "Icons/Components/MyComponent.svg")
+// HideInEditor - If true, component won't appear in Add Component menu
+// Abstract - If true, class cannot be instantiated directly
+//
+// Example:
+// O3DE_CLASS(Category="Gameplay", Description="Handles player movement")
+// class PlayerMovementComponent : public AZ::Component { ... };
+//
+#define O3DE_CLASS(...) \
+ /* This macro is parsed by O3DEReflect code generator */ \
+ /* Attributes: __VA_ARGS__ */
+
+// ============================================================================
+// O3DE_COMPONENT - Declare component with UUID (use instead of AZ_COMPONENT)
+// ============================================================================
+//
+// Automatically provides:
+// - AZ_COMPONENT_DECL macro expansion
+// - UUID generation (auto or explicit)
+// - Service declarations
+//
+// Attributes:
+// ProvidesServices - Comma-separated service names this component provides
+// RequiresServices - Services this component requires
+// IncompatibleServices - Services this component is incompatible with
+// DependentServices - Services that should activate before this component
+//
+// Example:
+// O3DE_COMPONENT(MyComponent, "{12345678-1234-1234-1234-123456789ABC}",
+// ProvidesServices="MyService",
+// RequiresServices="TransformService")
+//
+#define O3DE_COMPONENT(ClassName, ...) \
+ AZ_COMPONENT_DECL(ClassName) \
+ /* UUID and services parsed by O3DEReflect code generator */ \
+ /* Attributes: __VA_ARGS__ */
+
+// ============================================================================
+// O3DE_PROPERTY - Mark a member for serialization and editor exposure
+// ============================================================================
+//
+// Visibility Specifiers:
+// EditAnywhere - Editable everywhere in editor
+// EditDefaultsOnly - Editable only on prefabs/archetypes
+// EditInstanceOnly - Editable only on instances
+// VisibleAnywhere - Visible but read-only in editor
+// ScriptReadWrite - Exposed to Lua/ScriptCanvas read+write
+// ScriptReadOnly - Exposed to Lua/ScriptCanvas read-only
+//
+// Metadata Attributes:
+// Category - Property category in inspector (e.g., "Movement|Speed")
+// Tooltip - Hover tooltip text
+// DisplayName - Override display name (default: variable name)
+// Min - Minimum value for numeric types
+// Max - Maximum value for numeric types
+// UIMin - UI slider minimum (can differ from Min)
+// UIMax - UI slider maximum (can differ from Max)
+// Suffix - Unit suffix (e.g., "m/s", "degrees")
+// ChangeNotify - Function to call when value changes
+//
+// Examples:
+// O3DE_PROPERTY(EditAnywhere, ScriptReadWrite, Category="Movement", Tooltip="Speed in m/s")
+// float m_speed = 10.0f;
+//
+// O3DE_PROPERTY(EditAnywhere, Category="Combat", Min=0, Max=100)
+// int m_health = 100;
+//
+// O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly)
+// AZ::EntityId m_target;
+//
+#define O3DE_PROPERTY(...) \
+ /* This macro is parsed by O3DEReflect code generator */ \
+ /* Attributes: __VA_ARGS__ */
+
+// ============================================================================
+// O3DE_FUNCTION - Expose a function to reflection/scripting
+// ============================================================================
+//
+// Specifiers:
+// ScriptCallable - Can be called from Lua/ScriptCanvas
+// ScriptPure - Pure function (no side effects, can be cached)
+// CallInEditor - Can be invoked from editor buttons
+// Server - Only runs on server (multiplayer)
+// Client - Only runs on client (multiplayer)
+// NetMulticast - Runs on all connected clients
+//
+// Attributes:
+// Category - Category in script node palette
+// DisplayName - Override function display name
+// Tooltip - Hover tooltip text
+//
+// Example:
+// O3DE_FUNCTION(ScriptCallable, Category="Combat", Tooltip="Deals damage to target")
+// void DealDamage(float amount, AZ::EntityId target);
+//
+#define O3DE_FUNCTION(...) \
+ /* This macro is parsed by O3DEReflect code generator */ \
+ /* Attributes: __VA_ARGS__ */
+
+// ============================================================================
+// O3DE_STRUCT - Mark a standalone struct for reflection
+// ============================================================================
+//
+// Attributes:
+// ScriptType - Expose to Lua/ScriptCanvas
+// Atomic - Always serialized as a whole unit
+// Category - Category for organization
+// Description - Tooltip description
+//
+// Example:
+// O3DE_STRUCT(ScriptType, Category="Data")
+// struct DamageInfo
+// {
+// O3DE_PROPERTY(EditAnywhere)
+// float damage = 0.0f;
+//
+// O3DE_PROPERTY(EditAnywhere)
+// AZ::EntityId instigator;
+// };
+//
+#define O3DE_STRUCT(...) \
+ /* This macro is parsed by O3DEReflect code generator */ \
+ /* Attributes: __VA_ARGS__ */
+
+// ============================================================================
+// O3DE_ENUM - Mark an enum for reflection
+// ============================================================================
+//
+// Attributes:
+// ScriptType - Expose to Lua/ScriptCanvas
+// Flags - Treat as bitmask/flags enum
+// Category - Category for organization
+// Description - Tooltip description
+//
+// Use O3DE_ENUM_VALUE to provide display names for values:
+//
+// Example:
+// O3DE_ENUM(ScriptType, Category="AI")
+// enum class AIState
+// {
+// O3DE_ENUM_VALUE(Idle, DisplayName="Standing Idle")
+// Idle,
+//
+// O3DE_ENUM_VALUE(Patrol, DisplayName="Patrolling")
+// Patrol,
+//
+// O3DE_ENUM_VALUE(Combat, DisplayName="In Combat")
+// Combat
+// };
+//
+#define O3DE_ENUM(...) \
+ /* This macro is parsed by O3DEReflect code generator */ \
+ /* Attributes: __VA_ARGS__ */
+
+#define O3DE_ENUM_VALUE(Value, ...) \
+ /* Attributes for enum value: __VA_ARGS__ */
+
+// ============================================================================
+// O3DE_GENERATED_BODY - Include generated code in class body
+// ============================================================================
+//
+// Place this at the beginning of your class body to include generated code:
+//
+// Example:
+// O3DE_CLASS(Category="Gameplay")
+// class MyComponent : public AZ::Component
+// {
+// O3DE_GENERATED_BODY()
+// public:
+// // Your code here
+// };
+//
+// This will be replaced with #include "MyComponent.generated.h" pointing to
+// the auto-generated reflection code.
+//
+#define O3DE_GENERATED_BODY() \
+ /* Placeholder for generated includes - replaced during code generation */
+
+// ============================================================================
+// O3DE_IMPLEMENT_REFLECT - Generate Reflect() implementation
+// ============================================================================
+//
+// Place in .cpp file to include generated Reflect() implementation:
+//
+// Example (in MyComponent.cpp):
+// O3DE_IMPLEMENT_REFLECT(MyComponent)
+//
+// This generates the full Reflect() function with SerializeContext,
+// EditContext, and BehaviorContext registrations based on O3DE_PROPERTY
+// and O3DE_FUNCTION annotations.
+//
+#define O3DE_IMPLEMENT_REFLECT(ClassName) \
+ /* Placeholder - include generated source file */ \
+ /* #include "ClassName.generated.cpp" */
diff --git a/Gems/O3DEReflect/Code/Include/O3DEReflect/O3DEReflectTypes.h b/Gems/O3DEReflect/Code/Include/O3DEReflect/O3DEReflectTypes.h
new file mode 100644
index 0000000000..74ba1dadc0
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Include/O3DEReflect/O3DEReflectTypes.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ * O3DE Reflect System - Common types and utilities
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace O3DEReflect
+{
+ //! Property visibility flags
+ enum class PropertyVisibility : AZ::u8
+ {
+ None = 0,
+ EditAnywhere = 1 << 0,
+ EditDefaultsOnly = 1 << 1,
+ EditInstanceOnly = 1 << 2,
+ VisibleAnywhere = 1 << 3,
+ VisibleDefaultsOnly = 1 << 4,
+ VisibleInstanceOnly = 1 << 5,
+ };
+
+ AZ_DEFINE_ENUM_BITWISE_OPERATORS(PropertyVisibility);
+
+ //! Script visibility flags
+ enum class ScriptVisibility : AZ::u8
+ {
+ None = 0,
+ BlueprintReadWrite = 1 << 0,
+ BlueprintReadOnly = 1 << 1,
+ };
+
+ AZ_DEFINE_ENUM_BITWISE_OPERATORS(ScriptVisibility);
+
+ //! Function flags
+ enum class FunctionFlags : AZ::u8
+ {
+ None = 0,
+ BlueprintCallable = 1 << 0,
+ BlueprintPure = 1 << 1,
+ CallInEditor = 1 << 2,
+ Server = 1 << 3,
+ Client = 1 << 4,
+ NetMulticast = 1 << 5,
+ };
+
+ AZ_DEFINE_ENUM_BITWISE_OPERATORS(FunctionFlags);
+
+ //! Metadata for a reflected property
+ struct PropertyMetadata
+ {
+ AZStd::string name;
+ AZStd::string displayName;
+ AZStd::string category;
+ AZStd::string tooltip;
+ AZStd::string suffix;
+ PropertyVisibility visibility = PropertyVisibility::None;
+ ScriptVisibility scriptVisibility = ScriptVisibility::None;
+ float minValue = AZStd::numeric_limits::lowest();
+ float maxValue = AZStd::numeric_limits::max();
+ float uiMin = AZStd::numeric_limits::lowest();
+ float uiMax = AZStd::numeric_limits::max();
+ bool hasMin = false;
+ bool hasMax = false;
+ };
+
+ //! Metadata for a reflected function
+ struct FunctionMetadata
+ {
+ AZStd::string name;
+ AZStd::string displayName;
+ AZStd::string category;
+ AZStd::string tooltip;
+ FunctionFlags flags = FunctionFlags::None;
+ };
+
+ //! Metadata for a reflected class/component
+ struct ClassMetadata
+ {
+ AZStd::string name;
+ AZStd::string displayName;
+ AZStd::string category;
+ AZStd::string description;
+ AZStd::string iconPath;
+ AZ::Uuid uuid;
+ bool hideInEditor = false;
+ bool isAbstract = false;
+ AZStd::vector providesServices;
+ AZStd::vector requiresServices;
+ AZStd::vector incompatibleServices;
+ AZStd::vector dependentServices;
+ AZStd::vector properties;
+ AZStd::vector functions;
+ };
+
+ //! Metadata for a reflected struct
+ struct StructMetadata
+ {
+ AZStd::string name;
+ AZStd::string displayName;
+ AZStd::string category;
+ AZStd::string description;
+ AZ::Uuid uuid;
+ bool blueprintType = false;
+ bool atomic = false;
+ AZStd::vector properties;
+ };
+
+ //! Metadata for a reflected enum value
+ struct EnumValueMetadata
+ {
+ AZStd::string name;
+ AZStd::string displayName;
+ AZStd::string tooltip;
+ int64_t value = 0;
+ };
+
+ //! Metadata for a reflected enum
+ struct EnumMetadata
+ {
+ AZStd::string name;
+ AZStd::string displayName;
+ AZStd::string category;
+ AZStd::string description;
+ AZ::Uuid uuid;
+ bool blueprintType = false;
+ bool isFlags = false;
+ AZStd::vector values;
+ };
+
+ //! Generate a deterministic UUID from a class name
+ //! Uses MD5 hash to create consistent UUIDs across builds
+ inline AZ::Uuid GenerateUuidFromName(const AZStd::string& fullyQualifiedName)
+ {
+ // Use CRC32 for a quick hash, then expand to UUID format
+ // For actual implementation, use MD5 like AzAutoGen.py does
+ AZ::Crc32 hash(fullyQualifiedName.c_str());
+
+ // Create a UUID with the hash in the data section
+ // Note: This is a simplified version - real implementation should use MD5
+ AZ::Uuid uuid = AZ::Uuid::CreateNull();
+ // Fill UUID with deterministic data based on name
+ // This ensures same name always generates same UUID
+ return uuid.CreateString(fullyQualifiedName.c_str());
+ }
+
+} // namespace O3DEReflect
diff --git a/Gems/O3DEReflect/Code/Source/O3DEReflectModule.cpp b/Gems/O3DEReflect/Code/Source/O3DEReflectModule.cpp
new file mode 100644
index 0000000000..3bb5476ef0
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Source/O3DEReflectModule.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ */
+
+#include
+#include
+
+#include "O3DEReflectSystemComponent.h"
+
+namespace O3DEReflect
+{
+ class O3DEReflectModule
+ : public AZ::Module
+ {
+ public:
+ AZ_RTTI(O3DEReflectModule, "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}", AZ::Module);
+ AZ_CLASS_ALLOCATOR(O3DEReflectModule, AZ::SystemAllocator);
+
+ O3DEReflectModule()
+ : AZ::Module()
+ {
+ m_descriptors.insert(m_descriptors.end(), {
+ O3DEReflectSystemComponent::CreateDescriptor(),
+ });
+ }
+
+ AZ::ComponentTypeList GetRequiredSystemComponents() const override
+ {
+ return AZ::ComponentTypeList{
+ azrtti_typeid(),
+ };
+ }
+ };
+
+} // namespace O3DEReflect
+
+#if defined(O3DE_GEM_NAME)
+AZ_DECLARE_MODULE_CLASS(AZ_JOIN(Gem_, O3DE_GEM_NAME), O3DEReflect::O3DEReflectModule)
+#else
+AZ_DECLARE_MODULE_CLASS(Gem_O3DEReflect, O3DEReflect::O3DEReflectModule)
+#endif
diff --git a/Gems/O3DEReflect/Code/Source/O3DEReflectSystemComponent.cpp b/Gems/O3DEReflect/Code/Source/O3DEReflectSystemComponent.cpp
new file mode 100644
index 0000000000..5229523a5f
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Source/O3DEReflectSystemComponent.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ */
+
+#include "O3DEReflectSystemComponent.h"
+
+#include
+#include
+#include
+#include
+
+namespace O3DEReflect
+{
+ AZ_COMPONENT_IMPL(O3DEReflectSystemComponent, "O3DEReflectSystemComponent",
+ "{F8E7A3B2-5C4D-4E6F-8A9B-1C2D3E4F5A6B}");
+
+ void O3DEReflectSystemComponent::Reflect(AZ::ReflectContext* context)
+ {
+ if (auto serializeContext = azrtti_cast(context))
+ {
+ serializeContext->Class()
+ ->Version(1);
+
+ if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+ {
+ editContext->Class("O3DE Reflect System",
+ "Provides runtime support for the O3DE Reflect code generation system")
+ ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+ ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
+ ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
+ }
+ }
+
+ if (auto behaviorContext = azrtti_cast(context))
+ {
+ behaviorContext->Class("O3DEReflectSystem")
+ ->Attribute(AZ::Script::Attributes::Category, "O3DEReflect");
+ }
+ }
+
+ void O3DEReflectSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+ {
+ provided.push_back(AZ_CRC_CE("O3DEReflectService"));
+ }
+
+ void O3DEReflectSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+ {
+ incompatible.push_back(AZ_CRC_CE("O3DEReflectService"));
+ }
+
+ void O3DEReflectSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+ {
+ }
+
+ void O3DEReflectSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+ {
+ }
+
+ void O3DEReflectSystemComponent::Init()
+ {
+ }
+
+ void O3DEReflectSystemComponent::Activate()
+ {
+ }
+
+ void O3DEReflectSystemComponent::Deactivate()
+ {
+ }
+
+} // namespace O3DEReflect
diff --git a/Gems/O3DEReflect/Code/Source/O3DEReflectSystemComponent.h b/Gems/O3DEReflect/Code/Source/O3DEReflectSystemComponent.h
new file mode 100644
index 0000000000..83517ce327
--- /dev/null
+++ b/Gems/O3DEReflect/Code/Source/O3DEReflectSystemComponent.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ */
+
+#pragma once
+
+#include
+
+namespace O3DEReflect
+{
+ //! System component for O3DEReflect gem
+ //! Provides runtime support for the reflection system
+ class O3DEReflectSystemComponent
+ : public AZ::Component
+ {
+ public:
+ AZ_COMPONENT_DECL(O3DEReflectSystemComponent);
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+ static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+ static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+ static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+ protected:
+ // AZ::Component interface
+ void Init() override;
+ void Activate() override;
+ void Deactivate() override;
+ };
+
+} // namespace O3DEReflect
diff --git a/Gems/O3DEReflect/Code/o3dereflect_files.cmake b/Gems/O3DEReflect/Code/o3dereflect_files.cmake
new file mode 100644
index 0000000000..fd5db1e126
--- /dev/null
+++ b/Gems/O3DEReflect/Code/o3dereflect_files.cmake
@@ -0,0 +1,11 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+
+set(FILES
+ Include/O3DEReflect/O3DEReflectMacros.h
+ Include/O3DEReflect/O3DEReflectTypes.h
+ Source/O3DEReflectSystemComponent.cpp
+ Source/O3DEReflectSystemComponent.h
+)
diff --git a/Gems/O3DEReflect/Code/o3dereflect_shared_files.cmake b/Gems/O3DEReflect/Code/o3dereflect_shared_files.cmake
new file mode 100644
index 0000000000..757a083381
--- /dev/null
+++ b/Gems/O3DEReflect/Code/o3dereflect_shared_files.cmake
@@ -0,0 +1,8 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+
+set(FILES
+ Source/O3DEReflectModule.cpp
+)
diff --git a/Gems/O3DEReflect/O3DEReflect.cmake b/Gems/O3DEReflect/O3DEReflect.cmake
new file mode 100644
index 0000000000..26601bf984
--- /dev/null
+++ b/Gems/O3DEReflect/O3DEReflect.cmake
@@ -0,0 +1,321 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+
+#
+# O3DEReflect CMake Module
+#
+# This module provides functions to enable O3DE Reflect code generation for targets.
+# It integrates with the AzAutoGen system to generate reflection code from XML files.
+#
+# Usage:
+# include(O3DEReflect)
+# ly_enable_o3de_reflect(TARGET MyTarget)
+#
+# Or use AUTOGEN_RULES in ly_add_target:
+# ly_add_target(
+# NAME MyTarget
+# ...
+# AUTOGEN_RULES
+# *.O3DEReflect.xml,O3DEReflect_Header.jinja,$path/$fileprefix.AutoReflect.h
+# *.O3DEReflect.xml,O3DEReflect_Source.jinja,$path/$fileprefix.AutoReflect.cpp
+# )
+#
+
+# Find the O3DEReflect gem directory - use CMAKE_CURRENT_LIST_DIR since this file is in the gem
+# Use FORCE to ensure the path is always recalculated when the file is re-included
+# This fixes issues when deploying projects where cached paths may be stale
+set(O3DE_REFLECT_GEM_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE PATH "Path to O3DEReflect gem" FORCE)
+
+# Template directory
+set(O3DE_REFLECT_TEMPLATE_DIR "${O3DE_REFLECT_GEM_DIR}/Code/Include/O3DEReflect/AutoGen" CACHE PATH "Path to O3DEReflect templates" FORCE)
+
+# Python script for code generation (V1: XML)
+set(O3DE_REFLECT_GENERATOR "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflectGen.py" CACHE FILEPATH "Path to O3DEReflect generator script" FORCE)
+
+# Python script for header parsing (V2: Header macros)
+set(O3DE_REFLECT_HEADER_PARSER "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflectHeaderParser.py" CACHE FILEPATH "Path to O3DEReflect header parser" FORCE)
+
+# Use O3DE's Python which has Jinja2 installed
+# LY_PYTHON_CMD is set by LYPython.cmake and points to O3DE's Python with all required packages
+if(NOT DEFINED LY_PYTHON_CMD)
+ # Fallback to Python_EXECUTABLE if LY_PYTHON_CMD is not set
+ set(O3DE_REFLECT_PYTHON_CMD "${Python_EXECUTABLE}")
+else()
+ set(O3DE_REFLECT_PYTHON_CMD ${LY_PYTHON_CMD})
+endif()
+
+#! ly_enable_o3de_reflect: Enable O3DE Reflect code generation for a target
+#
+# This function sets up automatic code generation for O3DEReflect XML files.
+# It will generate .AutoReflect.h and .AutoReflect.cpp files from *.O3DEReflect.xml
+# input files in the target's source directories.
+#
+# \arg:TARGET - Name of the target to enable O3DE Reflect for
+# \arg:INPUT_DIR - Optional input directory to search for XML files (defaults to target source dir)
+# \arg:OUTPUT_DIR - Optional output directory for generated files (defaults to ${CMAKE_CURRENT_BINARY_DIR}/AutoGen)
+#
+function(ly_enable_o3de_reflect)
+ set(options)
+ set(oneValueArgs TARGET INPUT_DIR OUTPUT_DIR)
+ set(multiValueArgs)
+ cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ if(NOT ARG_TARGET)
+ message(FATAL_ERROR "ly_enable_o3de_reflect requires a TARGET argument")
+ endif()
+
+ # Default output directory
+ if(NOT ARG_OUTPUT_DIR)
+ set(ARG_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/AutoGen/${ARG_TARGET}")
+ endif()
+
+ # Get source directory
+ get_target_property(TARGET_SOURCE_DIR ${ARG_TARGET} SOURCE_DIR)
+ if(NOT ARG_INPUT_DIR)
+ set(ARG_INPUT_DIR "${TARGET_SOURCE_DIR}")
+ endif()
+
+ # Find all O3DEReflect XML files
+ file(GLOB_RECURSE O3DE_REFLECT_XML_FILES
+ "${ARG_INPUT_DIR}/*.O3DEReflect.xml"
+ )
+
+ if(NOT O3DE_REFLECT_XML_FILES)
+ message(STATUS "No O3DEReflect XML files found in ${ARG_INPUT_DIR}")
+ return()
+ endif()
+
+ # Create output directory
+ file(MAKE_DIRECTORY ${ARG_OUTPUT_DIR})
+
+ # Generated file lists
+ set(GENERATED_HEADERS)
+ set(GENERATED_SOURCES)
+
+ foreach(XML_FILE ${O3DE_REFLECT_XML_FILES})
+ # Get file name without extension
+ get_filename_component(FILE_NAME ${XML_FILE} NAME_WE)
+ # Remove .O3DEReflect suffix if present
+ string(REGEX REPLACE "\\.O3DEReflect$" "" FILE_PREFIX "${FILE_NAME}")
+
+ # Output files - one per type defined in XML
+ # For now, use the file prefix as the component name
+ set(HEADER_FILE "${ARG_OUTPUT_DIR}/${FILE_PREFIX}.AutoReflect.h")
+ set(SOURCE_FILE "${ARG_OUTPUT_DIR}/${FILE_PREFIX}.AutoReflect.cpp")
+
+ # Add custom command to generate files
+ add_custom_command(
+ OUTPUT ${HEADER_FILE} ${SOURCE_FILE}
+ COMMAND ${O3DE_REFLECT_PYTHON_CMD} "${O3DE_REFLECT_GENERATOR}"
+ --input "${XML_FILE}"
+ --output-dir "${ARG_OUTPUT_DIR}"
+ --template-dir "${O3DE_REFLECT_TEMPLATE_DIR}"
+ DEPENDS
+ ${XML_FILE}
+ "${O3DE_REFLECT_GENERATOR}"
+ "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Header.jinja"
+ "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Source.jinja"
+ "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Common.jinja"
+ COMMENT "Generating O3DEReflect code from ${FILE_NAME}"
+ VERBATIM
+ )
+
+ list(APPEND GENERATED_HEADERS ${HEADER_FILE})
+ list(APPEND GENERATED_SOURCES ${SOURCE_FILE})
+ endforeach()
+
+ # Add generated files to target
+ target_sources(${ARG_TARGET}
+ PRIVATE
+ ${GENERATED_HEADERS}
+ ${GENERATED_SOURCES}
+ )
+
+ # Add include directory for generated headers
+ target_include_directories(${ARG_TARGET}
+ PRIVATE
+ ${ARG_OUTPUT_DIR}
+ )
+
+ # Create a custom target for the generated files
+ add_custom_target(${ARG_TARGET}_O3DEReflect_Generate
+ DEPENDS ${GENERATED_HEADERS} ${GENERATED_SOURCES}
+ )
+ add_dependencies(${ARG_TARGET} ${ARG_TARGET}_O3DEReflect_Generate)
+
+ message(STATUS "O3DEReflect enabled for ${ARG_TARGET}: ${O3DE_REFLECT_XML_FILES}")
+
+endfunction()
+
+#! ly_add_o3de_reflect_autogen_rules: Get AutoGen rules for O3DEReflect
+#
+# Returns the AUTOGEN_RULES suitable for ly_add_target.
+# Use this if you prefer the AzAutoGen approach over ly_enable_o3de_reflect.
+#
+# Example:
+# ly_add_o3de_reflect_autogen_rules(RULES)
+# ly_add_target(
+# NAME MyTarget
+# AUTOGEN_RULES ${RULES}
+# )
+#
+function(ly_add_o3de_reflect_autogen_rules OUT_VAR)
+ set(${OUT_VAR}
+ "*.O3DEReflect.xml,${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Header.jinja,$path/$fileprefix.AutoReflect.h"
+ "*.O3DEReflect.xml,${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Source.jinja,$path/$fileprefix.AutoReflect.cpp"
+ PARENT_SCOPE
+ )
+endfunction()
+
+#! ly_enable_o3de_reflect_v2: Enable V2 header-macro parsing for targets
+#
+# This function sets up automatic code generation from C++ header files
+# that use O3DE_CLASS, O3DE_PROPERTY, O3DE_FUNCTION, etc. macros.
+# It will generate .generated.h and .generated.cpp files.
+#
+# \arg:TARGET - One or more target names to enable O3DE Reflect V2 for
+# \arg:HEADERS - List of header files to parse for O3DE macros
+# \arg:OUTPUT_DIR - Optional output directory (defaults to ${CMAKE_CURRENT_BINARY_DIR}/Generated)
+#
+function(ly_enable_o3de_reflect_v2)
+ set(options)
+ set(oneValueArgs OUTPUT_DIR)
+ set(multiValueArgs TARGET HEADERS)
+ cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ if(NOT ARG_TARGET)
+ message(FATAL_ERROR "ly_enable_o3de_reflect_v2 requires at least one TARGET argument")
+ endif()
+
+ if(NOT ARG_HEADERS)
+ message(FATAL_ERROR "ly_enable_o3de_reflect_v2 requires HEADERS argument")
+ endif()
+
+ # Process each target
+ foreach(CURRENT_TARGET ${ARG_TARGET})
+ # Check if target exists
+ if(NOT TARGET ${CURRENT_TARGET})
+ message(WARNING "ly_enable_o3de_reflect_v2: Target '${CURRENT_TARGET}' does not exist, skipping")
+ continue()
+ endif()
+
+ # Default output directory per target
+ if(ARG_OUTPUT_DIR)
+ set(TARGET_OUTPUT_DIR "${ARG_OUTPUT_DIR}/${CURRENT_TARGET}")
+ else()
+ set(TARGET_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/Generated/${CURRENT_TARGET}")
+ endif()
+
+ # Create output directory
+ file(MAKE_DIRECTORY ${TARGET_OUTPUT_DIR})
+
+ # Generated file lists
+ set(GENERATED_HEADERS)
+ set(GENERATED_SOURCES)
+
+ foreach(HEADER_FILE ${ARG_HEADERS})
+ # Get absolute path
+ get_filename_component(HEADER_FILE_ABS "${HEADER_FILE}" ABSOLUTE)
+
+ # Check if file exists
+ if(NOT EXISTS "${HEADER_FILE_ABS}")
+ message(WARNING "ly_enable_o3de_reflect_v2: Header file '${HEADER_FILE_ABS}' does not exist")
+ continue()
+ endif()
+
+ # Get file name without extension
+ get_filename_component(FILE_NAME ${HEADER_FILE} NAME_WE)
+
+ # Output files (use .AutoReflect suffix to match Python generator output)
+ set(GEN_HEADER "${TARGET_OUTPUT_DIR}/${FILE_NAME}.AutoReflect.h")
+ set(GEN_SOURCE "${TARGET_OUTPUT_DIR}/${FILE_NAME}.AutoReflect.cpp")
+
+ # Add custom command to generate files
+ add_custom_command(
+ OUTPUT ${GEN_HEADER} ${GEN_SOURCE}
+ COMMAND ${O3DE_REFLECT_PYTHON_CMD} "${O3DE_REFLECT_HEADER_PARSER}"
+ --input "${HEADER_FILE_ABS}"
+ --output-dir "${TARGET_OUTPUT_DIR}"
+ --template-dir "${O3DE_REFLECT_TEMPLATE_DIR}"
+ DEPENDS
+ ${HEADER_FILE_ABS}
+ "${O3DE_REFLECT_HEADER_PARSER}"
+ "${O3DE_REFLECT_GENERATOR}"
+ "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Header.jinja"
+ "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Source.jinja"
+ "${O3DE_REFLECT_TEMPLATE_DIR}/O3DEReflect_Common.jinja"
+ COMMENT "Generating O3DEReflect code from ${FILE_NAME}.h for ${CURRENT_TARGET}"
+ VERBATIM
+ )
+
+ list(APPEND GENERATED_HEADERS ${GEN_HEADER})
+ list(APPEND GENERATED_SOURCES ${GEN_SOURCE})
+ endforeach()
+
+ if(GENERATED_HEADERS)
+ # Add generated files to target
+ target_sources(${CURRENT_TARGET}
+ PRIVATE
+ ${GENERATED_HEADERS}
+ ${GENERATED_SOURCES}
+ )
+
+ # Add include directory for generated headers
+ target_include_directories(${CURRENT_TARGET}
+ PRIVATE
+ ${TARGET_OUTPUT_DIR}
+ )
+
+ # Create a custom target for the generated files
+ add_custom_target(${CURRENT_TARGET}_O3DEReflect_V2_Generate
+ DEPENDS ${GENERATED_HEADERS} ${GENERATED_SOURCES}
+ )
+ add_dependencies(${CURRENT_TARGET} ${CURRENT_TARGET}_O3DEReflect_V2_Generate)
+
+ message(STATUS "O3DEReflect V2 enabled for ${CURRENT_TARGET}: ${ARG_HEADERS}")
+ endif()
+ endforeach()
+endfunction()
+
+#! ly_enable_o3de_reflect_auto: Automatically detect and enable O3DE Reflect for a target
+#
+# This function scans the target's source directory for both V1 (.O3DEReflect.xml)
+# and V2 (headers with O3DE_CLASS/O3DE_STRUCT macros) files and enables
+# code generation for both.
+#
+# \arg:TARGET - Name of the target
+# \arg:INPUT_DIR - Optional input directory (defaults to target source dir)
+# \arg:OUTPUT_DIR - Optional output directory for generated files
+#
+function(ly_enable_o3de_reflect_auto)
+ set(options)
+ set(oneValueArgs TARGET INPUT_DIR OUTPUT_DIR)
+ set(multiValueArgs)
+ cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ if(NOT ARG_TARGET)
+ message(FATAL_ERROR "ly_enable_o3de_reflect_auto requires a TARGET argument")
+ endif()
+
+ # Get source directory
+ get_target_property(TARGET_SOURCE_DIR ${ARG_TARGET} SOURCE_DIR)
+ if(NOT ARG_INPUT_DIR)
+ set(ARG_INPUT_DIR "${TARGET_SOURCE_DIR}")
+ endif()
+
+ # Enable V1 if XML files exist
+ file(GLOB_RECURSE O3DE_REFLECT_XML_FILES "${ARG_INPUT_DIR}/*.O3DEReflect.xml")
+ if(O3DE_REFLECT_XML_FILES)
+ ly_enable_o3de_reflect(TARGET ${ARG_TARGET} INPUT_DIR "${ARG_INPUT_DIR}")
+ endif()
+
+ # For V2, we would need to scan headers for O3DE_CLASS/O3DE_STRUCT macros
+ # This is more complex and typically requires explicit listing of files
+ # or using a pre-build script to identify them
+ #
+ # Users should use ly_enable_o3de_reflect_v2 with explicit HEADERS list
+ # for best results
+
+endfunction()
diff --git a/Gems/O3DEReflect/README.md b/Gems/O3DEReflect/README.md
new file mode 100644
index 0000000000..4869706773
--- /dev/null
+++ b/Gems/O3DEReflect/README.md
@@ -0,0 +1,495 @@
+# O3DE Reflect
+
+O3DE Reflect is a auto c++ generation gem that dramatically simplifies component, struct, and enum creation in O3DE.
+
+## Two Approaches
+
+O3DE Reflect supports two ways to define your reflected types:
+
+| Approach | Best For | Style |
+|----------|----------|-------|
+| XML Definitions | New projects, clean separation | XML files (`.O3DEReflect.xml`) |
+| Header Macros | Existing code, C++ developers | C++ macros in headers |
+
+## Benefits
+
+- Faster Iteration Time
+ The plugin allows you to create plugins faster, and saves time reflecting it to the editor, freeing up space in your cpp files.
+
+- Multiplayer Binding Support
+---
+
+Header Macros
+
+The standard approach lets you write clean C++ with simple macros:
+
+### Quick Example
+
+```cpp
+#include
+
+O3DE_CLASS(Category = "Gameplay", Description = "Player movement controller")
+class PlayerMovementComponent : public AZ::Component
+{
+ O3DE_GENERATED_BODY()
+ O3DE_COMPONENT(PlayerMovementComponent, "{12345678-...}")
+
+public:
+ O3DE_PROPERTY(EditAnywhere, ScriptReadWrite,
+ Category = "Movement", Tooltip = "Max speed in m/s",
+ Min = 0.0f, Max = 100.0f, Suffix = "m/s")
+ float m_maxSpeed = 10.0f;
+
+ O3DE_PROPERTY(VisibleAnywhere, ScriptReadOnly, Category = "State")
+ bool m_isGrounded = false;
+
+ O3DE_FUNCTION(ScriptCallable, Category = "Movement")
+ void Move(const AZ::Vector3& direction);
+
+ O3DE_FUNCTION(ScriptPure, Category = "State")
+ float GetCurrentSpeed() const;
+};
+```
+
+### Macros Reference
+
+| Macro | Purpose |
+|-------|---------|
+| `O3DE_CLASS(...)` | Mark class for reflection |
+| `O3DE_COMPONENT(Name, UUID, ...)` | Declare component with services |
+| `O3DE_PROPERTY(...)` | Mark member for serialization |
+| `O3DE_FUNCTION(...)` | Expose function to scripting |
+| `O3DE_STRUCT(...)` | Mark struct for reflection |
+| `O3DE_ENUM(...)` | Mark enum for reflection |
+| `O3DE_ENUM_VALUE(...)` | Add enum value metadata |
+| `O3DE_GENERATED_BODY()` | Include generated code |
+
+### Property Specifiers
+
+**Visibility:**
+- `EditAnywhere` - Editable on instances and prefabs
+- `EditDefaultsOnly` - Editable only on prefabs/archetypes
+- `EditInstanceOnly` - Editable only on instances
+- `VisibleAnywhere` - Visible but not editable
+
+**Scripting:**
+- `ScriptReadWrite` - Read+write from Lua/ScriptCanvas
+- `ScriptReadOnly` - Read-only from Lua/ScriptCanvas
+
+**Metadata:**
+- `Category = "Group|SubGroup"` - Property grouping
+- `Tooltip = "Description"` - Hover tooltip
+- `DisplayName = "Custom Name"` - Override display name
+- `Min = 0.0f, Max = 100.0f` - Value constraints
+- `UIMin, UIMax` - Slider range (can differ from Min/Max)
+- `Suffix = "m/s"` - Unit suffix
+- `ChangeNotify = "OnValueChanged"` - Change callback
+
+### Function Specifiers
+
+- `ScriptCallable` - Can be called from script canvas
+- `ScriptPure` - Pure function (can be cached, shown differently in graph)
+- `CallInEditor` - Can be invoked from editor UI
+- `Server` / `Client` / `NetMulticast` - Network replication (future)
+
+### CMake Integration
+
+```cmake
+include(O3DEReflect)
+
+ly_add_target(NAME MyTarget ...)
+
+# Explicit list of headers to parse
+ly_enable_o3de_reflect_v2(
+ TARGET MyTarget
+ HEADERS
+ Source/PlayerMovementComponent.h
+ Source/AIStateComponent.h
+ Source/DamageInfo.h
+)
+```
+
+
+
+## XML Definitions
+
+### 1. Define your component in XML
+
+Create a file named `MyComponent.O3DEReflect.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 2. Enable code generation in CMake
+
+```cmake
+include(O3DEReflect)
+
+ly_add_target(
+ NAME MyGem
+ ...
+)
+
+ly_enable_o3de_reflect(TARGET MyGem)
+```
+
+### 3. Build your project
+
+The generator creates:
+- `MyComponent.AutoReflect.h` - Complete header with class definition
+- `MyComponent.AutoReflect.cpp` - Complete implementation with Reflect() function
+
+## XML Schema
+
+### Component Definition
+
+```xml
+
+ Namespace="MyNamespace"
+ Uuid="{...}"
+ Category="Category/SubCategory"
+ Description="..."
+ Icon="Icons/MyIcon.svg"
+ HideInEditor="false"
+ AppearsInAddComponentMenu="Game">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Property Attributes
+
+| Attribute | Type | Description |
+|-----------|------|-------------|
+| `Name` | string | Property name (m_ prefix added automatically) |
+| `Type` | string | C++ type |
+| `Default` | string | Default value |
+| `DisplayName` | string | Override display name in editor |
+| `Category` | string | Property group in editor |
+| `Tooltip` | string | Hover tooltip text |
+| `Suffix` | string | Unit suffix (e.g., "m/s") |
+| `EditAnywhere` | bool | Editable in editor (default: true) |
+| `VisibleAnywhere` | bool | Visible but read-only in editor |
+| `ReadOnly` | bool | Read-only property |
+| `ScriptReadWrite` | bool | Exposed to scripts read+write |
+| `ScriptReadOnly` | bool | Exposed to scripts read-only |
+| `Min` / `Max` | float | Value constraints |
+| `ChangeNotify` | string | Function to call on value change |
+
+### Function Attributes
+
+| Attribute | Type | Description |
+|-----------|------|-------------|
+| `Name` | string | Function name |
+| `DisplayName` | string | Override display name |
+| `Category` | string | Script node category |
+| `Tooltip` | string | Function description |
+| `ScriptCallable` | bool | Callable from scripts (default: true) |
+| `ScriptPure` | bool | Pure function (no side effects) |
+| `CallInEditor` | bool | Can be called from editor buttons |
+
+### Struct Definition
+
+```xml
+
+
+
+
+
+
+```
+
+### Enum Definition
+
+```xml
+
+
+
+
+
+
+
+```
+
+### Flags Enum (Bitfield)
+
+```xml
+
+
+
+
+
+
+```
+
+## Generated Code
+
+For a component with properties and functions, O3DEReflect generates:
+
+### Header (.AutoReflect.h)
+- Class declaration with `AZ_COMPONENT_DECL`
+- Getters and setters for all properties
+- Function declarations
+- Member variables with defaults
+
+### Source (.AutoReflect.cpp)
+- `AZ_COMPONENT_IMPL` macro
+- Complete `Reflect()` function with:
+ - SerializeContext registration
+ - EditContext configuration (categories, tooltips, constraints)
+ - BehaviorContext bindings for scripting
+- Service functions (`GetProvidedServices`, etc.)
+- Empty `Init()`, `Activate()`, `Deactivate()` stubs
+- Empty function implementations (to be filled in)
+
+## Comparison: Traditional vs O3DEReflect
+
+### Traditional O3DE (~200+ lines)
+```cpp
+// Header
+class MyComponent : public AZ::Component {
+ AZ_COMPONENT(MyComponent, "{UUID}");
+ static void Reflect(AZ::ReflectContext* context);
+ static void GetProvidedServices(...);
+ // ... 50+ lines of declarations
+};
+
+// Source
+void MyComponent::Reflect(AZ::ReflectContext* context) {
+ // 100+ lines of SerializeContext, EditContext, BehaviorContext
+}
+void MyComponent::GetProvidedServices(...) { ... }
+// ... more boilerplate
+```
+
+### O3DEReflect (~80 lines of XML)
+```xml
+
+
+
+
+
+```
+
+## Examples
+
+See the `Examples/` directory for complete examples:
+
+
+## CMake Integration
+
+### XML Files
+
+**Option A: ly_enable_o3de_reflect**
+```cmake
+include(O3DEReflect)
+
+ly_add_target(
+ NAME MyTarget
+ ...
+)
+
+ly_enable_o3de_reflect(
+ TARGET MyTarget
+ INPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Source
+ OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/Generated
+)
+```
+
+**Option B: AUTOGEN_RULES (AzAutoGen pattern)**
+```cmake
+ly_add_o3de_reflect_autogen_rules(O3DE_REFLECT_RULES)
+
+ly_add_target(
+ NAME MyTarget
+ ...
+ AUTOGEN_RULES ${O3DE_REFLECT_RULES}
+)
+```
+
+### Header Macros
+
+```cmake
+include(O3DEReflect)
+
+ly_add_target(NAME MyTarget ...)
+
+ly_enable_o3de_reflect_v2(
+ TARGET MyTarget
+ HEADERS
+ Source/MyComponent.h
+ Source/MyStruct.h
+)
+```
+
+### Auto-Detection
+
+```cmake
+# Automatically enables V1 for any .O3DEReflect.xml files found
+ly_enable_o3de_reflect_auto(TARGET MyTarget)
+```
+
+## Future Plans
+
+- **IDE integration** - IntelliSense support for XML schema and macro completion
+- **Custom templates** - User-defined generation patterns
+- **Network replication** - Server/Client/NetMulticast specifiers for multiplayer
+- **Slate-like UI** - UI property specifiers for custom widgets
+
+---
+
+## Troubleshooting
+
+### "Jinja2 not available" Error
+
+The generator needs access to O3DE's Python environment with Jinja2 installed. The system automatically searches for it in `~/.o3de/Python/venv/*/lib/site-packages`. If this fails:
+
+1. Ensure O3DE's Python is set up by running `python/get_python.bat` from O3DE source
+2. Verify `LY_PYTHON_CMD` is set in your CMake configuration
+3. Check that Jinja2 is installed: `pip install jinja2`
+
+### Duplicate Symbol Linker Warnings
+
+If you have both a manual `.cpp` file and O3DEReflect generates one, you'll get LNK4006 warnings about duplicate symbols. Solutions:
+
+1. **Recommended**: Remove your manual `.cpp` and let O3DEReflect generate everything
+2. **Alternative**: Keep your `.cpp` but ensure it doesn't duplicate generated functions
+
+### Bus Handlers in AZ_COMPONENT_IMPL
+
+O3DEReflect automatically filters out EBus handlers (classes ending in `::Handler` or containing `BusHandler`) from:
+- `AZ_COMPONENT_IMPL` macro arguments
+- `SerializeContext::Class<>` template arguments
+
+This is correct behavior - bus handlers aren't serializable base classes.
+
+### CMake Cache Issues
+
+If changes to O3DEReflect.cmake aren't being picked up:
+
+```powershell
+# Remove CMake cache and regenerate
+Remove-Item build/windows/CMakeFiles -Recurse -Force
+cmake -B build/windows -G "Visual Studio 17 2022" ...
+```
+
+### Generated Files Not Found
+
+Check that the generated output directory is correct:
+- Default: `${CMAKE_CURRENT_BINARY_DIR}/Generated/${TARGET_NAME}/`
+- Files are named: `ComponentName.AutoReflect.h` and `ComponentName.AutoReflect.cpp`
+
+### Multi-Target Projects
+
+For gems with multiple targets (Client, Server, Unified), use the multi-target syntax:
+
+```cmake
+ly_enable_o3de_reflect_v2(
+ TARGETS
+ MyGem.Static
+ MyGem.Client.Static
+ MyGem.Server.Static
+ HEADERS
+ Source/MyComponent.h
+)
+```
+
+---
+
+## Architecture
+
+```
+O3DEReflect/
+├── O3DEReflect.cmake # CMake module (main entry point)
+├── README.md # This documentation
+├── Code/
+│ ├── CMakeLists.txt # Gem build configuration
+│ └── Include/
+│ └── O3DEReflect/
+│ ├── O3DEReflectMacros.h # C++ macro definitions
+│ └── AutoGen/
+│ ├── O3DEReflectGen.py # Code generator
+│ ├── O3DEReflectHeaderParser.py # header parser
+│ ├── O3DEReflect.xsd # XML schema
+│ ├── O3DEReflect_Header.jinja # Header template
+│ ├── O3DEReflect_Source.jinja # Source template
+│ └── O3DEReflect_Common.jinja # Shared Jinja macros
+└── Examples/ # Example files
+```
+
+### Build Flow
+
+1. **CMake Configuration**: `ly_enable_o3de_reflect_v2()` registers custom commands
+2. **Build Time**: For each header, runs `O3DEReflectHeaderParser.py`
+3. **Parsing**: Extracts O3DE_* macros and metadata from C++ headers
+4. **Generation**: `O3DEReflectGen.py` produces `.AutoReflect.h` and `.AutoReflect.cpp`
+5. **Compilation**: Generated files are compiled with your target
+
+---
+
+## License
+
+Apache-2.0 OR MIT.
diff --git a/Gems/O3DEReflect/gem.json b/Gems/O3DEReflect/gem.json
new file mode 100644
index 0000000000..8426524be5
--- /dev/null
+++ b/Gems/O3DEReflect/gem.json
@@ -0,0 +1,26 @@
+{
+ "gem_name": "O3DEReflect",
+ "display_name": "O3DE Reflect",
+ "license": "Apache-2.0 OR MIT",
+ "license_url": "https://opensource.org/licenses/Apache-2.0",
+ "origin": "WD Studios Corp.",
+ "origin_url": "https://wdstudios.tech",
+ "type": "Code",
+ "summary": "Unreal-style reflection system for O3DE. Simplifies component, struct, and enum creation",
+ "canonical_tags": [
+ "Gem",
+ "Code Generation",
+ "Reflection"
+ ],
+ "user_tags": [
+ "O3DEReflect",
+ "CodeGen",
+ "Reflection"
+ ],
+ "icon_path": "preview.png",
+ "requirements": "",
+ "documentation_url": "",
+ "dependencies": [],
+ "version": "1.0.0",
+ "compatible_engines": []
+}