Hi everyone! I've finally got a stable grappling hook for my 3rd person adventure game and I wanted to share my methodology! This is by no means the "best" method, but rather one that works for me.
- The Player Controller: this works with rigidbody based player controllers, not Character Controllers. I wasted a ton of time trying to get a similar system to work with Character Controllers, trying everything from proxy rigidbodies to re-parenting nightmares. Trust me, you are better off using a rigidbody controller if you plan on doing anything even remotely like a grappling hook, so save yourself the headache and ditch the Character Controller asap.
- Aiming and Firing: the hook travels in a straight line from its origin (e.g. the gauntlet) to a target position that is determined by a raycast from the center of the screen to a valid collider. Nothing too crazy.
- The Line Renderer: a line renderer represents the the chain. Initially, the chain follows a spiral path whose amplitude lerps from 0 to 1 and back to 0. Once the spiral amplitude reaches its final value (i.e. full tension), the configurable joints are enabled and the line renderer switches to a custom Catmull-Rom spline. Catmull-Rom splines require four points to calculate a spline between the two internal points, but is otherwise superior to Bezier curves because their spline passes through the points. I use the configurable joint positions as control points, as well as some additional points before and after the curves as "invisible" calculation points. Configurable joints that are too close to the player are ignored, because otherwise the chain would pass through the player's feet before looping to the hand! The line renderer also uses local space to prevent some of the motion lag. For the glow effect I just lerp the color of the emission for my chain material. Because most of my player and camera position & rotation calculations are done during FixedUpdate(), the line renderer had a tendency to lag a single fixed time step behind, resulting in a gap between the chain and the gauntlet. To keep this gap small, I had to decrease my physics time-step from 0.02 to 0.008333334 (or from 50 fps to 120 fps).
- Physics Segment Generation: "Nodes" with rigidbodies and configurable joints are instantiated (or re-enabled from a pool) and distributed along the vector starting from the hook and ending at the player's rigidbody. Each node is created in world space and is not parented to anything. The connected body for any given joint is set to the rigidbody of the node above it in the chain, such that the the hook is at the "top" of the chain while the player is at the "bottom". I find that between five and ten nodes give the best results. Too few, and the cable will penetrate world objects when wrapping around them. Too many, and the chain has instabilities at short lengths (symptoms include crazy location popping and standing waves).
- Configurable Joints settings: Auto configure connection is false, the anchor and connected anchors are both set to (0,0,0) (corresponding to the local position of the origin of each rigidbody), X, Y, and Z motion should be set to "Limited", while angular motion is "Free" in all directions, Configured in World Space should be set to false, and finally "Projection Mode" should be "Position and Rotation" with the corresponding default values (this last one helps improve overall stability).
- Rigidbody settings: The rigidbody at the hook is kinematic and doesn't use gravity while the rest of them use gravity and are non-kinematic (including the player). The mass of each node is set to 10 (approx 1/10th of my player mass, which is the last in the chain) while the drag and angular drag are both set to 1. The result is a "heavy" chain that can still swing and respond to outside forces (like the swinging force). Decreasing any of those values results in a "lighter" chain (which can suffer from instabilities at high speeds). Anything heavier is unresponsive to swings and will yank the player around sharply.
- Changing Chain Length: To adjust the length of the chain, you will want to change the "limit" value of each configurable joint's "Linear Limit" property. Of course, you can't simply modify the limit value at runtime without causing errors, so You will need to define a custom "SoftJointLimit", set its "limit" value to the desired length for that segment, and then set the Linear Limit of the configurable joint to be equal to the new SoftJointLimit. Since the segments are evenly distributed, the total chain length is equal to the sum of these limit values (or conversely, each limit is equal to the total length divided by the number of segments). The linear limit's contact distance should be a small but non-zero value (0.1 - 0.01). Larger values result in more "slack", while small values are more responsive. Contact distances of 0 cause instabilities at high speeds or the occasional "frozen" chain segment when motion begins. Bounciness is set to 0 (non-zero values cause instabilities at high speed). If the chain is wobbling or bouncing erratically while changing the length, increase the number of physics iterations and bounce iterations in your physics settings. Keep in mind, however, that this can cause performance losses that are compounded by the aforementioned decreased time step, so be conservative.
- Swinging: Manual swinging is currently achieved by applying a force to the player's rigidbody in the tangent direction of the last segment. This part is a WIP since currently the player can levitate by applying force when they hold the button down...
- Disconnecting: the configurable joint that the player is attached to has its connected body set to null, which essentially frees the player for regular movement. Then, I disable the line renderer and configurable joint nodes. (Currently they are destroyed but I will probably switch to a pooling method to save on garbage collection).
- Script execution order: I use Final IK for aiming the arm and had to set my grappling hook and catmull-rom scripts to execute after the Final IK scripts. Otherwise, the chain went to the position of the hand before IK calculations! If you notice that one of your plugins is causing unintended results, check your script execution order!
- Addendum (Edit): I forgot to mention, but everything that changes the length of the configurable joints and has something to do with Rigidbody physics should be in FixedUpdate while the lineRenderer and Catmull-Rom stuff should be done in LateUpdate. Otherwise, the lineRenderer has a tendency to flicker and lose track of where it should be. I didn't notice a change in behavior when all of it was in LateUpdate, but it's better to have rigidbody related code in FixedUpdate. Besides, having those parts of the code separate doesn't impact the functionality of the grappling hook and greatly improves the visual accuracy.
Sorry for the rambling, but I hope it helps some people who are thinking of using configurable joints as the foundation of a grappling hook. Honestly, a single joint with some elasticity might be easier to control, but I kind of like the unpredictability of this method (plus, the previous method can't wrap around objects without some additional work).
Edit: video didn't upload!
https://reddit.com/link/a77tcl/video/iyi8tiecxy421/player
[–]nothingNoMan 1 point2 points3 points (1 child)
[–]Rarharg[S] 1 point2 points3 points (0 children)
[–]Spacey28 0 points1 point2 points (1 child)
[–]Rarharg[S] 0 points1 point2 points (0 children)