HRealEngine is a custom game engine written in C++ with OpenGL rendering, 2D and 3D physics, and C# scripting. The goal of the engine is to build a strong AI gameplay framework with systems like Behavior Trees, Perception Systems, and Navigation Meshes. It currently features a built in Behavior Tree framework with a visual editor, an AI Perception System with sight and hearing senses. Navigation Meshes are actively in development.
# Clone with all submodules
git clone --recursive https://github.com/haktan313/HRealEngine
# If you already cloned without --recursive
git submodule update --init
# Generate Visual Studio project files
scripts/Win-GenProjects.batThis will invoke Premake5 and create the required
.slnfiles.
- Screenshots
- Video Demos
- Features
- Behavior Tree Integration
- Behavior Tree Usage (C#)
- AI Perception System Integration and Usage
- Project Setup (C# Workflow)
- Code Examples
- Roadmap
- Third-Party Submodules
| Behavior Tree and Percaption | Behavior Tree and Perception Runtime Debug |
|---|---|
| 3D Rendering (FBX, GLB, OBJ and Cube Batching) | 2D Rendering |
| Lighting | Project UI |
PercaptionSightAndHear.mp4
BehaviorTreeDebugWithPercaption.mp4
3DPhysicTest.mp4
2DPhysicTest.mp4
- ImGui based scene editor with viewport and gizmos
- Content Browser with file dialogs
- Scene serialization with YAML
- Entity Component System (entt)
- Behavior Tree tooling (visual editor + runtime debug view)
- Dual scripting support:
- C++ native scripts (engine side)
- C# scripting with Mono (hot reloadable assemblies)
- Script lifecycle events for entities (C#):
BeginPlay,Tick,OnDestroyOnOverlapBegin,OnOverlapEndOnEntityPerceived,OnEntityLost,OnEntityForgotten(AI Perception callbacks)- Entity lifetime utilities (
Destroy, etc.)
- Behavior Tree support in C#. Create custom Action, Decorator, Condition nodes and Blackboards
- Additional C# capabilities:
- Open scenes at runtime
- Spawn entities and add components dynamically
- RayCast queries
- Store, get, and persist data across levels with GameModeData
- TagComponent: supports multiple tags per entity with C# accessor functions
- Dockable Behavior Tree Editor window inside the engine
- BehaviorTreeComponent to assign trees to entities
- Asset Manager integration with drag & drop support
- Custom node creation in both C++ and C#
- Runtime debug visualization
- See Behavior Tree Integration for details
- Unreal like AI perception framework built on top of Jolt Physics
- AIControllerComponent with configurable sight and hearing senses
- PerceivableComponent to mark entities as detectable targets
- Sight sense: Field of view angle, sight radius, line of sight raycasts with detectable point offsets
- Hearing sense: Noise event system with loudness, range, and attenuation
- Dedicated physics layers (PERCEPTION / PERCEIVABLE) isolated from gameplay physics
- Throttled updates with configurable UpdateInterval per AI controller
- Perception lifecycle callbacks exposed to C# scripts:
- OnEntityPerceived โ first detection (with perception method and position)
- OnEntityLost โ target leaves perception range
- OnEntityForgotten โ target fully forgotten after ForgetDuration
- Type filtering: Both senses support filtering by PerceivableType (Player, Enemy, Neutral, Environment)
- Perception query API in C#:
GetCurrentPerceptions(),GetForgottenPerceptions(),IsEntityPerceived(),IsEntityForgotten()
- 2D Physics (Box2D)
Rigidbody2DComponentand collider components integrated into the ECS- Collision and overlap events forwarded to scripts
- 3D Physics (Jolt Physics)
- Separation between
Rigidbody3DComponentandCollidercomponents - Supports Static, Dynamic, and Kinematic body types
- Lock position and rotation constraints per axis
- isTrigger option for overlap only colliders
- Collision and overlap events forwarded to both C++ and C# scripts
- Debug collision visualization
- Separation between
- OpenGL rendering pipeline
- Framebuffer + RenderCommand / Renderer abstractions
- Orthographic and Perspective cameras with controllers
- 2D Renderer
- Batch renderer with textures and shaders
- 3D Rendering
MeshComponentfor rendering 3D entities with mesh and material assets- Configurable pivot point per mesh
OBJ,FBX,GLBmesh import through asset pipeline- Material system: Albedo, Normal maps, Specular maps
- Debug views for normals and UVs
- Text Rendering
TextComponentwith C# API for runtime text manipulation
- Centralized asset registry with asset handles
- Import pipeline for meshes, materials, scenes, textures, and Behavior Trees
- 3D asset importing using Assimp (OBJ, FBX, GLB/GLTF)
- Imported assets are converted into engine-native formats
- Dedicated Project Browser with startup screen
- Create new C# projects from templates
- Open existing projects
- Integrated with the Mono scripting workflow (hot reloadable assemblies)
- Default scene selection on project creation
- Build project to standalone
.exe
- Input handling and event system
- Logging with spdlog
- TagComponent with multi tag support and C# bindings
The integration is complete. As an ongoing project, there may still be minor bugs or edge cases.
- Open the editor from the menu: Window โ Behavior Tree Editor
- Assign trees to entities with BehaviorTreeComponent, trees start running when runtime begins
- Behavior Tree assets are recognized by the Asset Manager, drag & drop from Content Browser
- Create new trees: AI System โ Create Behavior Tree (
.btreefiles) - Import existing trees: AI System โ Load Behavior Tree As An Asset
- Custom nodes (C++): Register with
NodeRegistryfunctions insideRegisterBehaviorTreeStuffs()in bothEditorLayer.cppandRuntimeLayer.cpp. Example:// Example registration calls NodeRegistry::AddBlackBoardToEditor<MyBlackboard>("My Blackboard"); NodeRegistry::AddActionNodeToBuilder<MyAction, MyActionParams>("My Action"); NodeRegistry::AddConditionNodeToBuilder<MyCondition, MyConditionParams>("My Condition"); NodeRegistry::AddDecoratorNodeToBuilder<MyDecorator, MyDecoratorParams>("My Decorator");
- Custom nodes (C#): Just create your BT classes in your C# game project, the engine automatically discovers them at initialization. However, the
RegisterBehaviorTreeStuffs()function must be called in bothEditorLayerandRuntimeLayerto register the discovered C# classes intoCSharpNodeRegistry - For more details, see the BehaviorTreeLibrary documentation
Derive from BTBlackboard and use Create* functions in the constructor to define blackboard values.
Derive from BTActionNode and create a parameter class from BTActionParams. Use BTBlackboardKey attribute for blackboard key references and BTParameter attribute for regular parameters.
Derive from BTDecorator and create a parameter class from BTDecoratorParams. Override CanExecute() to control child execution and OnFinishedResult() to modify the child's result status.
Derive from BTCondition and create a parameter class from BTConditionParams. Override CheckCondition() to evaluate whether the condition passes.
See Code Examples below for full implementation samples.
The perception system provides Unreal like AI sensing capabilities. AI entities can detect targets through sight (FOV + line of sight) and hearing (noise events).
- Add an AIControllerComponent to your AI entity
- Enable desired senses (Sight, Hearing) and configure their parameters
- Add a PerceivableComponent to any entity that should be detectable
- Set the perceivable entity's Type (Player, Enemy, Neutral, Environment) and optionally add DetectablePointOffsets for more accurate line of sight checks
- Sight: The system creates sphere trigger bodies on the
PERCEPTIONphysics layer. When aPERCEIVABLEentity overlaps this sphere, the AI checks FOV angle (dot product) and performs line of sight raycasts. If the center raycast is blocked, fallback raycasts are attempted against DetectablePointOffsets on the target. - Hearing: Similar to sight, a sphere trigger body is created on the
PERCEPTIONlayer. Only noise events from entities already overlapping this sphere are processed. The system then applies type filtering, distance based loudness attenuation, and adds qualifying sources to CurrentPerceptions. - Lifecycle: Each AI controller ticks at its own UpdateInterval. Every tick, it builds a fresh CurrentPerceptions list and diffs against the previous tick to fire
OnEntityPerceived,OnEntityLost, andOnEntityForgottencallbacks.
- Override these virtual methods on your Entity script to respond to perception events:
public class EnemyAI : Entity
{
public override void OnEntityPerceived(ulong entityID, int perceptionMethod, Vector3 position)
{
// perceptionMethod: 0 = Sight, 1 = Hearing
Console.WriteLine($"Detected entity {entityID} via {(perceptionMethod == 0 ? "Sight" : "Hearing")} at {position}");
}
public override void OnEntityLost(ulong entityID, Vector3 lastKnownPosition)
{
Console.WriteLine($"Lost sight of entity {entityID}, last seen at {lastKnownPosition}");
}
public override void OnEntityForgotten(ulong entityID)
{
Console.WriteLine($"Completely forgot about entity {entityID}");
}
}// Emit a noise event at the current entity's position
Entity.ReportNoise(
position: transform.Position,
loudness: 1.0f,
maxRange: 15.0f,
sourceType: PerceivableType.Player);- Launch HRealEngine Editor, the Project Browser appears on startup
- Select New Project (C#)
- Choose a project location and name, a
.hprjfile will be created - The editor opens with a default scene and project layout
- Navigate to the project's
Scriptsdirectory - Run
Win-GenProject.bat, this invokes Premake and generates the.slnand.csprojfiles - Open the solution in Visual Studio or JetBrains Rider
- Build the C# solution, the assembly (
.dll) and debug symbols (.pdb) will be generated underScripts/Binaries/ - Restart the editor or reload the project, the engine automatically detects and loads the compiled assembly
Sight Percaption Update
void JoltWorld::SightPercaptionsUpdate(AIControllerComponent& ai, const TransformComponent& tc,
const glm::vec3& forward)
{
for (const UUID& targetID : ai.OverlappingEntities)
{
Entity targetEntity = m_Scene->GetEntityByUUID(targetID);
if (!targetEntity || !targetEntity.HasComponent<PerceivableComponent>())
continue;
auto& percComp = targetEntity.GetComponent<PerceivableComponent>();
if (!percComp.bIsDetectable)
continue;
auto& targetTC = targetEntity.GetComponent<TransformComponent>();
glm::vec3 toTarget = targetTC.Position - tc.Position;
float distance = glm::length(toTarget);
if (distance < 0.001f)
continue;
glm::vec3 dirToTarget = toTarget / distance;
if (ai.IsSightEnabled() && distance <= ai.SightSettings.SightRadius)
{
bool typeMatch = ai.SightSettings.DetectableTypes.empty();
for (auto& dt : ai.SightSettings.DetectableTypes)
for (auto& pt : percComp.Types)
if (dt == pt)
{
typeMatch = true;
break;
}
if (!typeMatch)
continue;
float dotProduct = glm::dot(glm::normalize(forward), dirToTarget);
float halfFOVcos = glm::cos(glm::radians(ai.SightSettings.FieldOfView * 0.5f));
if (dotProduct < halfFOVcos)
continue;
std::vector<uint64_t> ignoreList = { (uint64_t)ai.OwnerEntityID };
RaycastHit3D losHit = Raycast(tc.Position, dirToTarget, distance + 0.1f, false, 0.0f, ignoreList);
bool hasLOS = false;
if (losHit.Hit && losHit.HitEntityID == targetID)
hasLOS = true;
else if (!percComp.DetectablePointsOffsets.empty())
for (auto& offset : percComp.DetectablePointsOffsets)
{
glm::vec3 targetPoint = targetTC.Position + offset;
glm::vec3 dir = glm::normalize(targetPoint - tc.Position);
float dist = glm::length(targetPoint - tc.Position);
RaycastHit3D pointHit = Raycast(tc.Position, dir, dist + 0.1f, false, 0.0f, ignoreList);
if (pointHit.Hit && pointHit.HitEntityID == targetID)
{
hasLOS = true;
break;
}
}
if (hasLOS)
{
PercaptionResult result;
result.EntityID.ID = targetID;
result.Type = percComp.Types.empty() ? PerceivableType::Neutral : percComp.Types[0];
result.PercaptionMethod = PercaptionType::Sight;
result.SensedPosition = targetTC.Position;
result.TimeSinceLastSensed = 0.0f;
ai.CurrentPerceptions.push_back(result);
}
}
}
}Blackboard (C#)
using HRealEngine.BehaviorTree;
public class ExampleAIBlackboard : BTBlackboard
{
public ExampleAIBlackboard()
{
CreateBool("IsPlayerInRange", false);
CreateFloat("Health", 100.0f);
CreateString("PlayerTag", "Player");
CreateInt("AmmoCount", 10);
}
}Action Node (C#)
using System;
using HRealEngine.BehaviorTree;
namespace HRealEngine
{
public class ExampleActionParams : BTActionParams
{
[BTParameter("Example Parameter")]
public string exampleParameter = "Default Value";
[BTParameter("Example Number")]
public int exampleNumber = 42;
[BTParameter("Example Flag")]
public bool exampleFlag = true;
[BTBlackboardKey(BTBlackboardKeyAttribute.KeyType.String, "ExampleBlackboardKey")]
public string exampleBlackboardKey = "ExampleKey";
[BTBlackboardKey(BTBlackboardKeyAttribute.KeyType.Float, "ExampleFloatKey")]
public string exampleFloatBlackboardKey = "ExampleFloatKey";
}
public class ExampleAction : BTActionNode
{
private ExampleActionParams actionParams;
public ExampleAction()
{
actionParams = new ExampleActionParams();
SetParameters(actionParams);
}
public override void SetParameters(BTActionParams param)
{
base.SetParameters(param);
actionParams = param as ExampleActionParams;
}
protected override void OnInitialize()
{
actionParams = parameters as ExampleActionParams;
}
public override void OnStart()
{
Console.WriteLine("ExampleAction: Starting action with parameters:");
Console.WriteLine($"Example Parameter: {actionParams.exampleParameter}");
Console.WriteLine($"Example Number: {actionParams.exampleNumber}");
Console.WriteLine($"Example Flag: {actionParams.exampleFlag}");
blackboard.SetString(actionParams.exampleBlackboardKey, actionParams.exampleParameter);
string blackboardValue = blackboard.GetFloat(actionParams.exampleFloatBlackboardKey).ToString();
Console.WriteLine($"Example Blackboard Value for '{actionParams.exampleFloatBlackboardKey}': {blackboardValue}");
}
public override NodeStatus Update()
{
return NodeStatus.Success;
}
public override void OnFinished()
{
Console.WriteLine("ExampleAction: Finished action.");
}
}
}Decorator (C#)
using System;
using HRealEngine.BehaviorTree;
namespace HRealEngine
{
public class ExampleDecoratorParams : BTDecoratorParams
{
[BTParameter("Example Decorator Parameter")]
public string exampleDecoratorParameter = "Default Decorator Value";
[BTBlackboardKey(BTBlackboardKeyAttribute.KeyType.String, "ExampleDecoratorBlackboardKey")]
public string exampleDecoratorBlackboardKey = "ExampleDecoratorKey";
}
public class ExampleDecorator : BTDecorator
{
private ExampleDecoratorParams decoratorParams;
public ExampleDecorator()
{
decoratorParams = new ExampleDecoratorParams();
SetParameters(decoratorParams);
}
protected override void OnInitialize()
{
decoratorParams = parameters as ExampleDecoratorParams;
}
public override void SetParameters(BTDecoratorParams param)
{
base.SetParameters(param);
decoratorParams = param as ExampleDecoratorParams;
}
public override bool CanExecute()
{
string blackboardValue = blackboard.GetString(decoratorParams.exampleDecoratorBlackboardKey);
Console.WriteLine($"ExampleDecorator: Checking CanExecute with Blackboard Value for '{decoratorParams.exampleDecoratorBlackboardKey}': {blackboardValue}");
return blackboardValue == decoratorParams.exampleDecoratorParameter;
}
public override void OnFinishedResult(ref NodeStatus status)
{
Console.WriteLine($"ExampleDecorator: Finished with status: {status}");
}
public override void OnFinished()
{
Console.WriteLine("ExampleDecorator: Finished execution.");
}
}
}Condition (C#)
using System;
using HRealEngine.BehaviorTree;
namespace HRealEngine
{
public class ExampleConditionParams : BTConditionParams
{
[BTParameter("Example Condition Parameter")]
public string exampleConditionParameter = "Default Condition Value";
[BTBlackboardKey(BTBlackboardKeyAttribute.KeyType.String, "ExampleConditionBlackboardKey")]
public string exampleConditionBlackboardKey = "ExampleConditionKey";
}
public class ExampleCondition : BTCondition
{
private ExampleConditionParams conditionParams;
public ExampleCondition()
{
conditionParams = new ExampleConditionParams();
SetParameters(conditionParams);
}
protected override void OnInitialize()
{
conditionParams = parameters as ExampleConditionParams;
}
public override void SetParameters(BTConditionParams param)
{
base.SetParameters(param);
conditionParams = param as ExampleConditionParams;
}
public override bool CheckCondition()
{
string blackboardValue = blackboard.GetString(conditionParams.exampleConditionBlackboardKey);
Console.WriteLine($"ExampleCondition: Checking condition with Blackboard Value for '{conditionParams.exampleConditionBlackboardKey}': {blackboardValue}");
return blackboardValue == conditionParams.exampleConditionParameter;
}
public override void OnFinished()
{
Console.WriteLine("ExampleCondition: Finished checking condition.");
}
}
}BT Integration in Scene.cpp (C++)
void Scene::StartBTs()
{
Root::RootClear();
auto view = m_Registry.view<BehaviorTreeComponent>();
for (auto e : view)
{
Entity entity = {e, this};
auto& btComponent = entity.GetComponent<BehaviorTreeComponent>();
if (btComponent.BehaviorTreeAsset)
{
if (m_BehaviorTreeCache.find(btComponent.BehaviorTreeAsset) == m_BehaviorTreeCache.end())
{
auto metaData = Project::GetActive()->GetEditorAssetManager()->GetAssetMetadata(btComponent.BehaviorTreeAsset);
auto path = Project::GetAssetDirectory() / metaData.FilePath;
auto name = metaData.FilePath.stem().string();
YAML::Node data = YAML::LoadFile(path.string());
BehaviorTree* bt = Root::CreateBehaviorTree(name, path.string());
BTSerializer serializer(bt);
serializer.Deserialize(data);
m_BehaviorTreeCache[btComponent.BehaviorTreeAsset] = data;
UUID ownerUUID = entity.GetUUID();
m_BTOwnerUUIDs[btComponent.BehaviorTreeAsset] = ownerUUID;
bt->SetOwner<UUID>(&m_BTOwnerUUIDs[btComponent.BehaviorTreeAsset]);
bt->StartTree();
}
else
{
YAML::Node& data = m_BehaviorTreeCache.at(btComponent.BehaviorTreeAsset);
auto metaData = Project::GetActive()->GetEditorAssetManager()->GetAssetMetadata(btComponent.BehaviorTreeAsset);
auto path = Project::GetAssetDirectory() / metaData.FilePath;
auto name = metaData.FilePath.stem().string();
BehaviorTree* bt = Root::CreateBehaviorTree(name, path.string());
BTSerializer serializer(bt);
serializer.Deserialize(data);
UUID ownerUUID = entity.GetUUID();
m_BTOwnerUUIDs[btComponent.BehaviorTreeAsset] = ownerUUID;
bt->SetOwner<UUID>(&m_BTOwnerUUIDs[btComponent.BehaviorTreeAsset]);
bt->StartTree();
}
}
}
}
void Scene::StopBTs()
{
Root::RootClear();
m_BehaviorTreeCache.clear();
m_BTOwnerUUIDs.clear();
}Overlap / Collision Events (C++)
// --- Jolt Physics 3D Collision Events ---
void JoltWorld::UpdateRuntime3D()
{
std::vector<CollisionEvent> beginEvents, endEvents;
{
std::lock_guard<std::mutex> lock(m_EventQueueMutex);
beginEvents = std::move(m_CollisionBeginEvents);
endEvents = std::move(m_CollisionEndEvents);
m_CollisionBeginEvents.clear();
m_CollisionEndEvents.clear();
}
for (const auto& ev : beginEvents)
{
Entity a = m_Scene->GetEntityByUUID(ev.EntityA);
Entity b = m_Scene->GetEntityByUUID(ev.EntityB);
if (a && b)
{
if (m_Scene->GetRegistry().any_of<ScriptComponent>(a))
ScriptEngine::OnCollisionBegin(a, b);
if (m_Scene->GetRegistry().any_of<ScriptComponent>(b))
ScriptEngine::OnCollisionBegin(b, a);
if (a.HasComponent<NativeScriptComponent>())
{
auto& nsc = a.GetComponent<NativeScriptComponent>();
if (nsc.Instance) nsc.Instance->OnCollisionBegin(b);
}
if (b.HasComponent<NativeScriptComponent>())
{
auto& nsc = b.GetComponent<NativeScriptComponent>();
if (nsc.Instance) nsc.Instance->OnCollisionBegin(a);
}
}
}
for (const auto& ev : endEvents)
{
Entity a = m_Scene->GetEntityByUUID(ev.EntityA);
Entity b = m_Scene->GetEntityByUUID(ev.EntityB);
if (a && b)
{
if (m_Scene->GetRegistry().any_of<ScriptComponent>(a))
ScriptEngine::OnCollisionEnd(a, b);
if (m_Scene->GetRegistry().any_of<ScriptComponent>(b))
ScriptEngine::OnCollisionEnd(b, a);
if (a.HasComponent<NativeScriptComponent>())
{
auto& nsc = a.GetComponent<NativeScriptComponent>();
if (nsc.Instance) nsc.Instance->OnCollisionEnd(b);
}
if (b.HasComponent<NativeScriptComponent>())
{
auto& nsc = b.GetComponent<NativeScriptComponent>();
if (nsc.Instance) nsc.Instance->OnCollisionEnd(a);
}
}
}
}
// --- Jolt Contact Listener ---
class MyContactListener : public JPH::ContactListener
{
public:
MyContactListener(Scene* scene, JoltWorld* joltWorld)
: m_Scene(scene), m_JoltWorld(joltWorld) {}
virtual JPH::ValidateResult OnContactValidate(const JPH::Body& inBody1, const JPH::Body& inBody2,
JPH::RVec3Arg inBaseOffset, const JPH::CollideShapeResult& inCollisionResult) override
{
return JPH::ValidateResult::AcceptAllContactsForThisBodyPair;
}
virtual void OnContactAdded(const JPH::Body& inBody1, const JPH::Body& inBody2,
const JPH::ContactManifold& inManifold, JPH::ContactSettings& ioSettings) override
{
auto entity1ID = (UUID)inBody1.GetUserData();
auto entity2ID = (UUID)inBody2.GetUserData();
Entity entity1 = m_Scene->GetEntityByUUID(entity1ID);
Entity entity2 = m_Scene->GetEntityByUUID(entity2ID);
if (m_Scene->GetRegistry().valid(entity1) && m_Scene->GetRegistry().valid(entity2))
m_JoltWorld->m_CollisionBeginEvents.push_back({ entity1, entity2 });
}
virtual void OnContactPersisted(const JPH::Body& inBody1, const JPH::Body& inBody2,
const JPH::ContactManifold& inManifold, JPH::ContactSettings& ioSettings) override
{
}
virtual void OnContactRemoved(const JPH::SubShapeIDPair& inSubShapePair) override
{
auto body1ID = inSubShapePair.GetBody1ID();
auto body2ID = inSubShapePair.GetBody2ID();
const JPH::BodyInterface& bi = m_JoltWorld->physics_system.GetBodyInterfaceNoLock();
UUID entity1ID = (UUID)bi.GetUserData(body1ID);
UUID entity2ID = (UUID)bi.GetUserData(body2ID);
if (entity1ID == 0 || entity2ID == 0)
return;
Entity entity1 = m_Scene->GetEntityByUUID(entity1ID);
Entity entity2 = m_Scene->GetEntityByUUID(entity2ID);
if (m_Scene->GetRegistry().valid(entity1) && m_Scene->GetRegistry().valid(entity2))
m_JoltWorld->m_CollisionEndEvents.push_back({ entity1, entity2 });
}
private:
Scene* m_Scene = nullptr;
JoltWorld* m_JoltWorld = nullptr;
};
// --- Box2D 2D Physics Setup ---
void Scene::OnPhysics2DStart()
{
m_PhysicsWorld = new b2World({0.0f, -9.81f});
m_PhysicsWorld->SetContactListener(new GameContactListener(this));
auto view = m_Registry.view<Rigidbody2DComponent>();
for (auto e : view)
{
Entity entity = {e, this};
auto& transform = entity.GetComponent<TransformComponent>();
auto& rb2d = entity.GetComponent<Rigidbody2DComponent>();
b2BodyDef bodyDef;
bodyDef.type = RigidBody2DTypeToBox2D(rb2d.Type);
bodyDef.position.Set(transform.Position.x, transform.Position.y);
bodyDef.angle = transform.Rotation.z;
b2Body* body = m_PhysicsWorld->CreateBody(&bodyDef);
body->SetFixedRotation(rb2d.FixedRotation);
body->GetUserData().pointer = entity.GetUUID();
rb2d.RuntimeBody = body;
if (entity.HasComponent<BoxCollider2DComponent>())
{
auto& bc2d = entity.GetComponent<BoxCollider2DComponent>();
b2PolygonShape shape;
shape.SetAsBox(bc2d.Size.x * transform.Scale.x, bc2d.Size.y * transform.Scale.y);
b2FixtureDef fixtureDef;
fixtureDef.shape = &shape;
fixtureDef.density = bc2d.Density;
fixtureDef.friction = bc2d.Friction;
fixtureDef.restitution = bc2d.Restitution;
fixtureDef.restitutionThreshold = bc2d.RestitutionThreshold;
body->CreateFixture(&fixtureDef);
}
if (entity.HasComponent<CircleCollider2DComponent>())
{
auto& cc2d = entity.GetComponent<CircleCollider2DComponent>();
b2CircleShape shape;
shape.m_p.Set(cc2d.Offset.x, cc2d.Offset.y);
shape.m_radius = cc2d.Radius * transform.Scale.x;
b2FixtureDef fixtureDef;
fixtureDef.shape = &shape;
fixtureDef.density = cc2d.Density;
fixtureDef.friction = cc2d.Friction;
fixtureDef.restitution = cc2d.Restitution;
fixtureDef.restitutionThreshold = cc2d.RestitutionThreshold;
body->CreateFixture(&fixtureDef);
}
}
}
// --- Box2D Contact Listener ---
void Scene::GameContactListener::BeginContact(b2Contact* contact)
{
b2Body* bodyA = contact->GetFixtureA()->GetBody();
b2Body* bodyB = contact->GetFixtureB()->GetBody();
auto entityA = m_Scene->GetEntityByUUID((UUID)bodyA->GetUserData().pointer);
auto entityB = m_Scene->GetEntityByUUID((UUID)bodyB->GetUserData().pointer);
if (m_Scene->m_Registry.valid(entityA) && m_Scene->m_Registry.valid(entityB))
m_Scene->m_CollisionBeginEvents.push_back({ entityA, entityB });
}
void Scene::GameContactListener::EndContact(b2Contact* contact)
{
b2Body* bodyA = contact->GetFixtureA()->GetBody();
b2Body* bodyB = contact->GetFixtureB()->GetBody();
auto entityA = m_Scene->GetEntityByUUID((UUID)bodyA->GetUserData().pointer);
auto entityB = m_Scene->GetEntityByUUID((UUID)bodyB->GetUserData().pointer);
if (m_Scene->m_Registry.valid(entityA) && m_Scene->m_Registry.valid(entityB))
m_Scene->m_CollisionEndEvents.push_back({ entityA, entityB });
}- Custom Navigation Mesh library
- Animation system
HRealEngine uses the following libraries (all included as Git submodules):
| Library | Description |
|---|---|
| Jolt Physics | 3D physics engine |
| Box2D | 2D physics engine |
| Mono | C# scripting runtime |
| entt | Entity Component System |
| GLFW | Window and input handling |
| Glad | OpenGL function loader |
| ImGui | Immediate mode GUI |
| ImGuizmo | Gizmo manipulation for ImGui |
| spdlog | Fast logging library |
| stb_image | Image loading |
| yaml-cpp | YAML serialization |
| glm | Math library for graphics |
| Assimp | Asset importing (OBJ/FBX/GLB/GLTF) |
| FileWatch | File system watcher |