Introduction
My name is DAMN|EvilJesús aka Jeffrey Bakker. This is my attempt to teach how to make weapons for Unreal Tournament 2003/2004, and for other games based on the Unreal engine. I cannot teach you entirely everything I know here, because you might be overwhelmed by the amount of info taken in all at once. So, I'll take it slow, and start off with the basics. After you get past learning foundation code, there will be a point where things you'll have to learn on your own, because a lot of the Epic code is undocumented. Throughout this tutorial, I will assume that you have a working knowledge in an Object Oriented programming, the UnrealScript syntax, and the Unreal Editor. This document is not meant to teach either of these things, but there are other fine documents out there that do. Some may be able to get by this tutorial without a complete grasp of the above, but I strongly recommend it if you want to actually know exactly what you're doing when you're making Unreal mods.
The example weapon that I will be making is actually being coded as I write this tutorial. I am doing it this way because I believe it is the best way to teach it, and a more interactive way of writing it. Rather than writing an analysis or explanation for pre-written code, I am documenting my experience as I am coding. And in fact, I am trying a couple of things in this example that I've never done before, so we are both learning something here. :)
Top^
Getting Started
Folders and Files
To start making a gun, or any mod for UT2003, you must first make a folder inside the main directory of UT2003. The name you choose for this folder will be the name of your "Package". A good example would be:
C:\UT2003\MyUTGun
Now, inside the folder of your mod, you must also make a Classes folder. This is where all the source code files will go. Files contained in this folder are just plain text files created in notepad (yes, that is what I use), saved with .uc as the extension instead of .txt at the end. Each of the classes you make will be in a separate .uc file.
Exporting Epic Code
At any given time while you're making a mod, you'll want to be able to look up the classes for data members and functions. A lot of the code is undocumented or uncommented, and you will find yourself reading code trying to figure out what functions to call, and what parameters they accept and data types they return.
To be able to look up Epic Games' source code, you can open up the Unreal Editor and go to the Actor Classes Browser. From the menu, click on File->Export all scripts. Then you'll notice a bunch of new folders full of .uc source code files for the packages that make up the game. For making weapons, I recommend reading some of the source code in the XWeapons, XEffects, and a little from the Engine folders.
Compiling
This isn't going to be until later, but once you have made your class(es), you will need to compile the code. In order to do that you'll need to tell add your package to the edit packages list in the UT2003.ini file. For example, find the section, and add the name of your package to the list...
[Editor.EditorEngine]
...
EditPackages=MyUTGun
..to let the compiler know that it needs to be compiled. You only have to do this once. After that, any time you need to compile, you can open up an MS-Dos prompt window and change into your UT2003\System directory, and type the following:
ucc make
And that will compile the code and let you know if there were any errors or warnings. An error usually tells you which .uc file the error took place and on which line of code, with a brief description of the error. Let's hope not to get any, shall we. ;) And if there are no errors, you should have a new .u file named after your package name, eg. MyUTGun.u in your UT2003\System folder. It's a good thing to compile often, after even just small changes to your code. This will prevent any surprises from happening after writing 300 lines of code at once, then getting 15 errors all in a row. Note that every time you recompile, you must delete (or preferably backup) the old .u file first, or ucc make will not compile it.
Useful Tools
As long as you have your UT2003/UT2004 installed, you're all set for Unreal modding. UT200x already comes installed with UEd (Unreal Editor), UDebugger.exe, and ucc.exe (or ucc-bin on Linux) for compiling. And your operating system of course has a text editor which you can write the code in. I personally use notepad.
Although they are not required to make mods, here are a few useful tools that make some of the work a little bit easier:
UMake - A GUI program that handles the compiling for you, if you'd rather stay away from the command line. It automates most of the compiling for you. All you need to do is select the root directory of your Package and click on compile. It will generate its own make.ini file in your package directory, so there is no need to edit your ut2004.ini file to compile you mod.
WOTgreal - A very nicely done IDE (Integrated Development Environment) for UnrealScript. You can buy it for a fairly low price, or use the trial version, if you don't mind nag screens.
Top^
The Classes
A Quick Note
The classes you make in UnrealScript have to be the same name as their filename. For instance if you name your class 'MyOwnGun', you must also name the file 'MyOwnGun.uc'. And this following information important to know for making .int files that identify your mod to the engine: to refer to a class in your mod, you must use the PackageName.ClassName format. For example:
MyUTGun.MyOwnGun
This is not completely necessary to refer to other classes inside the same package, but just in case another package has a class with the same name, you should use the PackageName.ClassName convention. Technically, if another package had a class with the same name, the compiler would check the current package first for a class of that name...but why not keep with the coding standard?
The Classes All Fit Together
In the Unreal engine, a single weapon consists of many classes. Each of the classes are linked together in one way or another. You can mix and match existing classes or your own classes, as long as you know how they connect with each other. The main class for a weapon of course, is the one that is derived either directly or indirectly from the Weapon class. Here is a simple hierarchy of the relations between each of the classes, as far as weapons go:
Weapon | ----------------------------------------- | | | xWeaponAttachment WeaponFire[2] UTWeaponPickup | / | \ xEmitter xEmitter | Ammunition(opt) | \ Projectile(opt) UTAmmoPickup / \ xEmitter(multi) WeaponDamageType / \ SpecialKillMessage(opt) xEmitter[4](opt)
Don't be too alarmed, you don't have to make all of these classes. You can use some of the existing classes if they suit your needs. And if you want to add on functionality to an existing class, always subclass it. Subclassing is good, because it reduces code redundancy, your code will be smaller and more clear to read, plus it promotes object oriented programming.
The Weapon Class
Alright, if you've made it this far, then you're serious about making weapons. ;) This is where the fun begins...time to see some code. Don't worry, I'll start off easy. The first class to define for your weapon, should be the weapon itself. As you can see here, you can derive your subclass from an existing weapon class, and your class will inherit all of it's data members and functions (unless they are declared private). You may override or expand on those functions, as well as default properties for the data members, or add your own data members as well. I strongly recommend subclassing and overriding whenever possible, rather than copying Epic code line-by-line and modifying it after. I am not going to override anything too fancy here, I am just showing you the basics, and which important defaultproperties to change to modify the functionality of a weapon.
1: // MyOwnGun.uc
2:
3: // This class will inherit all the code from it's parent class
4: // Look at the source code of the parent class(es) to see which
5: // functions can be overidden
6:
7: Class MyOwnGun extends ShockRifle;
8:
9: // Primary or alt-fire?
10: function byte BestMode()
11: {
12: return byte(Rand(2)); // choose randomly
13: }
14:
15: defaultproperties
16: {
17: ItemName="My Own Gun" // Name of the gun
18:
19: AttachmentClass=Class'MyUTGun.MyGunAttachment' // 3rd person view class
20:
21: // You can make your own fire mode
22: // classes or use existing ones
23: FireModeClass(0)=Class'MyUTGun.MyGunFire' // Primary Fire class
24: FireModeClass(1)=Class'MyUTGun.MyGunAltFire' // Alternate Fire class
25:
26:
27: PickupClass=None // Pickup class
28: InventoryGroup=4 // Weapon bar
29:
30:
31: // To change these, you'd have to first look up the names
32: // of the models and animations in UEd's Animation Browser
33: Mesh=SkeletalMesh'Weapons.ShockRifle_1st' // 1st person Model and animations
34: SelectAnim="Pickup" // Pickup animation sequence
35: PutDownAnim="PutDown" // Putdown animation sequence
36:
37: // Skins for the weapon
38: Skins(0)=FinalBlend'DeRez.Shaders.DeRezFinalBody'
39: Skins(1)=FinalBlend'DeRez.Shaders.DeRezFinalHead'
40: Skins(2)=Shader'WeaponSkins.ShockLaser.LaserShader'
41:
42: // Weapon select sound
43: SelectSound=Sound'WeaponSounds.ShockRifle.SwitchToShockRifle'
44: SelectForce="SwitchToShockRifle" // Force feedback
45: }
As you can see, I've derived the class from the ShockRifle class. You can choose any class you'd like and It will inherit the look and feel of that weapon, or you can just derive from the Weapon class and make and specify your own models. Notice under the defaultproperties in lines 25-27, you can choose the mesh to use. You don't have to do this unless you want the gun to use a different 3d model. You can simply exclude those lines if you are not planning on using a custom model. In lines 38-40, you can specify to use a different skin, whether it's a custom skin or an existing texture or shader from the game. We'll talk more about that later on in this tutorial.
The Attachment Class
This class defines the gun's appearance and effects at a third person perspective. If you have derived your weapon class from another weapon, it is also wise to derive your Attachment class from that weapon as well. There's not much to override here, as far as functions go. Members in the defaultproperties can be modified to change the way it looks. Don't mind all that code in the ThirdPersonEffects() function, that's just how to place the particular muzzle flash class that I've chosen onto the weapon.
A note on weapon models...
As you can see, the attachment class uses a 3rd person perspective mesh, unlike the weapon class which uses a 1st person perspective mesh. That's right...weapons in the engine have 2 different meshes (animated 3d models). The 1st person mesh is a visual representation of the weapon on your side, and only viewed on your screen when you're holding the weapon. What everybody else sees is the lower polygon, 3rd person mesh defined in the attachment class. Weapons also have a static mesh (non-animated model) used for weapon pickup classes. So now that you know all this, if you are planning on subclassing existing weapons to inherit their models, please also sublcass the attachment class for that weapon as well, and use the same skins.
1: // MyGunAttachment.uc
2:
3: class MyGunAttachment extends ShockAttachment;
4:
5: // Link muzzle flash
6: simulated event ThirdPersonEffects()
7: {
8: local Rotator R;
9:
10: if ( Level.NetMode != NM_DedicatedServer && FlashCount > 0 )
11: {
12: if (MuzFlash == None)
13: {
14: MuzFlash = Spawn(class'LinkMuzFlashProj3rd');
15: AttachToBone(MuzFlash, 'tip');
16: }
17: if (MuzFlash != None)
18: {
19: MuzFlash.Skins[0] = FinalBlend'XEffectMat.LinkMuzProjGreenFB';
20: MuzFlash.Trigger(self, None);
21: R.Roll = Rand(65536);
22: SetBoneRotation('bone flashA', R, 0, 1.0);
23: }
24: }
25: Super.ThirdPersonEffects();
26: }
27:
28: defaultproperties
29: {
30: // Class to use for the muzzle flash
31: MuzFlashClass=class'LinkMuzFlashProj3rd'
32:
33: // 3rd person model and animation
34: Mesh=SkeletalMesh'Weapons.ShockRifle_3rd'
35:
36: // Skins
37: Skins(0)=FinalBlend'DeRez.Shaders.DeRezFinalBody'
38: Skins(1)=FinalBlend'DeRez.Shaders.DeRezFinalHead'
39: }
The FireMode Classes
The WeaponFire class has 2 main subclasses, which are subclassed by many classes for the default weapons in the game. There is InstantFire, and there is ProjectileFile. The InstantFire class traces a line of sight in front of the weapon, and will instantly reach a target, regardless of distance. Damage information is contained within this class. Examples of fire modes that subclass it are the Sniper Rifle fire, the Assault Rifle fire, and the Super Shock Rifle fire. The ProjectileFile class will shoot out a projectile, and the projectile will contain the data and functions to process damage and effects. Examples for Projectile fire are the Link Gun fire, Shock Rifle alt-fire, and the Rocket Launcher fire. Since weapons have two fire modes, I'll make examples derived both InstantFire and ProjectileFile.
The InstantFire class traces the sight to hit the player in the DoTrace() function. You can modify it to do custom effects for locational damage, or enable headshots. To see how it's implemented, please read the XWeapons\InstantFire.uc file, it is very interesting. The function SpawnBeamEffect(Vector,Rotator,Vector,Vector,int) can be overrided to spawn an xEmitter-derived class for special effects, like drawing a laser beam of some sort. And yes, that is what I did here. The member BeamEffectClass refers to the type MyUTGun.LaserBeamEffect, which I derived from ShockBeamEffect, which itself is also a subclass of xEmitter. I will talk a little more about xEmitter later.
1: // MyGunFire.uc
2:
3: class MyGunFire extends InstantFire;
4:
5: var() class<ShockBeamEffect> BeamEffectClass;
6:
7: function DrawMuzzleFlash(Canvas Canvas)
8: {
9: // rotate link gun mussle flash
10: if (FlashEmitter != None)
11: {
12: FlashEmitter.SetRotation(Weapon.Rotation);
13: Super.DrawMuzzleFlash(Canvas);
14: }
15: }
16:
17: function SpawnBeamEffect(Vector Start, Rotator Dir, Vector HitLocation, Vector HitNormal, int ReflectNum)
18: {
19: local ShockBeamEffect LaserBeam;
20: LaserBeam = Spawn(BeamEffectClass,,, Start, Dir);
21:
22: if (ReflectNum != 0) LaserBeam.Instigator = None; // prevents client side repositioning of beam start
23: LaserBeam.AimAt(HitLocation, HitNormal);
24: }
25:
26: function StartBerserk()
27: {
28: DamageType = class'MyUTGun.MyVeryOwnDamageType';
29:
30: DamageMin = default.DamageMin * 10.500000;
31: DamageMax = default.DamageMax * 10.500000;
32: }
33:
34: function StopBerserk()
35: {
36: DamageType = default.DamageType;
37:
38: DamageMin = default.DamageMin;
39: DamageMax = default.DamageMax;
40: }
41:
42: defaultproperties
43: {
44: BeamEffectClass=Class'MyUTGun.LaserBeamEffect'
45:
46: DamageType=class'MyUTGun.MyOwnDamageType'
47: DamageMin=50
48: DamageMax=65
49:
50: TraceRange=10000.000000
51: Momentum=666.000000
52:
53: AmmoClass=class'MyUTGun.MyGunAmmo'
54: AmmoPerFire=1
55:
56: FireRate=0.470000
57: BotRefireRate=0.720000
58:
59: FireSound=Sound'WeaponSounds.BaseFiringSounds.BLightningGunFire'
60: NoAmmoSound=Sound'WeaponSounds.BaseGunTech.BLockOn1'
61: FlashEmitterClass=class'XEffects.LinkMuzFlashProj1st'
62: SmokeEmitterClass=None
63:
64: aimerror=79.000000
65: Spread=0.023000
66: SpreadStyle=SS_Random
67: }
The ProjectileFire class is quite different from InstantFire. It spawns a projectile each time it is fired, and the rest of it (as far as effects or damaging other players) is up to the Projectile code to do. If you really wanted to, you can override the SpawnProjectile(Vector,Rotator) function to randomly spawn a different type of projectile each time, or multiple projectiles at once.
1: // MyGunAltFire.uc
2:
3: class MyGunAltFire extends ProjectileFire;
4:
5: function DrawMuzzleFlash(Canvas Canvas)
6: {
7: // rotate link gun mussle flash
8: if (FlashEmitter != None)
9: {
10: FlashEmitter.SetRotation(Weapon.Rotation);
11: Super.DrawMuzzleFlash(Canvas);
12: }
13: }
14:
15: function projectile SpawnProjectile(Vector Start, Rotator Dir)
16: {
17: return Spawn(ProjectileClass,,, Start, Dir);
18: }
19:
20: defaultproperties
21: {
22: FlashEmitterClass=class'XEffects.LinkMuzFlashProj1st'
23: ProjectileClass=class'MyUTGun.MyGunProj'
24: ProjSpawnOffset=(X=24.000000,Y=8.000000,Z=-12.000000)
25:
26: // Animation, sounds, and force feedback
27: FireAnim="AltFire"
28: FireAnimRate=1.500000
29: FireSound=SoundGroup'WeaponSounds.ShockRifle.ShockRifleAltFire'
30: FireForce="ShockRifleAltFire"
31:
32: FireRate=0.267000
33: BotRefireRate=0.350000
34:
35: AmmoClass=class'MyUTGun.MyGunAmmo'
36: AmmoPerFire=1
37:
38: // Fire Shaking
39: ShakeRotMag=(X=60.000000,Y=20.000000)
40: ShakeRotRate=(X=1000.000000,Y=1000.000000)
41: ShakeRotTime=2.000000
42:
43: bSplashDamage=True
44: bRecommendSplashDamage=True
45: // AI calculate splash damage
46: }
The WeaponFire classes of course have properties to adjust fire rate, and wait times between bots firing. And you may have also noticed reference to an ammo class, which can be set to any class derived from Ammunition. Each fire mode can have it's own ammo class, but most guns just have them share the same one. There are other subclasses of WeaponFire, like SniperZoom and TransRecall, which I have also used before. Another neat thing to play around with in a WeaponFire class is the holding the fire down and using the charge bar. Many interesting things can be achieved here, only limited by your imagination (or skill level). To get an idea of how this is done, check out the source code for XWeapons\AssaultGrenade.uc.
The Projectile Class
The Projectile class plays a large role in what makes a weapon do what it does. Here, you can control how it travels through space, the physics of it, whether it bounces, explodes, or just sticks to walls, detonates, splash damage. You can even code in exactly what it does when it hits something, or hits a player. You can calculate locational damage, and use different damage type classes for each part of the body if you wanted. Or modify the attributes of the player it just hit (you can also do these things in the Instant Fire classes in the DoTrace() function). The projectile's appearance can be set or altered, and projectile classes usually spawn off other effects, like coronas, trails, explosions, and decals.
1: // MyGunProj.uc
2:
3: class MyGunProj extends Projectile;
4:
5: var xEmitter Trail;
6: var class<DamageType> DamageTypeHead;
7:
8: //cleanup code
9: simulated function Destroyed()
10: {
11: // destroy effect
12: if (Trail != None)
13: {
14: Trail.Destroy();
15: }
16: Super.Destroyed();
17: }
18:
19: simulated function PostBeginPlay()
20: {
21: Super.PostBeginPlay();
22:
23: // creat trail effects (spawn an xEmitter derived class)
24: if ( EffectIsRelevant(vect(0,0,0),false) )
25: Trail = Spawn(class'MyOwnTrailSmoke',self);
26:
27: Velocity = Vector(Rotation);
28: Acceleration = Velocity * 3000.0;
29: Velocity *= Speed;
30:
31: if ( Level.bDropDetail )
32: {
33: bDynamicLight = false;
34: LightType = LT_None;
35: }
36: }
37:
38: // creat effects and cleanup
39: simulated function Explode(vector HitLocation, vector HitNormal)
40: {
41: if( EffectIsRelevant(Location,false) )
42: {
43: // create explosion effect (spawn an xEmitter derived class)
44: Spawn(class'LinkSmoke',,, HitLocation, rotator(HitNormal));
45: }
46: BlowUp(HitLocation);
47:
48: Destroy();
49: }
50:
51: simulated function BlowUp(vector HitLocation)
52: {
53: // splash damage is weaker than a direct hit, also adds to direct hit damage
54: HurtRadius(Damage/3, DamageRadius, MyDamageType, MomentumTransfer, HitLocation );
55: PlaySound(Sound'WeaponSounds.BExplosion3');
56: }
57:
58: simulated function ClientSideTouch(Actor Other, Vector HitLocation)
59: {
60: if(Other == Instigator) return;
61: if(Other == Owner) return;
62:
63: Other.TakeDamage(Damage, instigator, Location, MomentumTransfer * Normal(Velocity), MyDamageType);
64: }
65:
66: // what happens when the projectile touches something
67: simulated function ProcessTouch(Actor Other, Vector HitLocation)
68: {
69: local Vector X;
70: local float dist;
71:
72: // don't touch the shooter
73: if(Other == Instigator) return;
74: if(Other == Owner) return;
75:
76: X=Normal(Velocity+vect(0,0,0.5));
77:
78: if (!Other.bWorldGeometry)
79: {
80: // if projectile touches a player, and hits the player's head
81: if( (Pawn(Other) != None) &&
82: (Other.GetClosestBone(HitLocation,X,dist,'head',10) == 'head') )
83: {
84: Other.TakeDamage(Damage*2, Instigator, HitLocation, 1*X, DamageTypeHead);
85: }
86: else Other.TakeDamage(Damage, Instigator, HitLocation, 1*X, MyDamageType);
87: }
88:
89: Explode(HitLocation,Normal(HitLocation-Other.Location));
90: }
91:
92: defaultproperties
93: {
94: // how fast it travels through space
95: Speed=9397.000000
96: MaxSpeed=12397.000000
97:
98: Damage=65.000000
99: MomentumTransfer=65000.000000
100: // how hard to knock the target
101:
102: // Damage messages and properties
103: MyDamageType=class'MyUTGun.MyGunDamageType'
104: DamageTypeHead=class'MyUTGun.MyGunHeadDamageType'
105: ExplosionDecal=class'XEffects.LinkScorch'
106:
107: DrawType=DT_StaticMesh
108:
109: // unanimated model to use
110: StaticMesh=StaticMesh'WeaponStaticMesh.LinkProjectile'
111: DrawScale3D=(X=2.650000,Y=1.850000,Z=1.850000)
112:
113: // ProjSpawnOffset=(X=24.000000,Y=8.000000,Z=-12.000000)
114:
115: LightBrightness=125.000000
116: LightRadius=4.000000
117: LightHue=41
118:
119: LifeSpan=5.333000
120: }
Alright, I hoped you survived that last one. :) I could have made it a little more simple, but I thought we'd take it up a notch since you're probably bored of just changing defaultproperties and calling it programming. Take a good look at it, because this is about as complicated as it will get in this tutorial. Depending on your skill level, that can be a good thing or a bad thing. If you'd like to see more code like this, try reading the Epic source code, there's lots of it, or you can read the source code of the other mods I've made. If you are one who is struggling to grasp these concepts just hang in there...the worst is over, take a deep breath and go over it slowly. A lot of the code speaks for itself at that pace.
The Damage Type Class
The WeaponDamageType class serves a small, but very good purpose. It mainly contains information on the type of damage done to a player. From small things like the console message it says when a player dies, to whether or not the player is severed into pieces and what direction they fly. You can also set an overlay on the damaged player's skin for a lightning, plasma, or custom effect. Additional xEmitter effects can be added to show the player is on fire. And of course, damage type classes can also be headshots, and use custom messages and announcements.
Most weapons will just have two damage types, one for each mode of fire. I've added two more for this weapon just to show that you can add as many as you want if you program in the functionality. One of the extra damage types is for primary fire, and is only used when the player is in a 'Berserk' combo. The other is for headshots when the projectile calculates that it touched a player's head.
1: // MyOwnDamageType.uc
2:
3: class MyOwnDamageType extends WeaponDamageType;
4:
5: static function GetHitEffects(out class<xEmitter> HitEffects[4], int VictemHealth )
6: {
7: HitEffects[0] = class'HitSmoke';
8: HitEffects[1] = class'HitFlame';
9: }
10:
11: defaultproperties
12: {
13: WeaponClass=class'MyUTGun.MyOwnGun'
14:
15: DeathString="%o felt the love and warmth of %k's laser."
16: FemaleSuicide="%o is feeling the love."
17: MaleSuicide="%o is feeling the love."
18:
19: bCauseConvulsions=True
20: bDetonatesGoop=True
21: DamageOverlayMaterial=Shader'XGameShaders.PlayerShaders.LinkHit'
22: DamageOverlayTime=1.230000
23: GibPerterbation=0.470000
24: GibModifier=0.340000
25: }
This following damage type skeletizes players when it kills, and is only used when the attacking player (instigator) is berserk.
1: // MyVeryOwnDamageType.uc
2:
3: // This damage type is only used when the player
4: // is in a berserk combo
5:
6:
7: class MyVeryOwnDamageType extends MyOwnDamageType;
8:
9: defaultproperties
10: {
11: DeathString="%k burnt %o to a crisp."
12: bSkeletize=True
13: }
Damage class for alt-fire...
1: // MyGunDamageType.uc
2:
3: class MyGunDamageType extends MyOwnDamageType;
4:
5: defaultproperties
6: {
7: DeathString="%k did something awful to %o."
8: FemaleSuicide="Congrats to %o! She has killed herself."
9: MaleSuicide="%o finally decided to end his misery."
10: }
Damage class for alt-fire head shots, using a custom headshot message.
1: // MyGunHeadDamageType.uc
2:
3: class MyGunHeadDamageType extends DamTypeSniperHeadShot;
4:
5: defaultproperties
6: {
7: WeaponClass=class'MyUTGun.MyOwnGun'
8: KillerMessage=class'MyUTGun.MyGunHeadShot'
9:
10: DeathString="%k put one in %o's dome."
11: FemaleSuicide="%o's head fell off."
12: MaleSuicide="%o's head fell off."
13:
14: DamageOverlayMaterial=Shader'XGameShaders.PlayerShaders.LinkHit'
15: }
Just to make a note on the DamageType classes, most weapons use a specific naming convention, starting with 'DamType' then the name or description of the fire mode. eg. DamTypeLaserBeam would be appropriate. In this case though, I was naming all the classes starting with "My", just for the sake of this being a tutorial. So just keep the naming convention in mind, unlike my careless disregard for it (which was just this once).
The Special Kill Message Class
Then, we have the a custom headshot message class, derived from SpecialKillMessage. There is an object of this class called KillerMessage as a member of the DamTypeSniperHeadShot class and its subclasses. You can change the text on the screen, its colour, and the sound it plays when a headshot is made. Only the killer will see and hear this message, as it is done locally.
1: // MyGunHeadShot.uc
2:
3: class MyGunHeadShot extends SpecialKillMessage;
4:
5: defaultproperties // Highlander
6: {
7: DecapitationString="There Can Only Be One!!"
8: }
The Ammo Class
Of course, weapons will consume ammo (in the weapon fire classes), so there is also an Ammunition class. Derive your class from it, or any of its subclasses. You can change quite a few properties of it. If you want to make a gun with unlimited ammo, the easiest way to do that is subclass from the SuperShockAmmo class. You can also change the little icon that shows up in the corner by your ammo count on the HUD.
1: // MyGunAmmo.uc
2:
3: class MyGunAmmo extends SuperShockAmmo;
4:
5: defaultproperties
6: {
7: ItemName="My Gun Ammo"
8: PickupClass=None
9:
10: MaxAmmo=1
11: InitialAmount=1
12:
13: IconMaterial=Texture'InterfaceContent.HUD.SkinA'
14: IconCoords=(X1=545,Y1=75,X2=644,Y2=149)
15: }
The xEmitter Class
Low and behold, the xEmitter class. This class is spawned in functions of many other classes for all types of effects, ranging from muzzle flashes to blood spray, smoke, sparks, explosions, adrenaline combo effects, and projectile trails. You can check out the code in the XEffects folder to see some examples. Most xEmitter subclasses don't need to everride functions unless you want to program in specialized functionality. If you know of an effects class that you like, but it's not the colour you want, you can easily change that by subclassing and changing the vaules of mColorRange[2]. You can also change the texture, physics type, and particle type, combined with many other attributes that cause many different effects.
1: // MyOwnTrailSmoke.uc
2:
3: class MyOwnTrailSmoke extends RocketTrailSmoke;
4:
5: defaultproperties
6: {
7: mStartParticles=1
8: mMaxParticles=16
9:
10: // make the smoke green
11: mColorRange(0)=(B=45,G=215,R=35)
12: mColorRange(1)=(B=45,G=225,R=50)
13: }
This xEmitter class is subclassed from ShockBeamEffect, a more rare and complicated example of an effects class. Most of them are like the above class, just override the defaultproperties, and just sometimes a function or two.
1: // LaserBeamEffect.uc
2:
3: class LaserBeamEffect extends ShockBeamEffect;
4:
5: simulated function SpawnImpactEffects(rotator HitRot, vector EffectLoc)
6: {
7: Spawn(class'LinkSmoke',,, EffectLoc, HitRot);
8: Spawn(class'LinkProjSparks',,, EffectLoc, HitRot);
9: Spawn(class'LinkScorch',,, EffectLoc, Rotator(-HitNormal));
10: }
11:
12: defaultproperties
13: {
14: MuzFlashClass=class'XEffects.LinkMuzFlashBeam1st'
15: MuzFlash3Class=class'LinkMuzFlashProj3rd'
16:
17: LifeSpan=0.950000
18:
19: Texture=FinalBlend'XEffectMat.Link.LinkBeamGreenFB'
20: Skins(0)=FinalBlend'XEffectMat.Link.LinkBeamGreenFB'
21: }
If you backtrack through the tutorial, you will find several references to xEmitter subclasses. I've only defined two of my own in this whole mod, but have been using existing effects classes all throughout the tutorial. We have also used LinkMuzFlashProj3rd, LinkMuzFlashProj1st, LinkSmoke, HitSmoke, HitFlame, and LinkProjSparks, all of which are existing subclasses derived from xEmitter.
The Mutator Class
Sure, now we have all the classes for the gun made, but the game still uses default weapons. What we have to do is make a mutator, that puts your weapon in the player's inventory. You can use a mutator to replace all weapons and pickups, or just replace specific weapons with whichever weapon you want. The mutator in this example is an "Arena" type mutator, that removes all other weapons from the map, and makes the players spawn already holding our gun we just made.
1: // MutMyOwnGunArena.uc
2:
3: class MutMyOwnGunArena extends Mutator;
4:
5: function bool CheckReplacement(Actor Other, out byte bSuperRelevant) {
6:
7: bSuperRelevant = 0;
8:
9: // if (Other.isA('xPawn'))
10:
11: if(xPawn(Other) != None)
12: {
13: xPawn(Other).RequiredEquipment[0] = "MyUTGun.MyOwnGun";
14: xPawn(Other).RequiredEquipment[1] = "None";
15: }
16:
17: if((Weapon(Other) != None)) {
18: if(Other.IsA('BallLauncher')) {
19: return true;
20: }
21: } else if(Other.isA('ShieldGun')) {
22: return false;
23: } else if(Other.isA('UTWeaponPickup')) {
24: return false;
25: } else if(UTAmmoPickup(Other) != None) {
26: return false;
27: }
28:
29: return true;
30: }
31:
32: defaultproperties
33: {
34: GroupName="Arena"
35: DefaultWeaponName="MyUTGun.MyOwnGun"
36: FriendlyName="My Own Gun Arena"
37: Description="My Own Gun Arena.|By me"
38: }
Feeling Ripped Off?
As you may have noticed, there is no UTWeaponPickup or UTAmmoPickup classes in this example. Well, 1) this weapon is made with an arena style mutator, and 2) The weapon in the example was made for infinite ammo. Oh...almost forgot 3) Too bad, I don't care!! ;) If you were able to read the tutorial up to this point and would like to make weapons that do run out of ammo and have pickups, it isn't hard to figure out. Just check out some of the classes in the XWeapons folder.
Identifying Your Modification
Now that you've made all your classes and compiled them into a .u file, the game still doesn't know that the weapon or mutator in your package even exist. To make the engine see your modification, you have to make an .int file for it. Just create a new file in notepad, and in this save it as YouPackageName.int in the UT2003\System folder, and in this case that would be MyUTGun.int. The file should contain information to point to the class for your weapon, mutator, or pickup. Here's an example.
[Public] Object=(Class=Class,MetaClass=Engine.Weapon,Name=MyUTGun.MyOwnGun,Description="My UT Gun.|By me") Object=(Class=Class,MetaClass=Engine.Mutator,Name=MyUTGun.MutMyOwnGunArena,Description="My UT Gun Arena.|By me") [MyUTGun] FriendlyName="My UT Gun" Description="My UT Gun.|By me"
Top^
Importing Media
Custom Media
If you want to use your own sounds or skins/textures you made for the weapon, you can import them. I'd usually would make textures for coronas, projectile trails, ammo icons, or HUD overlays. And you can also import your own firing sounds, explosion sounds, or headshot announcer sounds. To import, you'd use the directive #exec usually right below your class declaration line and pass operands to it to tell it what to do. To use your own texture as the ammo icon for example:
1: class YourAmmo extends SuperShockAmmo;
2:
3: #exec TEXTURE IMPORT NAME=EvilIcon FILE=Textures\EvilIcon.pcx GROUP=Skins
4:
5: defaultproperties
6: {
7: // Access it with PackageName.GroupName.Name ...
8:
9: IconMaterial=Texture'YourPackage.Skins.EvilIcon'
10: IconCoords=(X1=16,X2=96,Y1=27,Y2=124)
11:
12: // ...
13: }
And for importing sounds, it's more or less the same thing, except to tell #exec to do an AUDIO IMPORT instead. Here's an example from a custom headshot message from one of my other weapons:
1: // by EvilJesús 2003
2:
3: class LoSHeadShot extends SpecialKillMessage;
4:
5: #exec AUDIO IMPORT FILE=Sounds\Toasty.wav NAME=Toasty GROUP=Sounds
6:
7: defaultproperties
8: {
9: DecapitationString="Toasty!!"
10: HeadShotSound=Sound'LoSsOl.Sounds.Toasty'
11: }
So, after reading those two examples you may be wondering where those two files came from (Textures\Evilicon.pcx and Sounds\Toasty.wav). Well, at the start of this tutorial, I said to make a folder in the UT2003 directory named after your package. And also to make a Classes folder in there, to put all your .uc source code files. Well, same goes for textures and sounds. You can make a Textures and a Sounds folder, and put your texture and sound files in them, respectively.
The Correct File Format
As far as I know, you can use .bmp, .dds, .pcx, or .tga files for textures and skins. The size must be in POWERS of 2, for example 8x32, 64x64, 256x128, 1024x1024 are all acceptable sizes.
For audio imports, I believe .wav files are the only type that you can import this way. And the quality of the format does matter, so far I've only had luck with using 8-bit mono, unsigned .wav files. You may be able to import higher quality .wav's without any errors, but the sounds may not play in the game.
Texture Compression
Just so your mod isn't so bloated with huge textures, you can greatly reduce the file size by compressing your textures in the Unreal Editor. Open up UEd's Texture browser, go to File->Open, choose *.* as the file filter and open up your mod's compiled .u file in the System folder. The textures you've imported should open up in the texture browser. Then for each of the textures, you can right-click on them and choose Compress and choose DXT3 or DXT5, you'll have to look that up in Epic's documentation for which compression method is suitable for certain situations. Then you can click on File->Save, then choose *.* as the format again, and save as your package's .u file and overwrite it. The file size of your mod will be considerably smaller.
Top^
Existing Media
Unreal Editor
If you want to use skins, sounds, meshes, or static meshes from the game in your Unreal modifications, you'll need to know their fully qualified names to call it up from your code. The fully qualified name would be in the PackageName.GroupName.Name format. Packages that have many groups and item are contained withing single files. Texture packages are stored as .utx files, .uax for sound packages, .usx for static meshes, and .ukx for meshes (animations). The Unreal Editor can open up such files and view their contents in its browser. You can use the UEd broswer to preview the content within the packages. When you find something you'd like to use in your mod, you can right-click on it and choose rename. This will reveal the package name, the group name, and its name so you can refer to it in your code.
Materials
When you want to use a texture from the existing game content, you can choose anything in UEd's texture browser, even if it says they're not exactly textures. All items in a texture package are actually based from Material, a superclass of Texture, TexpPanner, TexOscillator, Combiner, Shader, and FinalBlend. Some of these may be animated, and some may have transparency. So even if a final blend or shader is not exactly a "texture", you can still use it, because they are all a type of Material, just as long as you call it properly in the code. For example in UEd, if you find a shader that you want to use then in your code refer to it as Shader'PackageName.GroupName.ShaderName'. A good example of seeing that in code is in the MyOwnGun class from this tutorial:
37: // Skins for the weapon
38: Skins(0)=FinalBlend'DeRez.Shaders.DeRezFinalBody'
39: Skins(1)=FinalBlend'DeRez.Shaders.DeRezFinalHead'
40: Skins(2)=Shader'WeaponSkins.ShockLaser.LaserShader'
Top^
Conclusion
Well, for now that is all I can share with you. Not too bad, in my opinion, I think we've covered more than what you need to get started. There's so much more to making weapons to cover all at once. I hope this tutorial has been useful to you, and feel free to use it and any of its code as your guide. And if I've taught you correctly, you don't need to give me credit for the work you come up with. Your work is your work.
If you'd like more examples, all of the mods on the DAMN Clan homepage are free to download, and do include the source code. Please check out the copyright notices in them before copying my code from those mods line for line. Don't worry, the copyright isn't very restrictive, it's Open Source because I like to share my knowledge, rather than making it proprietary information. Yet, I'd still like you to read the license, you may even be inspired by the freedom of open source. ;)
And like I said earlier, there's a lot of things you will have to learn on your own, so I hope you took my advice and exported some of Epic's code to read it. I think that is the best way to go that extra mile that may separate you from programmer's mediocrity. There's so much you can do with this engine, and for some things there won't be someone there to tell you how to do it. You can get some good class documentation at http://wiki.beyondunreal.com. The Unreal Wiki is very helpful, but don't expect it to cover how to do everything, just use it as a guide.
Happy Hacking! :)