www.neocomputers.com

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