UDN
Search public documentation:

DevelopmentKitGemsAddingOnScreenIndicatorsCH
English Translation
日本語訳
한국어

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 主页 > 虚幻开发工具包精华文章 > 添加屏幕指示器

添加屏幕范围内指示标志


于 2011 年 5 月对 UDK 进行最后测试
可以与 PC 兼容

概述


默认情况下,虚幻竞技场 3 具有一个单纯的屏幕指示器,可以在玩家位于相机向前的时候显示玩家的名字。该开发工具包精华文章将会向您说明如何创建可以指向屏幕范围内的目标的屏幕指示器。如果这个目标由于在相机后面而不在屏幕范围内,那么屏幕指示器将仍然位于屏幕底部。如果由于目标超出相机截椎体范围而不在屏幕范围内,那么屏幕指示器将会位于屏幕的边缘或者指向目标所在地。如果当前没有在屏幕上进行渲染,屏幕指示器也会出现淡出的效果。最后,屏幕指示器也会根据目标所在的组更改颜色。

  1. 这些 pawn 在屏幕上是可见的,并且在蓝色的组。他们的屏幕指示器会轻微地转移到左边或者右边,同时箭头会相应地指出。
  2. 这个 pawn 被隐藏在桥的后面,这样屏幕指示器变为透明的。
  3. 这是一个组伙伴,他不在相机截椎体的视线范围内,所以看不到。
  4. 这些 pawn 在玩家后面,所以看不到。

OnScreenRadarExample.jpg

指示器材质


屏幕指示器材质负责一些事情。它负责旋转量、不透明度和团队颜色分配。这是可以用作屏幕指示器的贴图。下面的图片显示的是贴图的各个通道。

  1. 这是可以用作屏幕指示器的灰度漫反射。它有很多空闲距离,否则,由于贴图重叠可能会出现贴图旋转失真的情况。
  2. 它是用于设置屏幕指示器中间图标的不透明度的蒙板。
  3. 它是用于创建屏幕指示器的阴影的蒙板。
  4. 它是作为屏幕指示器的不透明度使用的蒙板。

将阴影蒙板和不透明度蒙板分隔开,这样可以允许阴影单独调节它的长度;但是最后没有添加。如果需要,您可以自己添加。

OnScreenIndicatorThumbnail.jpg

屏幕指示器是一个透明、不带光照的材质。如果材质会被渲染到 HUD 上,那么应该不带光照。同时只使用了自发光通道。

自发光通道由两个分支构成。第一个分支会处理屏幕指示器本身。通过使用名为 Rotation(旋转量) 的标量参数旋转屏幕指示器贴图取样器。然后将红色通道 (#1) 与名为 TeamColor 的向量参数相乘。然后再与由 sine 曲线生成的脉冲灰色相乘。第二个分支会处理指向中心位置的屏幕指示器图标。一个名为 Portrait(画像) 的贴图参数会提供这个贴图。收缩它,将它推到中心,这样将会看到大量竖向贴图。通过合成上面的平移贴图实现 TV 屏幕叠加效果。然后将其与屏幕指示器贴图的绿色通道 (#2) 合成。将这两个分支的结果添加在一起,然后与屏幕指示器贴图的 alpha 通道 (#4) 合成。

透明度通道只会添加到蓝色通道 (#3) 和 alpha 通道 (#4)。然后将结果与名为 Opacity(透明度) 的标量参数合成。

OnScreenIndicatorMaterialThumbnail.jpg

Unrealscript


SRadarInfo 结构体

可以用来包含与每个屏幕指示器有关的信息的结构体。

  • UTPawn - 对这个屏幕指示器指向的 pawn 的引用。
  • MaterialInstanceConstant - 供屏幕指示器使用的材质实例。
  • DeleteMe - 用于删除的布尔变量。
  • Offset - 为制作动画的屏幕指示器的偏移量。
  • Opacity - 当前屏幕指示器的透明度。

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

AddPostRenderedActor function

默认情况下,虚幻竞技场 3 会使用后渲染的 actor 渲染现有名称指示器。该功能仅仅为 UTPawn 删除了这项操作。

YourHUD.uc
  function AddPostRenderedActor(Actor A)
  {
    // 为 UTPawn 删除后渲染调用,因为我们不需要名称冒泡显示
    if (UTPawn(A) != None)
    {
      return;
    }
  
    Super.AddPostRenderedActor(A);
  }
  

PostRender 事件

虚幻脚本中的后渲染事件会被引擎引用,使虚幻脚本有机会将贴图、材质或文本渲染到屏幕上。这其中包含很多与这篇开发工具包精华文章的逻辑规则;所以会将它分割为几个小部分。

Update 通道

在这个通道中,通过首先将 DeleteMe 设置为 true 对雷达信息全部进行了删除测试。

接下来使用 ForEach 遍历所有的 UT Pawn。对于不归玩家所有的全部 pawn,首先要检查雷达信息是否引用了它。通过在 RadarInfo 数列中查找一个有效的索引来进行这项操作。如果这个索引是有效的,同时这个 pawn 具有生命值,那么我们在它通过删除检查的时候将 DeleteMe 设置为 false。如果这个索引无效同时这个 pawn 具有生命值,那么会创建一个新的雷达信息。创建一个新的雷达信息的时候,同时也会为该雷达信息创建一个新的材质实例。此外还会设置组颜色,而且在它也通过删除检查的时候将 DeleteMe 设置为 false。

YourHUD.uc
    // 将所有雷达信息设置为在没有找到的情况下删除
    for (i = 0; i < RadarInfo.Length; ++i)
    {
      RadarInfo[i].DeleteMe = true;
    }
  
    // 更新雷达信息,并查看我们是否需要添加或删除任何雷达信息
    ForEach DynamicActors(class'UTPawn', UTPawn)
    {
      if (UTPawn != PlayerOwner.Pawn)
      {
        Index = RadarInfo.Find('UTPawn', UTPawn);
        // 在我们的雷达信息中没有找到这个 pawn,所以添加它
        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 通道

在计算这个通道之前,首先会计算屏幕指示器的尺寸然后将其存储为 PointerSize 。在这个过程中,它与屏幕分辨率宽度相关。相机视线方向也可以通过相机位置和旋转量得到。可以使用相机视线方向检查 pawn 是否在玩家后面。

在这个通道中,会在 DeleteMe 为 false 的情况下渲染雷达信息。否则,会从雷达信息数列中将它们删除。

在渲染屏幕指示器的时候,首先要检查在最后 0.1 秒内是否渲染了相关联的 pawn。如果它没有进行渲染,那么将透明度线性插值降低到 40%。否则,会将线性插入的透明度上升为 100%。在计算透明度后,会在材质实例常量中对它进行设置。

计算玩家的 pawn 和这个 pawn 之间的方向。相机方向和这个玩家 pawn 到 pawn 方向之间的点积可以确定玩家当前是否在看着这个 pawn。在下面的图片中,红色的箭头代表相机方向。绿色箭头代表玩家 pawn 到这个 pawn 的方向。点积将会是 1.f 和 0.f 之间的值,因此将会在相机前面。蓝色箭头代表玩家 pawn 到另一个 pawn 的方向。点积将会是 0.f 和 -1.f 之间的值,因此将会在相机后面。

DotProduct.jpg

YourHUD.uc
    // 处理所有雷达信息的渲染工作
    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)
        {
          // 处理这个指针的不透明度。如果该玩家无法看到这个 pawn,
          // 那么使其在中途淡出,否则在他可以看到这个 pawn 的情况下,淡入
          if (WorldInfo.TimeSeconds - RadarInfo[i].UTPawn.LastRenderTime > 0.1f)
          {
            // 玩家在最后 0.1 秒的时候没有看到这个 pawn
            RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 0.4f, RenderDelta * 4.f);
          }
          else
          {
            // 玩家在最后 0.1 秒的时候看到了这个 pawn
            RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 1.f, RenderDelta * 4.f);
          }
          // 应用这个不透明度
          RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Opacity', RadarInfo[i].Opacity);
  
          // 获取玩家的 pawn 到这个 pawn 的方向
          PawnDirection = Normal(RadarInfo[i].UTPawn.Location - PlayerOwner.Pawn.Location);
  
          // 检查这个 pawn 是否在我的前面
          if (PawnDirection dot CameraViewDirection >= 0.f)
          {
            // 当 actor 在相机前面的时候处理屏幕指示器的渲染工作
          }
          else
          {
            // 当 actor 在相机后面的时候处理屏幕指示器的渲染工作
          }
        }
      }
      else
      {
        // 清空之前存储的变量,这样可以进行垃圾回收工作
        RadarInfo[i].UTPawn = None;
        RadarInfo[i].MaterialInstanceConstant = None;
        // 从雷达信息数组中删除
        RadarInfo.Remove(i, 1);
        // 返回到第一步,保持循环状态
        --i;
      }
    }
  

在相机通道前面渲染

首先通过将 pawn 的位置与碰撞高度累加在一起作为偏移量计算 WorldHUDLocation 。然后将它投射到屏幕坐标中。如果这个屏幕坐标位于屏幕的左侧,那么将偏移量插入到左侧;或者如果在屏幕的右侧,那么将偏移量插入到右侧。它可以为屏幕指示器提供一点动画效果。限定这个偏移量确保它在任何方向上都不会太大。然后计算并设置材质实例的旋转量。最后,将屏幕指示器渲染到屏幕上。

YourHUD.uc
            // 获取世界 HUD 位置,它就位于该 pawn 的头上方
            WorldHUDLocation = RadarInfo[i].UTPawn.Location + (RadarInfo[i].UTPawn.GetCollisionHeight() * Vect(0.f, 0.f, 1.f));
            // 将世界 HUD 位置投射到屏幕 HUD 位置
            ScreenHUDLocation = Canvas.Project(WorldHUDLocation);
  
            // 如果屏幕 HUD 位置更偏向右侧,那么将其转向左侧
            if (ScreenHUDLocation.X > (Canvas.ClipX * 0.5f))
            {
              RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f;
            }
            else
            {
              // 如果屏幕 HUD 位置更偏向左侧,那么将其转向右侧
              RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
            }
            RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
  
            // 设置材质图标的旋转量
            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));
  
            // 绘制材质指针
            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);
  

在相机通道后面渲染

对于位于相机后面的 actor,屏幕指示器通常都在屏幕底部。虽然仍然可以使用投射功能,但是它将会返回需要进行修改的结果。这是为什么反转投射的水平坐标的原因。如果水平屏幕坐标在左侧边上,那么将水平偏移量向右侧推,反之亦然。这样可以确保屏幕指示器一直在屏幕上。进行同样的旋转量计算,并将其应用到材质实例。最后,将屏幕指示器渲染到屏幕上。

YourHUD.uc
            // 投射这个 pawn 的位置
            ScreenHUDLocation = Canvas.Project(RadarInfo[i].UTPawn.Location);
  
            // 反转屏幕 HUD 位置
            ScreenHUDLocation.X = Canvas.ClipX - ScreenHUDLocation.X;
  
            // 如果屏幕 HUD 位置在右侧边上,那么将其转向左侧
            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)
            {
              // 如果屏幕 HUD 位置在左侧边上,那么将其转向右侧
              RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
              RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
            }
            else
            {
              // 如果屏幕 HUD 位置在中间某一位置,那么对它进行改进
              RadarInfo[i].Offset.X = Lerp(RadarInfo[i].Offset.X, 0.f, 4.f * RenderDelta);
            }
  
            // 设置屏幕 HUD 位置
            ScreenHUDLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8);
            ScreenHUDLocation.Y = Canvas.ClipY - 8;
  
            // 设置实际的指针位置
            ActualPointerLocation.X = ScreenHUDLocation.X + RadarInfo[i].Offset.X;
            ActualPointerLocation.Y = ScreenHUDLocation.Y - (PointerSize * 0.5f);
  
            // 设置材质图标的旋转量
            RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation));
  
            // 绘制材质指针
            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);
  

角度计算

在这种角度计算方法中,黑色箭头是参考向量。这两个向量可以组成红色线或者绿色线。计算的角度如蓝色的弧度所示。以弧度为单位进行计算,因为材质预计旋转量值会使用弧度为单位。进行一些早期的检查并返回简单的结果,例如,0.f(0 度),Pi(180 度),Pi * 1.5f(270 度)以及 Pi * 0.5f(90 度)。这种方法比四边形检查所方法(使用 SOH CAH TOA)和其他反余弦方法快。

AngleCalculation.jpg

YourHUD.uc
  function float GetAngle(Vector PointB, Vector PointC)
  {
    // 检查是否可以轻松地确定角度向上还是向下
    if (PointB.X == PointC.X)
    {
      return (PointB.Y < PointC.Y) ? Pi : 0.f;
    }
  
    // 检查是否可以轻松地确定角度向左还是向右
    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);
  }
  

完整的 Unrealscript 类

这是一个完整的 Unrealscript 类;在这里显示是为了说明如何将它拼合在一起。

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)
  {
    // 为 UTPawn 删除后渲染调用,因为我们不需要名称冒泡显示
    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;
    }
  
    // 设置渲染深度
    RenderDelta = WorldInfo.TimeSeconds - LastHUDRenderTime;
  
    // 将所有雷达信息设置为在没有找到的情况下删除
    for (i = 0; i < RadarInfo.Length; ++i)
    {
      RadarInfo[i].DeleteMe = true;
    }
  
    // 更新雷达信息,并查看我们是否需要添加或删除任何雷达信息
    ForEach DynamicActors(class'UTPawn', UTPawn)
    {
      if (UTPawn != PlayerOwner.Pawn)
      {
        Index = RadarInfo.Find('UTPawn', UTPawn);
        // 在我们的雷达信息中没有找到这个 pawn,所以添加它
        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;
        }
      }
    }
  
    // 处理所有雷达信息的渲染工作
    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)
        {
          // 处理这个指针的不透明度。如果该玩家无法看到这个 pawn,
          // 那么使其在中途淡出,否则在他可以看到这个 pawn 的情况下,淡入
          if (WorldInfo.TimeSeconds - RadarInfo[i].UTPawn.LastRenderTime > 0.1f)
          {
            // 玩家在最后 0.1 秒的时候没有看到这个 pawn
            RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 0.4f, RenderDelta * 4.f);
          }
          else
          {
            // 玩家在最后 0.1 秒的时候看到了这个 pawn
            RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 1.f, RenderDelta * 4.f);
          }
          // 应用这个不透明度
          RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Opacity', RadarInfo[i].Opacity);
  
          // 获取玩家的 pawn 到这个 pawn 的方向
          PawnDirection = Normal(RadarInfo[i].UTPawn.Location - PlayerOwner.Pawn.Location);
  
          // 检查这个 pawn 是否在我的前面
          if (PawnDirection dot CameraViewDirection >= 0.f)
          {
            // 获取世界 HUD 位置,它就位于该 pawn 的头上方
            WorldHUDLocation = RadarInfo[i].UTPawn.Location + (RadarInfo[i].UTPawn.GetCollisionHeight() * Vect(0.f, 0.f, 1.f));
            // 将世界 HUD 位置投射到屏幕 HUD 位置
            ScreenHUDLocation = Canvas.Project(WorldHUDLocation);
  
            // 如果屏幕 HUD 位置更偏向右侧,那么将其转向左侧
            if (ScreenHUDLocation.X > (Canvas.ClipX * 0.5f))
            {
              RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f;
            }
            else
            {
              // 如果屏幕 HUD 位置更偏向左侧,那么将其转向右侧
              RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
            }
            RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
  
            // 设置材质图标的旋转量
            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));
  
            // 绘制材质指针
            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
          {
            // 当 actor 在相机后面的时候处理屏幕指示器的渲染工作
            // 投射这个 pawn 的位置
            ScreenHUDLocation = Canvas.Project(RadarInfo[i].UTPawn.Location);
  
            // 反转屏幕 HUD 位置
            ScreenHUDLocation.X = Canvas.ClipX - ScreenHUDLocation.X;
  
            // 如果屏幕 HUD 位置在右侧边上,那么将其转向左侧
            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)
            {
              // 如果屏幕 HUD 位置在左侧边上,那么将其转向右侧
              RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f;
              RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f);
            }
            else
            {
              // 如果屏幕 HUD 位置在中间某一位置,那么对它进行改进
              RadarInfo[i].Offset.X = Lerp(RadarInfo[i].Offset.X, 0.f, 4.f * RenderDelta);
            }
  
            // 设置屏幕 HUD 位置
            ScreenHUDLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8);
            ScreenHUDLocation.Y = Canvas.ClipY - 8;
  
            // 设置实际的指针位置
            ActualPointerLocation.X = ScreenHUDLocation.X + RadarInfo[i].Offset.X;
            ActualPointerLocation.Y = ScreenHUDLocation.Y - (PointerSize * 0.5f);
  
            // 设置材质图标的旋转量
            RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation));
  
            // 绘制材质指针
            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
      {
        // 清空之前存储的变量,这样可以进行垃圾回收工作
        RadarInfo[i].UTPawn = None;
        RadarInfo[i].MaterialInstanceConstant = None;
        // 从雷达信息数组中删除
        RadarInfo.Remove(i, 1);
        // 返回到第一步,保持循环状态
        --i;
      }
    }
  
    // 设置渲染深度
    LastHUDRenderTime = WorldInfo.TimeSeconds;
    Super.PostRender();
  }
  
  function float GetAngle(Vector PointB, Vector PointC)
  {
    // 检查是否可以轻松地确定角度向上还是向下
    if (PointB.X == PointC.X)
    {
      return (PointB.Y < PointC.Y) ? Pi : 0.f;
    }
  
    // 检查是否可以轻松地确定角度向左还是向右
    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
  {
  }
  

下载


  • 下载在这篇精华文章中使用的源代码和地图。(OnScreenIndicator.zip)