Worst-case scenario: the UEd Goblin wipes the map and burns down your house.
Legacy:Mutator Config GUI (UT2003)
Making a configuration window for your mutator in UT2003. Written By LedZep
NOTE: If you want the UT2004 version then please see Mutator Config GUI (UT2004).
This tutorial will guide you through creating a simple mutator and a configuration window for it. This tutorial assumes that you have basic knowledge in UnrealScript and that you know how to create UC files and how to setup and compile them: see Setting Up UnrealScript.
As an example, this tutorial will show you how to create a MultiJumping mutator and how to create a configuration window to setup it. You are not going to need any external files other than the ones you will create yourself.
For a class tree of the GUI components see GUI Class Hierarchy.
Contents
Creating the Mutator Class
First we will create the mutator, basically it is going to have three components: the configurable variables, the ModifyPlayer function and the Default Properties. Here is how it is going to look like:
Class MultiJumpMut extends Mutator Config(User); var() config int MJMax, MJBoost; function ModifyPlayer(Pawn Other) { local xPawn x; x = xPawn(Other); // Check if pawn is xPawn if(x != None) // If it is, assign the new variables to it { x.MaxMultiJump = MJMax; x.MultiJumpBoost = MJBoost; } } defaultproperties { IconMaterialName="MutatorArt.nosym" ConfigMenuClassName="MultiJumpMutator.MJMutConfig" // Note that this assumes that your package's name is MultiJumpMutator GroupName="Jumping" FriendlyName="MultiJumpMutator" Description="Configure Your Multi Jumping Ability." MJMax=1 MJBoost=25 }
Note that I added "Config(User)" to the class declaration so it will know where to store its variables. When declaring Configurable variables, always put the word "config" after the "var" to let the compiler know that they are configurable. If you want those varibles to have default properties incase the user wont specify any, make your "var" look like this: "var()", and in the default properties section give those variable a value (dont forget that you dont use semi colons in the default properties section). See also Variable Syntax.
In this mutator's case, MJMax is the maximum number of extra jumps the player can make, and MJBoost is the boost each extra jump gives him. The ModifyPlayer function is really simple, it recieves a Pawn in its parameters, checks if it is an xPawn and then assigns our mutator's variables to the xPawn's variables. Now default props, basically what they do is give the mutator its name and its description, assign values to our configurable variable and specify our mutator's config class (which we will make in a second)
Creating the Configuration Window for the Mutator
The ConfigWindow is a little more tricky, it has four parts to it; declaring the two edit boxes we are going to be using, the InitComponent and InternalOnClick functions and the DefaultProperties. First I'll give you the code, then I'll explain. Dont freak out when you see the Default Properties, it will all become clear to you very soon.
class MJMutConfig extends GUIPage; var moEditBox MJCountBox, MJBoostBox; // Declare two EditBox type Variables // Called when our GUI component is initiated (in this case our config window) function InitComponent(GUIController MyController, GUIComponent MyOwner) { Super.InitComponent(MyController, MyOwner); // Call Parent's InitComponent // Assign variables to EditBoxes that were created in the default properties MJCountBox = moEditBox(Controls[4]); MJBoostBox = moEditBox(Controls[5]); // Set the EditBoxes to be integer only because we are only dealing with numbers MJCountBox.IntOnly(true); MJBoostBox.IntOnly(true); // Set the EditBoxes' text to our mutator's config variables' values MJCountBox.SetText(string(class'MultiJumpMut'.default.MJMax)); MJBoostBox.SetText(string(class'MultiJumpMut'.default.MJBoost)); } // Called when the OK button is clicked function bool InternalOnClick(GUIComponent Sender) { // Set our Mutator's variables to the new variables that were set by the user class'MultiJumpMut'.default.MJMax = int(MJCountBox.GetText()); class'MultiJumpMut'.default.MJBoost = int(MJBoostBox.GetText()); class'MultiJumpMut'.static.StaticSaveConfig(); // Save all the Mutator's config variables Controller.CloseMenu(false); // Close the window return true; // Return True (hard to figure out eh?) ;) } defaultproperties { Begin Object Class=GUIButton name=DialogBackground // The window's background WinWidth=1.0 // Set Window's width WinHeight=1.0 // Set Window's height WinTop=0 // Set the windows location on the Y axis WinLeft=0 // Set the windows location on the X axis bAcceptsInput=false // Since its just a background it wont accept input bNeverFocus=true // Just a background, no need for focus StyleName="ComboListBox" // The style of the background bBoundToParent=true // is it bound to its parent window? bScaleToParent=true // is it in scale to its parent window? End Object Controls(0)=GUIButton'DialogBackground' // Sets Controls[0] to the DialogBackround Begin Object Class=GUIButton Name=OkButton // An "OK" Button Caption="OK" // The button's caption WinWidth=0.2 WinHeight=0.04 WinLeft=0.4 WinTop=0.63 OnClick=InternalOnClick // Assigns the button's OnClick to our InternalOnClick function End Object Controls(1)=GUIButton'OkButton' // Sets Controls[1] to the OkButton Begin Object class=GUILabel Name=DialogText // A text label Caption="MultiJump Configuration" TextALign=TXTA_Center // Text allignment TextColor=(R=220,G=180,B=0,A=255) // Text color TextFont="UT2HeaderFont" // Text font WinWidth=1.000000 WinHeight=32.000000 WinLeft=0.000000 WinTop=0.325000 End Object Controls(2)=GUILabel'DialogText' Begin Object class=GUILabel Name=DialogText2 // Another text label Caption="Configure your MultiJump Options (default JumpBoost is 25)" TextALign=TXTA_Center TextColor=(R=220,G=180,B=0,A=255) TextFont="UT2MenuFont" WinWidth=1.000000 WinHeight=32.000000 WinLeft=0.000000 WinTop=0.390626 End Object Controls(3)=GUILabel'DialogText2' Begin Object class=moEditBox Name=MJCounter // An edit box (for our MJMax) WinWidth=0.431641 WinHeight=0.04 WinLeft=0.293750 WinTop=0.467448 Caption="Number of MultiJumps" bReadOnly=false // Will take Input, therefor isn't readonly End Object Controls(4)=moEditBox'MJCounter' // Set Controls[4] to the MJCounter Begin Object class=moEditBox Name=MJBooster // Another edit box (for our MJBoost) WinWidth=0.431641 WinHeight=0.04 WinLeft=0.293750 WinTop=0.517448 Caption="MultiJump Boost" bReadOnly=false End Object Controls(5)=moEditBox'MJBooster' // Set Controls[5] to the MJBooster // The Window's own properties (MJMutConfig) WinLeft=0 WinTop=0.3 WinWidth=1 WinHeight=0.4 bRequire640x480=True // Required Minimum resolution }
First we have the Class Declaration, only one line, easy wasnt it? (note that you should subclass GUIPage) Then we have the variable declaration, also one line! you see how easy coding is? Well, only this time, since we are making editboxes, the variables are of the moEditBox type. Ok, now we will skip the two function (we will come back to them later) and go straight to the defaultprops. If you are new to UnrealScript, this might seem a little weird to you (it sure did to me), but dont worry, it is really simple! in the classic UT, we had to create around 3 or 4 classes just to make one window, basically what all this code does is create this window for you using only one class, how cool is that? To make it easy to understand for you I put a whole bunch of comments into it so just read through it and read the comments and it will give you the general idea. Basically, Begin Object creates an object, names it, manipulates its variables and then assigns it to something. It is commonly used in the UT2003 window system as followes:
Begin Object class=<ClassName> Name=<ObjectName> WinWidth=<Width> WinHeight=<Height> WinLeft=<LocationOnXAxis> WinTop=<LocationOnYAxis> <OtherVars>=<OtherValues> End Object Controls(n)=<ClassName>'<ObjectName>' // n is the object's number - 1 (so the first object is 0)
all what our DefaultProperties do is create a background, an "OK" Button, two text labels, two edit boxes and then set the variables for its own class (MJMutConfig). There are two important things you should note, one is "OnClick=InternalOnClick" which is found in the "OK" button's creation code. Basically what it does is call our InternalOnClick function whenever the "OK" button is clicked. The second important thing is that after the creation of each window, it is assigned to a Controls(n) (n being a number), this is important to us because we will soon use this.
Ok, we are done with the hard part, all what is left to do is the last two functions. the function InitComponent is pretty much like UT's Created, it is called when our GUI component is initiated. First it calls its parent's InitComponent to do all the important stuff of initiating the window. Then we assign the two moEditBoxes (that were created in the default properties) to our own EditBoxes.
Then we set them as IntOnly because we are only dealing with numbers, and finally we set their text to our mutator's configurable variables' values. Basically all what our InitComponent function does is getting our EditBoxes setup and ready to use. And our last function for this tutorial, InternalOnClick, which is called when the "OK" button is clicked. All this function does is set our mutator's variables to the new variables that were typed into the editboxes, then is saves all of our mutator's config variables (which is only two) and finally it closes the window.
And thats it!
By the way, you can also add checkboxes and combolists and all sorts of GUI components, look around the GUI components, or more specifically, look at moEditBox and moCheckBox and basically do the same thing with them as I did with moEditBox in this tutorial. (also look at their classes to see what kind of variables and functions they have)
Creating the INT file
it is very simple, just follow this syntax:
[Public] Object=(Class=Class,MetaClass=Engine.Mutator,Name=<PackageName>.<MutatorClass>,Description="<MutatorName>,<Description>")
so in our case, if lets say the mutator's package will be named MultiJumpMutator, our INT File will look like this:
[Public] Object=(Class=Class,MetaClass=Engine.Mutator,Name=MultiJumpMutator.MultiJumpMut,Description="MultiJump,Configure your multi jumping ability.")
and it will be named MultiJumpMutator.int
The Grand Finale
Compile the whole enchilada, then start UT2003. Go to instant action and click on Mutators, in there find MultiJumpMutator, add it to the mutator list and click on Configure Mutator, the window we created will pop up and you will be able to change the values to whatever you want, then start a game session and see the mutator in action.
I hope you enjoyed this tutorial.
LedZep (for comments EMail me at LedZepperus@Yahoo.com)
Comments
Dma: Dude, you took my idea! dma/MutMultiJump :-)
RoninLord: Using the property bBoundToParent=true in a widget, including GUIButtons etc., makes the WinLeft and WinTop properties relative to the parent widget. So WinTop=0.9 is 90% of the parent widget, rather than 90% of the whole window.
This makes it easier to lay out widgets, especially if you later move the parent widget, you then don't have to adjust the properties of the child widgets.
LedZep: Wow thanks for the info RoninLord. Dma, sorry if you also used this idea (although i came up with it by myself, i didnt steal anything :-D) but the real point of this tutorial is to teach you how to make a GUI Config window, not a multi jump mutator ;-)
JoeDark: Does this tut apply to UT or UT2003 so it can be filed correctly (if it already isn't). Please label it on here somewhere.
Wormbo: Uhm, I found this at the top of the page:
Tutorial #1: Making a configuration window for your mutator in UT2003
JoeDark: LOL! It's late.. need sleep.
Tarquin: This page isn't linked form anywhere useful. Could someone find it a home, eg UnrealScript ?
JoeDark: Done.
LedZep: Hehe JoeDark, it happens to everyone... Ill just make the title a bit bigger so it'll be easier to see ;)
EntropicLqd: If you follow the example above all positioning and scaling is relative to the window as a whole. This is fine if your dialog window (with the dirty hack of using a button as the window background - ugh) takes up the whole screen. If it doesn't then setting the MenuOwner proeprty to the background button will make all scalings relative to the actual button (the "real" window) - rather than the entire window itself. I found it made laying out the widgets a little easier.
defaultproperties { Begin Object Class=GUIButton name=FMConfigBackground // Blah blah blah End Object Begin Object Class=GUIButton Name=FMOkButton // Object properties go here and add the following to make everything relative to your config background: bBoundToParent=true bScaleToParent=true MenuOwner=FMConfigBackground End Object
EntropicLqd: One other thing that is worth noting is that you can specify your own functions as callbacks from the OnChange events and such like. As long as the interface matches it works fine. For example I have three colour bar sliders that need to change the colour of a widget. Rather than use the "InternalOnChange" function I simply created my own and set the OnChange property appropriately. Example below:
// I define the callback function I want to use to handle my three colour bar sliders function ColourSliderChanged(GUIComponent Sender) { // Code goes in here } // And then in the default property section of my slider I set the following property: Begin Object class=GUISlider Name=FMRedSlider OnChange=ColourSliderChanged // Rest of details End Object
Brox: How would you create a new class (also extended from GUIPage) in the onClick function of a button?
EntropicLqd: Like this: Code is taken from XInterface.Tab_InstantActionMutators.uc
function bool MutConfigClick( GUIComponent Sender ) { if(MutConfigMenu == "") return true; // In this case MutConfigMenu is set to the value of the Mutator.ConfigMenuClassName property Controller.OpenMenu( MutConfigMenu ); return true; } // And as for the actual button definition - I've cut out all bar the relevant properties Begin Object Class=GUIButton Name=IAMutatorConfig OnClick=MutConfigClick End Object
the_viking: But how can I create a GUIPage where i might display the contents of my inventory?
Where do I have to put my code into?
spoon: You can have an in-game GUIPage by using ClientOpenMenu. Just create a GUIPage, then call ClientOpenMenu from a mutate command or an exec function. The above GUIPage information still applies.
function Mutate( string MutateString, PlayerController Sender ) { if( Caps( Left( MutateString, 3 ) ) == "INV" ) { Sender.ClientOpenMenu( "GUIInvPage" ); } Super.Mutate( MutateString, Sender ); }
Your GUIPage class will have to play a nice game of find-the-actor since you can't pass anything.
VonStrohmen: In what instances do WinHeight/Width and WinTop/Left correspond to actual pixel coordinates versus fractions of the parent window? It seems to me that if I set any of those variables to be a decimal value less than 1 (e.g. 0.5) it translates that to percent, whereas greater than one is interpreted as a pixel coordinate. However, it doesn't always seem to work that way. Any advice?
Also, why does Controls(n) work? I thought dynamic arrays use [] instead of ().
Wormbo: Default Properties are always different. ;) The size/position properties really work the way you described, but the position of a GUIComponent depends on more than just WinTop/Left and its size depends on more than just WinHeight/Width. There are also the bBoundToParent and bScaleWithParent properties which affect the position and size of a control.
VonStrohmen: I experimented with those as well, with mixed results. Then I realized that I never set the properties of the GUIPage itself. That seemed to help.
EntropicLqd: Any chance someone could rename this page to Mutator Config GUI (UT2003)?
Mr.bob: Help out a poor newb here, You said "The second important thing is that after the creation of each window, it is assigned to a Controls(n) (n being a number), this is important to us because we will soon use this."
And then you never used it? What does control(n) do anyway?
I have another problem. I took the text that was shown, copied an pasted it into a .uc file. Then it tired to compile it, everything went fine until it said:
Parsing MultiJumpMut
C;\UT2004\MultijumpMut\ClassesMultiJumpMut.uc<20> : Error Unexpected 'class'
Complie aborted due to errors.
Failure - 1 error, 0 warnings
Yes I did change the code so it knew that it was MultiJumpMut instead of MultiJumpMutator, also you may have noticed that I have Unreal Tourny 2004. I have never had 2003
Tarquin: I am not sure it is useful for this tutorial page to include an actual mutator – it rather detracts from the point, doesn't it?
M.r.bob: I see what you mean, but I exspected to see what this code did, and then change it so it did something else, and that way figure out how code like this worked. Anyway I found out my problem, GUI's work very different in 2004 then in 2003.