MiceMenGrav: A Mutator Tutorial for UT2003.
This is a beginner level mutator development tutorial. The reader should have a basic knowledge of UnrealScript and should be able to make a basic mutator. This tutorial will help the reader understand potential uses for the following classes:
- Mutator
- GameRules
- GUIPage
- Inventory
This mutator was implemented in UT2003. We have no development experience on other versions of Unreal, so we are not sure how specific this code is to UT2003.
Contents
Conceptual Origins
This mutator is the combination of two other mutators:
- MiceMen, coded by Wedge, affects the player size based on the current configuration and the number of player kills.
- GravMod, coded by ThePelkus, draws players together when they attack each other.
Code Download
While snippets of the source are shown below when relevant, the full source is available here. While there is no candy surprise, there are plenty of comments to keep you in the know.
Making your own GameRules
The GameRules class is easy to bend to your purposes. Like the Mutator objects, the GameRules objects live in a linked list. When the game needs to do something using the GameRules objects, it recurses through the list, giving each object a chance to do its thing. Technically speaking, the game is not responsible for making sure that every object in the list gets its turn at bat – that's why you'll need lines like this one:
/* Sampled from a ModifyPlayer definition in a custom mutator */ // If there is another mutator in the chain if ( NextMutator != None ) // Call this method in the next mutator; pass the same parameters NextMutator.ModifyPlayer(Other);
Keeping this list structure in mind, all we need to do to have a little fun is derive our own rules and override some of the member functions!
Placing your rules in your project
Before you go overriding functions left and right, you want to incorporate your custom rules into your mutator so that you can check changes as you make them. As we just said, the hook for the engine is a chain of GameRules objects that lives somewhere in the system. Specifically, this chain is accessed as Level.Game.GameRulesModifiers from any Actor class. How convenient, then, that Mutator is a subclass of Actor!
To add your rules to the chain, just add the following code to PostBeginPlay() in the mutator that you derived from the Mutator class:
/* The following code assumes that you have a variable called myRules representing your rules; see MutMiceMen.uc for an example. */ // Add the rules object to the list of GameRules in the game if ( Level.Game.GameRulesModifiers == None ) Level.Game.GameRulesModifiers = myRules; else Level.Game.GameRulesModifiers.AddGameRules(myRules);
Building GravShot into our custom rules
We derived a class called MiceMenRules from GameRules so that we could take advantage of some of the built-in functionality. In fact, almost all of the GravShot functionality lives in the NetDamage function of our custom rules.
As stated in the GameRules page, the NetDamage function "allows the GameRules to modify the damage (return value) and momentum. The OriginalDamage parameter should be passed to the next GameRules without any modifications." Technically, GravShot doesn't alter either of these values. But NetDamage also takes as parameters the attacking pawn (InstigatedBy) and the target pawn (Injureed). Using these two pawns, our rules:
- Calculate the normalized vector from the victim to the instigator
-
// Get normalized vector from target to shooter
myVec = Normal(InstigatedBy.Location - Injured.Location);
- Scale that vector
-
// Set up multiplier vector
multVec.x = mutInstance.MovementMultiplier * 100; multVec.y = mutInstance.MovementMultiplier * 100; multVec.z = mutInstance.MovementMultiplier * 100; // scale normalized vector; better way?
myVec = myVec * multVec;
- Add the scaled vector to the victim's velocity.
-
Injured.AddVelocity(myVec);
Note once again that, after all of the GravShot code, the NetDamage member of the next GameRules object (if it exists) is called with the same parameters.
With this mutator active, the Injured pawn is moved towards the InstigatedBy pawn by some fixed amount. We'll see where this amount is set later when we look at the config menu. For practice you might try making the scaling factor situational e.g. scale based on distance or damage inflicted.
Building MiceMen into our custom rules
The MiceMen part of the mutator overrides the ScoreKill member of the rules. From the GameRules page, we know that the ScoreKill function "Augements the action taken when a player scores a kill. Not the score itself, but having to do with the Controllers involved in the Kill."
The following two lines of code are all that we added to the ScoreKill method:
if ( (Killer != None) && (Killer.Pawn != None) ) mutInstance.ChangePlayerSize(Killer.Pawn, mutInstance.GetScaleFor(Killer.Pawn));
In this case, the Killer parameter (a Controller) is used for its associated pawn. This pawn is passed to ChangePlayerSize(), a custom function in our mutator, along with a scaling factor. The scaling factor is calculated using GetScaleFor(), yet another custom function in our mutator, which also takes the pawn associated with Killer as a parameter. Again, after our code is called, ScoreKill is called in the next GameRules object.
Clearly a lot of the logic for MiceMen is included in the mutator, so let's take a look at that now!
Making your own Mutator, briefly
Presumably you already have the basics of making a mutator down, so we're just going to show you what we did with ours.
PostBeginPlay()
Our PostBeginPlay() code is pretty standard, going through the following steps:
- Call method in parent class
- Instantiate derived GameRules
- Initialize mutator instance in derived GameRules
- Add GameRules to GameRulesModifiers linked list
The only really odd step in there is Step 3. Because the mutator class contains functions and data needed for the MiceMen scaling in the rules, the rules contain an instance of the mutator. It may seem a little circular, but it works out fine.
ModifyPlayer()
The Mutator page lists the following description for the ModifyPlayer() function:
This function can be used to update the player's inventory, speed, and other attributes. this function should always call ModifyPlayer() on the next mutator in the chain (a call to the superclass method would do the trick).
We used ModifyPlayer() to do the following:
- Scale the player
- Player scaling is handled in our custom functions called ChangePlayerSize() and GetScaleFor(). The comments included in the code make the function self-explanatory. Unfortunately, we're uncertain as to whether certain functions are necessary.
- Create a custom Inventory item to scale the player's weapon
- In order to make sure that the player's weapon model scales properly when weapons are switched, the player is given a WeaponScaler, a custom class we derived from Inventory.
Making your own Inventory items
In earlier revisions of this mutator, the player weapon would not resize to match the player scale when the player switched weapons. Our solution: we derived a class from Inventory that would handle the resizing for us.
Our use of Inventory-related functionality was very minimal. The Inventory class has a method called OwnerEvent. This method is called - surprise, surprise - whenever an event is triggered in owner. Since we wanted to change the rescale the player weapon whenever the weapon was changed, we watched for an event called 'ChangedWeapon':
function OwnerEvent(name EventName) { if(EventName == 'ChangedWeapon') { WeaponAttachment(Instigator.Weapon.ThirdPersonActor).SetCollisionSize(WeaponAttachment(Instigator.Weapon.ThirdPersonActor).Default.CollisionRadius * Instigator.DrawScale, WeaponAttachment(Instigator.Weapon.ThirdPersonActor).Default.CollisionHeight * Instigator.DrawScale); WeaponAttachment(Instigator.Weapon.ThirdPersonActor).SetDrawScale(WeaponAttachment(Instigator.Weapon.ThirdPersonActor).Default.DrawScale * Instigator.DrawScale); } }
When this event occurred, the collision and draw size were rescaled based on the player's DrawScale. Note that these properties were only set on the third person mesh as indicated by the use of WeaponAttachment and ThirdPersonActor above. Also note that the player is referred to as Instigator since we need a Pawn to get a weapon.
Using GUIPage to configure your muator, super briefly!
This section is unnecessary as the information that would go here is explained perfectly well over in Mutator Config GUI (UT2003). In fact, most of the config code for this mutator was taken straight from the provided example code.
While Mutator Config GUI (UT2003) is fairly complete in terms of explaining the connection between GUI items and mutator variables, it's also a good idea to look through the documentation on other classes in the GUI_Class_Hierarchy to make sure you don't fall victim to the old problems of giant checkboxes and the like.
Problems
It's true - we're not perfect, which makes this problematic as a tutorial. Help us make this tutorial more useful for beginners by helping us catch the following bugs:
Levitating players
Just run around for a little while and it won't take long until you'll see some of the other players flying around in midair. It's been hard for us to establish exactly when this happens, so even figuring that out would be very useful.
Tricadex - I think this is caused by problems with the collision box and crouching
Funny skeletons
Resizing players seems to do funny things to their corpses. We attempted to fix this problem using the BoneRefresh() method of the Actor class, but it's not clear whether there are still funny effects.
Feel free to add comments to this page to help us fix these problems!
About this mutator
The MiceMenGrav mutator is a class project from the Bellevue Community College gaming curriculum in Bellevue, WA. This program is also responsible for the VampireShoppers mutator.
Category:Legacy Tutorial