UE5蓝图实现鼠标框选Actor:从输入到检测的完整方案

📅 2026/7/19 21:12:59
UE5蓝图实现鼠标框选Actor:从输入到检测的完整方案
1. 项目概述与核心价值最近在带几个刚入门的UE5新人发现他们最头疼的不是怎么摆模型、调材质而是如何与场景里的物体进行“有效沟通”。比如你想让玩家框选一群士兵然后指挥他们冲锋这个看似简单的“框选”功能就成了第一道坎。很多教程要么讲得太浅只告诉你拖几个节点要么直接上C对蓝图新手不太友好。所以我决定把带新人时反复打磨的这套“蓝图框选Actor”方案整理出来它不只是一个功能实现更是一套理解UE5输入、渲染、碰撞检测联动的思维模型。这个教程的目标很明确让你仅用蓝图就实现一个手感扎实、性能可靠、可扩展性强的鼠标框选系统。完成后你将得到一个完整的项目文件里面包含了从创建HUD绘制框选框到处理鼠标事件再到精准筛选被框选Actor的所有逻辑。更重要的是你会明白每一步“为什么”要这么做——比如为什么要在HUD里画框而不是在PlayerController里框选检测时Box和Sphere检测该怎么选这些决策背后的考量才是新手成长为熟手的关键。无论你是想做RTS、策略游戏还是需要场景编辑工具这个功能都是基石。接下来我会假设你已经有UE5的基础操作知识比如打开编辑器、创建蓝图类但完全没接触过框选逻辑。我们从最根本的设计思路开始拆解。2. 核心设计思路与架构拆解在动手写任何蓝图之前我们必须先想清楚一个完整的框选流程在UE5里究竟是怎么跑起来的如果脑子里没有这张“数据流图”直接对着节点生搬硬套出了问题根本不知道从哪查起。2.1 框选流程的数据流全景图一个标准的鼠标框选其核心流程可以概括为四个阶段输入捕获 - 视觉反馈 - 物理检测 - 结果处理。在UE5的蓝图框架下这四个阶段分别由不同的“责任人”来承担输入捕获阶段主角是PlayerController。它像是一个总指挥专门负责接收来自玩家鼠标、键盘的原始输入信号。当玩家按下鼠标左键时这个信号首先被PlayerController拿到。它需要记录下“按下那一刻”鼠标在屏幕上的位置一个2D坐标。视觉反馈阶段主角是HUD。总指挥知道了起点但玩家需要看到自己框选的范围。HUD就像一个透明的画布覆盖在游戏画面上方专门负责绘制UI和这类临时性的几何图形。它根据PlayerController传来的起点和当前鼠标位置实时绘制一个矩形框给玩家最直接的视觉反馈。物理检测阶段主角是PlayerController或GameMode。当玩家松开鼠标左键框选结束时我们需要知道这个2D的屏幕矩形框对应在3D游戏世界里罩住了哪些物体。这个过程需要将2D屏幕坐标转换为3D世界中的检测体比如一个立方体范围然后使用碰撞查询去检测范围内的Actor。结果处理阶段主角是被选中的Actor和管理它们的系统。检测到目标后通常需要通知这些Actor“你被选中了”例如高亮显示同时可能还有一个全局的选择管理器来记录当前选中的所有单位以便后续进行群体移动或释放技能。为什么要这么分工这是UE框架的职责分离原则。PlayerController处理输入逻辑HUD处理绘制碰撞检测是引擎的基础服务。如果把绘制逻辑塞进PlayerController或者用HUD去做复杂的碰撞查询代码会变得混乱且难以维护。理解这个架构是避免后面踩坑的基础。2.2 关键蓝图类的职责与选型基于上面的流程我们需要创建和修改几个关键的蓝图类MyPlayerController继承自PlayerController。我们将在这里处理鼠标按下、移动、抬起的输入事件存储框选的起点和终点坐标并在框选结束时发起物理检测。MyHUD继承自HUD。我们将重写它的DrawHUD函数在这个函数里根据MyPlayerController提供的起点和当前鼠标位置使用绘制函数画出一个矩形框。SelectableActor这是一个接口或父类。并非所有Actor都能被框选比如背景天空球。我们最好创建一个Selectable接口蓝图接口里面定义诸如OnSelected、OnDeselected这样的函数。然后让所有希望被框选的单位如BP_Character_Soldier实现这个接口。这样检测逻辑可以只关注实现了该接口的Actor更清晰高效。为什么用接口而不是直接检测所有Actor这是性能与设计清晰度的双重考量。场景中可能有成百上千个Actor但只有几十个是可选的单位。如果每帧都检测所有Actor会做大量无用功。通过接口我们可以在碰撞查询时指定特定的接口类引擎会帮我们过滤大幅提升效率。同时这也是一种良好的契约设计任何想要获得框选能力的Actor只需实现几个规定的方法即可扩展性很强。2.3 框选检测的核心算法选择当框选结束时我们手里有一个2D的屏幕矩形。如何找到3D世界中被这个矩形“罩住”的Actor主要有两种主流思路视锥体剔除 包围盒检测将屏幕矩形的四个角通过摄像机矩阵反推出四条射线这四条射线在3D空间中构成一个平截头体Frustum。然后检查每个候选Actor的包围盒Bounding Box是否与这个视锥体相交。这种方法在RTS游戏中非常经典检测精度高但计算量相对较大需要自己处理视锥体构建和相交测试。射线投射 体积检测这是本教程采用的方法更直观且易于用蓝图实现。其原理是在框选起点和终点位置从屏幕向世界各发射一条射线Deproject Screen to World。根据这两条射线获得的世界空间起点可以构造出一个3D的盒子Box或胶囊体Capsule。使用OverlapMultiBy...或BoxOverlapActors等节点对这个3D体积进行重叠检测返回所有在其中的Actor。我们选择第二种方法射线体积检测的原因对于新手来说蓝图节点支持完善逻辑直观易懂。Deproject和Overlap节点都是现成的不需要我们手动进行复杂的矩阵运算。在中小规模单位几百个的场景下性能完全足够。第一种方法更适合C实现或超大规模单位选择的极端优化场景。3. 逐步实现从零搭建框选系统理论清晰了现在开始动手。我会按照创建蓝图、绑定输入、绘制反馈、实现检测的顺序一步步带你把系统搭起来。请打开你的UE5项目建议用空白模板或ThirdPerson模板跟着操作。3.1 创建核心蓝图类与设置首先创建我们需要的蓝图类。在内容浏览器中右键选择“蓝图类”。在弹出窗口中搜索并选择“PlayerController”作为父类命名为BP_MyPlayerController。同样方法创建以“HUD”为父类的蓝图命名为BP_MyHUD。接着创建一个蓝图接口右键 - 蓝图 - 蓝图接口命名为BPI_Selectable。接下来修改项目设置让游戏使用我们新建的类。点击菜单栏的“编辑” - “项目设置”。在“项目”分类下找到“地图和模式”。在“默认模式”中将“默认Pawn类”设为你的角色如ThirdPerson示例中的BP_ThirdPersonCharacter将“玩家控制器类”设为BP_MyPlayerController将“HUD类”设为BP_MyHUD。保存设置。这样游戏运行时就会自动使用我们自定义的控制器和HUD了。3.2 处理鼠标输入事件打开BP_MyPlayerController我们需要添加几个变量来记录框选状态。添加变量bIsSelecting(布尔型)是否正在框选鼠标左键按下未松开。SelectionStart(Vector2D)框选开始的屏幕坐标。SelectionEnd(Vector2D)框选当前的屏幕坐标。CurrentSelectionBox(Box Extent 或 Vector): 用于存储计算出的3D检测框范围可选可临时计算。绑定输入事件在事件图表中右键搜索“Enable Input”节点并调用确保控制器能接收输入。右键搜索“InputAction”或“InputAxis”事件。我们需要绑定三个LeftMouseButton(Pressed)鼠标左键按下事件。MouseX和MouseY(Axis Events)鼠标移动事件。LeftMouseButton(Released)鼠标左键释放事件。实现输入逻辑在LeftMouseButton (Pressed)事件后设置bIsSelecting true。使用Get Mouse Position节点获取当前鼠标位置存入SelectionStart。同时也将这个值赋给SelectionEnd因为刚开始框选起点和终点相同。在MouseX和MouseY(Axis Events) 事件后先做一个分支Branch判断bIsSelecting是否为真。只有正在框选时才更新终点。如果为真再次使用Get Mouse Position更新SelectionEnd变量。这里有个关键点我们需要把最新的SelectionEnd告诉HUD让它重绘框选框。可以通过一个自定义事件如UpdateSelectionBox来传递坐标或者直接设置HUD的公开变量。为了降低耦合我常用一个简单方法在PlayerController里定义一个事件OnSelectionBoxUpdated带两个Vector2D参数然后在HUD里绑定这个事件。这里我们先采用设置HUD变量的方式。在LeftMouseButton (Released)事件后设置bIsSelecting false。此时SelectionStart和SelectionEnd构成了一个完整的矩形。调用我们即将编写的“执行框选检测”函数例如PerformSelection。检测完成后应该清空或重置SelectionStart和SelectionEnd并通知HUD停止绘制可以将SelectionEnd设置为一个无效值如(-1,-1)。注意事项输入坐标的坐标系Get Mouse Position返回的坐标是相对于视口Viewport的像素坐标原点(0,0)在左上角。而HUD绘制时使用的也是这个坐标系所以可以直接使用。但在后续进行屏幕到世界的反投影时需要将像素坐标转换为标准化设备坐标NDC即X和Y范围在[-1, 1]或[0, 1]之间。Deproject Screen to World节点通常接受的是视口坐标但内部会处理这个转换。我们只需确保传入的坐标是正确的视口坐标即可。3.3 在HUD中绘制框选框打开BP_MyHUD。HUD的绘制发生在每一帧的DrawHUD事件中。我们需要在这里根据PlayerController传来的坐标画一个矩形。添加变量来接收绘制信息SelectionBoxStart(Vector2D)SelectionBoxEnd(Vector2D)bDrawSelectionBox(布尔型)从PlayerController获取数据在事件图表中我们需要在游戏开始时获取到PlayerController的引用。可以在Event BeginPlay节点后使用Get Player Controller节点然后类型转换为BP_MyPlayerController将返回的引用保存到一个变量中比如MyPC。在BP_MyPlayerController中更新SelectionEnd时除了更新自己的变量还应调用HUD上的一个自定义事件如UpdateSelectionBox将起点和终点坐标传过来并设置bDrawSelectionBox为真。重写DrawHUD逻辑在BP_MyHUD的事件图表中找到或重写Receive Draw HUD事件在UE5中通常是Event Receive Draw HUD节点。在这个事件后先判断bDrawSelectionBox是否为真并且起点和终点不能相同避免画一个点。如果为真使用Draw Rect节点来绘制矩形。Rect Position: 矩形的左上角坐标应该是SelectionBoxStart和SelectionBoxEnd中X和Y较小的值。可以用Min of Float分别计算X和Y的最小值。Rect Size: 矩形的宽和高是终点坐标减起点坐标的绝对值。用Abs (Float)节点计算SelectionBoxEnd.X - SelectionBoxStart.X和SelectionBoxEnd.Y - SelectionBoxStart.Y。Color Opacity: 设置一个半透明的颜色比如淡蓝色(R:0.2, G:0.5, B:1.0, A:0.3)。当PlayerController在鼠标释放后通知HUD停止绘制时将bDrawSelectionBox设为假。实操心得绘制优化DrawHUD每帧都会调用里面的绘制操作要尽量轻量。Draw Rect本身开销不大。但要避免在DrawHUD里做复杂的计算比如每帧都去计算矩形的最小/最大值。最好在收到PlayerController坐标更新时就提前计算好矩形的Position和Size存为变量在DrawHUD里直接使用。此外只有当bDrawSelectionBox为真时才执行绘制逻辑这也是一个重要的性能优化点。3.4 实现框选检测逻辑这是最核心的一步在BP_MyPlayerController的PerformSelection函数中实现。我们的目标是根据屏幕矩形找到所有被框住的、实现了BPI_Selectable接口的Actor。屏幕坐标到世界空间的转换我们需要将屏幕矩形的两个对角点起点和终点转换到3D世界空间。但注意屏幕坐标是一个2D点对应世界中的一条射线从摄像机出发穿过该屏幕点。我们真正需要的是这条射线与某个“平面”的交点来作为3D检测体积的角点。通常我们选择与地面平行的平面比如Z0的高度。使用Deproject Screen to World节点。它需要Player Controller自身、Screen Position屏幕坐标和World Position World Direction输出。我们主要需要World Direction射线方向。为了得到一个世界位置我们需要定义一个检测平面。假设我们的可选中单位都在大致相同的高度比如Z100我们可以计算射线与这个水平面的交点。公式是WorldOrigin WorldDirection * T其中T (PlaneZ - OriginZ) / DirectionZ。在蓝图中这需要一些向量计算。构建3D检测体积一个更稳健且蓝图友好的方法是我们不直接计算精确的角点而是利用起点和终点在世界空间中的“意义”。我们可以从起点和终点分别发射射线击中我们关心的“选择平面”得到两个3D点WorldPointA和WorldPointB。这两个点不一定能直接构成一个立方体的对角点因为它们的Z值可能相同如果平面是水平的。我们可以用这两个点的X和Y坐标来定义底面的矩形然后赋予这个矩形一个高度比如从地面向上200单位从而构造出一个3D的盒子Box。盒子的中心( (WorldPointA.X WorldPointB.X)/2, (WorldPointA.Y WorldPointB.Y)/2, PlaneZ (BoxHeight/2) )盒子的半高范围Box Extent( Abs(WorldPointA.X - WorldPointB.X)/2, Abs(WorldPointA.Y - WorldPointB.Y)/2, BoxHeight/2 )执行重叠检测有了盒子的中心Box Location和范围Box Extent我们就可以进行物理检测了。使用Overlap Multi By Profile或Box Overlap Actors节点。推荐使用Box Overlap Actors因为它更直观。你需要指定World Context Object: 通常用Get World。Box Pos和Box Extent: 上面计算出的值。Object Types: 选择你想要检测的物体类型比如WorldDynamic可移动物体。Actor Class Filter: 这里留空因为我们用接口过滤。Actors to Ignore: 可以忽略玩家控制的Pawn自身。这个节点会返回一个Actor数组包含了所有与这个盒子重叠的Actor。接口过滤与通知遍历返回的Actor数组。对每个Actor使用Does Implement Interface节点检查它是否实现了BPI_Selectable接口。如果实现了将其添加到一个最终的选择结果数组SelectedActors中。然后可以调用该Actor上接口定义的函数例如On Selected通知它已被选中。同时也可以取消之前选中但现在不在列表中的Actor的选中状态这需要一个记录上次选中列表的变量。关键细节处理反向框选玩家框选时可以从左往右拖也可以从右往左拖。我们计算矩形Position和Size时用了Min和Abs这能正确处理。但在计算世界空间盒子的Box Extent时Abs(WorldPointA.X - WorldPointB.X)同样保证了范围始终为正。这是构建检测体积时必须注意的否则会得到一个负向的范围导致检测失败。3.5 为Actor添加可选中功能最后让我们创建一个可被框选的示例单位。创建一个新的角色蓝图比如BP_SelectableUnit基于Character或简单的Actor。打开这个蓝图在“类设置”中点击“已实现的接口”旁边的“添加”按钮选择BPI_Selectable。这会自动在事件图表中生成接口函数的定义如Event On Selected和Event On Deselected。在Event On Selected事件后你可以添加任何被选中时的反馈例如动态改变材质颜色创建一个动态材质实例设置自发光颜色。在头顶显示一个选中圈附加一个粒子系统或静态网格体。播放一个音效。在Event On Deselected事件后恢复原来的状态。现在将这个单位拖到场景中多个。运行游戏用鼠标左键拖拽框选你应该能看到半透明的蓝色选框并且被框住的单位会触发你设置的选中反馈比如变亮。4. 性能优化与高级技巧基础功能跑通后我们得考虑效率和实用性。直接每帧或每次框选都做全场景的BoxOverlap在单位数量多的时候可能会成为性能瓶颈。4.1 优化检测查询使用对象通道Object Channel进行粗筛在项目设置的“碰撞”中新建一个对象通道命名为Selectable。为你所有可选中单位的网格体在它们的碰撞设置中将“对象类型”设为WorldDynamic并在“碰撞预设”中确保它们对Selectable通道有响应或者自定义一个预设。在Box Overlap Actors节点中将Object Types参数只勾选Selectable。这样物理引擎在检测时会首先过滤掉所有不属于这个通道的物体大幅减少需要精细检测的对象数量。分帧处理或异步检测如果一次框选可能选中数百个单位遍历数组并逐个调用接口函数可能会造成帧率卡顿。可以将选中单位的处理如高亮分散到多帧中进行。使用Delay节点或更优雅的Timeline、Async节点来实现。一个简单的方法是在PerformSelection函数中将最终选中的Actor数组保存起来。然后每帧在Tick事件中处理其中固定数量比如10个的单位直到处理完毕。空间数据结构进阶对于超大规模的场景如成千上万个可交互单位可以考虑使用空间划分数据结构如四叉树2D或八叉树3D将单位按位置组织起来。框选时只需检测与选框相交的那些空间格子里的单位而不是检测全部。这需要较多的C编码或利用引擎已有的Navigation Mesh、Grid等系统进行近似管理。对于纯蓝图项目如果单位数量在几百以内前面的优化通常已足够。4.2 提升交互体验框选框的视觉美化基础的Draw Rect矩形框比较生硬。可以在HUD的绘制中使用Draw Line或Draw Texture来绘制一个带边框和半透明填充的更好看的选框。例如用四条Draw Line画出边框再用Draw Rect填充半透明色。边框的颜色可以根据框选状态变化比如按下时红色拖动时蓝色。添加框选音效在鼠标按下开始框选时播放一个简短的开始音效。在鼠标释放完成框选时如果选中了单位播放一个“单位已选中”的反馈音效如果没选中任何单位播放一个取消音效或无声。实现追加选择与取消选择标准的RTS操作是单击取消当前选择并选择单个单位按住Ctrl框选可以追加到当前选择按住Shift框选可以取消已选中的单位。这需要在BP_MyPlayerController中维护一个CurrentSelection数组。在PerformSelection中先检测是否按下了Ctrl或Shift键使用Is Input Key Down节点。如果按下了Ctrl将新选中的单位追加到CurrentSelection数组中注意去重。如果按下了Shift遍历新选中的单位如果它已经在CurrentSelection中则调用其On Deselected并将其移除否则将其加入并调用On Selected。如果都没按则先清空CurrentSelection并取消所有之前单位的选中状态再处理新选中的单位。处理摄像机边缘滚动与框选在RTS游戏中常常需要将鼠标移动到屏幕边缘来移动摄像机。这可能会和框选操作冲突。一个常见的处理方法是当鼠标左键按下并拖动时优先视为框选操作此时禁用或降低边缘滚动的灵敏度。可以在PlayerController的Tick中根据bIsSelecting变量来决定是否处理边缘滚动输入。5. 常见问题与调试技巧实录即使跟着步骤做也难免会遇到一些“坑”。这里记录了几个我教学和开发中最常被问到的问题和解决方法。5.1 框选框绘制不出来检查HUD类绑定确保在项目设置中BP_MyHUD已正确设置为默认HUD类。检查绘制条件在BP_MyHUD的DrawHUD事件中打印bDrawSelectionBox、SelectionBoxStart和SelectionBoxEnd的值确保在拖拽鼠标时这些值被正确更新且有效。检查坐标计算Draw Rect的Position和Size计算错误会导致矩形画在屏幕外。打印计算后的Rect Position和Rect Size确保它们是在屏幕范围内的合理值如Position为正数Size不为负。检查绘制顺序确保Draw Rect节点确实被连接到DrawHUD的执行引脚上并且前面没有导致流程中断的分支。5.2 框选检测不到任何Actor检查检测体积的位置和大小这是最常见的问题。在PerformSelection函数中在调用Box Overlap Actors之前使用Draw Debug Box节点需要启用游戏中的调试绘制将计算出的Box Location和Box Extent画出来。运行游戏并框选看看这个绿色的调试盒子是否确实包裹住了你想要选择的单位。如果盒子位置完全不对比如在天上或地下说明从屏幕坐标到世界坐标的转换出了问题。仔细检查Deproject Screen to World节点的使用以及计算与世界平面交点的公式。如果盒子大小是0或者非常小说明WorldPointA和WorldPointB计算有误或距离太近。检查碰撞设置确保你想要选中的Actor的网格体有碰撞体。在静态网格体编辑器中检查碰撞是否已生成简单碰撞或复杂碰撞。确保在Box Overlap Actors节点中设置的Object Types包含了你的Actor所属的类型如WorldDynamic并且你新建的Selectable通道如果用了已正确配置。尝试将Actor Class Filter暂时留空看看是否能检测到任何Actor即使是不可选的。如果能说明是接口过滤的问题如果不能说明是碰撞检测本身的问题。检查接口实现在框选检测的循环中添加一个调试字符串输出打印每个被检测到的Actor名称以及Does Implement Interface节点的返回结果。确认你的单位蓝图确实正确添加了BPI_Selectable接口。5.3 框选性能低下游戏卡顿缩小检测范围确保你的检测盒子大小是合理的。如果玩家在屏幕角落轻轻一点你构建的盒子却覆盖了整个地图那性能肯定好不了。可以通过限制Box Extent的最大值来避免这种情况。启用对象通道过滤如前所述使用自定义的Selectable对象通道是减少检测对象数量最有效的方法之一。减少检测频率确保PerformSelection函数只在鼠标释放时调用一次而不是在拖拽过程中每帧调用。优化被选中单位的反馈如果On Selected事件里执行了昂贵的操作如加载材质、生成粒子也可能造成卡顿。考虑使用更轻量级的反馈或者将反馈效果延迟加载。5.4 框选结果不准确多选、漏选2D屏幕框与3D体积的映射误差这是固有难点。我们构建的3D盒子是一个近似。对于高度差异很大的单位空中单位和地面单位或者摄像机俯角很大时可能会漏选。可以考虑构建一个更厚的检测盒子增加Box Extent的Z值。使用多个不同高度的检测平面进行多次检测性能开销增大。对于重要的单位使用Overlap Multi By Channel并指定ECC_Visibility通道模拟从摄像机发射一束通过屏幕矩形的“视锥体”进行检测更精确但更复杂。单位包围盒不准如果单位的碰撞体用于Overlap检测的比其视觉模型小很多就容易漏选。确保视觉网格体的碰撞体能够较好地包裹住模型。对于复杂模型可以使用多个简单碰撞体Box、Capsule组合或者使用Complex Collision As Simple性能开销大慎用。整个项目文件的核心逻辑就搭建完毕了。我提供的完整项目文件会包含以上所有步骤的蓝图以及一个带有选中高亮效果的示例单位。你可以直接打开学习或者作为模板集成到你自己的项目中。记住理解每一步背后的“为什么”比复制粘贴节点更重要。这套框选框架就像一套乐高积木你可以在此基础上轻松地添加音效、更酷的视觉效果、单位编组、命令队列等高级功能打造属于你自己的游戏交互体验。