Jump to content

Josh

Staff
  • Posts

    26,171
  • Joined

  • Last visited

About Josh

Profile Information

  • Location
    USA

Recent Profile Visitors

1,521,881 profile views

Josh's Achievements

Grand Master

Grand Master (14/14)

  • Well Followed
  • Dedicated
  • Conversation Starter
  • Reacting Well
  • Problem Solver

Recent Badges

16.5k

Reputation

1.1k

Community Answers

  1. I forgot about you saying this on Discord, until now. Thank you for adding the thread!
  2. When I drag the file cabinet prefab into the scene, drawers are in wrong place. It's fine if I make a copy of the prefab with translaate + shift key.
  3. Were you modifying a map file or a script file?
  4. If I make the decision for file cabinets to be static (too heavy to be moved), then I could simply set the collision type of the drawers to "Scene". Objects of this type do not collide with each other, but still collide with the player and with dynamic props. This would prevent the file drawers from colliding with each other, and I would not have to be so careful with the collider shape.
  5. 1.0.0 Fixed a bug that would cause duplicate child limbs to be saved when the Save as Model feature was used in the editor.
  6. 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. SliderJoint.zip 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!
  7. 1.0.0 Replaced File > Load Collider menu item in model editor interface with Load Static Collider and Load Dynamic Collider, for a polygon or multi-convex hull collider.
  8. 1.0.0 Added missing Scene:GetEntity definition method to Lua syntax highlighting. Component is now using virtual inheritance from Object class. Corrected the case of one include file. Added Lua utilities file in Scripts/Start with these functions: isnumber(), isstring(), isboolean(), isfunction(), istable(), isuserdata().
  9. It's actually in the API, but was not highlighted because it didn't appear in the docs. The fix will be rolled out today. Documentation is updated, will recache automatically in a few hours.
  10. 1.0.0 In this build of the editor, when a material is generated, if no metalness texture is present then the material metalness value will be set to 0, while the blue channel of the rough / metal texture will still by 255 (full bright). So if no metal texture is present, the material will be assumed to have zero metalness, but still allow you to adjust the material metal value to make it more metallic if you want to.
  11. Okay, if you find that useful, I don't have any objection to doing this in the next build.
  12. Are you sure this is needed? In the last build I got rid of all virtual inheritance because it does not appear to be needed for anything, and I think you said there was no problem in your previous post. Regarding components, one possible solution might be for the editor to detect base components and display them as if they were separate components. Here is an example of a base component and another component derived from that: class MyBaseComponent : public Component { Vec4 lower;//"Min color" COLOR Vec4 upper;//"Max color" COLOR void Update() { Vec4 color; color.r = Random(lower.r, upper.r); color.g = Random(lower.g, upper.g); color.b = Random(lower.b, upper.b); color.a = Random(lower.a, upper.a); GetEntity()->SetColor(color); } }; class MyDerivedComponent : public MyBaseComponent { Vec3 speed;//"Speed" void Update() { GetEntity()->Turn(speed); MyBaseComponent::Update(); } }; When you add MyDerivedComponent to an entity, tabs for MyDerivedComponent and MyBaseComponent would appear, with properties displayed in each one, as if they were two different components. The differences would be: You would not be mixing and matching different components in the editor. Combination of different components would be achieved by declaring a class that inherits another. You can't have two properties or methods with the same name, since they would overwrite each other, since they are on one object. This might solve several problems: This would retain visual separation of properties and flowgraph subobjects in the editor. This would retain separation of code in different files. Problems involving the order different component methods are executed in would go away. The component would just be a single object to access in code.
  13. Yes, I often wish to use this myself!
  14. Here is an example of a component derived from another component, using virtual inheritance. The base component can still be used on its own, or its functionality can be combined with the derived component. DerivedComponent.zip Edit: I don't think virtual inheritance is actually needed at all for this.
  15. Okay, so with my Monster example above, given that there is still a necessity to cast to Monster class, I don't see a lot of difference between that and this single-component approach: auto pickinfo = world->Pick(p0, p1, true); if (pickinfo.entity and pickinfo.entity->actor) { auto monster = pickinfo.entity->actor->As<Monster>(); if (monster and monster->health > 0) monster->Kill(); } Currently, interactions between entities have to be done like this: auto pickinfo = world->Pick(p0, p1, true); if (pickinfo.entity) { for (auto c : pickinfo.entity->components) { auto monster = c->As<Monster>(); if (monster and monster->health > 0) monster->Kill(); } } I have three objections to this: 1) Any time you call a method, set a value, or get a value from another entity, a for loop is required. This is tedious and makes me want to avoid entity interactions, unless I absolutely have to do something. 2) Entity interactions get very complicated and ambiguous. Look at the bullet impact code: for (auto c : pickinfo.entity->components) { auto base = c->As<BaseComponent>(); if (base) base->Damage(damage, entity); } Where is the health value stored? If you have a separate health value stored for each component, you better make sure they all start at the same value, and it's easy for them to get out of sync. If there is just one health value stored in another component, like a health manager, then you can run into two problems: If each component decrements HealthManger->health, and you have two components that do this, you are applying double damage. If only the HealthManager component decrements the health value, then you had better make sure the HealthManger gets called first! Otherwise other components won't know if they are supposed to respond with a pain sound, or respond with a death sound, because the health value might not have been set to zero yet. This is a problem I actually ran into when developing the stock components. The player would not perform all their "dying" actions correctly because the health value was on a separate component and was sometimes decreased last, so the camera and controls of the dead player were staying active, even though health had dropped below zero. It took me a long time to realize the cause of this. You could solve this by defining a component order, but it's very easy to make mistakes with that, and I would feel like apologizing if I was making a tutorial and trying to explain it. 3) Retrieving a value from another entity involves a lot of code and is ambiguous. int team = 0; for (auto c : entity->components) { auto npc = c->As<NPC>(); if (npc) team = npc->team; } If the user has everything set up correctly so that there aren't two components with the same base class and different values, everything should be fine...but people make mistakes and adding a layer of confusion always makes it harder to see those mistakes. All the same things can be said about Lua, only with Lua no casting is necessary, you just check if the values you are expecting are present on the entity. With the single component design, all the methods and properties are attached directly to the entity object, something that was not possible in Leadwerks 4: local pickinfo = world:Pick(p0, p1, true); if pickinfo.entity then if type(pickinfo.entity.health) == "number" and type(pickinfo.entity.Kill) == "function" then if pickinfo.entity.health > 0 then pickinfo.entity:Kill() end end end Retrieving values from another entity becomes very simple: if type(entity.team) == "number" and type(entity.health) == "number" then if entity.team ~= self.team and entity.health > 0 then self:Attack(entity) end end
×
×
  • Create New...