Changing Procedural Graph Parameters from Scripts¶
PodComponent is the scripting side of the
Procedural Mesh component. It changes the exposed parameters of an entity's
procedural graph for that entity only, with the same effect as
editing a value in the Parameters section of the Inspector. The graph file is not
touched. On a graph with generated bodies, it can also make a body's materials glow.
In short
Get the component with GetComponent<PodComponent>() and call
SetParameter(name, value) with the parameter's full name, folder included, such as
"Straight / Length". ResetParameter(name) puts it back to the graph's default.
Both return true when the change was applied. While the scene plays, only procedural
meshes without collision or generated bodies accept changes. A refused call writes the
reason to the console. To light up part of an articulated graph, call
SetBodyMaterialEmission(body, material, colour).
Getting the component¶
Add a script to an entity that has a Procedural Mesh component, and fetch the component
in OnCreate:
private PodComponent? m_Mesh;
protected override void OnCreate()
{
m_Mesh = GetComponent<PodComponent>();
}
PodComponent works with Procedural Meshes that use a graph: the Inspector shows a
Graph field for them. A primitive, such as a Procedural Cube that shows
Primitive in the Inspector, has no exposed parameters. Click Promote to Graph
first: its shape settings, such as Size X, then become parameters. See
Procedural Graphs → Promote to Graph.
Setting and resetting a parameter¶
| Method | What it does |
|---|---|
SetParameter(string name, float value) |
Sets a number parameter |
SetParameter(string name, int value) |
Sets a whole-number parameter, or a number parameter |
SetParameter(string name, bool value) |
Sets a checkbox parameter |
SetParameter(string name, Vector3 value) |
Sets a vector parameter |
ResetParameter(string name) |
Removes this entity's value, so the parameter follows the graph's default again |
Each method returns true when the change was applied. When it returns false, nothing
changed and the reason is in the console. See When a call is refused.
Setting a parameter to the graph's default value has the same effect as
ResetParameter. While the scene plays, the new mesh, and any lights the graph generates,
are in place by the time the call returns.
Parameter names¶
name is the parameter's full name, exactly as written in the graph, and it must match
exactly one exposed parameter. Grouped parameters include their folder and the slash, with
any spaces around it exactly as written.
The Inspector shows grouped parameters by the last part of their name only, inside a folder. Some rows also add a unit in brackets after the name, which is not part of it either. To find the full name, open the graph and look at the Parameters tab of the graph editor, which lists every parameter by its full name.
| Inspector shows | Full name to pass |
|---|---|
| Size Y, with no folder | "Size Y" |
| Length in the Straight folder | "Straight / Length" |
| Offset in the Bevel Edge folder | "Bevel Edge / Offset" |
Value types¶
The overload you call must fit the parameter:
| Parameter (Inspector control) | Accepts |
|---|---|
| Number | float or int |
| Whole number | int only |
| Whole number with named choices (a dropdown) | int: the index of the choice, starting at 0 |
| Checkbox | bool |
| Vector (three numbers) | Vector3 |
| Menu, colour or text | Not settable from scripts. Change them in the Inspector |
A whole-number parameter refuses a float, so pass 3, not 3.0f. A number parameter
accepts either. Some dropdowns are menu parameters rather than whole numbers with
choices, for example the Output Role of a promoted primitive. Scripts cannot set
those.
While the scene is playing¶
Scripts run while the scene plays, and during that time a stricter rule applies. Only a render-only procedural mesh accepts a change: one with no collision and no generated bodies, both before and after the change. Rooms, decorations and light rigs are typical examples.
A procedural mesh that takes part in physics refuses every change while the scene plays, because its collision and bodies were fixed when you pressed Play. The same goes for a change that would give a render-only procedural mesh collision. Make such changes in Edit mode instead, from the Inspector.
A primitive or a promoted primitive is render-only when its Output Role is Render.
When a call is refused¶
A refused call writes a warning to the console in this form:
ResetParameter reports refusals with the same message. The reasons are:
| Reason | What to check |
|---|---|
the entity has no Procedural Mesh (PodComponent) with a graph |
The entity has no Procedural Mesh, or its Procedural Mesh has no graph, such as a primitive that has not been promoted |
the graph is not loaded, not compiled, or did not evaluate |
The graph file is missing, or it has errors. Open it in the graph editor |
no single exposed parameter has that name |
The name is misspelt, is missing its folder, or is not exposed. Two parameters with the same name also cause this |
the parameter does not take that kind of value |
The overload does not fit the parameter, or the parameter is a menu, colour or text parameter |
the value is not one of the parameter's choices |
The index is outside the parameter's list of choices |
the procedural mesh feeds MuJoCo, whose model was compiled when play started |
The procedural mesh has collision or generated bodies, or the change would give it some. Make the change in Edit mode |
Example: a pulsing block¶
This script makes a block grow and shrink while the scene plays. To try it, create a Procedural Cube, leave its Output Role at Render, and click Promote to Graph. Then attach the script to the cube.
using Hazel;
public class PulsingBlock : Entity
{
public float BaseHeight = 0.5f; // metres
public float Amplitude = 0.2f; // metres
public float Speed = 2.0f; // radians per second
private PodComponent? m_Mesh;
private float m_Time;
private bool m_Pulsing = true;
protected override void OnCreate()
{
m_Mesh = GetComponent<PodComponent>();
}
protected override void OnUpdate(float ts)
{
if (m_Mesh is null)
return;
// R stops the pulse and puts the block back to the height stored in the graph.
// Press it again to restart the pulse.
if (Input.IsKeyPressed(KeyCode.R))
{
m_Pulsing = !m_Pulsing;
if (!m_Pulsing)
m_Mesh.ResetParameter("Size Y");
}
if (!m_Pulsing)
return;
m_Time += ts;
float height = BaseHeight + Amplitude * Mathf.Sin(m_Time * Speed);
// Stop after a refusal, rather than repeating the same warning every tick.
if (!m_Mesh.SetParameter("Size Y", height))
m_Mesh = null;
}
}
Each accepted change rebuilds the mesh, so send a value when it changes rather than repeating an unchanged one every tick.
Making generated bodies glow¶
A graph with several bodies connected by joints builds a child entity for each body. See Articulated graphs. While the scene plays, a script can make a material on one of those bodies glow, for example to switch an indicator lamp on and off. Only the look of this procedural mesh changes: its parameters, the graph and the physics are left alone, and nothing is rebuilt.
| Method | What it does |
|---|---|
FindBody(string bodyName) |
Returns the body's entity, for example to read where a moving part is. Returns null when no body, or more than one, has that name |
SetBodyMaterialEmission(string bodyName, string materialName, Vector3 linearRadiance) |
Makes a material of the body glow with the given colour |
ResetBodyMaterialEmission(string bodyName, string materialName) |
Puts that material's glow back to what the graph gives it |
ClearMaterialOverrides() |
Puts the glow of every material on every body of this procedural mesh back to what the graph gives it |
SetBodyMaterialEmission and ResetBodyMaterialEmission return true when the change was
applied. Only the bodies of this procedural mesh are searched, not those of another
procedural mesh or robot placed under it.
Body and material names¶
bodyNameis the body's name in the graph. The editor names each body entity after its body when it builds it, but renaming the entity afterwards does not change the body's name, so keep using the original one.materialNameis the name the graph gives a material on the body's mesh. To see the names, expand the body entity in the Scene Hierarchy and select the entity under it, named<body>_Render. The Materials section of its Generated Mesh component shows the graph's material in each slot that has no Material asset assigned. A material that the graph does not name is calledGraph Material.
The colour¶
linearRadiance is a colour in linear RGB, and its size sets how bright the glow is: double
the values for twice the light. Vector3.Zero gives no glow. Each value must be between 0
and 65504. How a glowing surface looks in each render mode is described in
Emissive materials.
The change is not saved, and it lasts until the scene stops playing. It is also lost when the body's mesh is rebuilt or a different material is assigned to the slot, so set it again if you need it.
Each body can use up to 256 different colours, across all its materials, each time the scene plays. Going back to a colour it has already used costs nothing, so switch between a few fixed colours, such as on and off, rather than sending a new value every tick.
When an emission call is refused¶
A refused emission call returns false, changes nothing, and writes nothing to the console.
Check the following:
- The body name matches exactly one body of this procedural mesh.
- The material name matches exactly one material on that body's mesh.
- Each part of the colour is between 0 and 65504.
- The graph is ready. While it is still being prepared, for example just after it changed, calls are refused: try again on a later update.
- The body has not used up its 256 colours.
Some graph materials cannot be made to glow from a script, and always refuse.
Example: a lamp switch¶
This script switches a lamp on and off with L. It assumes a graph with a body named
Lamp, whose mesh has a material named Bulb. Attach it to the entity with the Procedural
Mesh, and change the names in the Inspector to match your graph.
using Hazel;
public class LampSwitch : Entity
{
public string BodyName = "Lamp";
public string MaterialName = "Bulb";
public Vector3 Glow = new Vector3(8.0f, 6.0f, 2.0f); // warm white, linear RGB
private PodComponent? m_Mesh;
private bool m_On;
protected override void OnCreate()
{
m_Mesh = GetComponent<PodComponent>();
if (m_Mesh is not null && m_Mesh.FindBody(BodyName) is null)
Log.Warn("LampSwitch: this procedural mesh has no single body named " + BodyName);
}
protected override void OnUpdate(float ts)
{
if (m_Mesh is null || !Input.IsKeyPressed(KeyCode.L))
return;
bool applied = m_On
? m_Mesh.ResetBodyMaterialEmission(BodyName, MaterialName)
: m_Mesh.SetBodyMaterialEmission(BodyName, MaterialName, Glow);
if (applied)
m_On = !m_On;
}
}
Where to go next¶
- Procedural Graphs covers exposing parameters in the graph editor, tuning them in the Inspector, and articulated graphs.
- Controlling robots drives a robot's motion-graph inputs, which work in a similar way.
- The
PodComponentAPI reference lists the methods.