Constraint
From RoboBlitz Editor Wiki
RB_ConstraintActors represent invisible forces which pull or push objects in the physics simulation. Constraints can be attached to any object, however they will only move that object when it's physics are set to PHYS_RigidBody. Some examples of constraint actors include joints, pulleys, and bungy cords. All constraints are powered by the PhysX engine.
Contents |
Placing a Constraint Actor
To place a constraint Actor open the Actor Classes Browser to Actor->RB_ConstraintActor and select the desired constraint type. Right click in a viewport and select Add <Actor Name> Here. Constraint actors can be moved and rotated like other actors. The arrow represents the actors relative X axis.
All constraint actors must be attached to two other actors. To set these actors open the Constraint's properties and find the ConstraintActor fields in the RB_ConstraintActor section. Now press the "Lock" button (looks like this:
)to prevent the properties from changing when you select another actor. Now select an object in the view port and click in the ConstraintActor1 field in the properties box (if you selected the lock icon correctly it should still be displaying the properties of the constraint actor) and click the green left arrow in the ConstraintActor1 field. This will copy a reference to the selected actor in the viewport. Now select another actor and repeat for the ConstraintActor2 field.
One of the ConstraintActors is allowed to be the "world". The world actor represents a static point in space. Attaching a constraint to the world means one end of the constraint will never move or rotate (it's like the magic that holds up all those floating platforms in other games). You can attach the constraint to the world actor by leaving one of the ConstraintActor fields equal to None.
How a Constraint Works
Because constraints are powered by the PhysX it's important to understand how joints and constraints work in the PhysX engine. Because this is a complex and lengthy discussion, and documentation is available for the PhysX engine from other sources, it is recommended that you gain a familiarity with the PhysX joint types from outside this wiki. One such resource is: [1]. In particular, chapter 8 "Joints", will give a clear overview. Full documentation is available from AGEIA, the makers of PhysX, for those with appropriate access. [2].
The properties of a joint are found in the RB_ConstraintActor section of the joint. TODO figure out the difference between ConstraintInstance and ConstraintSetup
Linear Setups and the Angular section are the bread and butter sections of constraints. Each restricts how the object moves. Drive applies forces which try to move the object back to it's correct position. Limits just prevent the object from moving.
Types of Constraint Actors
RB_BSJointActor
RB_HingeActor
RB_PrismaticActor
RB_PulleyJointActor
RFixedJointActor
RPSpinBallJointActor
RWeakGripJointActor
Dive In
For an example, lets make a RoboGuillotine that Blitz can pull back and have it snap down. Here's a few static meshes I choose:
The guillotine frame is just a doorway mesh and I used a spike for the blade. And I added a large blitz head for effect.
First we need to make the blade only move horizontally along the frame (since the frame would prevent it from moving up and down, or sideways out of the frame). The blade doesn't actually need to be touching the frame for this to happen. But we will need a prismatic actor to tie the two pieces together.
A First Attempt
Place a Prismatic actor from the Actor Classes Browser and set the doorframe as the ConstraintActor1 and the spike as ConstraintActor2. By default Prismatic joints allow movement along the joint's X axis, but not along it's Y or Z axis. The X axis is denoted by the arrow coming out of the Prismatic Actor. Align this axis with the frame.
A joint "attaches" itself to the object whereever the ConstraintActor is placed. This means that if the ConstraintActor is outside the object you might get some unusual outcomes. The minimize this, I'm going to place the ConstraintActor at the center of the spike. Since the spike and the frame don't intersect, there's no way to get around placing the joint outside the frame. It won't matter much in my scene, especially since the frame is a static mesh, but it's easy to imagine a scene where it would. Take for example a jet with retractable wings. If the joint is outside of the jet, whenever the wing gets impacted it will apply force to the jet several feet outside of the jet, which means that impact will have more leverage on the jet. The jet will probably roll unusually fast whenever the wing gets shot.
When I set the scene up, I had to set bWakeOnLevelStart in the head to true so the blade wouldn't fly back the first time it touched the head. In order for the head to be damaged you also have to uncheck bPassDamageToCore in the RPart section of it's properties.
Now the blade should slide back and forth along the frame.... and off the end.... and off into space... To stop that, we need to limit movement in the X direction. In the properties of the constraint actor find the RB_ConstraintActor section and open the Constraint Setup. In the Linear section set the LinearXSetup bLimited to 1. This will lock the constraint along the X axis. At this point the object won't move at all. To allow some movement set the LimitSize to a positive value. The LimitSize determines how far the joint is allowed to move in both directions. That means the joint can actually move 2*LimitSize units. If you'd like an object to start the level somewhere other than the center of it's two limits, setup the constraint and then uncheck bUpdateActorRefFrame for the actor you'd like to move, then move the actor like you normally would.
We're not there yet though. We need the blade to snap back towards the head. The simplest way to do this is to enable the bLinearXPositionDrive in the Linear section of the ConstraintInstance. To control the force that the drive applies adjust the LinearDriveDamping, LinearDriveForceLimit and LinearDriveSpring.
The default LinearDriveSpring value is a bit much for Blitz, so I'm gonna lower it to 7.0. This allows Blitz to pull the blade back all the way to the limit. I also don't like the LinearDriveDamping, so I'll set that to 0.
The unfortunate part about this method is that the drive controling the blades position is either too strong for Blitz to pull back or too weak to smash into the head at any appriciable speed.
A better way
Few things come easy in life, and making magic-physics in an engine designed around realistic-physics isn't one of them.
Battle Plan
Here's the battleplan: We'll make a "handle" for the blade. The handle will have it's own prismatic joint with the LinearDriveSpring adjusted to pulling back on it with Blitz feels right. Whenever Blitz grabs the handle the Blade will begin moving to where the handle is, continually following the handle. When Blitz releases the handle, the handle starts following the blade. The blade's LinearDriveSpring is set high enough to snap back into the head. When the handle is released the "magic force" that makes the blade follow the handle is lost, and the blade LinearDriveSpring will send it flying.
Prepping the Meshes
Now we have to pull it off. Place your handle mesh with the same position as the blade (use the properties to make sure they're in exactly the same spot). When you teleport an object it moves the teleporting object's origin to the target object's origin (the translation/rotation widget arrows center on the object's origin). You probably want to pick a handle mesh which sticks out enough from it's origin to be able to grab when it shares the same origin as the blade. Now copy the Prismatic joint we made before; change it's ConstraintActor2 to be the handle instead of the blade.
Our first hurdle is that the blade and handle collide with each other. We could turn collision off on one of the objects, but that would have negative consequences (either you couldn't grab the handle or the blade wouldn't collide with the head). Luckily joints have a feature which disables the collision between the two objects attached to the joint. To exploit this feature we'll make another ConstraintActor and attach it to both the spike and the handle. We need to turn off all the limits or the Blade's Prismatic Joint's LinearDriveSpring will pull the handle along with it. Once the limits are turned off we can check bDisableCollision. Now the blade and handle won't collide.
Now you should have a blade and handle which move freely along the frame, don't collide with each other, and don't seem to be attached to each other. (I had my LinearDriveSpring variables set to 0 at this point to make it easier to test).
Moving the Blade
Next we need to find a "magic force" to move the blade to the handle. Interping to a dynamic position doesn't seem to be possible (although if you figure out how, that would probably be the ideal solution, share you technique on the discussion page!). We could also apply an impulse periodically to keep the blade aligned with the handle; unfortunately the force of a spring is not constant, so we would have to adjust the impulse relative to the position of the blade. Ugh. We're left with teleporting the blade to the handle. If we teleport often enough, the movement should look smooth enough.
First off, we'll have to teleport the object about once per frame (give or take) so it stays close to the handle and looks like it's moving slowly. Checking to see if we should teleport the blade each frame from the time the map loads until the time it's finished probably isn't a good use of processing time, so we shouldn't even bother to look for an event which fires each frame.
The Loop
Instead we should do something a little dangerous... a loop in Kismet. You can tell by the way the impulse lines curve that Kismet wasn't designed to loop, but it's the only practical thing to do in this situation. They say C gives you enough rope to hang yourself... Kismet gives you enough little black strings to hang a large truck.
Each sequence of Kismet either finishes executing or pauses in a latent action (like a Delay, Play Sound or a Matinee) BEFORE moving on to the next frame. That means if you do too much in Kismet, or go into an infinate loop, your framerates will plumet or your game will lock up. This puts nasty power in the hands of an unskilled map maker. We need to be careful with our loop. If there isn't a latent action which fires often enough, the loop will slow down framerates to a crawl. To make sure this happens, we'll plop down a delay at the end of the loop. Since we want the position of the blade to update about once a frame, I'll set the delay for .03 seconds. This is similiar to putting a thread to sleep in a multi-threaded enviroment.
We should also only be running the loop when we have to. We already know that we only want the blade to teleport when Blitz has his hand's on the handle, so we can use that to start and stop the loop. Select the handle and add a Grabbed event. Now it would be wonderful if we could just check the Is Grabbing comparison at the end of the delay to see if Blitz is still holding the handle before we do another iteration of the loop. Unfortunately the retail version of the game shipped with a broken Is Grabbing comparions. >_<
Instead we'll make our own with a Gate. When the handle's grabbed event fires we open the gate. When the handle's Grab Released event fires we close the gate. This controls when the loop should be running and when the loop should exit. Now we need a way to start the loop. The handle's Grabbed event is the appropriate place to do this, but we can't count on the gate being open on the same frame as the event fires. Branches in Kismet are non-deterministic, so the gate might be open or closed when impulse travels from the Grab event to the In tab. To get around this we can add another delay. We should set this delay fairly high to insure atleast one frame passes before input reaches the gate's In tab, .2 seconds should be enough. Now impulse will hang out in the Delay so the gate has time to open before it starts the loop.
There's no way to make this pretty, but here it is:
Finishing Up
One last issue remains. When the blade teleports to the handle, the joints get very very angry. This is happening for two reasons. First, the default Teleport doesn't actually move the teleported object to the exact position it's teleported to. Instead it moves it slightly higher than the specified location (in case you were careless and teleported the object into the ground). To fix this we should use a Teleport With Offset element instead. Set the Offset to 0, 0, 0 to it teleports exactly to it's target's origin. Since our joint also forbids rotation we should uncheck bUpdateRotation as well.
Reenable the LinearDriveSprings and the blade should snap back to it's neutral position. As a final touch I set my LinearDriveXTarget to the limit of the joint, that way the LinearDriveSpring will force the blade all the way to the base of the RoboGuillotine.
One unfortunate side effect of our method is that when the handle is released, the blade doesn't stay attached to it. If this disappoints you Clean Up The Handle.


