Working with Components¶
Components hold the data and behaviour attached to an entity: its transform, mesh,
physics body, camera, lights, audio, robot policy, and more. Scripts read and modify
them through a small generic API on Entity.
In short
Fetch a component with GetComponent<T>() in OnCreate, check it for null, and keep
the reference. Robots are scripted through RobotControllerComponent and
MujocoSceneComponent, and Jolt and Box2D bodies through their rigid body components.
Components without a C# class, such as the MuJoCo bodies and colliders, are edited only
in the Inspector.
The component API¶
| Method | Purpose |
|---|---|
GetComponent<T>() |
Returns the component of type T, or null if the entity does not have one. |
HasComponent<T>() |
true if the entity has a component of type T. |
CreateComponent<T>() |
Adds a component of type T and returns it. If the entity already has one, returns the existing component. |
RemoveComponent<T>() |
Removes the component of type T. Returns true if a component was removed. |
GetComponent<T>() returns a nullable reference, so check before using it:
protected override void OnCreate()
{
m_Mesh = GetComponent<MeshComponent>();
if (m_Mesh is null)
return;
// ... configure m_Mesh
}
Cache components in OnCreate rather than calling GetComponent every step.
Only components that have a C# class can be fetched this way. Some components exist only in the Inspector, such as the MuJoCo bodies and colliders described below. Scripts cannot fetch those; for a robot, they work through the robot's MuJoCo Scene component instead.
Transforms¶
Every entity has a TransformComponent.
Entity exposes shortcuts for the local-space transform, so the component itself rarely
needs to be fetched for everyday work:
// Shortcuts on Entity. All are LOCAL space, relative to the parent.
Translation = new Vector3(0.0f, 1.0f, 0.0f); // metres
Rotation = new Vector3(0.0f, 3.14f, 0.0f); // euler angles, radians
Scale = Vector3.One;
RotationQuat = Quaternion.Identity; // quaternion form of Rotation
For world-space values, or for the raw transform matrix, use the entity's Transform
property, which returns its TransformComponent:
TransformComponent t = Transform;
Vector3 worldPos = t.WorldTranslation;
Quaternion worldRot = t.WorldRotationQuat;
Matrix4 matrix = t.WorldTransformMatrix;
Transform world = t.World; // packed local-to-world transform
| What | Local (shortcut on Entity) |
World (on TransformComponent) |
|---|---|---|
| Position | Translation |
WorldTranslation |
| Euler rotation | Rotation (radians) |
WorldRotation |
| Quaternion rotation | RotationQuat |
WorldRotationQuat |
| Scale | Scale |
WorldScale |
| Matrix | TransformComponent.Matrix |
WorldTransformMatrix |
Physics¶
Lucky Engine ships three physics solvers. The solver that simulates an entity is chosen by the body and collider components it carries. The solvers can run in the same scene at the same time, each on its own entities.
| Solver | Use it for | In new scenes |
|---|---|---|
| MuJoCo | Robots: articulated chains, joints, actuators and contact-rich manipulation | On |
| Jolt | Game-style 3D rigid bodies: props, triggers and character controllers | On |
| Box2D | 2D physics for 2D scenes | Off |
Each solver is turned on or off per scene in Settings → Scene → Physics, with Enable MuJoCo Physics, Enable Jolt Physics and Enable Box2D Physics. A solver's components are only offered in Add Component while that solver is on.
MuJoCo bodies (robotics)¶
A robot is anchored by a MuJoCo Scene component, which scripts reach as
MujocoSceneComponent. The rest of a
robot's MuJoCo setup is edited in the Inspector and has no C# class, so GetComponent
cannot fetch it:
- MuJoCo Body, MuJoCo Geom and MuJoCo Site
- MuJoCo Box Collider, MuJoCo Sphere Collider, MuJoCo Capsule Collider, MuJoCo Cylinder Collider, MuJoCo Mesh Collider and MuJoCo Compound Collider
- MuJoCo Joint Component and MuJoCo Equality Constraint
Scripts work with a robot through two components on the robot's entity:
| To do this | Use |
|---|---|
| Activate trained policies and send them commands | RobotControllerComponent |
| Write the inputs of the robot's motion graph | RobotControllerComponent |
| Drive a single actuator directly | MujocoSceneComponent.GetActuator |
| Read body, joint and site state, test contacts, or reset the robot | MujocoSceneComponent |
Bodies, joints, sites, geoms and actuators are looked up by the names they have in the
robot's MuJoCo model, for example with GetBodyID("hand") or GetJointID(...).
Driving a policy¶
A trained policy is activated by its slot ID, and its commands are written by ID each step. The slot ID is the Id shown for the slot under Policy Slots in the Robot Controller's Inspector. For a walker policy in slot 1, commands 1, 2 and 3 are forward velocity, sideways velocity and yaw rate:
private const uint k_WalkerSlot = 1u;
private const uint k_SetVx = 1u;
private const uint k_SetYawRate = 3u;
private RobotControllerComponent? m_Robot;
protected override void OnCreate()
{
m_Robot = GetComponent<RobotControllerComponent>();
m_Robot?.SetPolicyActive(k_WalkerSlot, true);
}
protected override void OnUpdate(float ts)
{
if (m_Robot is null)
return;
m_Robot.SetFloat(k_WalkerSlot, k_SetVx, 0.5f); // m/s
m_Robot.SetFloat(k_WalkerSlot, k_SetYawRate, 0.0f); // rad/s
}
Controlling Robots covers the typed slot and command names, switching between policies, and more complete examples.
Driving IK targets through a motion graph¶
A robot's motion graph is a procedural graph (.hpodgraph) assigned to the Motion Graph field
of its Robot Controller. Its inputs, such as an IK target, are written by name through
the same RobotControllerComponent. The example below uses the input names of the IK graph
for the Panda arm in the bundled Welcome project: the script writes a target pose and a
duration, then fires Start to begin the move.
public class ArmReacher : Entity
{
// Input names must match the inputs of the robot's motion graph.
private static readonly Identifier k_TargetPosition = new Identifier("TargetPosition");
private static readonly Identifier k_TargetOrientation = new Identifier("TargetOrientation");
private static readonly Identifier k_Duration = new Identifier("Duration");
private static readonly Identifier k_Start = new Identifier("Start");
private RobotControllerComponent? m_Robot;
protected override void OnCreate()
{
m_Robot = GetComponent<RobotControllerComponent>();
}
[Button("Reach to test pose")]
public void ReachToTestPose()
{
if (m_Robot is null)
return;
Vector3 worldPosition = new Vector3(0.3f, 0.5f, 0.1f); // metres
Vector3 orientationRad = Vector3.Zero; // identity rotation
m_Robot.SetInputVector3(k_TargetPosition, worldPosition);
m_Robot.SetInputVector3(k_TargetOrientation, orientationRad * Mathf.Rad2Deg); // graph reads degrees
m_Robot.SetInputFloat (k_Duration, 2.0f); // seconds
m_Robot.SetInputTrigger(k_Start); // starts one move
}
}
For this graph, the position is in metres in world space and the orientation is in
degrees. Each Start begins a new move from the arm's current pose to the most recently
written target.
Input names and units are defined by each graph, so they differ between robots. To find the exact names, select the robot: the Inspector lists the graph's inputs below the Robot Controller's Motion Graph field. See Procedural Graphs for working with the graphs themselves.
Driving an actuator directly¶
MujocoSceneComponent.GetActuator(name) returns a MujocoActuator, or null if the
robot has no actuator with that name. Set its IK property to false so the motion
graph leaves it alone, then write its control value through Value:
private MujocoActuator? m_Gripper;
protected override void OnCreate()
{
MujocoSceneComponent? mujoco = GetComponent<MujocoSceneComponent>();
m_Gripper = mujoco?.GetActuator("actuator8"); // name from the robot's MuJoCo model
if (m_Gripper is not null)
m_Gripper.IK = false; // this script, not the motion graph, drives the gripper
}
[Button("Close gripper")]
public void CloseGripper()
{
if (m_Gripper is not null)
m_Gripper.Value = 0.0;
}
The range and meaning of Value come from the actuator's definition in the robot's
model. The Panda's gripper actuator takes values from 0 (closed) to 255 (open).
Sharing joints
Policies, the motion graph and scripts can drive the same robot. Limit each policy to
its own joints with SetDrivenJoints(slotId, jointNames). The motion graph leaves
alone the joints an active policy drives and any actuator a script has taken over
with IK = false. Controlling Robots covers joint
ownership in detail.
A working example
The Welcome script in the bundled Welcome project drives the Panda arm with these
patterns: it writes motion graph inputs to move the arm and drives the gripper
actuator directly.
Jolt rigid bodies (game-style 3D)¶
RigidBodyComponent (the Jolt RigidBody
component) plus a collider puts an entity under Jolt. A new body's Type is Static;
set it to Dynamic for the body to move under forces. The colliders scripts can reach
are BoxColliderComponent,
SphereColliderComponent,
CapsuleColliderComponent and
MeshColliderComponent (Jolt Box
Collider, Jolt Sphere Collider, Jolt Capsule Collider and Jolt Mesh Collider
in the Inspector). Forces and impulses are applied through the body:
private RigidBodyComponent? m_Body;
protected override void OnCreate()
{
m_Body = GetComponent<RigidBodyComponent>();
}
protected override void OnUpdate(float ts)
{
if (m_Body is null)
return;
m_Body.AddForce(Vector3.Up * 25.0f); // newtons
}
OnUpdate runs in the Control phase, before the Physics phase of the same step, so the
force is applied in that step. To run physics logic in the Physics phase instead, override
OnPhysicsUpdate and turn on Enable OnPhysicsUpdate Scripts in Settings → Scene →
Scripts. It is off in new scenes; see Entity Lifecycle.
The body's velocity is available as LinearVelocity. For character movement and stair
stepping, use
CharacterControllerComponent
(the Jolt Character Controller component) rather than a raw rigid body.
Box2D rigid bodies (2D)¶
RigidBody2DComponent with
BoxCollider2DComponent drives 2D
physics. They are the Box2D RigidBody and Box2D Box Collider components in the
Inspector. The Box2D Circle Collider component has no C# class.
Box2D is off in new scenes. Turn on Enable Box2D Physics in Settings → Scene → Physics before adding Box2D components.
Common components by area¶
| Area | Components |
|---|---|
| Transform | TransformComponent |
| Rendering | MeshComponent, StaticMeshComponent, CameraComponent, DirectionalLightComponent, PointLightComponent, SpotLightComponent, SkyLightComponent |
| Procedural graphs | PodComponent (the Procedural Mesh component): changes a graph's exposed parameters on one entity, and the emission of its generated bodies. See Changing Procedural Graph Parameters from Scripts. |
| MuJoCo (robotics) | MujocoSceneComponent, RobotControllerComponent |
| Jolt (3D physics) | RigidBodyComponent, BoxColliderComponent, SphereColliderComponent, CapsuleColliderComponent, MeshColliderComponent, CharacterControllerComponent |
| Box2D (2D physics) | RigidBody2DComponent, BoxCollider2DComponent |
| Audio | AudioComponent, AudioListenerComponent |
| Animation | AnimationComponent (the Animation component): sets the inputs of the entity's animation graph. The mesh source wizard adds it; Add Component does not offer it. See Procedural Graphs → Animation graphs. |
Browse them all in the Components reference.
Where to go next¶
- Controlling Robots: policies, joint ownership and IK in depth.
- Changing Procedural Graph Parameters from Scripts: changing a procedural mesh's exposed parameters from a script.
- Entity Lifecycle: which callbacks run, and when.
- Robots: adding robots, policies and motion graphs in the editor.