|
This tutorial
explains the basics of adding floating pop-up labels in the hud that track
objects in the game, much like the info frames in DeusEx (if you enhance
the rendering quite a bit). I call them ActorTips.
First of all we need to create a new HUD-type that implements the
ActorTip feature. Here we'll add it to the normal UT ChallengeHUD, but you
could base it on any HUD-type available to you:
class TipHUD expands
ChallengeHUD;
| Now,
we need to decide which object(s) we're going to label. You can of course
make that selection according to any criteria you wish (your friends,
objects in a certain state, or ones you've previously somehow selected),
but for this example we'll just make a guess at which object the player is
most likely "looking" at:
simulated function Actor
GetFocusedActor() { local vector Look,
EyePos; local Actor Candidate,
Winner; local float Score, HiScore;
// Find out where the camera is, and where it's
looking: EyePos =
Owner.Location; EyePos.Z +=
Pawn(Owner).EyeHeight; Look =
vector(Pawn(Owner).ViewRotation);
// Iterate through all actors closer than 200 UUs to
the player, // trying to find the one most directly
in front of him: HiScore = 0; foreach
VisibleActors(class'Actor', Candidate, 200.0,
EyePos) { // The dot
product of two normalised vectors is a
nice // measure of the angle between
them: // (1 = perfect match, 0 = right
angles, -1 = direct opposites) Score =
Normal(Candidate.Location - EyePos) dot Look;
// Ignore actors too far to the sides
(too low scores): if (Score >= 0.9) //
adjust this threshold to
taste { //
See if this candidate is an
improvement: if (Score >
HiScore) { HiScore
= Score; Winner =
Candidate; } } }
return
Winner; }
| We
also need to work out where on the screen an object will appear, based on
its 3D coordiantes, so we can position our floating graphic correctly on
top of it. (The math is explained in the appendix at the end.)
simulated function vector MapToScreen(vector TargetPos,
Canvas Canvas) { local vector Result,
EyePos; local rotator Look,
Focus; local float TanFOV, dYaw, dPitch, scale;
// Again, our current point of view is sort of
essential: EyePos =
Owner.Location; EyePos.Z += Pawn(Owner).EyeHeight;
// Find out where we're facing, but his time as a
'rotator' providing // angles rather than a
vector: // (The conversion to vector and back is just
a suboptimal but robust // way to make sure we get
nice signed values for the pitch, instead // of
wrap-around from 0 to 65535 at the horizon). Look =
rotator(vector(Pawn(owner).ViewRotation));
// Find the corresponding rotator angles of the
direction from // the camera to our
target: Focus = rotator(TargetPos - EyePos);
// Now find out how much the target angles are offset
from the // look angles. (And while we're at it,
convert to radians, since // that's what Unreal's
trig functions work with): dYaw =
(focus.yaw-look.yaw) * Pi / 32768.0; dPitch =
(focus.pitch-look.pitch) * Pi / 32768.0;
// The field of view obviously affects how things are
projected // to the screen, as does the canvas
resolution. // In fact, the two can be combined into
a single scaling factor // that tells us all we need
to know. Scale = (Canvas.ClipX/2) /
tan(Pawn(Owner).FOVangle * Pi / 360.0);
// Finally calculate actual screen
coordinates: Result.x = Canvas.ClipX/2 + tan(dYaw) *
Scale; Result.y = Canvas.ClipY/2 - tan(dPitch) *
Scale;
return
Result; }
| Now
that we have the tools we need, we do the actual drawing. For this
tutorial we just print the name of the object over it, but you could write
any other information, or draw icons or frames or anything you like. This
function also wraps up our entire HUD module in a single call:
simulated function DrawActorTip(Canvas
Canvas) { local Actor
FocusedActor; local vector ScreenPos;
// Find Target FocusedActor =
GetFocusedActor();
// Label Target if (FocusedActor !=
None) { Canvas.DrawColor =
WhiteColor; Canvas.Style =
ERenderStyle.STY_Translucent; Canvas.Font
= MyFonts.GetSmallFont( Canvas.ClipX
); ScreenPos =
MapToScreen(FocusedActor.Location,
Canvas); Canvas.SetPos(Screenpos.x,
Screenpos.y); Canvas.DrawText(FocusedActor.Name); } }
| Finally
we need to make sure our drawing routine is actually called each time the
HUD is rendered:
// Override the old HUD-redrawing function: simulated
function PostRender(Canvas Canvas) { // -But make
sure all the old drawing is still done first: // (If
you want to, that is) super.PostRender(Canvas);
// Then add our ActorTip on
top: DrawActorTip(Canvas); }
| And
it's done!
APPENDIX:The math of projecting a direction-offset onto the
screen.
The problem is to find out where the vector from the camera to the
target will intersect the screen. We can solve this for X and Y
separately. Considering X first, looking at the figure below, our problem
is to figure out dx:
From the definition of the tangent we know that
and
that
ClipX/2 tan(FOV/2)
=
---------- z
| These
easily reduce to:
ClipX/2 dx
= tan(dYaw)
----------------- tan(FOV/2)
| This
is almost our code exactly. All we add is some conversion to radians and
offsetting the origin to the centre of the canvas.
We then do the same thing for Y versus Pitch. Note that we then use
same scaling fraction (with ClipX, not ClipY), since we don't have any
separate vertical FOV.
ErrataSince publishing the tute the author has informed us the
following: "I've discovered a mistake in it: my way of projecting the
position onto the screen is incorrect: It works OK around horizontal,
but when looking steeply up or down it becomes clear that the assumption
that you can solve for X and Y separately doesn't hold" We'll try to
resolve this soon.
|