HUD Interactions
Okay, so you want a HUDMutator in UT2003? Tough - they're dead. You'll need to use something called an interaction instead. Interactions recieve PostRender and PreRender events from the engine, as well as intercept keypresses and messages.
Interactions also contain a whole load of useful functions like "WorldToScreen", which will take a location as a vector and turn it into a set of screen co-ordinates. Of course, it's also possible to use the client-sided interaction to spawn a HUD, and use its ability to receive PostRender calls to call this function for the HUD it spawned. This allows you to use all the functionality of a HUD class, most notably the DrawSpriteWidget? function, which is defined in the HudBase class.
More on this method of making a HUD mutator 2003-style on the bottom of this page.
A couple of nights ago, I started work on something I though would be simple. All it would do is draw a small icon around a player to make them more visible. HUD mutators are dead, so I needed to use an interaction.
First off, you need to make your mutator class that will create your interaction. See Creating An Interaction From A Mutator to find out how.
Done that? Good...
So thats the mutator part done. Next is the easy part!
The interaction is very easy to use. Just create a subclass of Interaction, chuck in a "Simulated Function PostRender (canvas Canvas)", add a couple of lines to defaultproperties, and you're ready to go! Obviously what you want to draw on-screen goes in PostRender.
You need this, at least, in defaultproperties:
bVisible=true bActive=true
Setting bActive
to true means that this Interaction will actually be used. Setting bVisible
to true means it will recieve PostRender and PreRender calls.
If you need to use a tick, add:
bRequiresTick=True
Remember when making your interaction, that an interaction IS NOT AN ACTOR! To use anything that requires an actor (ie: DynamicActors), use
ForEach ViewportOwner.Actor.DynamicActors(Class'Foo', Bar)
Also remember than interactions are clientside. You only have access to variables and actors that are replicated to clients.
If you need to do anything when the Interaction is initialized, use
event Initialized() { Log("foo"); }
The interaction I have used for my mutator is detailed below for example.
Class icu_interaction extends interaction; #exec TEXTURE IMPORT NAME=red FILE=TEXTURES\red.PCX GROUP="Icons" MIPS=OFF Flags=2 #exec TEXTURE IMPORT NAME=green FILE=TEXTURES\green.PCX GROUP="Icons" MIPS=OFF Flags=2 Var GameReplicationInfo GRI; event Initialized() { //log("Interaction Initialized"); foreach ViewportOwner.Actor.DynamicActors(class'GameReplicationInfo', GRI) If (GRI != None) Break; } function PostRender( canvas Canvas ) { local Pawn P; local vector CameraLocation, dir, ScreenLocation; local rotator CameraRotation; local float dist, draw_scale; foreach ViewportOwner.Actor.DynamicActors(class'Pawn', P) { if (ViewportOwner.Actor.Pawn == None || P == None) Return; //A trace to tell if you can see this thing If ((Canvas.Viewport.Actor.FastTrace(P.Location, ViewportOwner.Actor.Pawn.Location)) && (P != ViewportOwner.Actor.Pawn) && (P.PlayerReplicationInfo != None) && (P.Health > 0)) { //Convert 3d location to 2d for display on the Canvas ScreenLocation = WorldToScreen(P.location); Canvas.GetCameraLocation(CameraLocation, CameraRotation); dir = P.Location - CameraLocation; dist = VSize(dir); //Distance between me and them if ((dist < 8000) && (dist > -8000) && (dir dot vector(CameraRotation) > 0)) { draw_scale = 512 / dist; //Calculate the drawscale, 512 is the "1:1" distance. //Set drawing params Canvas.SetPos(ScreenLocation.X - (32 * draw_scale), ScreenLocation.Y - (32 * draw_scale)); Canvas.Style = 3; Canvas.SetDrawColor(255,255,255); if ((P.PlayerReplicationInfo.Team.TeamIndex != ViewportOwner.Actor.Pawn.PlayerReplicationInfo.Team.TeamIndex) || (!GRI.bTeamGame)) Canvas.DrawIcon(texture'red', draw_scale); else if (P.PlayerReplicationInfo.Team.TeamIndex == ViewportOwner.Actor.Pawn.PlayerReplicationInfo.Team.TeamIndex) Canvas.DrawIcon(texture'green', draw_scale); } } } } defaultproperties { bVisible=true bActive=true }
And if you are interested what the end result of my mutator is, see ICU (I See You) on Will.
Boksha:
As stated at the top of this page, you can use interaction's PostRender calls to spawn and feed a HUD for each client. This method gives you both the ability to completely replace the old HUD, or add small features to it, only the former case allows only one modified HUD, and it would be easier to do it using only a clientside mutator. That's very similar to what Will did above, only you set the player's HUD instead of spawning an interaction for him.
Anyway, if you want to make only a small addition to the player's HUD, while keeping the original HUD intact, here's how:
First, make sure your mutator spawns an interaction for every client, like Will explained in Creating An Interaction From A Mutator. Done that? OK, now comes the easy part. Just make sure your Interaction object contains code like this
Class MyHUDInteraction extends Interaction; var HUD ThisHudOfMine; event Initialized() { ThisHudOfMine = ViewportOwner.Actor.spawn(class'GravHud', ViewportOwner.Actor); } function PostRender( canvas Canvas ) { ThisHudOfMine.PostRender(Canvas); } defaultproperties { bVisible=true bActive=true }
And really, that's ALL there is to it! If your interaction does that, you have a nice extra HUD that does not replace, but rather augment the original HUD. This will become very useful once the DrawSpriteWidget? function is documented, as it allows you to quickly draw on the HUD independant of the resolution.
Helpful Information
Wormbo: "HUD interactions" don't even touch the HUD itself. (See Maintaining Compatibility for good reasons for that.) Once created they just draw on the Canvas each frame after the HUD finished drawing on the very same Canvas. One example of using HUD interactions and keypress interactions is my mutator Mod Ideas/Mercury Missile InstaGib. Just download that, extract its UnrealScript sources and have a look at the MercZoomConfig class.
What Boksha did in his code is spawning an additional HUD actor and calling that HUD's PostRender() from the Interaction's PostRender(). There are only very few cases where you really need to do something like this, because e.g. the DrawSpriteWidget() function also works when called for the player's HUD from another class, like this:
HudBase(ViewportOwner.Actor.myHUD).DrawSpriteWidget(someSpriteWidgetVariable);
Of course you can also save a reference to the HUD in a variable for easier access to it.
Foxpaw: In the mutator, you can add the interaction, and the drawing takes place in the interactions' PostRender() function. You can call a function on the Canvas to draw stuff, depending on what you want to draw. I believe that there is a Wiki page about the Canvas that details the various drawing functions you can use on the canvas.
Wormbo: If you need an example of a HUD interaction, I can offer JumpStats (see Movement Metrics (UT2003)) which draws a lot of text.
How do I draw a text?
CorDharel:
Canvas.SetPos(50,100); Canvas.SetDrawColor(0,0,0); Canvas.DrawText("a text");
bVisible is set to true, but I can't see a text
The values (0,0,0) sets the color to be black, which doesn't show up very easily. Maybe its there but you just can't see it.
Related Topics
- Interaction
- Canvas
- Mutator Topics and [Mod Making]?
- HUD
- Creating An Interaction From A Mutator
- HudOverlay
Discussion
DJPaul: The tone of this page could be wrong; is this page meant to be about HUD Interactions, or how to implement one? I think it should be the former, and the implementation moved to a tutorial (it sounds very tutorial-ly at the moment anyway).
BTW, where should coding tutorials live?
Foxpaw: Why does the code above create a HUD and then call that? How is that better than just using the PostRender in the interaction itself?
CorDharel: I refactored this page. When I can draw a text on the hud () I will expand the "how do I draw a text" section or create a new site. What do you mean?
Lilguy: It's really not a good idea to call foreach DynamicActors(class'Pawn', P)in a function that's called every frame. Even though this is faster than AllActors, it still has to iterate through a pretty big list to find all the pawns. It'd be much better to call foreach VisibleActors(...) since it uses the engine's fast collision logic to find the actors in your viewport, and it saves you all the logic of trying to determine that stuff.
Foxpaw: That seems a bit strange. VisibleActors does a trace on every thing it finds, which is not very fast. I believe that there is linked list of Pawns that would likely be faster than any iterator.
Wormbo: In a reasonably large radius and with extra checks that do not depend on traces DynamicActors is actually faster than Visible(Colliding)Actors. See [Optimization Techniques]? for details.
Kungfu Hampster: Is there a way to contol whether the Interaction HUD will be visible or not through the user control (the bHideHud command)