UT2003 - Weapon Coding Tutorial

Posted:

A lot of people seem to want to create their own weapons, so I will attempt to put what I have learnt down in writing. This tutorial addresses the coding part only, leaving modelling, animation, sound-effects etc. for which there are other tutorials in existence. You will need to have a basic knowledge of how to write UScript and compile it. If you have no idea about that yet, then a good place to start is the Wiki, and also the UDN. Another invaluable source of information is the UT2003 source code, available on the Unreal Technology site.

Stage 1: Creating a 'blank' weapon.

A good way to get started is to create an empty framework for the weapon, with just what is necessary to get it to appear in-game. It is necessary to subclass a number of classes to make a weapon. I suggest you look through each of them to get an idea of what they do and all the useful functions and variables they contain:

  • Weapon: The weapon itself, including 1st person model and effects.
  • UTWeaponPickup: What you pick up to get the weapon.
  • WeaponFire (usually one if its subclasses, InstantFire or ProjectileFire): For handling most of the actual functionality.
  • xWeaponAttachment: The 3rd person model and effects.
  • Ammunition: Determines how much ammo can be held and some other stuff.
  • UTAmmoPickup: Ammo that you see on the ground.
  • WeaponDamageType: For the death messages and any special damage/death effects.

At first, all that is necessary are the classes themselves with a few default properties. I like to copy most of the default properties from an existing weapon as a starting point. The example weapon will thus look like the lightning gun for the most part.

Weapon:

I shall call the example weapon the 'Conspigulator'. This will also be the package name.

class Conspigulator extends Weapon config(user); //So weapon preference settings are stored alongside all the standard weapons. defaultproperties { ItemName="Conspigulator" //The text you see when you switch to the weapon and in various other places. IconMaterial=Material'InterfaceContent.Hud.SkinA' //Texture where the icon to be show in the weapon bar is. IconCoords=(X1=322,Y1=372,X2=444,Y2=462) //Position of icon in that texture. FireModeClass(0)=ConspigulatorFire FireModeClass(1)=ConspigulatorAltFire DrawScale=0.4 Mesh=mesh'Weapons.Sniper_1st' //What you see in 1st person. BobDamping=2.3 //How much the weapon moves about as you walk. PickupClass=class'ConspigulatorPickup' EffectOffset=(X=100,Y=28,Z=-19) //Where effects are drawn. Used for start location of InstantFire beam effects. PutDownAnim=PutDown DisplayFOV=60 PlayerViewOffset=(X=0,Y=2.3,Z=0) //Position of the weapon in 1st person PlayerViewPivot=(Pitch=0,Roll=0,Yaw=0) //Rotation of the model in 1st person. UV2Texture=Material'XGameShaders.WeaponEnvShader' //What does this do? AttachmentClass=class'ConspigulatorAttachment' SelectSound=Sound'WeaponSounds.LightningGun.SwitchToLightningGun' SelectForce="SwitchToLightningGun" //The various 'Force' variables are for force feedback mice etc. //There is no way to test them without such a device, but it is probably a good idea to include them anyway, for completeness. AIRating=0.69 //How much bots want to use this weapon. CurrentRating=0.69 DefaultPriority=3 InventoryGroup=9 //Which slot the weapon occupies. GroupOffset=1 //This must be different to all other weapons that occupy the same InventoryGroup. }

UTWeaponPickup:

class ConspigulatorPickup extends UTWeaponPickup; defaultproperties { InventoryType=class'Conspigulator' PickupMessage="You got the Conspigulator." PickupSound=Sound'PickupSounds.SniperRiflePickup' PickupForce="SniperRiflePickup" MaxDesireability=0.65 //How much bots want this weapon. StaticMesh=StaticMesh'WeaponStaticMesh.SniperRiflePickup' DrawType=DT_StaticMesh DrawScale=0.5 }

WeaponFire:

Primary fire will be an InstantFire, and secondary will be a ProjectileFire.

class ConspigulatorFire extends InstantFire; defaultproperties { FireSound=Sound'WeaponSounds.BLightningGunFire' FireForce="LightningGunFire" TraceRange=17000 //Maximum range of the weapon. Momentum=1000 //How much the victim is pushed when hit. AmmoClass=class'ConspigulatorAmmo' AmmoPerFire=1 DamageType=class'DamTypeConspigulator' DamageMin=10 //Min and Max can be different, to do a random damage between the two, or the same to do consistent damage. DamageMax=10 FireRate=0.3 //How fast it fires. BotRefireRate=0.3 //How fast bots should try to fire. AimError=800 //How accurate bots are. bPawnRapidFireAnim=false //These settings control how your view shakes when you fire. ShakeOffsetMag=(X=-15.0,Y=0.0,Z=10.0) ShakeOffsetRate=(X=-4000.0,Y=0.0,Z=4000.0) ShakeOffsetTime=1.6 ShakeRotMag=(X=0.0,Y=0.0,Z=0.0) ShakeRotRate=(X=0.0,Y=0.0,Z=0.0) ShakeRotTime=2 } class ConspigulatorAltFire extends ProjectileFire; defaultproperties { AmmoClass=class'ConspigulatorAmmo' AmmoPerFire=1 FireAnim=Fire FireAnimRate=1.0 ProjectileClass=class'XWeapons.RocketProj' //ProjectileFire will require a Projectile class too, but here I shall just use standard rockets. ProjSpawnOffset=(X=25,Y=6,Z=-6) //Where the projectile is spawned. FireSound=Sound'WeaponSounds.RocketLauncher.RocketLauncherFire' FireForce="RocketLauncherFire" FireRate=1.0 BotRefireRate=1.0 bSplashDamage=true bRecommendSplashDamage=true bSplashJump=true WarnTargetPct=0.9 ShakeOffsetMag=(X=-20.0,Y=0.00,Z=0.00) ShakeOffsetRate=(X=-1000.0,Y=0.0,Z=0.0) ShakeOffsetTime=2 ShakeRotMag=(X=0.0,Y=0.0,Z=0.0) ShakeRotRate=(X=0.0,Y=0.0,Z=0.0) ShakeRotTime=2 }

xWeaponAttachment:

class ConspigulatorAttachment extends xWeaponAttachment; defaultproperties { bHeavy=false //Whether the 3rd person player should hold the weapon up to their face or down by their waist. bRapidFire=false //Determines what sort of animation the 3rd person player model will play when firing. bAltRapidFire=false Mesh=mesh'Weapons.Sniper_3rd' }

Ammunition:

class ConspigulatorAmmo extends Ammunition; defaultproperties { ItemName="Conspigulator Ammo" IconMaterial=Material'InterfaceContent.Hud.SkinA' IconCoords=(X1=445,Y1=150,X2=544,Y2=224) PickupClass=class'ConspigulatorAmmoPickup' MaxAmmo=60 //Can hold up to this much ammo. InitialAmount=20 //Start with this much ammo when the weapon is first picked up. }

UTAmmoPickup:

class ConspigulatorAmmoPickup extends UTAmmoPickup; defaultproperties { InventoryType=class'ConspigulatorAmmo' PickupMessage="You picked up some Conspigulator ammo." PickupSound=Sound'PickupSounds.SniperAmmoPickup' PickupForce="SniperAmmoPickup" AmmoAmount=20 //Get this much ammo from picking up an ammo pickup. CollisionHeight=19.0 StaticMesh=StaticMesh'WeaponStaticMesh.SniperAmmoPickup' DrawType=DT_StaticMesh DrawScale=1.0 }

WeaponDamageType:

class DamTypeConspigulator extends WeaponDamageType abstract; defaultproperties { DeathString="%o was conspigulated by %k." MaleSuicide="%o conspigulated himself." FemaleSuicide="%o conspigulated herself." WeaponClass=class'Conspigulator' //Must point to the weapon. Used for stats and other things. }

Conspigulator.int

In addition to all those classes, you will need in .int file which UT2003 and various mutators can read to know that the weapon exists. It must have the same name as the main package and will need one line for the weapon:

Object=(Class=Class,MetaClass=Engine.Weapon,Name=Conspigulator.Conspigulator,Description="The Conspigulator conspigulates things.")

Stage 2: Testing.

Compile all those classes and you should end up with a shiny new file called Conspigulator.u which contains a fully functional weapon (isn't OOP great :) ). Start a game in UT2003 and manually summon the weapon by typing this at the console:

summon Conspigulator.ConspigulatorPickup

A weapon pickup should appear in front of you. Pick it up! Pressing primary fire will be invisible because no effects have been added yet, but it will do damage as normal. Secondary fire will launch a rocket.

Stage 3: The interesting bit.

Now you have a weapon which is useable in-game, so it's time to make it work in a more interesting way, add some effects and generally make it a proper weapon...

...but I have to leave something for you to do yourself :)