Interactive Multi-body Physics
I love the physics interactions in Half-Life 2, Penumbra, and similar games, and I want this to be a key part of gameplay in our upcoming SCP game. Physics interactions should should be everywhere, all around the player, so that the world feels alive and interactive.
Furniture is a great way to add physics interactions to your game, because each object can be set up one, and many instances can of the object can be placed in your levels. Cabinets and doors can swivel open and closed using a hinge joint. Drawers can slide in and out using a slider joint. This simple interactivity can turn a search for an important item into a mini-game, and encourages the player to explore the world in hopes of finding a valuable item.
I decided to start with a filing cabinet that @Rich created. Each drawer is a separate limb in the model, so they can move independently.
Colliders
This item will require five different oddly-shape colliders. One for the cabinet, and another for each of the four drawers. I want to be able to place items in the drawers and close the drawers, while still having collision with the exterior of the cabinet, so the colliders need to be pretty detailed.
The first thing I did was open the model in the model editor, delete everything except the bottom drawer, collapse it, then save that model as a separate drawer model.
Because the drawers are dynamic objects, they could not use polygon mesh colliders. There isn't a single collider shape that is appropriate for drawers, and a convex hull would not give me the abililty to place items in the drawer. What I needed was a multi-part collider made up of convex hulls.
I added the drawer model to a new scene, positioned it as (0,0,0), and started building a collision shape out of brushes. I saved this as a map file and will keep it in case I want to modify the collider a bit more in the future: My minimum thickness here is four centimeters, a number I will keep in mind in the future and revise if it turns out this is needed after play testing.
I selected all the brushes, right clicked in the scene editor, and selected the "Save as Model" popup menu item, and saved a MDL file. Now I had a model file with separate limbs for the different convex hulls I wanted to make the shape out of.
I did the same thing for the cabinet exterior. Entities that are connected to each other with a joint will not collide with each other, fortunately, so we don't have to worry about making the drawer and cabinet colliders match exactly. If the drawers intersect the file cabinet collider a little bit, it's not a problem. The individual drawers will collide with each other, since they are not connected to the same joint, so take care that they do not interpenetrate.
I am not sure yet if I want the file cabinets to be immovable or dynamic. Since it's not difficult, I will make the collider out of separate convex pieces so I can make it dynamic in the future if I decide I want to.
Now we are ready to add the colliders to the models. I opened the drawer model in the model editor and selected the new File > Load Dynamic Collider menu item. This will load a model and turn each limb into a separate convex hull, for multi-part colliders. It worked perfectly!
For this to work as expected, you usually need the root limb in your visual model to be positioned at (0,0,0), with rotation (0,0,0), and scale (1,1,1). You may get weirdly rotated, offset, or scaled results if this is not the case. Fortunately, you can reset the model's transformation matrix just by selecting the Tools > Reset Transform menu item. You might need to do this to the visual model, the collider model, or both!
Now that I had my individual models, with colliders, I added the file cabinet to the scene, added four drawers, positioned them each in the right place, and parented them to the cabinet. Positioning drawers was actually pretty easy and their placement does not have to be exact, so don't get obssessive about making them perfect.
I saved the assembly as a prefab, and now I can place the cabinet anywhere I like. I could even save this assembly as a new model file, and it would retain the colliders for each limb, but I think I am going to leave it in its separate pieces, referencing the file cabinet and draw models. This way I can easily modify those models in the future, if I am not happy with how their colliders are shaped.
Since I ended up just using one repeating drawer model, I recommend all furniture in the future be exported from the modeling program this way: the cabinet object with no drawers, doors, or whatever moving parts there are, and then one copy of the drawer or door. Since it's pretty easy to line the drawers up in the right place, you don't even need any nodes to hint where they are supposed to be placed.
Joints
The next step was to add a slider joint for each drawer. I added a new class that just creates a slider joint. There are two pieces of information I am interested in, the joint pin and the limits. The pin defines the vector the joint slides along, and the limits determine where the joint starts and stops. I want the drawer to be locked to the cabinet so it can't be pulled out all the way, and the joint limits will help me achieve that behavior. I could add more properties in the future, but for now let's just keep it simple.
In the Start method, a slider joint is created between the entity and its own parent. If there is no parent, this is actually okay. The joint will just be created as if a non-movable parent object is used.
void SliderJoint::Start() { auto entity = GetEntity(); if (entity->GetMass() == 0.0f) { Print("Error: Slider joint requires entity to have non-zero mass"); return; } auto gpin = TransformVector(pin, entity, NULL); joint = CreateSliderJoint(entity->GetPosition(true), gpin, entity->GetParent(), entity); if (limits[1] > limits[0]) joint->SetLimits(limits[0], limits[1]); }
I created a simple scene with a floor, a pivot for the player start position, and our filing cabinet. I selected one drawer, pulled it out a bit, set the pass to 2.0, and added the slider joint to it. If it works, I should be able to walk into the drawer and push it back into the cabinet.
When I run the game, I am able to push the drawer in a bit, but it seems like it is hitting something when it gets close to the filing cabinet. Since the drawer does not collide with the cabinet, it must be hitting the other drawers.
I went back to the editor and moved the drawer above and below out of the way, to make sure that was actually the problem:
When I ran the game I could push the drawer in all the way, and then some. Because there is no collision between the drawer and its parent, the cabinet itself, there is nothing to stop the drawer unless I set the joint limits. I'm not worried about this, so it's fine for now. I'm more worried about these collidring drawers!
If I pull the other drawers out all the way, the problem becomes can be easily seen. The front panel overlaps with the drawer above it. How should I resolve this? I already said I want 4 centimeters to be my minimum thickness, since very thin objects could potentially have problems with small objects falling through them.
Fortunately, I still have my map file where I created the drawer collider shape. I decided to just lower the height of the front piece so that it matched the sides and the back. Then I saved the brush objects as a model, and loaded it back into the fiel cabinet drawer model as a dynamic collider. We might change this design in the future, but for now I just want to get it working!
Here is the drawer with the new collider loaded:
When I load my scene back up, it looks like the drawers have plenty of clearance.
The game now works as expected. I can push the drawer in all the way without hitting anything.
I added the slider joint to the other drawers and set their mass to 2.0, and all the drawers could now be pushed freely.
I discovered an interesting problem while doing this! It's easy for the drawers to form a stair case shape. When this happens, the player will step almost instantly up all the drawers, to the top of the cabinet! In the future, maybe we will need a per-entity setting that makes it so an entity is non-steppable. For right now, I am not too concerned about it because I want to manually push and pull these drawers, not lean into them.
Tactile Physics
I love the player physics in Amnesia: The Dark Descent, and I wanted to bring this type of interaction to our game. It seemed like it would be achievable, since we are using the same excellent physics library, Newton Game Dynamics, that is in use by Frictional Games.
We already have logic in our player class for picking up objects, and to my delight this mostly worked with the drawers. When a new object is picked up, a kinematic joint is created. The kinematic joint will then exert force and torque on the object the player is grabbing.
if (pickedentity) { float mass = pickedentity->GetMass(); if (mass > 0.0f and mass < maxcarrymass + maxcarrytolerance) { carryjoint = CreateKinematicJoint(pickedentity->GetPosition(true), pickedentity); carryjoint->SetMaxForce(1000); carryjoint->SetMaxTorque(1000); carrycollisiontype = pickedentity->GetCollisionType(); pickedentity->SetCollisionType(COLLISION_DEBRIS); carryposition = TransformPoint(pickedentity->GetPosition(true), NULL, camera); carryposition.z -= 0.1f; carryrotation = TransformRotation(pickedentity->GetQuaternion(true), NULL, camera); pickedobject.reset(); } }
I made a small improvement to disable torque if the picked up object has any joints attached to it. This makes sense, because you don't want to rotate the drawer, just move it on one axis:
if (not pickedentity->GetJoints().empty()) carryjoint->SetMaxTorque(0);
When the game is played, the physics basically work the way I wanted. Of course there are no joint limits here, so there is nothing from stopping the drawers from going through the cabinet, or being thrown very far away.
I set the joint limits property to [-40, 0], since the drawer starts closed by default.
When I ran the game again, here was the result. You can see the joint limits are working as intended.
The behavior is not perfect. It's jumpy and glitchy. I might need to treat jointed objects a little differently than objects you pick up. But it's basically working, and I am confident with more testing and refinement it will turn into a polished and fun game mechanic.
The last step was to save my assembly as a new prefab, now that I am pretty sure it's done.
Now I can place an infinite number of file cabinets, with an infinite number of interactive drawers, and force the player to search through them all! The true horror of the SCP beauracracy awaits you! Bwahahahahahaha!
-
3
1 Comment
Recommended Comments