news | about | forums | links | mods | tools | tutorials | contact

Grappling Hook - author: [mr.self-destruct]
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 rotation w = 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 squared F = 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?).
suggestions? questions? comments? please send them all to ray@beyondunreal.com

copyright 2001 ray davis. this site and all content belongs to me unless otherwise noted, with the exception of some tutorials. the authors retain the rights to their material, and are hosted here with their express permission. everything else is mine though, so don't steal it. bastards.
privacy