Grappling Hook

From Oldunreal-Wiki
Jump to navigation Jump to search

In this we shall go through how to make a grappling hook that uses real world physics. This grappling hook is a offshoot of Pfhoenix's early work on the RealGrapple, so thanks Pfhoenix, Frederico & Gopher42, this grapple wouldn't exist without you guys ;)

Theory

To allow the player to swing (and perform some cool stunts) with the grapple we need to approximate real world physics, in this particular case circular motion.Angular velocity measures how rapidly something is rotating, angular velocity is the angle turned through in one second. The units for angular velocity is radians per second:


angular velocity = 2 x Pi / period of rotationw = 2π / T

Any object moving in a circular path accelerates towards the centre of the circle. Newton's second law says that a force is needed to cause this acceleration. This is sometimes referred to as the centripetal force. The centripetal force is not an extra force that arises due to the circular motion; it is the resultant of all the real forces acting on the body:


centripetral force = mass x radius x angular velocity squaredF = mrw^2

So now we know the magnitude of the force required to make the player move in a circle. Note that this is a simplification of the problem, there are further complications, such as the effect of gravity when moving in a vertical circle. But we're only aiming for an approximation adequate enough to allow the player to swing on the grapple.

Code

Here comes the code for the grappling hook, I've implemented it as a weapon and even thrown in a chain effect that links the player to the grapple. The grapple works on an instant hit basis, as this allows the player to more readily pull of cool stunts. Lacking any custom models for the grapple gun or the chain, I've used some pre-existing UT meshes.

Grappling hook:


class GrapplingHook extends TournamentWeapon;

var bool bHooked; var vector HookLocation, Force; var float GrappleLength, Omega, CForce; var int Counter; var GChain Chain;

replication {

 // Things the server tells the client
 unreliable if (ROLE == ROLE_Authority)
   HookLocation, GrappleLength, bHooked;

}

simulated function Destroyed() {

 if (Chain != None)
   Chain.Destroy();
 Super.Destroyed();

}

simulated function Tick(float DeltaTime) {

 if (Owner == None) return;
 if (bHooked)
 {
   if (Owner.Physics != PHYS_Falling)
   {
     StopGrapple();
     return;
   }
   Counter++;
   // Update angular velocity every 3rd tick; gives a less spiralled path
   if (Counter == 3)
   {
     // Angular velocity
     GrappleLength = VSize(HookLocation - Owner.Location);
     // If the chain gets too long
     if (GrappleLength > 2500)
     {
       StopGrapple();
       return;
     }
     Omega = VSize(Owner.Velocity / GrappleLength) * DeltaTime;
     Counter = 0;
   }
   // Centripetal force
   CForce = Owner.Mass * GrappleLength * Omega ^ 2;
   // Centripetal force is towards centre of circle
   Force = Normal(HookLocation - Owner.Location);
   Force *= CForce;
   Owner.Velocity += DeltaTime * Force;
   // Clamp maximum velocity
   if (VSize(Owner.Velocity) > 1100)
     Owner.Velocity = Normal(Owner.Velocity) * 1100;
   else if (VSize(Owner.Velocity) < -1100)
     Owner.Velocity = - (Normal(Owner.Velocity) * 1100);
 }
}
function StartGrapple()
{
 local vector X, Y, Z, End, HitLocation, HitNormal;
 local Actor Other;
 // Don't start grappling if we're already grappling
 if (bHooked)
   return;
 GetAxes(Pawn(Owner).ViewRotation, X, Y, Z);
 // End trace to stop people grappling stupidly long distances
 Other = Trace(HitLocation, HitNormal, Owner.Location + X * 2500.0, Owner.Location);
 if (Other == None)
   return;
 Other.TakeDamage(10.0, Pawn(Owner), HitLocation, 400.0 * X, 'grapple');
 if (!Other.IsA('Pawn') && !Other.IsA('Projectile') && !Other.IsA('Decoration'))
 {
   bHooked = True;
   HookLocation = HitLocation;
   GrappleLength = VSize(HitLocation - Owner.Location);
   if (Chain == None)
   {
     Chain = Spawn(class'StarterChain', Owner,, Owner.Location);
     if (Chain.IsA('StarterChain'))
       StarterChain(Chain).HookLocation = HookLocation;
   }
   // Give the player a "kick" to get them moving
   Owner.Velocity += Normal(HookLocation - Owner.Location) * 200;
 }
}
function StopGrapple()
{
 bHooked = False;
 if (Chain != None)
 {
   Chain.Destroy();
   Chain = None;
 }
}
// Fire to begin grappling
function Fire(float Value)
{
 GotoState('NormalFire');
 StartGrapple();
}
// AltFire to stop grappling
function AltFire(float Value)
{
 GotoState('AltFiring');
 StopGrapple();
}
state NormalFire
{
ignores Fire, AltFire;
Begin:
 FinishAnim();
 LoopAnim('boltloop');
 Finish();
}
state AltFiring
{
ignores Fire, AltFire;
Begin:
 FinishAnim();
 LoopAnim('idle');
 Finish();
}
// Putting down weapon in favor of a new one

state DownWeapon

{
ignores Fire, AltFire, AnimEnd;
 function BeginState()
 {
   Super.BeginState();
   bCanClientFire = false;
   // Destroy chain
   bHooked = False;
   if (Chain != None)
   {
     Chain.Destroy();
     Chain = None;
   }
 }
}
defaultproperties
{
    Counter=0
    PickupMessage="You got a Grapple Gun"
    ItemName="Grappling Hook"
    Mesh=LodMesh'Botpack.PulsePickup'
    InventoryGroup=1
    PlayerViewOffset=(X=1.500000,Z=-2.000000)
    PlayerViewMesh=LodMesh'Botpack.PulseGunR'
    PickupViewMesh=LodMesh'Botpack.PulsePickup'
    ThirdPersonMesh=LodMesh'Botpack.PulseGun3rd'
    ThirdPersonScale=0.400000
}

Chain (two classes), essentially a tweaked form of PBolt:

class GChain extends Projectile;
var GChain Chain;
var int Position;
var float BeamSize;
simulated function Destroyed()
{
 Super.Destroyed();
 if (Chain != None)
   Chain.Destroy();
}
simulated function CheckBeam(vector X, float DeltaTime)
{
 if (Position * BeamSize < 2500.0)
 {
   if ( Chain == None )
   {
     Chain = Spawn(class'GChain',,, Location + BeamSize * X);
     Chain.Position = Position + 1;
   }
   else
     Chain.UpdateBeam(self, X, DeltaTime);
 }
}
simulated function UpdateBeam(GChain ParentChain, vector Dir, float DeltaTime)
{
 SetLocation(ParentChain.Location + BeamSize * Dir);
 SetRotation(ParentChain.Rotation);
 CheckBeam(Dir, DeltaTime);
}
defaultproperties
{
    BeamSize=81.000000
    MaxSpeed=0.000000
    bNetTemporary=False
    Physics=PHYS_None
    RemoteRole=ROLE_None
    LifeSpan=60.000000
    Skin=Texture'Botpack.Skins.pbolt0'
    Mesh=LodMesh'Botpack.PBolt'
    bUnlit=True
    bCollideActors=False
    bCollideWorld=False
}
class StarterChain extends GChain;
var vector HookLocation;
replication
{
 // Things the server tells the client
 unreliable if (ROLE == ROLE_Authority)
   HookLocation;
}
simulated function Tick(float DeltaTime)
{
 // Just to ensure that the chain disappears when the grappling hook is not in use
 if (!PlayerPawn(Owner).Weapon.IsA('GrapplingHook'))
   Destroy();
 // orient with respect to instigator
 if ( Instigator != None )
 {
   SetRotation(rotator(HookLocation - Location));
   SetLocation(Instigator.Location);
 }
 CheckBeam(Normal(HookLocation - Location), DeltaTime);
}
defaultproperties
{
    Skin=Texture'Botpack.Skins.sbolt0'
    RemoteRole=ROLE_SimulatedProxy
}

And there you have it, a working grappling hook. It takes some practice to get the hang of using it properly (hint: take a running jump before grappling), but it's great fun once you've mastered it. The grappling hook works in network games too, but does lack bot support (anyone fancy having a go at adding bot support?).