UIScript Structure
User Interface scripts appear to be quite complicated at first, but they have quite a simple hierarchical structure. It appears to be quite straightforward to define any kind of interface function, but the work is in figuring out what all the commands mean!
The whole of the Unreal 2 UI seems to derive from a single root object, defined like this:
[Root] Class=FixedSizeContainer Component=SomeComponent
and so on. At this level, the "Component" is something like the main menu, the objectives screen, or the main HUD.
I shall pick a relatively simple component to illustrate the main points of the UIScript functionality.
I've been working on UIScripts for a while, and thought I'd write out how to create your own HUD element in Unreal 2. This is my first try at editing a Wiki, so bear with me. With that said, let's get to it.
First, let me say one thing; The UIScript interface isn't as illogical as it may seem at first...it's nature is just really counter-intuitive. This is because UIScripts, while robust and logical, are a quite separate entity from the UnrealScript hierarchy. Indeed, it appears Legend had some problems with the HUD, and the UIScript is a hacked-out system that happened to stay in for the final version...
Quoth HUD.uc from U2's source...
//NEW: disabled because it doesn't compile. Currently using UIConsole, etc to do all drawing so we really don't need this anyway.
Ugh. Okay, anyway, let's make a UI component. We'll create a health bar, so that we don't have to tangle with extra functionality, and focus on the intricacies of UIScripting.
Our UIScript will define two textures: one to serve as the background for our component, and one to serve as a visual meter of the remaining health. We'll use two different "subcomponents" to accomplish this: one to hold the textures, and one to be the actual health bar.
Open up Notepad (or WOTgreal, etc.) and create a file in the UIScripts subdirectory called "PVNNinjaHUD.ui" (if you want to follow the nomenclature of my mod, Pirates versus Ninjas):
[Root] Class=FixedSizeContainer Component=NinjaHUDTextures Component=NinjaHealth [NinjaHUDTextures] Class=Image Material=khakiPVN.HUD.PVNNinjaHUD Image2=HealthMeter,0,32,134,37 Image2=NinjaHealthTex,0,0,81,27 [NinjaHealth] Class=FixedSizeContainer Component=ImageComponent Image=NinjaHealthTex Component=ImageWipe TopImage=HealthMeter Access=Player,GetHealthPct AutoSize=true Location=30,5 Component=LabelShadow Access=Player,GetHealthString Font=HUD_Fonts.Micro12 Location=-5,6 Align=HardLeft,HardTop Location=100,100
UIScripts consist of a bunch of "component" definitions. At the very least, the component must be named (accomplished in the first line), and given a type (accomplished in the second line, "Class="). Following that, what attributes are assigned depends on the component's nature.
So, let's deconstruct what's going on here, section by section.
[Root] defines, well, the "root" component, which is actually named after the text file. Thus, [Root] defines the UI component named "PVNNinjaHUD", whose prototype is the filename of the very UIScript we're editing. [Root]'s type is always FixedSizeContainer...it appears to be pointless to use any other types here. Now you provide "prototypes," as it were, for each of your subcomponents, just to let Unreal 2 know what's going to be built. In this file, we declare the two aforementioned components for textures and a health bar.
Now, you create a subsection that is named after each one of your subcomponents, and define them. NinjaHUDTextures's type is image, to signify that it holds images for your other components. The source of your textures, defined in the "Material=" line, must be a texture that exists in an Unreal package. For mine, the HUD images for this particular HUD are stored in khakiPVN.HUD.PVNNinjaHUD. After that, you simply have to split the image into pieces, using "Image2=" lines to declare individual components. First, you name the subcomponent, then give the Cartesian coordinates of the object's upper-leftmost pixel, then the Cartesian coordinates of the object's lower-rightmost pixel. Simple, eh? Yeah, I thought so too.
Now we're getting to the most important part; the subcomponent definition of the actual health bar. "Class=FixedSizeContainer" defines the health bar's type. The health bar consists of three different "sub-subcomponents." The "Component=" lines are not names that you invent here, as they were in the [Root] definition–instead, they are predefined types that you instanciate and modify. We'll have three parts: The background, the health meter, and the numeric health value. The background is defined by the "ImageComponent" section. The "ImageWipe" line declares an image whose size changes depending on a value of some sort. First, you define the image that will be used, then how the object will be accessed. "GetHealthPct" is a member function of Player's class (which is U2Controller). Next, AutoSize will do just that; automatically resize itself whenever the GetHealthPct value changes. The "Location=" line establishes Cartesian coordinates relative to the upper-leftmost pixel of the entire object. Finally, LabelShadow is an object that displays text. To get its text, it calls Player's GetHealthString function, and renders using the Micro12 font that is part of a HUD_Fonts package. The Location represents, again, relative Cartesian coordinates.
After that, you establish absolute screen coordinates for the object, and you're set!
...But, how do you use it? Well, here's the approach I used. Create a custom gametype...
class PVNGameInfo extends U2GameInfo; #exec texture Import File=Textures\khakiPVN.UIMisc.Common.tga Group=UIMisc name=Common #exec texture Import File=Textures\khakiPVN.HUD.PVNNinjaHUD.tga Group=HUD name=PVNNinjaHUD //#exec OBJ LOAD FILE=Textures\khakiPVN.utx var ComponentHandle NinjaHealth; event NotifyHack() { NinjaHealth = class'UIConsole'.static.LoadComponent( "PVNNinjaHUD" ); class'UIConsole'.static.AddComponent(NinjaHealth); Super.NotifyHack(); } defaultproperties { bDisplayHud=true GameName="Pirates Versus Ninjas" AirControl=0.850000 }
The NotifyHack() function is what matters here. You make a ComponentHandle variable, and that variable holds a loaded instance of your UI. To instanciate your UI, use the LoadComponent() function, as shown. The argument is a string that represents the name of your UIScript file (without the ".ui" on the end). If you want, you can use an optional second argument that only loads a subcomponent out of that script. That wouldn't work here, as we have to load both the textures AND the health bar. After that, you pass the newly-loaded NinjaHealth object into the AddComponent() function, and you have yourself a working UI! Just create a map in UnrealEd that specifies your new GameType in its LevelInfo.
scumble: working on it. I'm just trying to put some stuff down and build it up...
please provide backlinks!
I don't know what else I can link that's relevant at the moment.
Well... User Interface, the page that comes here.
Khakionion: Decided to contribute what I've learned thus far. What'd be really useful is a listing of all possible class/component types...
Foxpaw: Eek. I made a system like this for my UT2003 mod. And here I thought I was being original. Uhmmm, welll.... Mines better! Fahahahaha!