Implementing a basic Radar System

In this post we’re going to create a simple radar hud for our character. Before we start, take a look at the end result:

All the source code is available on mygithub repo.

Drawing the radar in our HUD

Create a C++ First Person Template project and open up the generated HUD class.Before we draw our radar, we need to decide its location on the player’s screen.

Be aware that players may have different resolutions, so we need a way to tell UE4 to draw our radar in a relative location instead of hardcoding the widthand heightvalues. Fortunately, inside the HUD class UE4 has includeda Canvas, which holds the actual width and height of the players’ screen. In order to leverage that functionality, we will expose a 2D vector, named RadarStartLocation, which will be a multiplier in order to decide the position of our radar by giving relative values instead of hardcoding.

So, let’s say that my resolution is 1920 x 1080. If I multiply both the 1920 and the 1080 with a value of 0 I would get the upper left corner of the screen. With that said, here is a graph explaining how this works:

The X and Y values, correspond to the values of our multiplier (in this case: RadarStartLocation). So, if I enter thevalues 0.9 and 0.2 on the radar start location, UE4 will place our radar somewhere close to the top right corner. Once we draw our radar, I suggest to temper with the values a bit in order to get the hang of it!

Having explained that, let’s draw our radar. Open up the HUD class provided in the First Person C++ Template project that you’ve created and enter the following property:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 / protected:
/*The start location of our radar*/
UPROPERTY(EditAnywhere, Category = Radar)
FVector2D RadarStartLocation = FVector2D(0.9f,0.2f);
/*The radius of our radar*/
UPROPERTY(EditAnywhere, Category = Radar)
float RadarRadius = 100.f;
UPROPERTY(EditAnywhere, Category = Radar)
float DegreeStep = 0.25f;
/*The pixel size of the drawable radar actors*/
UPROPERTY(EditAnywhere, Category = Radar)
float DrawPixelSize = 5.f;

Then, create the following private functions:

1
2
3
4
5 / /*Returns the center of the radar as a 2d vector*/
FVector2D GetRadarCenterPosition();
/*Draws the radar*/
void DrawRadar();

Switch to the source file and implement the following logic for the previouslydeclared functions:

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 / FVector2D AMinimapHUD::GetRadarCenterPosition()
{
//If the canvas is valid, return the center as a 2d vector
return (Canvas) ? FVector2D(Canvas->SizeX*RadarStartLocation.X, Canvas->SizeY*RadarStartLocation.Y) : FVector2D(0, 0);
}
void AMinimapHUD::DrawRadar()
{
FVector2D RadarCenter = GetRadarCenterPosition();
for (float i = 0; i < 360; i+=DegreeStep)
{
//We want to draw a circle in order to represent our radar
//In order to do so, we calculate the sin and cos of almost every degree
//It it impossible to calculate each and every possible degree because they are infinite
//Lower the degree step in case you need a more accurate circle representation
//We multiply our coordinates by radar size
//in order to draw a circle with radius equal to the one we will input through the editor
float fixedX = FMath::Cos(i) * RadarRadius;
float fixedY = FMath::Sin(i) * RadarRadius;
//Actual draw
DrawLine(RadarCenter.X, RadarCenter.Y, RadarCenter.X + fixedX, RadarCenter.Y + fixedY, FLinearColor::Gray, 1.f);
}
}

Here is the explanation of the drawline parameters:

  1. The X coordinate ofstartof the line
  2. The Y coordinate ofstartof the line
  3. The X coordinate ofendof the line
  4. The Y coordinate ofendof the line
  5. The Color of the line
  6. The thickness of the line

Assuming you’ve implemented the logic above,navigate inside the DrawHUDfunction and after the default pre-implemented code, type incall the DrawRadar() function.

Once you save and compile your code, switch to your Editor and:

  • Create a game mode Blueprint based on the default game mode Blueprint
  • Create a hud Blueprint based on our C++ hud class
  • Assign the game mode and the blueprint hud in the world settings:

You can assign the C++ class in the hud class as well, however since we will expose more properties later on I suggest to follow this approach in order to avoid compiling your code if you temper with the exposed properties.

By now, you should be able to see the gray radar in the upper right corner.

Drawing the Player’s position in the radar

Since the player will always be located in the center of the radar, create a function named DrawPlayerInRadar and implement the following logic:

1
2
3
4
5
6 / void AMinimapHUD::DrawPlayerInRadar()
{
FVector2D RadarCenter = GetRadarCenterPosition();
DrawRect(FLinearColor::Blue, RadarCenter.X, RadarCenter.Y, DrawPixelSize, DrawPixelSize);
}

Then. navigate to the DrawHUD function, and right after the DrawRadar() function,call the DrawPlayerInRadar().

Locating nearby Actors for our Radar

In this case, I decided to raycast for multiple actors near the player and reference all the actors that contain the tag “Radar” in an array, to display them in my Radar. However, depending on your game, your case may vary! I suggest to follow my approach and once you have a fully working radar, implement your own logic.

Having said that, create the followingprotectedproperties in the header file our HUD class:

1
2
3
4
5
6
7
8
9 / /*Sphere height and radius for our raycast*/
UPROPERTY(EditAnywhere, Category = Radar)
float SphereHeight = 200.f;
UPROPERTY(EditAnywhere, Category = Radar)
float SphereRadius = 2750.f;
/*Holds a reference to every actor we are currently drawing in our radar*/
TArray<AActor*> RadarActors;

I will not be covering in length the logic for this raycast, since I’ve already written a dedicated tutorialhere.

Then, create the followingprivate function:

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 / void AMinimapHUD::PerformRadarRaycast()
{
APawn* Player = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (Player)
{
TArray<FHitResult> HitResults;
FVector EndLocation = Player->GetActorLocation();
EndLocation.Z += SphereHeight;
FCollisionShape CollisionShape;
CollisionShape.ShapeType = ECollisionShape::Sphere;
CollisionShape.SetSphere(SphereRadius);
//Perform a the necessary sweep for actors.
//In case you're wondering how this works, read my raycast tutorial here:
GetWorld()->SweepMultiByChannel(HitResults, Player->GetActorLocation(), EndLocation, FQuat::Identity, ECollisionChannel::ECC_WorldDynamic, CollisionShape);
for (auto It : HitResults)
{
AActor* CurrentActor = It.GetActor();
//In case the actor contains the word "Radar" as a tag, add it to our array
if (CurrentActor & CurrentActor->ActorHasTag("Radar")) RadarActors.Add(CurrentActor);
}
}
}

Once you’re done with that,right after the DrawPlayerInRadar()function call inside the DrawHUD function,call the PerformRadarRaycast()function.

Drawing Raycasted Actors

In order to draw the raycasted actors, we will create two functions:

  • One that will convert their location from world location to local location, based on our player and
  • One that will draw the raycasted actors inside the radar

Declare the following property and functions in the header file of our HUD class:

1
2
3
4
5
6
7
8
9 / /*The distance scale of the radar actors*/
UPROPERTY(EditAnywhere, Category = Radar)
float RadarDistanceScale = 25.f;
/*Converts the given actors' location to local (based on our character)*/
FVector2D ConvertWorldLocationToLocal(AActor* ActorToPlace);
/*Draws the raycasted actors in our radar*/
void DrawRaycastedActors();

Here is the logic of the convertion function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 / FVector2D AMinimapHUD::ConvertWorldLocationToLocal(AActor* ActorToPlace)
{
APawn* Player = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (Player & ActorToPlace)
{
//Convert the world location to local, based on the transform of the player
FVector ActorsLocal3dVector = Player->GetTransform().InverseTransformPosition(ActorToPlace->GetActorLocation());
//Rotate the vector by 90 degrees counter-clockwise in order to have a valid rotation in our radar
ActorsLocal3dVector = FRotator(0.f, -90.f, 0.f).RotateVector(ActorsLocal3dVector);
//Apply the given distance scale
ActorsLocal3dVector /= RadarDistanceScale;
//Return a 2d vector based on the 3d vector we've created above
return FVector2D(ActorsLocal3dVector);
}
return FVector2D(0,0);
}

Then, type in the following logic for the DrawRaycastedActors function:

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 / void AMinimapHUD::DrawRaycastedActors()
{
FVector2D RadarCenter = GetRadarCenterPosition();
for (auto It : RadarActors)
{
FVector2D convertedLocation = ConvertWorldLocationToLocal(It);
//We want to clamp the location of our actors in order to make sure
//that we display them inside our radar
//To do so, I've created the following temporary vector in order to access
//the GetClampedToMaxSize2d function. This functions returns a clamped vector (if needed)
//to match our max length
FVector tempVector = FVector(convertedLocation.X, convertedLocation.Y, 0.f);
//Subtract the pixel size in order to make the radar display more accurate
tempVector = tempVector.GetClampedToMaxSize2D(RadarRadius - DrawPixelSize);
//Assign the converted X and Y values to the vector we want to display
convertedLocation.X = tempVector.X;
convertedLocation.Y = tempVector.Y;
DrawRect(FLinearColor::Red, RadarCenter.X + convertedLocation.X, RadarCenter.Y + convertedLocation.Y, DrawPixelSize, DrawPixelSize);
}
}

Once you’re done with that, right after the PefromRadarRaycast inside the DrawHUD function, type in the following code:

1
2
3
4
5 / DrawRaycastedActors();
//Empty the radar actors in case the player moves out of range,
//by doing so, we have always a valid display in our radar
RadarActors.Empty();

Here is the full implementation of my DrawHUD function:

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 / void AMinimapHUD::DrawHUD()
{
//Default template code
Super::DrawHUD();
// Draw very simple crosshair
// find center of the Canvas
const FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f);
// offset by half the texture's dimensions so that the center of the texture aligns with the center of the Canvas
const FVector2D CrosshairDrawPosition((Center.X),
(Center.Y));
// draw the crosshair
FCanvasTileItem TileItem(CrosshairDrawPosition, CrosshairTex->Resource, FLinearColor::White);
TileItem.BlendMode = SE_BLEND_Translucent;
Canvas->DrawItem(TileItem);
//------Radar logic------
DrawRadar();
DrawPlayerInRadar();
PerformRadarRaycast();
DrawRaycastedActors();
//Empty the radar actors in case the player moves out of range,
//by doing so, we have always a valid display in our radar
RadarActors.Empty();
}

Save and compile your code.

Then, navigate to your editor andDO NOT FORGET to assign the TagRadarin some actors:

Once you’re done with that, test your radar!