资讯动态

UE5鼠标点击移动实现:屏幕坐标转世界坐标与AI导航

发布时间:2026/9/23 17:13:04 来源:尧图企业网站定制
简介这是一份面向UE4/UE5开发者的鼠标点击寻路交互示例工程适合具备蓝图基础、希望快速实现点击地面移动角色的学习者参考。资源围绕射线碰撞检测、模型边缘高亮、鼠标样式自定义切换以及DoTween移动动画四个技术点展开并附有说明若未购买DoTween插件删除蓝图中对应移动方法即可正常运行。压缩包共572个文件以413个uasset蓝图与资源文件、14个umap关卡地图、41个ini配置及92个log日志为主另含uproject工程文件与少量bin、json等辅助数据整体约675.41MB工程结构完整可直接打开研究。目前已有4416人学习下载读者可从中掌握点击寻路的完整实现链路、高亮与光标切换的细节处理以及借助DoTween平滑移动的动画思路便于迁移到RPG、策略或塔防类项目中。1. 鼠标点地移动从玩家直觉到引擎里的一条射线在第三人称或俯视角项目里玩家最自然的操作就是「鼠标点哪角色走哪」。这个需求听起来像一行代码实际落地时会牵出三件事屏幕坐标怎么变成世界坐标、角色怎么绕过障碍走到目标点、以及移动过程中怎么和动画、输入系统对齐。很多新手第一次做直接拿鼠标位置当世界坐标用结果角色往天上飞或者原地抽搐本质是没理解屏幕空间和世界空间之间隔着相机投影。这套方案适合做 RTS、MOBA、俯视角 RPG、点击式冒险也适合 UE5 里做原型验证。UE4 和 UE5 在这条链路上差异不大主要区别在 UE5 默认启用了 Enhanced Input以及导航系统对 Large World Coordinates 的处理。核心链路是鼠标点击事件 → 屏幕坐标 → 去投影射线 → 与地面碰撞 → 得到世界坐标 → 交给 AI 导航移动。下面按这条链路拆开讲每一段都能直接抄进项目里跑。2. 屏幕坐标转世界坐标Deproject 与射线检测的取舍2.1 为什么不能直接用鼠标坐标当世界坐标鼠标事件给的是屏幕空间坐标原点在视口左上角单位是像素。世界坐标是三维空间里的厘米单位。两者之间差了一个相机投影矩阵和一次深度求解。把屏幕坐标直接赋给SetActorLocationZ 值会是 0 或者视口高度角色自然乱跑。常见做法有两种一种是用DeprojectScreenPositionToWorld把屏幕点转成世界空间的一条射线再和地面求交另一种是用GetHitResultUnderCursorByChannel直接让引擎帮你做射线检测。前者灵活能自己控制射线长度和碰撞通道后者省事适合快速原型。2.2 用 GetHitResultUnderCursorByChannel 拿到地面点这是最省代码的方式适合俯视角和第三人称。在 PlayerController 里绑定输入然后调用// AMyPlayerController.cpp void AMyPlayerController::MoveToCursor() { FHitResult HitResult; // 用 Visibility 通道检测地面需要设置成 Block Visibility bool bHit GetHitResultUnderCursorByChannel( UEngineTypes::ConvertToTraceType(ECC_Visibility), true, // bTraceComplex复杂碰撞更精确但更慢 HitResult); if (bHit MoveTargetActor) { // HitResult.Location 就是鼠标点到的世界坐标 MoveTargetActor-MoveToLocation(HitResult.Location, 50.f); } }ECC_Visibility是碰撞通道地面 StaticMesh 的碰撞预设要能挡住 Visibility否则射线穿过去打不到。bTraceComplex设为 true 时会用复杂碰撞体地形起伏大时更准但每帧调用会有开销点击事件里用一次无所谓。MoveToLocation的第二个参数是接受半径50 表示离目标点 50 厘米内就算到达避免角色在目标点附近抖动。2.3 用 DeprojectScreenPositionToWorld 自己控制射线需要控制射线长度、忽略某些物体、或者做多层检测时用这个void AMyPlayerController::MoveToCursorManual() { FVector WorldLocation, WorldDirection; // 把鼠标屏幕坐标转成世界空间射线 if (!DeprojectMousePositionToWorld(WorldLocation, WorldDirection)) return; const float TraceDistance 10000.f; // 射线长度单位厘米 FVector TraceEnd WorldLocation WorldDirection * TraceDistance; FHitResult Hit; FCollisionQueryParams Params; Params.AddIgnoredActor(GetPawn()); // 忽略角色自身 Params.bTraceComplex true; bool bHit GetWorld()-LineTraceSingleByChannel( Hit, WorldLocation, TraceEnd, ECC_Visibility, Params); if (bHit) { // 命中点就是目标位置 DrawDebugSphere(GetWorld(), Hit.Location, 20.f, 12, FColor::Green, false, 2.f); } }DeprojectMousePositionToWorld内部会处理视口缩放和相机投影比手动算矩阵可靠。TraceDistance给 10000 厘米100 米足够覆盖大多数场景太小会导致远处点不到。AddIgnoredActor把角色自己排除否则射线可能打在角色胶囊体上。DrawDebugSphere是调试用的确认命中点位置对不对正式发布时去掉。2.4 两种方式的对比与选择方式代码量可控性适用场景GetHitResultUnderCursorByChannel少低快速原型、俯视角DeprojectScreenPositionToWorld中高需要过滤、多层检测手动算投影矩阵多最高特殊相机、自定义投影提示如果地面是 Landscape 或者复杂地形bTraceComplex必须开否则射线会穿过凹陷处打到下面的碰撞体。3. 让角色真正走过去AI 导航与 MoveTo 的落地细节3.1 导航网格是前提没有 NavMesh 一切白搭拿到目标点只是第一步角色要绕开障碍走过去必须依赖导航系统。在场景里放一个NavMeshBoundsVolume缩放到覆盖可行走区域按 P 键能看到绿色导航网格。如果地面没变绿检查三件事Volume 是否覆盖、地面碰撞是否设为Blocking Volume或可导航、项目设置里 Navigation Mesh 的 Agent 半径是否合理。UE5 里导航系统默认开启但 Large World Coordinates 下如果场景坐标超过百万级别需要调整Navigation System的Cell Size和Agent Max Step Height。常见做法是保持场景原点附近建模避免坐标过大。3.2 用 AIController 的 MoveToLocation 驱动移动角色蓝图里如果用的是Character需要给它挂一个AIController或者在 PlayerController 里直接调用MoveToLocation。更规范的做法是走 AI 导航// 在 AIController 或 Character 里 void AMyCharacter::MoveToTarget(FVector TargetLocation) { if (AAIController* AICon CastAAIController(GetController())) { // 接受半径 50超时 5 秒绘制调试路径 EPathFollowingRequestResult::Type Result AICon-MoveToLocation(TargetLocation, 50.f, true, true, false, true); if (Result EPathFollowingRequestResult::Failed) { UE_LOG(LogTemp, Warning, TEXT(MoveToLocation failed, check NavMesh)); } } }MoveToLocation的参数依次是目标点、接受半径、是否使用路径优化、是否投影到导航网格、是否绘制调试、是否允许部分路径。第三个参数bProjectDestinationToNavigation设为 true 时如果目标点不在导航网格上会自动投影到最近的可行走点这个在点击地面边缘时很有用。3.3 移动过程中的动画与旋转对齐角色移动时朝向不对会显得很假。常见做法是在CharacterMovementComponent里开启Orient Rotation to Movement让角色自动转向移动方向// 在 Character 构造函数里 GetCharacterMovement()-bOrientRotationToMovement true; GetCharacterMovement()-RotationRate FRotator(0.f, 540.f, 0.f); // 每秒转 540 度 bUseControllerRotationYaw false; // 关闭控制器控制朝向RotationRate控制转身速度540 度每秒比较跟手太小会转得很慢太大像陀螺。bUseControllerRotationYaw必须关掉否则控制器朝向会覆盖移动朝向。动画蓝图里用Velocity的长度驱动 BlendSpace速度为零时切到 Idle这样移动和动画就对齐了。3.4 点击移动的输入绑定与 Enhanced InputUE5 默认用 Enhanced Input需要在IMC_Default里建一个IA_MoveTo映射触发方式选Triggered。然后在 PlayerController 里绑定void AMyPlayerController::SetupInputComponent() { Super::SetupInputComponent(); if (UEnhancedInputComponent* EIC CastUEnhancedInputComponent(InputComponent)) { EIC-BindAction(MoveToAction, ETriggerEvent::Triggered, this, AMyPlayerController::MoveToCursor); } }ETriggerEvent::Triggered在按键按下时触发一次适合点击移动。如果要做按住持续移动改成Started加定时器。UE4 里用InputComponent-BindAction(MoveTo, IE_Pressed, this, ...)即可逻辑一样。4. 实战排错点不到、走不过去、抖动这三类问题4.1 鼠标点地面没反应先查碰撞通道最常见的原因是地面没有阻挡Visibility通道。打开地面 StaticMesh 的碰撞设置确认Collision Presets是BlockAll或至少Block Visibility。如果是自定义通道GetHitResultUnderCursorByChannel里的通道要对应上。另一个原因是 PlayerController 的bShowMouseCursor没开或者bEnableClickEvents没开鼠标事件根本传不到。// 在 PlayerController 初始化里 bShowMouseCursor true; bEnableClickEvents true; bEnableMouseOverEvents true;bEnableClickEvents控制点击事件是否派发bEnableMouseOverEvents控制悬停事件。只做点击移动的话前两个就够。4.2 角色走到一半停住检查导航网格和接受半径如果角色走到障碍物旁边就停多半是导航网格没覆盖到目标点或者接受半径太小导致角色在目标点附近反复寻路。把MoveToLocation的接受半径从 50 调到 80 试试。另外检查NavMeshBoundsVolume是否覆盖了目标区域按 P 键看绿色网格有没有断。还有一种情况是目标点在导航网格边缘bProjectDestinationToNavigation设为 false 时直接失败。设为 true 会自动投影到最近可行走点但投影距离有限太远的目标点还是会失败。常见做法是在点击时先判断目标点是否在导航网格上不在就忽略这次点击。4.3 角色到达后原地抖动用接受半径和停止逻辑解决抖动的原因是角色在目标点附近反复修正位置。MoveToLocation的接受半径设大一点或者在OnMoveCompleted回调里把速度清零void AMyCharacter::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult Result) { if (Result.Code EPathFollowingResult::Success) { GetCharacterMovement()-StopMovementImmediately(); } }StopMovementImmediately会立刻停止移动组件避免惯性导致的微小位移。如果用的是MoveToLocation的返回值判断注意Success和Blocked的区别Blocked表示路径被挡住需要重新寻路。4.4 调试手段DrawDebug 和导航网格可视化排错时最有用的是把射线和路径画出来。DrawDebugLine画射线DrawDebugSphere画命中点DrawDebugPath画导航路径。按 P 键切换导航网格显示按键打开 AI 调试。这些手段能快速定位是射线没打中、还是导航没连通、还是移动组件没响应。注意调试绘制在 Shipping 包里会被编译掉正式发布前确认没有依赖调试绘制的逻辑。5. 进阶点击移动与多播委托、触摸输入的衔接5.1 用多播委托解耦点击事件和移动逻辑点击移动做大了以后点击事件可能还要触发 UI 高亮、技能指示器、音效。用多播委托把「点击到地面」这个事件广播出去比在 PlayerController 里堆逻辑干净// 在 PlayerController 里声明 DECLARE_MULTICAST_DELEGATE_OneParam(FOnGroundClicked, FVector /*Location*/); FOnGroundClicked OnGroundClicked; // 命中后广播 if (bHit) { OnGroundClicked.Broadcast(HitResult.Location); }其他系统比如 UI、音效、特效订阅这个委托即可不用改 PlayerController。UE5 里也可以用Dynamic Multicast Delegate配合蓝图让策划也能挂逻辑。5.2 触摸输入下的点击移动适配移动端或触屏设备上鼠标点击变成触摸。UE5 里用bEnableTouchEvents和bEnableTouchOverEvents触摸事件会走和鼠标类似的派发路径。双指触摸蓝图里常见做法是单指点击移动、双指缩放视角需要在 Input 里区分触摸点数。GetHitResultUnderFingerByChannel是触摸版的命中检测参数和鼠标版类似第一个参数传手指索引。5.3 性能与边界高频点击和大地图的处理高频点击时每次都做射线检测和寻路会有开销。常见做法是加一个最小点击间隔比如 0.1 秒内只响应一次。大地图上导航网格分块加载点击远处时先判断目标点是否在已加载的导航网格内不在就忽略或者提示。UE5 的 World Partition 下导航网格也是流式加载的跨区域点击需要等导航数据加载完再寻路否则会失败。// 简单的点击节流 float LastClickTime 0.f; if (GetWorld()-GetTimeSeconds() - LastClickTime 0.1f) return; LastClickTime GetWorld()-GetTimeSeconds();这段放在MoveToCursor开头能避免连点导致的寻路风暴。0.1 秒是经验值手感上几乎无感但能挡掉大量重复请求。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价