{
bShowDebugMenu=
false
CurrentPage=-
1
CurrentIndex=
0
Pages(
0
)=(PageName=
"General"
,PageCommands[
0
]=(CmdName=
"Toggle Debug Camera"
,Command=
"ToggleDebugCamera"
))
Pages(
1
)=(PageName=
"Debug Info"
,PageCommands[
0
]=(CmdName=
"Turn off Debug Info"
,Command=
"showdebug none"
),
PageCommands[
1
]=(CmdName=
"Toggle Pawn Debug Info"
,Command=
"showdebug pawn"
),
PageCommands[
2
]=(CmdName=
"Toggle Camera Debug Info"
,Command=
"showdebug camera"
),
PageCommands[
3
]=(CmdName=
"Toggle Pawn Weapon Info"
,Command=
"showdebug weapon"
))
Pages(
2
)=(PageName=
"HUD"
,PageCommands[
0
]=(CmdName=
"Toggle HUD"
,Command=
"ToggleHUD"
))
}
|
Now, for the various responses to input (Read the article on Input commands to see how to bind functions to keys.):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
exec
function
ToggleDebugMenu()
{
CurrentIndex =
0
;
CurrentPage = -
1
;
bShowDebugMenu = !bShowDebugMenu;
SetCinematicMode(bShowDebugMenu,
false
,
false
,
true
,
true
,
true
);
}
exec
function
NextMenuItem()
{
local
int
maxIndex;
if
(bShowDebugMenu)
{
//Only do stuff when we actually are in the menu.
if
(CurrentPage != -
1
)
{
maxIndex = Pages[CurrentPage].PageCommands.Length-
1
;
}
else
{
maxIndex = Pages.Length-
1
;
}
CurrentIndex = Min(CurrentIndex +
1
, maxIndex);
}
}
exec
function
PreviousMenuItem()
{
if
(bShowDebugMenu)
{
CurrentIndex = Max(CurrentIndex -
1
,
0
);
}
}
exec
function
DoDebugCommand()
{
local
SandboxDebugCommand command;
if
(bShowDebugMenu)
{
if
(CurrentPage != -
1
)
{
//Leave the menu and execute the selected command
command = Pages[CurrentPage].PageCommands[CurrentIndex];
ToggleDebugMenu();
ConsoleCommand(command.Command);
}
else
{
//Change page
CurrentPage = CurrentIndex;
CurrentIndex =
0
;
}
}
}
exec
function
DebugMenuBack()
{
if
(bShowDebugMenu)
{
if
(CurrentPage != -
1
)
{
//Get back to the top level.
CurrentPage = -
1
;
CurrentIndex =
0
;
}
else
{
//We're at the top level: leave
ToggleDebugMenu();
}
}
}
|
All that is left to do is the drawing of the menu:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
function
DrawDebugMenu(HUD H)
{
local
float
XL, YL, YPos;
local
SandboxDebugCommand command;
SandboxDebugCommandPage page;
local
int
array_index;
local
Color command_color;
if
(bShowDebugMenu)
{
H.Canvas.Font =
class
'Engine'
.Static.GetLargeFont();
H.Canvas.StrLen(
"X"
, XL, YL);
YPos =
0
;
H.Canvas.SetPos(
0
,
0
);
H.Canvas.SetDrawColor(
10
,
10
,
10
,
128
);
H.Canvas.DrawRect(H.Canvas.SizeX,H.Canvas.SizeY);
//Adding a dark overlay to visually notify we're in the menu.
if
(CurrentPage == -
1
)
{
SandboxHud(H).DrawTextSimple(
"Debug Menu"
,vect2d(
0
,YPos), H.Canvas.Font,H.WhiteColor);
YPos += YL;
foreach Pages(page,array_index)
{
if
(array_index == CurrentIndex)
{
command_color = H.GreenColor;
}
else
{
command_color = H.WhiteColor;
}
SandboxHud(H).DrawTextSimple(array_index$
":"
@page
.PageName,vect2d(
0
,YPos), H.Canvas.Font,command_color);
YPos += YL;
}
}
else
{
page = Pages[CurrentPage];
SandboxHud(H).DrawTextSimple(
"Debug Menu - "
$page.PageName,vect2d(
0
,YPos), H.Canvas.Font,H.WhiteColor);
YPos += YL;
foreach page.PageCommands(command,array_index)
{
//I know, I could have put that in a function as the body of the loop is the same as above.
if
(array_index == CurrentIndex)
{
command_color = H.GreenColor;
}
else
{
command_color = H.WhiteColor;
}
SandboxHud(H).DrawTextSimple(array_index$
":"
@command
.CmdName,vect2d(
0
,YPos), H.Canvas.Font,command_color);
YPos += YL;
}
}
}
}
|
We’re missing two things: A place where to call DrawDebugMenu() and the definition of DrawTextSimple():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
SandboxHUD
extends
UDKHUD;
event
PostRender()
{
super
.PostRender();
SandboxDebugMenu(PlayerOwner.CheatManager).DrawDebugMenu(
self
);
}
function
DrawTextSimple(
string
text, Vector2D position, Font font,Color text_color)
{
Canvas.SetPos(position.X,position.Y);
Canvas.SetDrawColorStruct(text_color);
Canvas.Font = font;
Canvas.DrawText(text);
}
|
And we’re done!
Post-Rendered Actors
Complying with the idea of letting actors drawing the bit of HUD they’re responsible for,
Unreal provides a really nice feature: the post-rendered actors. Basically,
any Actor can draw things on the Canvas, provided that:
- The actor lets the HUD know it has something to draw on the HUD.
- The actor is actually rendered (i.e. it has at least one primitive component currently visible by the player’s camera).
- The HUD allows to draw actor overlays.
To illustrate this, I’ll use a custom actor which is a Note you place in your level and will show text on screen when you look at it.
1
2
3
4
|
class
Sandbox3DNote
extends
Actor
placeable
;
var
()
string
Title;
var
()
string
Text;
|
Registering a PostRendered Actor
To do this, an actor must call the HUD’s AddPostRenderedActor() and pass itself as the function’s parameter.
Now, the potentially tricky part is that as the HUD is owned by the PlayerController, you need a reference to it.
Fortunately, the Actor class has a function called GetALocalPlayerController() which will get the first PlayerController it can find.
In local multiplayer (same computer) it shouldn’t really matter which Controller you get as it’ll end up on the same screen
However, you can’t use that function at any moment. Namely, an actor’s PostBeginPlay is out of the question as you can’t say for sure the PlayerController will be created.
So if you don’t have access to the HUD or its PlayerController, a workaround is to create a state in your Actor and register it at the beginning of that state:
1
2
3
4
5
6
|
auto
state
Initialize
{
Begin
:
GetALocalPlayerController().myHUD.AddPostRenderedActor(
self
);
}
|
It sounds like a very dodgy method (mostly because I don’t exactly know when the actor’s states are started),
but it does seem to work. Of course, when the actor has a direct reference to the PlayerController (such as weapons or vehicles),
you don’t need to do that complicated stuff and PostBeginPlay should be a safe place to register.
Implementing the drawing function
The function which will be called (by native code) is Actor.PostRenderFor(). It provides the following parameters:
- PlayerController PC: The Controller whose HUD you’ll be drawing on.
- Canvas Canvas: The Canvas you’ll be drawing on.
- Vector CameraPosition: Position of the controller’s camera.
- Vector CameraDir: direction of the controller’s camera.
It might be important to know who we’re drawing for. For instance, in a team-based game,
maybe we only need to draw an actor overlay for only one of the teams.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
simulated
event
PostRenderFor(PlayerController PC, Canvas Canvas,
vector
CameraPosition,
vector
CameraDir)
{
local
Vector vec;
local
Font previous_font;
//We store the font and restore it at the end, otherwise it will mess up the drawing of the rest of the HUD.
previous_font = Canvas.Font;
Canvas.Font =
class
'Engine'
.Static.GetMediumFont();
vec = Canvas.Project(Location);
//This wil transform the 3D position to screen space-coordinates.
Canvas.SetPos(vec.X,vec.Y);
Canvas.SetDrawColor(
255
,
255
,
255
,
255
);
Canvas.DrawText(Title,
true
);
Canvas.DrawText(Text);
Canvas.Font = previous_font;
}
|
And to finish, there are a few properties we need to set:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
DefaultProperties
{
Begin
Object
Class=SpriteComponent Name=Sprite
Sprite=Texture2D
'EditorResources.S_Note'
AlwaysLoadOnClient=
False
AlwaysLoadOnServer=
False
End
Object
Components.Add(Sprite)
bAlwaysRelevant=
true
bNoDelete=
true
bMovable=
false
}
|
Note that the SpriteComponent doesn’t have HiddenGame set to true. Because if it did,
the actor wouldn’t have anything to render, and thus PostRenderFor() would never get called.
Also, in the HUD class, we need to make sure bShowOverlays is set to true (in its default properties, or wherever it’s appropriate).
If you’re having problems seeing this working, make sure the HUD is actually enabled (try the ToggleHUD console command).