UDN
Search public documentation:

DevelopmentKitGemsAddingOnScreenIndicators
日本語訳
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 Home > Unreal Development Kit Gems > Adding on screen indicators

Adding on screen indicators


Last tested against UDK May, 2011
PC compatible

Overview


By default, Unreal Tournament 3 has a simplistic on screen indicator to show the player's name when in front of the camera. This development kit gem will show you how to create on screen indicators which point to targets on screen. If the target is off screen because the target is behind the camera then the on screen indicator will stay at the bottom of the screen. If the target is off screen because the target is out of the camera's frustum, then the on screen indicator will either stay on the edges of the screen and point where the target is. The on screen indicator also fades out if the target is currently not being rendered on screen. Lastly, the on screen indicators also change color depending on the team that the target is on.

  1. These pawns are visible on screen, and are on the blue team. Their on screen indicators shift slightly to the left or the right and the arrow points accordingly.
  2. This pawn is hidden behind the bridge, thus the on screen indicator goes transparent.
  3. This a team mate, who is out of the camera frustum's view and is out of sight.
  4. These pawns are behind the player and out of sight.

OnScreenRadarExample.jpg

Indicator material


The on screen indicator material is responsible for a few things. It's responsible for the rotation, opacity and team colorization. Here is the texture used for the on screen indicator. The below image shows the texture's individual channels.

  1. This is the gray scale diffuse used for the on screen indicator. It has a lot of clear space otherwise when the texture rotates artifacts may appear due to texture wrapping.
  2. This is the mask used to set the opacity of the icon in the middle of the on screen indicator.
  3. This is the mask used to create a shadow for the on screen indicator.
  4. This is the mask used as the opacity for the on screen indicator.

The shadow mask and the opacity mask were separated out to allow the shadow to adjust its strength independently; but wasn't added at the end. You could add this yourself if desired.

OnScreenIndicatorThumbnail.jpg

The on screen indicator is a transparent, unlit material. Materials should be unlit if they are to be rendered onto the HUD. Only the emissive channel is used as well.

The emissive channel is composed of two branches. The first branch handles the on screen indicator itself. The on screen indicator texture sampler is rotated by a scalar parameter called Rotation. The red channel (#1) is then multiplied by a vector parameter called TeamColor. This is then multiplied by a pulsing gray color produced by a sine curve. The second branch handles the on screen indicator icon that goes in the center. A texture parameter called Portrait provides the texture. It is shrunk and pushed into the center, so that a reasonable amount of the portrait texture will be seen. A TV screen overlay effect is achieved by multiplying a panning texture on top. This is then multiplied with the green channel (#2) of the on screen indicator texture. The results of the two branches are added together and multiplied by the alpha channel (#4) of the on screen indicator texture.

The opacity channel simply adds up the blue channel (#3) and the alpha channel (#4). The results is then multiplied by a scalar parameter called Opacity.

OnScreenIndicatorMaterialThumbnail.jpg

Unrealscript


SRadarInfo struct

A struct was used to contain information about each of the on screen indicators.

  • UTPawn - A reference to the pawn that this on screen indicator points to.
  • MaterialInstanceConstant - An instance of the material used by the on screen indicator.
  • DeleteMe - A boolean for deletion purposes.
  • Offset - Offset of the on screen indicator used for animation purposes.
  • Opacity - Current opacity of the on screen indicator.

YourHUD.uc
struct SRadarInfo
{
  var UTPawn UTPawn;
  var MaterialInstanceConstant MaterialInstanceConstant;
  var bool DeleteMe;
  var Vector2D Offset;
  var float Opacity;
};

AddPostRenderedActor function

By default, Unreal Tournament 3 uses post rendered actors to render the existing name indicators. This function removes that behavior just for the UTPawn.

YourHUD.uc
function AddPostRenderedActor(Actor A)
{
  // Remove post render call for UTPawns as we don't want the name bubbles showing
  if (UTPawn(A) != None)
  {
    return;
  }

  Super.AddPostRenderedActor(A);
}

PostRender event

The post render event in Unrealscript is called by the engine to allow Unrealscript an opportunity to render textures, materials or text onto the screen. This is where the bulk of the logic is for this development kit gem; thus will be split up into smaller sections.

Update pass

In this pass, the radar info's are all tested for deletion by first setting DeleteMe to true.

Next all UT Pawns are iterated over using ForEach. For all pawns that isn't owned by the player, first check if a radar info references it. This is done by attempting to find a valid index in the RadarInfo array. If the index is valid and the pawn has health then we set DeleteMe to false as it passes the deletion check. If the index is invalid and the pawn has health, then a new radar info is created. When a new radar info is created, a new material instance is also created for that radar info. The team color is also set, and DeleteMe is set false as it also passes the deletion check.

YourHUD.uc
  // Set all radar infos to delete if not found 
  for (i = 0; i < RadarInfo.Length; ++i)
  {
    RadarInfo[i].DeleteMe = true;
  }
  
  // Update the radar infos and see if we need to add or remove any
  ForEach DynamicActors(class'UTPawn', UTPawn)
  {
    if (UTPawn != PlayerOwner.Pawn)
    {
      Index = RadarInfo.Find('UTPawn', UTPawn);
      // This pawn was not found in our radar infos, so add it
      if (Index == INDEX_NONE && UTPawn.Health > 0)
      {
        i = RadarInfo.Length;
        RadarInfo.Length = RadarInfo.Length + 1;
        RadarInfo[i].UTPawn = UTPawn;
        RadarInfo[i].MaterialInstanceConstant = new () class'MaterialInstanceConstant';

        if (RadarInfo[i].MaterialInstanceConstant != None)
        {
          RadarInfo[i].MaterialInstanceConstant.SetParent(Material'GemOnscreenRadarContent.PointerMaterial');

          if (UTPawn.PlayerReplicationInfo != None && UTPawn.PlayerReplicationInfo.Team != None)
          {
            TeamLinearColor = (UTPawn.PlayerReplicationInfo.Team.TeamIndex == 0) ? Default.RedLinearColor : Default.BlueLinearColor;
            RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', TeamLinearColor);
          }
          else
          {
            RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', Default.DMLinearColor);
          }
        }

        RadarInfo[i].DeleteMe = false;
      }
      else if (UTPawn.Health > 0)
      {
        RadarInfo[Index].DeleteMe = false;
      }
    }
  }

Rendering pass

Before the pass is calculated, the on screen indicator size is first calculated and stored as PointerSize. In this implementation, this is relative to the screen resolution width. The camera view direction is also derived from the camera location and rotation. The camera view direction is used to detect if a pawn is behind the player or not.

In this pass radar info's are rendered if DeleteMe is false. Otherwise they are removed from the radar info array.

When the on screen indicator is rendered, it is first checked if the associated pawn has been rendered in the last 0.1 second. If it hasn't, then it's opacity is linearly interpolated down to 40%. Otherwise it is linearly interpolated up to 100%. When the opacity is calculated, it is then set in the material instance constant.

The direction between the player's pawn and the pawn is calculated. The dot product between the camera direction and the player pawn to pawn direction determines if the player is currently looking at the pawn or not. In the image below, the red arrow represents the camera direction. The green arrow represents the direction of the player pawn to the pawn. The dot product will be a value between 1.f and 0.f, thus will be in front of the camera. The blue arrow represents the direction of the player pawn to another pawn. The dot product will be a value between 0.f and -1.f, thus will be behind the camera.

DotProduct.jpg

YourHUD.uc
  // Handle rendering of all of the radar infos
  PointerSize = Canvas.ClipX * 0.083f;
  PlayerOwner.GetPlayerViewPoint(CameraLocation, CameraRotation);
  CameraViewDirection = Vector(CameraRotation);

  for (i = 0; i < RadarInfo.Length; ++i)
  {
    if (!RadarInfo[i].DeleteMe)
    {
      if (RadarInfo[i].UTPawn != None && RadarInfo[i].MaterialInstanceConstant != None)
      {
        // Handle the opacity of the pointer. If the player cannot see this pawn,
        // then fade it out half way, otherwise if he can, fade it in
        if (WorldInfo.TimeSeconds - RadarInfo[i].UTPawn.LastRenderTime > 0.1f)
        {
          // Player has not seen this pawn in the last 0.1 seconds
          RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 0.4f, RenderDelta * 4.f);
        }
        else
        {
          // Player has seen this pawn in the last 0.1 seconds
          RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 1.f, RenderDelta * 4.f);
        }
        // Apply the opacity
        RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Opacity', RadarInfo[i].Opacity);

        // Get the direction from the player's pawn to the pawn
        PawnDirection = Normal(RadarInfo[i].UTPawn.Location - PlayerOwner.Pawn.Location);

        // Check if the pawn is in front of me
        if (PawnDirection dot CameraViewDirection >= 0.f)
        {
          // Handle rendering the on screen indicator when the actor is in front of the camera
        }
        else
        {
          // Handle rendering the on screen indicator when the actor is behind the camera
        }
      }
    }
    else
    {
      // Null the variables previous stored so garbage collection can occur
      RadarInfo[i].UTPawn = None;
      RadarInfo[i].MaterialInstanceConstant = None;
      // Remove from the radar info array
      RadarInfo.Remove(i, 1);
      // Back step one, to maintain the for loop
      --i;
    }
  }

Rendering in front of the camera pass

First the WorldHUDLocation is calculated by adding together the pawn's location with the collision height as an offset. This is then projected into screen coordinates. If the screen coordinate is on the left hand side of the screen an offset is interpolated to the left; or if it is on the right hand side of the screen an offset is interpolated to the right. This provides a little bit of animation to the on screen indicator. The offset is clamped to ensure that it doesn't get too large in either direction. The rotation of the material instance is then calculated and set. Finally, the on screen indicator is rendered onto the screen.

YourHUD.uc
          // Get the world HUD location, which is just above the pawn's head
          WorldHUDLocation = RadarInfo[i].UTPawn.Location + (RadarInfo[i].UTPawn.GetCollisionHeight() * Vect(0.f, 0.f, 1.f));
          // Project the world HUD location into screen HUD location
          ScreenHUDLocation = Canvas.Project(WorldHUDLocation);

          // If the screen HUD location is more to the right, then swing it to the left
          if (ScreenHUDLocation.X > (Canvas.ClipX * 0.5f))
          {
            RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f;
          }
          else
          {
            // If the screen HUD location is more to the left, then swing it to the right
            RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
          }
          RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
        
          // Set the rotation of the material icon
          ActualPointerLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8) + RadarInfo[i].Offset.X;
          ActualPointerLocation.Y = Clamp(ScreenHUDLocation.Y - PointerSize + RadarInfo[i].Offset.Y, 8, Canvas.ClipY - 8 - PointerSize) + (PointerSize * 0.5f);
          RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation));

          // Draw the material pointer
          Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f));
          Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f);

Rendering behind the camera pass

For actors behind the camera, the on screen indicator is always at the bottom of the screen. While it is possible to still use the project function, it will return results that needs to modified. This is why the projected horizontal coordinates is inversed. If the horizontal screen coordinates is on the left edge then the horizontal offset is pushed to the right and vice versa. This ensures that the on screen indicator is always on screen. Similar rotation calculations are done and applied to the material instance. Finally, the on screen indicator is rendered onto the screen.

YourHUD.uc
          // Project the pawn's location
          ScreenHUDLocation = Canvas.Project(RadarInfo[i].UTPawn.Location);

          // Inverse the Screen HUD location
          ScreenHUDLocation.X = Canvas.ClipX - ScreenHUDLocation.X;

          // If the screen HUD location is on the right edge, then swing it to the left
          if (ScreenHUDLocation.X > (Canvas.ClipX - 8))
          {
            RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f;
            RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
          }
          else if (ScreenHUDLocation.X < 8)
          {
            // If the screen HUD location is on the left edge, then swing it to the right
            RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
            RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
          }
          else
          {
            // If the screen HUD location is somewhere in the middle, then straighten it up
            RadarInfo[i].Offset.X = Lerp(RadarInfo[i].Offset.X, 0.f, 4.f * RenderDelta);
          }

          // Set the screen HUD location
          ScreenHUDLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8);
          ScreenHUDLocation.Y = Canvas.ClipY - 8;

          // Set the actual pointer location
          ActualPointerLocation.X = ScreenHUDLocation.X + RadarInfo[i].Offset.X;
          ActualPointerLocation.Y = ScreenHUDLocation.Y - (PointerSize * 0.5f);

          // Set the rotation of the material icon
          RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation));

          // Draw the material pointer
          Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f));
          Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f);

Angle calculation

In this angle calculation method, the black arrow is the reference vector. The two vectors form either the red line or the green line. The angle calculated is shown as the blue arc. The calculations are done in radians since the material expects the rotation value in radians. Some early checks are done to return simple results such as 0.f (0 degrees), Pi (180 degrees), Pi * 1.5f (270 degrees) and Pi * 0.5f (90 degrees). This method is a faster than the quadrant checking method (using SOH CAH TOA) and the other acos method.

AngleCalculation.jpg

YourHUD.uc
function float GetAngle(Vector PointB, Vector PointC)
{
  // Check if angle can easily be determined if it is up or down
  if (PointB.X == PointC.X)
  {
    return (PointB.Y < PointC.Y) ? Pi : 0.f;
  }

  // Check if angle can easily be determined if it is left or right
  if (PointB.Y == PointC.Y)
  {
    return (PointB.X < PointC.X) ? (Pi * 1.5f) : (Pi * 0.5f);
  }

  return (2.f * Pi) - atan2(PointB.X - PointC.X, PointB.Y - PointC.Y);
}

Completed Unrealscript class

This is the completed Unrealscript class; shown here for clarity on how to piece it all together.

YourHUD.uc
class UTRadarHUD extends UTTeamHUD;

struct SRadarInfo
{
  var UTPawn UTPawn;
  var MaterialInstanceConstant MaterialInstanceConstant;
  var bool DeleteMe;
  var Vector2D Offset;
  var float Opacity;
};

var array<SRadarInfo> RadarInfo;

function AddPostRenderedActor(Actor A)
{
  // Remove post render call for UTPawns as we don't want the name bubbles showing
  if (UTPawn(A) != None)
  {
    return;
  }

  Super.AddPostRenderedActor(A);
}

event PostRender()
{
  local int i, Index;
  local Vector WorldHUDLocation, ScreenHUDLocation, ActualPointerLocation, CameraViewDirection, PawnDirection, CameraLocation;
  local Rotator CameraRotation;
  local UTPawn UTPawn;
  local LinearColor TeamLinearColor;
  local float PointerSize;

  if (PlayerOwner == None || PlayerOwner.Pawn == None)
  {
    return;
  }

  // Set up the render delta
  RenderDelta = WorldInfo.TimeSeconds - LastHUDRenderTime;

  // Set all radar infos to delete if not found 
  for (i = 0; i < RadarInfo.Length; ++i)
  {
    RadarInfo[i].DeleteMe = true;
  }
  
  // Update the radar infos and see if we need to add or remove any
  ForEach DynamicActors(class'UTPawn', UTPawn)
  {
    if (UTPawn != PlayerOwner.Pawn)
    {
      Index = RadarInfo.Find('UTPawn', UTPawn);
      // This pawn was not found in our radar infos, so add it
      if (Index == INDEX_NONE && UTPawn.Health > 0)
      {
        i = RadarInfo.Length;
        RadarInfo.Length = RadarInfo.Length + 1;
        RadarInfo[i].UTPawn = UTPawn;
        RadarInfo[i].MaterialInstanceConstant = new () class'MaterialInstanceConstant';

        if (RadarInfo[i].MaterialInstanceConstant != None)
        {
          RadarInfo[i].MaterialInstanceConstant.SetParent(Material'GemOnscreenRadarContent.PointerMaterial');

          if (UTPawn.PlayerReplicationInfo != None && UTPawn.PlayerReplicationInfo.Team != None)
          {
            TeamLinearColor = (UTPawn.PlayerReplicationInfo.Team.TeamIndex == 0) ? Default.RedLinearColor : Default.BlueLinearColor;
            RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', TeamLinearColor);
          }
          else
          {
            RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', Default.DMLinearColor);
          }
        }

        RadarInfo[i].DeleteMe = false;
      }
      else if (UTPawn.Health > 0)
      {
        RadarInfo[Index].DeleteMe = false;
      }
    }
  }

  // Handle rendering of all of the radar infos
  PointerSize = Canvas.ClipX * 0.083f;
  PlayerOwner.GetPlayerViewPoint(CameraLocation, CameraRotation);
  CameraViewDirection = Vector(CameraRotation);

  for (i = 0; i < RadarInfo.Length; ++i)
  {
    if (!RadarInfo[i].DeleteMe)
    {
      if (RadarInfo[i].UTPawn != None && RadarInfo[i].MaterialInstanceConstant != None)
      {
        // Handle the opacity of the pointer. If the player cannot see this pawn,
        // then fade it out half way, otherwise if he can, fade it in
        if (WorldInfo.TimeSeconds - RadarInfo[i].UTPawn.LastRenderTime > 0.1f)
        {
          // Player has not seen this pawn in the last 0.1 seconds
          RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 0.4f, RenderDelta * 4.f);
        }
        else
        {
          // Player has seen this pawn in the last 0.1 seconds
          RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 1.f, RenderDelta * 4.f);
        }
        // Apply the opacity
        RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Opacity', RadarInfo[i].Opacity);

        // Get the direction from the player's pawn to the pawn
        PawnDirection = Normal(RadarInfo[i].UTPawn.Location - PlayerOwner.Pawn.Location);

        // Check if the pawn is in front of me
        if (PawnDirection dot CameraViewDirection >= 0.f)
        {
          // Get the world HUD location, which is just above the pawn's head
          WorldHUDLocation = RadarInfo[i].UTPawn.Location + (RadarInfo[i].UTPawn.GetCollisionHeight() * Vect(0.f, 0.f, 1.f));
          // Project the world HUD location into screen HUD location
          ScreenHUDLocation = Canvas.Project(WorldHUDLocation);

          // If the screen HUD location is more to the right, then swing it to the left
          if (ScreenHUDLocation.X > (Canvas.ClipX * 0.5f))
          {
            RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f;
          }
          else
          {
            // If the screen HUD location is more to the left, then swing it to the right
            RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
          }
          RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
        
          // Set the rotation of the material icon
          ActualPointerLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8) + RadarInfo[i].Offset.X;
          ActualPointerLocation.Y = Clamp(ScreenHUDLocation.Y - PointerSize + RadarInfo[i].Offset.Y, 8, Canvas.ClipY - 8 - PointerSize) + (PointerSize * 0.5f);
          RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation));

          // Draw the material pointer
          Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f));
          Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f);
        }
        else
        {
          // Handle rendering the on screen indicator when the actor is behind the camera
          // Project the pawn's location
          ScreenHUDLocation = Canvas.Project(RadarInfo[i].UTPawn.Location);

          // Inverse the Screen HUD location
          ScreenHUDLocation.X = Canvas.ClipX - ScreenHUDLocation.X;

          // If the screen HUD location is on the right edge, then swing it to the left
          if (ScreenHUDLocation.X > (Canvas.ClipX - 8))
          {
            RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f;
            RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
          }
          else if (ScreenHUDLocation.X < 8)
          {
            // If the screen HUD location is on the left edge, then swing it to the right
            RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
            RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
          }
          else
          {
            // If the screen HUD location is somewhere in the middle, then straighten it up
            RadarInfo[i].Offset.X = Lerp(RadarInfo[i].Offset.X, 0.f, 4.f * RenderDelta);
          }

          // Set the screen HUD location
          ScreenHUDLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8);
          ScreenHUDLocation.Y = Canvas.ClipY - 8;

          // Set the actual pointer location
          ActualPointerLocation.X = ScreenHUDLocation.X + RadarInfo[i].Offset.X;
          ActualPointerLocation.Y = ScreenHUDLocation.Y - (PointerSize * 0.5f);

          // Set the rotation of the material icon
          RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation));

          // Draw the material pointer
          Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f));
          Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f);
        }
      }
    }
    else
    {
      // Null the variables previous stored so garbage collection can occur
      RadarInfo[i].UTPawn = None;
      RadarInfo[i].MaterialInstanceConstant = None;
      // Remove from the radar info array
      RadarInfo.Remove(i, 1);
      // Back step one, to maintain the for loop
      --i;
    }
  }

  // Setup the render delta
  LastHUDRenderTime = WorldInfo.TimeSeconds;
  Super.PostRender();
}

function float GetAngle(Vector PointB, Vector PointC)
{
  // Check if angle can easily be determined if it is up or down
  if (PointB.X == PointC.X)
  {
    return (PointB.Y < PointC.Y) ? Pi : 0.f;
  }

  // Check if angle can easily be determined if it is left or right
  if (PointB.Y == PointC.Y)
  {
    return (PointB.X < PointC.X) ? (Pi * 1.5f) : (Pi * 0.5f);
  }

  return (2.f * Pi) - atan2(PointB.X - PointC.X, PointB.Y - PointC.Y);
}

defaultproperties
{
}

Downloads


  • Download the content and source code used for this development kit gem. (OnScreenIndicator.zip)