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:
These are the steps needed to create a solver programmatically:
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);
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().
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());
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.
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());
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());
These are the steps needed to create an actor programmatically:
Now, let's take a look at some examples:
You'll need to add the following components to the actor GameObject:
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;
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;
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;
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;