Scripting Actors

Obi organizes particles and constraints into ObiActors. Examples of actors include: a piece of cloth, a rope, or a fluid emitter. Most of the time actors can be created in the editor, turned into a prefab and instantiated at runtime. However there are situations where creating actors entirely trough code is needed (e.g. procedural level generation). In this section we will learn how to do it.

All actors need to have a solver somewhere up their hierarchy. The solver is in charge of simulating all actors it finds in its hierarchy, so you may also need to create it at runtime.

You will also need a blueprint, so the actor knows what the particle-based representation of your object looks like, how many particles to create and where, etc.

And finally, the actor component itself. Depending on which kind of actor (cloth, rope, or emitter) you're creating, setup will be slightly different.

So, there's 3 things we will need:

  • A solver to simulate particle physics.
  • A blueprint asset to store the particle/constraint data.
  • An actor to instantiate the blueprint into the solver.

Creating a Solver

These are the steps needed to create a solver programmatically:

  • Create a new GameObject (or use a existing one) and add a ObiSolver component to it.
  • Add a updater component (ObiFixedUpdater, ObiLateFixedUpdater, ObiLateUpdater or one of your own) to some object in the scene (may be the solver object itself).
  • Add the solver component to the updater's solvers list.

Translated to code, it looks like this (assuming solver and updater are added to the same object):

// create an object containing both the solver and the updater:
GameObject solverObject = new GameObject("solver", typeof(ObiSolver), typeof(ObiFixedUpdater));
ObiSolver solver = solverObject.GetComponent<ObiSolver>();
ObiFixedUpdater updater = solverObject.GetComponent<ObiFixedUpdater>();

// add the solver to the updater:
updater.solvers.Add(solver);

Creating Blueprints

If for some reason you cannot rely on having a pre-made blueprint somewhere in your resources folder, then you will need to create/generate one at runtime. This is done always in a very similar way regardless of the type of blueprint: by calling the blueprint's Generate() coroutine (and waiting for it to finish). The only difference are the blueprint parameters you need to set before calling Generate().

Cloth blueprints

You only need to provide a readable mesh (remember that non-manifold meshes are not supported!):

// create the blueprint: (ObiClothBlueprint, ObiTearableClothBlueprint, ObiSkinnedClothBlueprint)
var blueprint = ScriptableObject.CreateInstance<ObiClothBlueprint>();

// set the input mesh:
blueprint.inputMesh = yourMesh;

// generate the blueprint:
yield return StartCoroutine(blueprint.Generate());

Rope blueprints

You will need to create a path to define the rope's shape, by adding control points:

// create the blueprint: (ltObiRopeBlueprint, ObiRodBlueprint)
var blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();

// Procedurally generate the rope path (a simple straight line):
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
blueprint.path.Clear();
blueprint.path.AddControlPoint(Vector3.zero, -Vector3.right, Vector3.right, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "start");
blueprint.path.AddControlPoint(Vector3.one, -Vector3.right, Vector3.right, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "end");
blueprint.path.FlushEvents();

// generate the particle representation of the rope (wait until it has finished):
yield return StartCoroutine(blueprint.Generate());
		

Note that you need to create a collision filter to pass as an argument to AddControlPoint(). For more info on creating collision filters programmatically, see scripting collisions.

Emitter blueprints

If you wish to, you can change material parameters before generating:

// create the blueprint: (ObiFluidEmitterBlueprint, ObiGranularEmitterBlueprint)
var blueprint = ScriptableObject.CreateInstance<ObiFluidEmitterBlueprint>();

// set the blueprint's capacity and viscosity:
blueprint.capacity = 2000;
blueprint.viscosity = 0.1f;

// generate the blueprint:
yield return StartCoroutine(blueprint.Generate());

Softbody blueprints

Softbodies have quite a few generation parameters, that largely determine the end result behavior and performance. For convenience it's best to pre-create the blueprints in editor, but here's how to generate them at runtime:

// create the blueprint: (ObiSoftbodySurfaceBlueprint, ObiSoftbodyVolumeBlueprint)
var blueprint = ScriptableObject.CreateInstance<ObiSoftbodySurfaceBlueprint>();

// set the blueprint's particle radius and cluster radius:
blueprint.inputMesh = yourMesh;
blueprint.particleRadius = 0.05f;
blueprint.softClusterRadius = 0.1f;

// generate the blueprint:
yield return StartCoroutine(blueprint.Generate());

Creating Actors

These are the steps needed to create an actor programmatically:

  • Create a new GameObject and add the required Actor components to it.
  • Set the actor's blueprint.
  • Place the actor in a solver's hierarchy (if it wasn't already) to start simulating it.

Now, let's take a look at some examples:

Creating cloth

You'll need to add the following components to the actor GameObject:

  • One of these, depending on what type of cloth you need: ObiCloth / ObiSkinnedCloth / ObiTearableCloth
  • One of these, that matches the type of cloth actor you added in the previous step: ObiClothRenderer / ObiSkinnedClothRenderer / ObiTearableClothRenderer

You need to set the cloth's blueprint too. The easiest way to create a blueprint at runtime is to simply create a copy of an existing one using ScriptableObject.Instantiate. Assuming the blueprint is copied from an existing one, let's see an example:

// create the cloth actor/renderer:
GameObject clothObject = new GameObject("cloth", typeof(ObiCloth),typeof(ObiClothRenderer));

// get a reference to the cloth:
ObiCloth cloth = clothObject.GetComponent<ObiCloth>();

// instantiate and set the blueprint:
cloth.clothBlueprint = ScriptableObject.Instantiate(sourceBlueprint);

// parent the cloth under a solver to start simulation:
cloth.transform.parent = solver.transform;
			

Creating ropes/rods

  • Create a ObiRope or ObiRod component, depending on what you need to simulate.
  • Add a ObiRopeExtrudedRenderer, ObiRopeLineRenderer, ObiRopeChainRenderer or ObiRopeMeshRenderer.

You will also set to setup the rope renderer: If using a extruded renderer, set a section to extrude. If using a chain renderer, set the chain link prefabs. If using a mesh renderer, set the mesh, etc.

// create a rope:
GameObject ropeObject = new GameObject("rope", typeof(ObiRope), typeof(ObiRopeExtrudedRenderer));

// get component references:
ObiRope rope = ropeObject.GetComponent<ObiRope>();
ObiRopeExtrudedRenderer ropeRenderer = ropeObject.GetComponent<ObiRopeExtrudedRenderer>();

// load the default rope section:
ropeRenderer.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");

// instantiate and set the blueprint:
rope.ropeBlueprint = ScriptableObject.Instantiate(ropeBlueprint);

// parent the cloth under a solver to start simulation:
rope.transform.parent = solver.transform;
			

Creating fluid emitters

  • Add a ObiEmitter component
  • Add a ObiParticleRenderer component
  • Any ObiEmitterShape subclass (depending on your needs)

You will also need to set the shader used for particle rendering.

// create an emitter:
GameObject emitterObject = new GameObject("emitter", typeof(ObiEmitter), typeof(ObiParticleRenderer), typeof(ObiEmitterShapeDisk));

// get component references:
ObiEmitter emitter = emitterObject.GetComponent<ObiEmitter>();
ObiEmitterShape shape = emitterObject.GetComponent<ObiEmitterShapeDisk>();
ObiParticleRenderer particleRenderer = emitterObject.GetComponent<ObiParticleRenderer>();

// set the shape's emitter, and the renderer's shader:
shape.Emitter = emitter;
particleRenderer.shader = Shader.Find("Obi/Particles");

// instantiate and set the blueprint:
emitter.emitterBlueprint = ScriptableObject.Instantiate(emitterBlueprint);

// parent the emitter under a solver to start simulation:
emitter.transform.parent = solver.transform;
			

Creating softbodies

  • Add a ObiSoftbody component
  • Add a ObiSoftbodySkinner component to all objects you want to bind to the softbody.
  • Call the skinner.BindSkin() coroutine for each of them.

After generating both the softbody and the skinner, you need to bind the skinner to the softbody.

// create the softbody actor/renderer:
GameObject softbodyObject = new GameObject("softbody", typeof(ObiSoftbody), typeof(ObiSoftbodySkinner));

// get component references:
ObiSoftbody softbody = softbodyObject.GetComponent<ObiSoftbody>();
ObiSoftbodySkinner skinner = softbodyObject.GetComponent<ObiSoftbodySkinner>();
SkinnedMeshRenderer meshRenderer = softbodyObject.GetComponent<SkinnedMeshRenderer>();
skinner.Source = softbody;

// instantiate and set the blueprint:
softbody.softbodyBlueprint = ScriptableObject.Instantiate(surfaceBlueprint);

// set the mesh for the skinned mesh renderer:
meshRenderer.sharedMesh = surfaceBlueprint.inputMesh;

// bind the mesh to the softbody (synchronously):
IEnumerator bind = skinner.BindSkin();
while (bind.MoveNext()) { }

// parent the softbody under a solver to start simulation:
softbody.transform.parent = solver.transform;