While our game may be running without any issues in the editor or even in…
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 my github 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 width and height values. Fortunately, inside the HUD class UE4 has included a 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 the values 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 previously declared 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:
- The X coordinate of start of the line
- The Y coordinate of start of the line
- The X coordinate of end of the line
- The Y coordinate of end of the line
- The Color of the line
- The thickness of the line
Assuming you’ve implemented the logic above, navigate inside the DrawHUD function and after the default pre-implemented code, type in call 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 following protected properties 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 tutorial here.
Then, create the following private 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: http://wp.me/p6hvtS-5F 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 and DO NOT FORGET to assign the Tag Radar in some actors:
Once you’re done with that, test your radar!
Hey there,
I’ve followed this exactly and even copy pasted your code over mine, but for some reason actors won’t show up on the radar at all (the radar appears fine and the player also appears). I’ve triple checked the actors have “Radar” as a tag and even added the tag to all actors, as well as created new ones with the tag, but none show up.
Where would be the best place to begin looking to see what I might have done wrong?
Thank you, and sorry for bothering you with questions the second tutorial in a row.
Oh, it’s because I was using component tags, not the ones under the Actor category…
Well, this was a very helpful tutorial anyway, many thanks
The same problem…Thank you bro 🙂
Thank you it was very helpful!!!!