Tutorials/sqUWin1

From Oldunreal-Wiki
Jump to navigation Jump to search

What is UWindows?

Probably the first thing about Unreal Tournament you'll notice is how much spiffier the interface is. Rather than having the Wolfenstein 3D throwback menu system, UT has implemented its own small Graphical User Interface (GUI). It has windows, menus, a mouse pointer, scrollbars, and a lot of other things which other GUIs have (such as Windows). GUI interfaces are famous for making information easier to convey, and harder to program.

Luckily, UT's interface isn't really all that hard to program. If you understand some basic object orientated principles, you shouldn't have much trouble with it. If you've programmed for Windows, Java, or the Macintosh, you'll find that UT's interface is much more simple and easier to use.

UWindows may or may not be the official title of the GUI interface. The core classes all have names with the prefix UWindow, like UWindowButton or UWindowTabControlLeft. For obvious reasons, I call the interface UWindows.

How does UWindows work?

UWindows makes up the core engine of the graphics interface. It does not include any game specific classes. This allows mod makers and liscencees to make their own games with the Unreal engine without having all that extra game specific code. UT's interface exists as a layer or two above the UWindows core. Right above UWindows is the UMenu interface. It provides the basic interface with the help system, menu bar, and configuration dialogs. Built on top of UMenu is UTMenu, which is the specific classes used in Unreal Tournament.

When working with UWindows, you really shouldn't find much need to mess around with the UMenu and UTMenu layers unless you are trying to specifically change a feature of Unreal Tournament. Working with classes outside of the standard UWindows core group could mean compatibility problems with other game engines. Creating a mutator configuration window could actually work between Unreal Tournament, Wheel of Time, and even Nerf Blast Arena if you never stray past the standard classes.

It all begins with UWindowBase?

The entire class tree for UWindows, UMenu, and UTMenu all start with a single node: UWindowBase. All classes in the UWindows system stem from this single point, and they all inherit what is in it. As such, UWindowBase doesn't really have a whole bunch of code. It provides several constants and enumerations that are used amonst the bunch.

After UWindowBase, the class tree splits three ways. Probably the least important path of the three is UWindowLookAndFeel. It provided the hooks for building in the look and feel of the application. Maybe you've noticed in UT, you can change your Look and Feel between Gold, Ice, and Metal? Well, each of those Look and Feels are objects under this class. They define how buttons are drawn, and what a menu looks like, and so on. Nerf Blast Arena even has its own Look and Feel. However, the provided L&Fs should be more than enough to satisfy most ideas.

The second branch past UWindowBase is the UWindowList tree. None of the classes under UWindowList are graphical objects. UWindowList provides a basic linked list functionality for its children classes. Most of the child classes are specific lists used in the GUI, such as a list of menu items, or the list of strings in a combo box. There really isn't a whole lot to monkey around with in this section, although you do need to subclass at least one thing here.

The third and final branch of the tree is UWindowWindow. While it says window, it really isn't. In this case, the window it is refering to is a rectangular graphic. This could be a window window, but this could also be a menu, or a button. All objects under UWindowWindow are visible objects that can be messed with by either the mouse or the keyboard. To distinguish between a UWindowWindow and a window with a title bar that can be moved around, I shall refer to the latter as a Framed Window.

The really cool thing about everything descending from UWindowWindow is that anything under this class can have anything else in this class inside it. You can have Framed Windows with menu bars, and menu bars with buttons, and even buttons that pop up menus. By careful construction of the class tree, Epic hsa made it very functional and very easy to use.

So everything is a window?

All visible graphical objects are descended from UWindowWindow. They all share the same properties and values defined in this class. Easily the most important of these are four variables: WinLeft, WinTop, WinWidth, and WinHeight. These variables form the rectangle that the window occupies. There are a few boolean variables of note, such as bAlwaysOnTop, bAlwaysBehind, and bWindowVisible.

There are several functions in UWindowWindow which don't do anything, but are worth pointing out. Functions like BeginPlay(), BeforeCreate(), Created(), AfterCreate(), Activated(), and Resized() allow their functionallity to be filled out by the children classes. You might override the Created() function of a window to add controls to it. Just be sure that when you override a function to call the parent function too (ie Super.Created()).

How do I start?

This tutorial assumes you are already a little familiar with UScript, and have a mutator or two done. The provided sample code assumes a mutator called sqFatMutator with two variables: fatness and bUseTwoGuns.

The first thing you need is a starting place. UT needs to know when and how to create a window for you. Luckily, UT makes this easy by providing a built in starting place: the MOD menu. Normally, this menu is invisible, but shows up when you add your first item to it. You create your own spot on the menu by subclassing UMenuModMenuItem (from the UWindowList tree). Notice that it is a UMenu class and not a UWindow one. The UMenuModMenuItem has only two functions: Setup() and Execute(). Here is the code for a sample mod menu item (remember to change the class names and what-not to your classnames first).

One the class is completed and compiled, add a line similar to the following to your .int file:

[Public]Object=(Name=SquidiMods.sqFatModMenuItem,Class=Class, MetaClass=UMenu.UMenuModMenuItem)

------------------------------------------------------------------class sqFatModMenuItem expands UMenuModMenuItem;

function Setup(){

//The MenuCaption variable is used to store the menu item's name on// the menu. Unfortunately, the menu item is created before Setup()// is called, and thus uses the default property. Still, this is// provided as a reference.MenuCaption = "FatMutator Config";

//MenuHelp is simply the string written in the status bar at the bottom// of the screen when the mouse hovers over this menu item. Luckily// for us, setting the help caption here DOES work.MenuHelp = "Sets up the FatMutator";

}

function Execute(){

//Create a window using the following function:// CreateWindow( class, top, left, width, height )

//The window I will create will automatically resize itself to// the right proportions (I'll show you how when we get to the// Framed Window part), so it doesn't matter what the top, left,// width or height variables are. Just the class portion matters.

MenuItem.Owner.Root.CreateWindow(class'sqFatConfigWindow',10,10,10,10);

}------------------------------------------------------------------

It didn't compile!

When UnrealEd tries to compile this code, it'll point out that you haven'twritten the sqFatConfigWindow class yet. So lets do that. What we are goingto do is subclass UWindowFramedWindow to create a nifty little frame whichwe drag around the screen to our heart's content.

------------------------------------------------------------------class sqFatConfigWindow expands UWindowFramedWindow;

function BeginPlay(){

Super.BeginPlay();

//Set the title of the Framed WindowWindowTitle = "Configure Fat Mutator";//The class of the contentClientClass = class'sqFatClientWindow';

//Make the Framed Window resizablebSizable = true;

}------------------------------------------------------------------

Most of this code is pretty self explanatory, with the exception of theClientClass variable. If this variable were set to None, you'd see thatwhen the window was created you would just get the framed part. The insideswould show through to the background! A FramedWindow is just that, a frame.When ClientClass is set to a valid client window class, when the window iscreated, that class will be created along with it. You don't need to worryabout making both of them.

You'll find the class UWindowClientWindow just under UWindowWindow, butthat isn't the client window type we are going to use. A child class ofClientWindow, UWindowDialogClientWindow has a few extra important functionsrequired to use controls. So we are going to subclass that one instead. Italso has an extra ability to suggest a desired width and height for the content. Just subclassing UWindowDialogClientWindow is enough to fill inthe empty space in the Framed Window.

Ooh. How do I add buttons and stuff?

Content is added in the form of controls. All controls are children ofUWindowDialogControl (which in turn is a child of UWindowWindow). So allcontrols are also windows. There are dozens of controls to choose from. Someof the controls share functionality (for instance UWindowCheckbox and UWindowSmallButton are both children of UWindowButton), but most haveindividual options to tweak. Controls are created, and added to your clientwindow via the UWindowDialogClientWindow.CreateControl() function thatyou'll have inherited. It has the same parameters as the CreateWindow()function. Here is a somewhat modified version of my sample client window class:

-----------------------------------------------------------------class sqFatClientWindow expands UWindowDialogClientWindow;

var UWindowCheckBox checkbox;var UWindowHSliderControl slider;

// called after object has been created...add contentfunction Created(){

Super.Created();

//Add checkbox via the CreateControl() function.checkbox = UWindowCheckBox(CreateControl(class'UWindowCheckBox', 10, 10, 150, 1));checkbox.SetText("Two Guns");checkbox.bChecked = true;

//create slider in same fashionslider = UWindowHSliderControl(CreateControl(class'UWindowHSliderControl', 10, 30, 150, 1));slider.SetText("Fatness");slider.SetRange(1,255,5);slider.SetValue(128,true);

}

//when a control changes, Notify is called with the changed control// and the message sent.function Notify(UWindowDialogControl C, byte E){

switch(E){case DE_Change: // the message sent by sliders and checkboxes

switch(C){case slider://get slider valuebreak;case checkbox://get checkbox infobreak;}

break;}

}------------------------------------------------------------------

The Notify() is how you know when one of your controls has changed invalue. This allows you to update the variables the controls were supposedto reflect. The message sent in this case is the DE_Change one, which isthe message most often used. You can also use Notify() to update the texton the controls. For instance, the fatness control can change from skinninessto fatness depending where the slider is.

Great! That's just about it. How do I interact with my mutator?

To make the config dialog more useful, those levers and buttons actuallyneed to affect something in your mutator. This is accomplished by settingthe default values. This can easily be accessed via a line similar to this:

class'SquidiMods.sqFatMutator'.default.fatness = slider.GetValue();class'SquidiMods.sqFatMutator'.static.StaticSaveConfig();

The final version of my client window sample follows:

-----------------------------------------------------------------class sqFatClientWindow expands UWindowDialogClientWindow;

var UWindowCheckBox checkbox;var UWindowHSliderControl slider;

// called after object has been created...add contentfunction Created(){

Super.Created();

//Add checkbox via the CreateControl() function.checkbox = UWindowCheckBox(CreateControl(class'UWindowCheckBox', 10, 10, 150, 1));checkbox.SetText("Two Guns");checkbox.bChecked = class'SquidiMods.sqFatMutator'.default.bUseTwoGuns;

//create slider in same fashionslider = UWindowHSliderControl(CreateControl(class'UWindowHSliderControl', 10, 30, 150, 1));slider.SetText("Fatness");slider.SetRange(1,255,5);slider.SetValue(class'SquidiMods.sqFatMutator'.default.fatness, true);

}

//when a control changes, Notify is called with the changed controlfunction Notify(UWindowDialogControl C, byte E){

local int sval;

switch(E){

case DE_Change: // the message sent by sliders and checkboxes

switch(C){

case slider:sval = slider.GetValue();class'SquidiMods.sqFatMutator'.default.fatness = sval;class'SquidiMods.sqFatMutator'.static.StaticSaveConfig();

if( sval > 0 ) slider.SetText("Fatness [Skinny]");if( sval > 50 ) slider.SetText("Fatness [Slim]");if( sval == 128) slider.SetText("Fatness [Normal]");if( sval > 175 ) slider.SetText("Fatness [Fat]");if( sval > 200 ) slider.SetText("Fatness [Fatboy]");break;

case checkbox:class'SquidiMods.sqFatMutator'.default.bUseTwoGuns = SomeCheck.bChecked;class'SquidiMods.sqFatMutator'.static.StaticSaveConfig();break;}

break;

}

}-----------------------------------------------------------------