Archive for the ‘UDK’ Category

Weapon firing logic basically goes like this

    1. When the fire button is pressed, PlayerController.StartFire is called. The call is then pushed to Pawn.StartFire then Weapon.StartFire, passing a flag to indicate if it was a primary or secondary fire (or even tertiary if you want). The flag is just an integer and by default is either 0 or 1 for primary or secondary respectively.
    2. The call ends up in InventoryManager.SetPendingFire. The inventory manager has a PendingFire array, with each item in the array corresponding to a supported fire type i.e. for a player that has primary and secondary fire modes, the PendingFire array should have 2 elements. The value for each firing mode is 0 when the mode is not active and 1 when it is. InventoryManager.SetPendingFire sets 1 to whatever firing mode was requested.
      The default inventory manager does not initialise this array, which prevents a custom weapon from firing. To get this to work, we need to subclass the inventory manager and initialize the PendingFire array with the number of firing modes we have, lets set up 2 firing modes.

      class SKInventoryManager extends InventoryManager;
      
      defaultproperties
      {
          PendingFire(0)=0
          PendingFire(1)=0
      }

      We then need to tell our pawn to use this as its inventory manager.

      class SKPawn extends UDKPawn;
      ...
      defaultproperties
      {
      ...
      InventoryManagerClass=class'SKInventoryManager '
      ...
      }
      ...
    3. Next the weapon is sent from the Active state to a weapon firing state. Each firing mode needs to define what its weapon firing state is. e.g. for a weapon that can fire bullets on primary fire and grenades on secondary fire, you could define two states in your weapon class, WeaponFiringBullets and WeaponFiringGrenade. Then whenever primary fire is used, the first state would be activated and when secondary fire is used, the weapon would be sent to the second state. It is however not nessecary to have multiple states for each fire mode. If the logic is the same, as it often is, you could/should reuse the same state. Lets define the state for our fire modes within our weapon. We’ll use the already defined state – WeaponFiring
      class SKWeapon extends UDKWeapon;
      ...
      defaultproperties
      {
          ...
          FiringStatesArray(0)=WeaponFiring
          FiringStatesArray(1)=WeaponFiring
          ...
      }

      We do this in our base weapon class since we’ll likely use the same firing state in subclasses and we can change the value if we need to within those subclasses.

    4. Next, we need to define what kind of fire each mode will initiate i.e. The deafult WeaponFiring state class Weapon.FireAmmunition which checks the fire type of the current fire mode to run the actual firing logic. We do this in our specific weapon because this is where we know what fire type each mode actually has.
      class SKGravityGun extends SKWeapon;
      ...
      defaultproperties
      {
          ...	
          WeaponFireTypes(0)=EWFT_InstantHit
          WeaponFireTypes(1)=EWFT_None
      }

      I’ve set the secondary fire type to none because we don’t support it yet. There is logic within the Weapon.SendToFiringState function that aborts firing if the requested firing mode’s fire type is EWFT_None.

    5. And finally, this being a side scroller, we need to change the aim logic becuase the default one assumes the player aim where the camera is looking. In our sidescroller, the player will aim wherever the pawn is looking. When you fire the weapon, a trace is done to determine what the shot hit. The start of the trace is determined by calling Instigator.GetWeaponStartTraceLocation where instigator is the pawn. The trace’s direction is determined by calling Pawn.GetBaseAimRotation (it is called through other functions that are allowed to adjust the aim for things like auto aim, aim lock etc but all these are based on the direction returned by Pawn.GetBaseAimRotation). Lets overide these two functions.
      class SKPawn extends UDKPawn;
      
      /**
       * Start our traces form the pawn's eye which is what GetPawnViewLocation returns
       *
       * @return	World location where to start weapon fire traces from
       */
      simulated event Vector GetWeaponStartTraceLocation(optional Weapon CurrentWeapon)
      {
      	return GetPawnViewLocation();
      }
      
      /**
       * returns base Aim Rotation without any adjustment (no aim error, no autolock, no adhesion.. just clean initial aim rotation!)
       *
       * @return	base Aim rotation.
       */
      simulated singular event Rotator GetBaseAimRotation()
      {
      	local Rotator   POVRot;
      	local Int Modulo;
      
      	Modulo = Rotation.Yaw % 65535;
      	Modulo = Rotation.Yaw % 65535;
      
              /* Pulled this from the sidescroller framework on UDN - It basically makes sure that if the pawn's rotation
                 is between 90 and 270 degrees, we force the aim direction directly to the left and its not, we force it directly to
                 the right. This allows us to have smooth rotations on the pawn but keep the aiming to either left or right
              */
      
      	POVRot = Rotation;
      	if( (Rotation.Yaw % 65535 > 16384 && Rotation.Yaw % 65535 < 49560) ||
            (Rotation.Yaw % 65535 < -16384 && Rotation.Yaw % 65535 > -49560) )
      	{
      		POVRot.Yaw = 32768;
      	}
      	else
      	{
      		POVRot.Yaw = 0;
      	}
      
      	if( POVRot.Pitch == 0 )
      	{
                      // use the remote view pitch that is replicated to all clients and retrieve the quantized value as per -
                      //http://udn.epicgames.com/Three/NetworkingOverview.html#Variable type notes
      		POVRot.Pitch = RemoteViewPitch << 8;
      	}
      
      	return POVRot;
      }
      
      defaultproperties
      {
      }

      To visualize our aiming, I added a couple of variables and drew a debug line as follows:

      class SKWeapon extends UDKWeapon
      	abstract;
      	
      `if(`isdefined(Debug))
      var Vector DebugWeaponTraceStart;
      var Vector DebugWeaponTraceEnd;
      `endif
      
      ...
      
      state Active
      {
      	event Tick(float DeltaTime)
      	{
      		`if(`isdefined(Debug))
      		DebugWeaponTraceStart = Instigator.GetWeaponStartTraceLocation();
      		DebugWeaponTraceEnd = DebugWeaponTraceStart + vector(GetAdjustedAim(DebugWeaponTraceStart)) * GetTraceRange();
      		DrawDebugLine(DebugWeaponTraceStart, DebugWeaponTraceEnd, 255, 0, 0, False);
      		`endif	
      	}
      }
      ...
      

More information on weapons can be found on UDN at http://udn.epicgames.com/Three/WeaponsTechnicalGuide.html

Our game’s pawn is not derived from UTPawn which makes it hard to make use of the UTWeapon derived weapons.

Start by creating a base class for our weapons, derived from UDKWeapon.

class SKWeapon extends UDKWeapon
abstract;

defaultproperties

{

}

This will serve as the base class for all our weapons, hence its abstract. Lets now create a more specific weapon class.

class SKGravityGun extends SKWeapon;

defaultproperties
{
}

For quick setup and testing our weapon, lets add our new weapon as a default inventory item. When a Pawn is spawned by the game, the game calls Pawn.AddDefaultInventory, we’ll override this function (which is empty by default) and add our weapon using the Pawn.CreateInventory function.

/* AddDefaultInventory:
	Add Pawn default Inventory.
	Called from GameInfo.AddDefaultInventory()
*/
function AddDefaultInventory()
{
	local SKGravityGun GravityGun;

	// pass bDoNotActivate as False - the default to request activation of this inventory item
        GravityGun = SKGravityGun(CreateInventory(class'SKGravityGun', False));
	`log(GravityGun);
}

Attaching the weapon’s mesh

Our custom weapon is at this point in the pawn’s inventory, but you cant see it. Lets now add a skeletal mesh so we can see it in the pawn’s hand.

When a weapon is first activated, it is sent into the WeaponEquipping state which calls the Weapon.TimeWeaponEquipping function that is basically sets a timer that allows you to play the equip animation, attach meshes etc before the Weapon goes into an Active state.

The base Weapon class has an empty AttachWeaponTo function that we will override and call to run the attach logic.

simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName )
{
}

Then call the AttachWeapon function from within the TimeWeaponEquipping function.

/**
 * Sets the timing for equipping a weapon.
 * The WeaponEquipped event is trigged when expired
 */
simulated function TimeWeaponEquipping()
{
	// The weapon is equipped, attach it to the mesh.
	AttachWeaponTo( Instigator.Mesh );

	Super.TimeWeaponEquipping();
}

NOTE: Most of this is already implemented within the UT* classes and I’m basing this code of those.

Within our AttachWeaponTo function, we basically need to figure out which mesh to use as our weapon and determine which socket on our player to attach the weapon to.

The UT classes do it very well by having a separate class that holds the weapon’s skeletal mesh. This allows us to spawn this class when the weapon is equipped and destroy it when the weapon is un-equipped. Start by creating a SKWeaponAttachment base class then a subclass for our SKGravityGun.

class SKWeaponAttachment extends Actor
	abstract;

/** Weapon SkelMesh */
var SkeletalMeshComponent Mesh;

/**
 * Called on a client, this function Attaches the WeaponAttachment
 * to the Mesh.
 */
simulated function AttachTo(AcventurePawn OwnerPawn)
{
}

/**
 * Detach weapon from skeletal mesh
 */
simulated function DetachFrom( SkeletalMeshComponent MeshCpnt )
{
}

defaultproperties
{
	// Weapon SkeletalMesh
	Begin Object Class=SkeletalMeshComponent Name=SkeletalMeshComponent0
		bOwnerNoSee=true
		bOnlyOwnerSee=false
		CollideActors=false
		AlwaysLoadOnClient=true
		AlwaysLoadOnServer=true
		MaxDrawDistance=4000
		bForceRefPose=1
		bUpdateSkelWhenNotRendered=false
		bIgnoreControllersWhenNotRendered=true
		bOverrideAttachmentOwnerVisibility=true
		bAcceptsDynamicDecals=FALSE
		CastShadow=true
		bCastDynamicShadow=true
		bPerBoneMotionBlur=true
	End Object
	Mesh=SkeletalMeshComponent0
}

And the subclass to act as the attachment for the GravityGun

class SKGravityGunAttachment extends SKWeaponAttachment;
defaultproperties
{
    // Weapon SkeletalMesh
    Begin Object Name=SkeletalMeshComponent0
        SkeletalMesh=SkeletalMesh'WP_RocketLauncher.Mesh.SK_WP_RocketLauncher_3P'
    End Object
}

Within the AttachTo  functions we’ll attach to pawn’s mesh like this.

/**
 * Called on a client, this function Attaches the WeaponAttachment
 * to the Mesh.
 */
simulated function AttachTo(SKPawn OwnerPawn)
{
	if (OwnerPawn.Mesh != None)
	{
		// Attach Weapon mesh to player skelmesh
		if ( Mesh != None )
		{
			// Weapon Mesh Shadow
			Mesh.SetShadowParent(OwnerPawn.Mesh);
			Mesh.SetLightEnvironment(OwnerPawn.LightEnvironment);

			OwnerPawn.Mesh.AttachComponentToSocket(Mesh, OwnerPawn.WeaponSocket);
		}
	}
}

/**
 * Detach weapon from skeletal mesh
 */
simulated function DetachFrom( SkeletalMeshComponent MeshCpnt )
{
	// Weapon Mesh Shadow
	if ( Mesh != None )
	{
		Mesh.SetShadowParent(None);
		Mesh.SetLightEnvironment(None);
	}
	if ( MeshCpnt != None )
	{
		// detach weapon mesh from player skelmesh
		if ( Mesh != None )
		{
			MeshCpnt.DetachComponent( mesh );
		}
	}
}

We simply just attach the mesh within the attachment class to the pawn’s WeaponSocket. We need to define this socket variable on our pawn, which must exist on your pawn’s mesh and have a matching name i.e. WeaponPoint

class SKPawn extends UDKPawn;

...
/** WeaponSocket contains the name of the socket used for attaching weapons to this pawn. */
var name WeaponSocket;

...

defaultproperties
{
...
	// default bone names
	WeaponSocket=WeaponPoint

...
}

Next, we need to call this function, this is where it gets a bit interesting because of network replication. Start by defining a couple of variables, one to hold the class of the current attachment and another to old the actual attachment.

/** This holds the local copy of the current attachment.  This "attachment" actor will exist independantly on all clients */
var SKWeaponAttachment CurrentWeaponAttachment;

/** Holds the class type of the current weapon attachment.  Replicated to all clients. */
var repnotify class<SKWeaponAttachment> CurrentWeaponAttachmentClass;

The repnotify declaration tells the engine to call the pawn’s ReplicatedEvent function whenever this variable is updated. The idea behind this is that when running on a network, if a player on one computer equips a weapon, the engine on that player’s computer will send the updated value s to the server, which will in turn send it to other clients on the network. To get this to work, we have to:

      1. Tell the engine that we want the variable sent to the server whenever its updated
        class SKPawn extends UDKPawn;
        ...
        replication
        {
        	if ( bNetDirty )
        		CurrentWeaponAttachmentClass;
        }
        ...
      2. Tell the engine which function to call when this variable is updated.
        /**
         * Check on various replicated data and act accordingly.
         */
        simulated event ReplicatedEvent(name VarName)
        {
        
        	// If CurrentWeaponAttachmentClass has changed, the player has switched weapons and
        	// will need to update itself accordingly.
        	if ( VarName == 'CurrentWeaponAttachmentClass' )
        	{
        		WeaponAttachmentChanged();
        		return;
        	}
        	else
        	{
        		Super.ReplicatedEvent(VarName);
        	}
        }

You can read more on this on UDN – http://udn.epicgames.com/Three/NetworkingOverview.html#Replication

You’ll notice that we are calling a WeaponAttachmentChanged function which we have not defined yet, here it is.

/**
 * Called when there is a need to change the weapon attachment (either via
 * replication or locally if controlled.
 */
simulated function WeaponAttachmentChanged()
{
        // 1. None when we changed to empty hands, 2. compare classes so we dont change to the same weapon and 3. just make sure we have a skeletal mesh
	if ((CurrentWeaponAttachment == None || CurrentWeaponAttachment.Class != CurrentWeaponAttachmentClass) && Mesh.SkeletalMesh != None)
	{
		// Detach/Destroy the current attachment if we have one
		if (CurrentWeaponAttachment!=None)
		{
			CurrentWeaponAttachment.DetachFrom(Mesh);
			CurrentWeaponAttachment.Destroy();
		}
		// Create the new Attachment.
		if (CurrentWeaponAttachmentClass!=None)
		{
			CurrentWeaponAttachment = Spawn(CurrentWeaponAttachmentClass,self);
			CurrentWeaponAttachment.Instigator = self;
		}
		else
			CurrentWeaponAttachment = none;

		// If all is good, attach it to the Pawn's Mesh.
		if (CurrentWeaponAttachment != None)
		{
			CurrentWeaponAttachment.AttachTo(self);
		}
	}
}

And finally to tie it all together from the weapon class by updating the CurrentWeaponAttachmentClass variable for other clients on the network, while calling the function directly on the client that is actually making the change.

class SKWeapon extends UDKWeapon
	abstract;
...
/** The class of the attachment to spawn */
var class<SKWeaponAttachment> AttachmentClass;

...
/**
 * Attach Weapon Mesh
 *
 * @param	who is the pawn to attach to
 */
simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName )
{
	local SKPawn P;

	P = SKPawn(Instigator);
	// Spawn the 3rd Person Attachment
	if (Role == ROLE_Authority && P != None)
	{
		P.CurrentWeaponAttachmentClass = AttachmentClass;
		if (WorldInfo.NetMode == NM_ListenServer || WorldInfo.NetMode == NM_Standalone || (WorldInfo.NetMode == NM_Client && Instigator.IsLocallyControlled()))
		{
                        // call the function directly if we are the server, we're running standalone or we are the client that is changing our weapon
			P.WeaponAttachmentChanged();
		}
	}
}
...

Notice, we’ve added an AttachmentClass variable, each weapon subclass should set this to its attachment class so we know what to spawn.

class SKGravityGun extends SKWeapon;

defaultproperties
{
	AttachmentClass=class'SKGravityGunAttachment'
}

This time, I’m not providing a code listing so you can understand how all the pieces tie together by implementing it yourselves.

In the last tutorial, I mentioned that we could implement our camera update logic in our SKThirdPersonCamera.UpdateCamera function, this would technically work but would also result in a lot of unnecessary code – inherited from GameThirdPersonCamera.

Lets instead create a new camera type – SKSideScrollerCamera that inherits from GameCameraBase and override its UpdateCamera function where we will add our view calculations.

class SKSideScrollerCamera extends GameCameraBase;

/**
 * Called by PlayerCamera.UpdateViewTarget
 */
function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT)
{
}

defaultproperties
{
}

Take a moment to to figure out how we can get the game to use our new camera type.

And the right answer goes to assigning it to the SKPlayerCamera’s ThirdPersonCameraClass variable.

We now need to implement the UpdateCamera function to calculate our desired side-scroller view.

The player will only be moving along the X axis, either back or forward. The camera will be positioned to the right of the positive X axis with an offset of 400 units and will always be looking at the player – like in the diagram below.

On every frame/UpdateCamera call, we start off the calculation at our offset. The initial position is therefore at X=0.0, Y=400.0, Z=0.0. This position is relative to the world’s center. Since the player is moving, we need to make this position relative to the player instead. We do this by simple vector addition, the player is at X=10.0, Y=-5.0, Z=0.0, by adding our initial position, we get X=10.0, Y=395.0, Z=0.0.

class SKSideScrollerCamera extends GameCameraBase;

var Vector CamOffset;

/**
 * Called by PlayerCamera.UpdateViewTarget
 */
function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT)
{
    local Vector CamLocation;

    CamLocation = P.Location + CamOffset;	
    OutVT.POV.Location = CamLocation;
}

defaultproperties
{
	CamOffset=(X=0.f,Y=400.f,Z=0.f)
}

The camera is now always 400 units away from the player on the positive Y axis but is also facing the positive X axis and not the player.

We need to rotate the camera to look at the player. There a number of ways to do this, the simplest to to rotate the camera -90 degrees around the Z axis i.e. the Yaw.

NOTE: Unreal rotation units are expressed by values ranging from 0 to 65536 with 0 being 0 degrees and 65536 being 360 degrees. We will use the DegToUnrRot constant to convert by simple multiplication.

...
OutVT.POV.Rotation = DegToUnrRot * -90;
...

Lets also modify our offset a bit to have a bit of elevation. We do this by modifying the Z offset value.

Our final camera code looks like this.

class SKSideScrollerCamera extends GameCameraBase;

var Vector CamOffset;

/**
 * Called by PlayerCamera.UpdateViewTarget
 */
function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT)
{
	local Vector CamLocation;
	local Rotator CamRotation;

	// calculate the new location
	CamLocation = P.Location + CamOffset;

    // calculate the new rotation
    CamRotation.Yaw = DegToUnrRot * -90;
    CamRotation.Pitch = 0;
    CamRotation.Roll = 0;

    OutVT.POV.Location = CamLocation;
    OutVT.POV.Rotation = CamRotation;
}

defaultproperties
{
	CamOffset=(X=0.f,Y=400.f,Z=50.f)
}

The camera rotation can also be calculated using the direction between the player’s and the camera’s locations. This would be a more flexible approach if  for example the player could move along an arbitrary axis. To get this direction, we do some vector subtraction. Since we want the direction from the camera to the player, we subtract the camera’s location from the player’s.

...
local Vector CamDir;

CamDir = P.Location - CamLocation;
...

From our diagram above, this will give us the vector X=0.0, Y=-400.0, Z=0.0. Since its a direction vector, we need to normalize it.

As a direction, there is no difference between our subtraction result vector and its normalized representation, X=0.0, Y=-1.0,  Z=0.0, they both go directly up the negative Y axis.

CamDir = Normal(P.Location - CamLocation);

We then need to convert the direction vector into a rotation. We do this by constructing a Rotator with the vector, we end up with a Rotator oriented in the vectors direction.

CamRotation = Rotator(CamDir);

And the final code listing using the subtraction method

class SKSideScrollerCamera extends GameCameraBase;

var Vector CamOffset;

/**
 * Called by PlayerCamera.UpdateViewTarget
 */
function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT)
{
	local Vector CamLocation;
	local Rotator CamRotation;
	local Vector CamDir;

	/// get the new location
	CamLocation = P.Location + CamOffset;

	/// get the new direction
    CamDir = Normal(OutVT.Target.Location - CamLocation);
    CamRotation = Rotator(CamDir);

    OutVT.POV.Location = CamLocation;
    OutVT.POV.Rotation = CamRotation;
}

defaultproperties
{
	CamOffset=(X=0.f,Y=400.f,Z=50.f)
}

There are a two ways of  implementing a Camera in UDK.

  1. The first and quickest is by overriding your Pawn’s CalcCamera function and calculating the view rotation and location and returning those.
  2. The second, more flexible and my personal choice is by using the camera class. It has a number of advantages one of which is allowing you full access to Camera animations and post process effects.

We’ll only be talking about the second but if you would like more information, you can look ate the CalCamera example here – http://udn.epicgames.com/Three/CameraTechnicalGuide.html

The Unreal Camera System

Here is a breakdown of how the camera system works. The PlayerController class has a variable – CameraClass which should point to a subclass of Camera, and is by default set to  Camera. A camera object of type CameraClass is spawned in the PlayerController.SpawnPlayerCamera event which is called by the engine. The spawned camera is then assigned to the PlayerController.PlayerCamera variable.

Camera update happens in the Camera’s UpdateCamera event, which is called by the engine on every frame (after all the actors have been ticked). It in-turn calls Camera.DoUpdateCamera. This function handles things like camera switching, the camera’s target switching and fading. It also calls the Camera.UpdateViewTarget which is where the actual camera update happens.

Custom Camera

Lets start by implementing a camera for our gametype, ideally this is where you would add logic to support all the different types of views and camera behaviors for your game.

class SKPlayerCamera extends GamePlayerCamera;

defaultproperties
{
}

I chose to extend from GamePlayerCamera (a subclass of Camera) only because it already implements some of the base Camera functionality as well as some useful variables we will make use of, chief of which are ThirdPersonCameraClass, ThirdPersonCam, FixedCameraClass and FixedCam.

These should be sub-classes of GameCameraBase. UDK by default has implementations for a 3rd person camera and a fixed camera.

NOTE: Our player controller controls the player camera – SKPlayerCamera – an indirect subclass of camera but our player camera controls Fixed and ThirdPerson camera types – subclasses of GameCameraBase. The player camera is the same for the entire game (or at least for a certain pawn) but the camera types can be changed on every frame.

Lets assign our new class as our PlayerController.CameraClass to have it spawned as our PlayerCamera.

class SKPlayerController extends UDKPlayerController;

defaultproperties
{
	CameraClass=class'SKPlayerCamera'
}

At the moment, our camera/view logic is the same as not having our own custom player camera. This is because our Camera.CameraStyle is set to FirstPerson. This happens when our Pawn is possessed and its Pawn.GetDefaultCameraMode function is called and returns FirstPerson.

Lets fix this by overriding the function and returning default. Which is what GamePlayerCamera.FindBestCameraType expects for it to return either a Fixed or ThirdPersonCam depending on whether we are using a CameraActor or just the player camera.

class SKPawn extends UDKPawn;
...
/**
 * returns default camera mode when viewing this pawn.
 * Mainly called when controller possesses this pawn.
 *
 * @param	PlayerController requesting the default camera view
 * @return	default camera view player should use when controlling this pawn.
 */
simulated function name GetDefaultCameraMode( PlayerController RequestedBy )
{
	return 'default';
}

defaultproperties
{
...

If you run the game now, you should just get a black view, that is because our ThirdPersonCam which is the camera type that we are using to view is missing a key component, a valid field of view.  By default, the ThirdPersonCam is spawned using the value of  GamePlayerCamera.ThirdPersonCameraClass which right now is GameThirdPersonCamera. It contains an event – GameThirdPersonCamera.GetDesiredFOV.

...
/** returns camera mode desired FOV */
event float GetDesiredFOV( Pawn ViewedPawn )
{
	if ( bFocusPointSet && (FocusPoint.CameraFOV > 0.f) && bFocusPointSuccessful )
	{
		return FocusPoint.CameraFOV;
	}

	return CurrentCamMode.GetDesiredFOV(ViewedPawn);
}
...

It returns a valid FOV if:

  1. The camera has been instructed to focus at a certain point or actor
  2. By querying its CurrentCamMode object for the FOV

Introducing the CameraMode object

So far we’ve seen that the player controller has a player camera and the player camera has one or more camera types.

The camera type also manages one or more camera modes e.g. your third person camera type could have a mode where it zooms out very far from the player to show a wider view of the world. We also need to implement a custom Camera mode and have it return a valid FOV angle to our camera type.

class SKThirdPersonCameraMode_Default extends GameThirdPersonCameraMode_Default;

/** Returns FOV that this camera mode desires. */
function float GetDesiredFOV( Pawn ViewedPawn )
{
	return 70.0;
}

defaultproperties
{
}

Then we need to assign our new class as the default third person camera mode for our third person camera type.

class SKThirdPersonCamera extends GameThirdPersonCamera;

defaultproperties
{
	ThirdPersonCamDefaultClass=class'SKThirdPersonCameraMode_Default'
}

Lets adjust the mode’s view offset a bit so we can have a better view of the player. I’d like to have the camera move further behind the player. We do this by adjusting the view offset variable by doubling the X offset in the default properties of the GameThirdPersonCameraMode_Default from

    ViewOffset={(
       OffsetHigh=(X=-128,Y=56,Z=40),
       OffsetLow=(X=-160,Y=48,Z=56),
       OffsetMid=(X=-160,Y=48,Z=16),
    )}

to

    ViewOffset={(
       OffsetHigh=(X=-256,Y=56,Z=40),
       OffsetLow=(X=-320,Y=48,Z=56),
       OffsetMid=(X=-320,Y=48,Z=16),
    )}

within our camera mode, SKThirdPersonCameraMode_Default default properties

EDIT: Finally, the the third person camera class must be set on our player camera.

class SKPlayerCamera extends GamePlayerCamera;

defaultproperties
{
   ThirdPersonCameraClass=class'SKThirdPersoncamera'
}

NOTE: Its worth noting that we did not have to go through the whole process of creating a camera mode, we could have simply implemented our own camera logic in our SKThirdPersonCamera.UpdateCamera which we will do in the next tutorial when we implement s side-scroller camera.

Our game is a 3rd person game where the player’s character is visible to the user. We therefore need to implement a 3rd person pawn. We’ll make use of the SK_CH_IronGuard_MaleA skeletal mesh for this.

Leats start by creating a custom pawn class – SKPawn,

class SKPawn extends UDKPawn;

defaultproperties
{
}

Lets set this as the default pawn for our game type. We do this in the game class by assigning the class to the DefaultPawnClass variable.

class SKGame extends UDKGame;
...
defaultproperties
{
  DefaultPawnClass=class'SKPawn'
}

Lets do the same witha custom player controller, create an empty player controller class naes SKPlayerController and extend UDkPlayerController

class SKPlayerController extends UDKPlayerController;

defaultproperties
{
}

Then set this our our game type’s player controller class by assigning the class to the PlayerControllerClass variable of the game class.

class SKGame extends UDKGame;
...
defaultproperties
{
  PlayerControllerClass=class'SKPlayerController'
  DefaultPawnClass=class'SKPawn'
}

Adding the player character’s mesh

Adding a skeletal mesh is done by creating a skeletal mesh component and adding it to our actors list of components. The base pawn class already has a variable aptly named Mesh for this. We simply need to create a mesh to our liking, add it to the pawn’s list of components and assign it to said variable so that we can refer to it when needed e.g. to turn the mesh into a ragdoll.

This is what we end up with:

class SKPawn extends UDKPawn;

defaultproperties
{
  Components.Remove(Sprite)

  Begin Object Name=CharacterSkeletalMeshComponent
    AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
    SkeletalMesh=SkeletalMesh'CH_IronGuard_Male.Mesh.SK_CH_IronGuard_MaleA'
    PhysAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'
    AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
  End Object
  Mesh=CharacterSkeletalMeshComponent
  Components.Add(CharacterSkeletalMeshComponent)
}

These are all variables of the SkeletalMeshComponent class. They are the bare minimum that we need to get our character mesh working.

AnimTreeTemplate – Points to the animation tree this skeletalmesh uses. This is what determines what animation to play when running as opposed to jumping, crouching etc.

SkeletalMesh – The actual skeletal mesh

PhysAsset – The physics representation of our skeletal mesh, mostly used when in ragdoll

Lets copy some more values from the UTPawn’s implementation. One important value is the LightEnvironment which allows an accurate representation of both static and dynamic lights affecting our skeletal mesh.

And here is the final outcome of our pawn class.

class SKPawn extends UDKPawn;

/** The pawn's light environment */
var DynamicLightEnvironmentComponent LightEnvironment;

defaultproperties
{
  Components.Remove(Sprite)

  Begin Object Name=DynamicLightEnvironment
    bSynthesizeSHLight=TRUE
    bIsCharacterLightEnvironment=TRUE
    bUseBooleanEnvironmentShadowing=FALSE
    InvisibleUpdateTime=1
    MinTimeBetweenFullUpdates=.2
  End Object
  Components.Add(DynamicLightEnvironment)
  LightEnvironment=DynamicLightEnvironment

  Begin Object Name=CharacterSkeletalMeshComponent
    bCacheAnimSequenceNodes=FALSE
    AlwaysLoadOnClient=true
    AlwaysLoadOnServer=true
    CastShadow=true
    BlockRigidBody=TRUE
    bUpdateSkelWhenNotRendered=false
    bIgnoreControllersWhenNotRendered=TRUE
    bUpdateKinematicBonesFromAnimation=true
    bCastDynamicShadow=true
    Translation=(Z=8.0)
    RBChannel=RBCC_Untitled3
    RBCollideWithChannels=(Untitled3=true)
    LightEnvironment=DynamicLightEnvironment
    bOverrideAttachmentOwnerVisibility=true
    bAcceptsDynamicDecals=FALSE
    AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
    bHasPhysicsAssetInstance=true
    TickGroup=TG_PreAsyncWork
    MinDistFactorForKinematicUpdate=0.2
    bChartDistanceFactor=true
    //bSkipAllUpdateWhenPhysicsAsleep=TRUE
    RBDominanceGroup=20
    Scale=1.075
    // Nice lighting for hair
    bUseOnePassLightingOnTranslucency=TRUE
    bPerBoneMotionBlur=true
    SkeletalMesh=SkeletalMesh'CH_IronGuard_Male.Mesh.SK_CH_IronGuard_MaleA'
    PhysAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'
    AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
  End Object
  Mesh=CharacterSkeletalMeshComponent
  Components.Add(CharacterSkeletalMeshComponent)
}

Now when we run the game, we can only see the mesh if we tilt the camera down, the camera is more or less still in first person view. We’ll fix this in the next tutorial.

This assumes that you are already farmiliar with UDK directory structure and you can setup your game directory and its entry in the .ini file to have its scripts compiled. If not, you should go here – http://udn.epicgames.com/Three/BasicGameQuickStart.html before proceeding.

A lot has been written about implementing game types – by me as well here – http://law3d.blogspot.com/2009/12/scripting-simple-game-in-udk_18.html

Rather than repeat myself, I’ll go ahead to talk about creating a custom game type for a side-scroller/platformer type game. The objective of the game is to move forward, avoid obstacles and use platforms to gather as many collectables as you can. I will only be using assets already available in the UDK distribution to make it easy to follow along.

I’ll call the game “Side Kick”, so lets start by creating a directory for it and naming it SKGame
Don’t forget to add the entry in the UnrealEd.EditorEngine section of your DefaultEngine.ini

We’re now ready to start writing some code for it so open up you prefered editor. I prefer to use Unrealscript IDE – http://uside.codeplex.com/ mostly because of its ability to set breakpoints and look at variables while the game is running. Its also a great editor in the usual way, jumping to function implementations, auto-completion, syntax highlighting etc.

Lets start by creating the game-type class. Name it SKGame and extend from GameInfo.

class SKGame extends GameInfo;

defaultproperties
{

}

I prefer to extend from GameInfo and other non-UT classes so my classes have a cleaner implementation, without all the UT specific implementations/variables etc. Plus I learn more about the inner workings of the engine if I have to do things myself – sometimes by looking at the UT implementation and doing something similar.

Next we need to let the engine know that it should load our game-type when it starts. We do this by modifying the DefaultGame.ini and setting the DefaultGame and DefaultServerGame to SKGame.SKGame

You can modify the UDKGame.ini but in-case DefaultGame.ini changes, the changes to UDKGame.ini get overridden so I prefer to make changes to the former.

Running the game gives you the default menu but trust me, the custom game is running, you can check this by looking at the logs at <UDK Directory>\UDKGame\Logs\Launch.log, there will be a line that says Log: Game class is ‘SKGame’

Lets change the map thats is loaded, although generally, you will want something similar, to have your menu show up as the first thing. Open up your DefaultEngine.ini and change the LocalMap entry from UDKFrontEndMap.udk to DM-Deck.udk, now when you run the game, it loads the DM-Deck map where you can move around but not do much else.

Of game-types and map prefixes
So now lets change the parent class of uour custom game from GameInfo to UDKGame.

class SKGame extends UDKGame;
…

Run the game, you will notice that it runs the UT game. The reason for this is that, since we are now extending from UDKGame (which extends from SimpleGame), the type of game loaded is determined by the prefix of the map. This is implemented by the SetGameType function in the SimpleGame class.

EDIT: If the game type is set on the map from within the editor, that takes precedence and the below steps do not apply.

It starts by letting the UTGame handle it if the map’s name is EnvyEntry or UDKFrontEndMap. If not, it then checks if the type of game was specified in the commandline. If that fails, which in our case it should, checks the map prefix i.e. anything before the dash. In our case it id DM. This prefix is looked up in the DefaultMapPrefixes array. You can find this in our DefaultGame.ini and looks something like this:

+DefaultMapPrefixes=(Prefix="DM",bUsesCommonPackage=FALSE,GameType="UTGame.UTDeathmatch")
+DefaultMapPrefixes=(Prefix="CTF",bUsesCommonPackage=FALSE,GameType="UTGameContent.UTCTFGame_Content")
+DefaultMapPrefixes=(Prefix="VCTF",bUsesCommonPackage=FALSE,GameType="UTGameContent.UTVehicleCTFGame_Content")

To fix this we simply override the function and return the current class to be used as the game-type.

static event class<GameInfo> SetGameType(string MapName, string Options, string Portal)
{
return Default.class;
}

We’ll add to it later when we are creating our main menu.

We now have a custom game type that works on any of the maps with a first person view.

The entire class is pretty empty and looks like this

class SKGame extends UDKGame;

static event class<GameInfo> SetGameType(string MapName, string Options, string Portal)
{
return Default.class;
}

defaultproperties
{

}

We’ll also keep adding to it as we implement more of the game.

I’ve been using UDK for about 2 years now and although I’ve learnt a lot in this period, I still have  lot to learn. This tutorial series is my way of learning some more and of sharing my experiences so others can learn as well.

My aim is to give a guideline on how to implement the basic features of a complete single player game in UDK. In my opinion these features are:

  • A custom gametype
  • Custom pawn and player-controller
  • A custom camera to suit your game
  • Player movement to go with your camera
  • Save checkpoints
  • Player death and restarting from last checkpoint
  • Simple scoring system


I will add-on to the list as required but as of now these are the features I have in mind.

Walk your Pawn

Posted: February 9, 2010 in UDK

So UTPawn, which many UDK users are subclassing does not walk, it only runs – has some place to be I guess. This short tutorial will show you how to make your Pawn walk.

When the PlayerController is calculating its movement, it calls its HandleWalking function to check if the Pawn wants to walk. In its definition, you will notice a check against a variable bRun.

function HandleWalking()
{
if ( Pawn != None )
Pawn.SetWalking( bRun != 0 );
}

bRun is an input variable whose value is set when you hold down the Left Shift key. A search for bRun in your UTInput.ini will give you this.

Bindings=(Name=”Walking”,Command=”Button bRun”)

and a search for walking will give you this.

Bindings=(Name=”LeftShift”,Command=”Walking”)

If (bRun != 0) i.e. Pawn wants to walk, the Pawn’s SetWalking funtion is called. Looking at this function’s definition in UTPawn, you will notice that its blank and doesnt do anything. Assuming you have a subclass of UTPawn as your game’s default pawn, you need to override the SetWalking function. You should end up with something similar to this.

class Mypawn extends UTPawn;

event SetWalking( bool bNewIsWalking )
{
super(Pawn).SetWalking(bNewIsWalking);
}

defaultproperties
{
}

Super refers to our parent class which in this case is UTPawn but by adding Pawn in parenthesis, we’re actually calling the the SetWalking function in the Pawn class and not in UTPawn.

If you try this out, you will notice that what we have done is just make the character move slower but he still looks like he is running. To fix this, we have to play around with the AnimTree so fire up the editor. What we want to do is use a different animation when the pawn is walking. Walking reduces the pawn’s speed/velocity.

Assuming you are using the default Anim Tree, add an UTAnimBlendBySpeed node like in the image below.

You will notice that I’ve set the minimum speed to 220 and the maximum speed to 440. By default, the UTPawn’s maximum speed is 440 (GroundSpeed=440.0 in UTPawn.uc default properties) and its walking speed is half of this – so 220 (WalkingPct=+0.5 in Pawn.uc). So basically when the pawn is at full speed, the fast branch of the anim node is used entirelyand when walking the slow branch is used and when in the middle, the animations are blended together.

Thats it, enjoy walking.