资讯动态

UE C++获取鼠标点击的常用两种方法:从APlayerController到射线检测配TaoToken

发布时间:2026/9/29 4:12:51 来源:尧图企业网站定制
1. 鼠标点击拾取在 UE C 里到底难在哪做 UE 项目时鼠标点击拾取几乎是绕不开的需求点地面让角色走过去、点箱子拾取道具、点敌人锁定目标、点 UI 外的场景物体触发交互。很多刚接触 UE C 的朋友第一反应是去查OnClicked事件结果发现它只对 Actor 的碰撞体生效而且和鼠标位置、相机、UI 遮挡搅在一起稍微复杂一点就失灵。真正的问题在于鼠标点击本质上是屏幕上的一个二维坐标而你要操作的是三维世界里的物体。这中间隔着一层「投影反算」——你得把屏幕坐标还原成世界空间里的一条射线再看这条射线打中了谁。UE 提供了两条主流路径一条是APlayerController帮你封装好的GetHitResultUnderCursor几行代码就能拿到命中结果另一条是手动DeprojectScreenPositionToWorld配合LineTraceSingleByChannel控制粒度更细能自定义检测通道、忽略列表和射线长度。这篇面向需要在项目里快速落地点击交互的开发者给出两种方法的完整 C 类骨架、PlayerController 配置和蓝图调用示例并说明怎么用 TaoToken 统一 Key/API 通道接入 AI 辅助调试最后附上验证点击命中 Actor 的日志与断点检查动作。适合已经会写基础 UE C、但被点击拾取卡住的同学。2. 前置准备TaoToken 统一 Key 与 API 通道在写代码之前先把调试环节的 AI 通道搭好。UE C 的编译报错、射线检测返回空、坐标转换对不上这些坑排查起来很费时间用 AI 辅助定位能省不少事。TaoToken 的作用是把模型调用统一到一个 Key 和一套 API 通道上不用在多个平台之间来回切换配置。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个不加 UTM。你需要先去控制台创建一个 API Key然后就能在模型对话、编码辅助、Agent 等场景里复用同一个 Key。具体入口按用途分想直接对话验证模型效果走模型对话https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite长期写 UE C、需要编码辅助或 Agent 能力走 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite管理 Key、查看用量走控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite创建和复制 API Key走 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite查接入文档、参数说明走文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你用 Claude Code 做 UE 项目走 ClaudeCodeAnthropic 接入说明https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite注意Key 只放在本地环境变量或项目外的配置文件里别硬编码进.cpp提交到仓库。UE 项目经常多人协作Key 泄露后要重新生成很麻烦。3. 方法一GetHitResultUnderCursor 快速落地3.1 原理与适用场景GetHitResultUnderCursor是APlayerController提供的封装函数内部其实调用的就是GetWorld()-LineTraceSingleByChannel但它帮你做了几件脏活确认当前有玩家屏幕、把鼠标屏幕坐标转成世界坐标和方向、默认排除 UI 命中。所以只要你的类继承自ACharacter或APawn就能通过GetController拿到APlayerController直接用。它适合绝大多数「点一下场景物体」的需求尤其是原型阶段和交互逻辑不复杂的项目。缺点是检测通道固定为传入的ECollisionChannel忽略列表和射线长度不好自定义。3.2 完整 C 类骨架下面是一个挂在角色上的点击拾取组件式写法直接放在ACharacter子类里也能跑。新建一个UClickPickComponent或者把逻辑写进你的AMyCharacter。// ClickPickComponent.h #pragma once #include CoreMinimal.h #include Components/ActorComponent.h #include ClickPickComponent.generated.h UCLASS(ClassGroup(Custom), meta(BlueprintSpawnableComponent)) class MYGAME_API UClickPickComponent : public UActorComponent { GENERATED_BODY() public: UClickPickComponent(); protected: virtual void BeginPlay() override; // 鼠标左键按下时调用 void OnLeftMousePressed(); // 检测通道默认 ECC_Visibility UPROPERTY(EditAnywhere, BlueprintReadWrite, CategoryClickPick) TEnumAsByteECollisionChannel TraceChannel ECC_Visibility; // 是否打印命中日志 UPROPERTY(EditAnywhere, BlueprintReadWrite, CategoryClickPick) bool bDrawDebugLine true; private: APlayerController* CachedPC nullptr; };// ClickPickComponent.cpp #include ClickPickComponent.h #include GameFramework/PlayerController.h #include Engine/World.h #include DrawDebugHelpers.h UClickPickComponent::UClickPickComponent() { PrimaryComponentTick.bCanEverTick false; } void UClickPickComponent::BeginPlay() { Super::BeginPlay(); if (APawn* OwnerPawn CastAPawn(GetOwner())) { CachedPC CastAPlayerController(OwnerPawn-GetController()); } if (CachedPC) { // 绑定输入需在项目设置里配置 Action Mapping LeftMouse CachedPC-InputComponent-BindAction( LeftMouse, IE_Pressed, this, UClickPickComponent::OnLeftMousePressed); } } void UClickPickComponent::OnLeftMousePressed() { if (!CachedPC) return; FHitResult Hit; // 关键调用传引用进去结果带出来 const bool bHit CachedPC-GetHitResultUnderCursor(TraceChannel, false, Hit); if (bHit Hit.bBlockingHit) { AActor* HitActor Hit.GetActor(); UE_LOG(LogTemp, Warning, TEXT(命中 Actor: %s, 位置: %s), HitActor ? *HitActor-GetName() : TEXT(None), *Hit.ImpactPoint.ToString()); if (bDrawDebugLine) { DrawDebugSphere(GetWorld(), Hit.ImpactPoint, 12.f, 12, FColor::Green, false, 2.f); } } else { UE_LOG(LogTemp, Warning, TEXT(未命中任何物体)); } }3.3 PlayerController 与输入配置在DefaultInput.ini里加一个 Action Mapping或者直接在项目设置的 Input 里加ActionMappings(ActionNameLeftMouse,bShiftFalse,bCtrlFalse,bAltFalse,bCmdFalse,KeyLeftMouseButton)如果你不想用输入绑定也可以在APlayerController子类里重写InputKey判断EKeys::LeftMouseButton和IE_Pressed效果一样。蓝图里调用的话把OnLeftMousePressed标成BlueprintCallable就能在事件图表里连。4. 方法二DeprojectScreenPositionToWorld 配合射线检测4.1 为什么需要手动射线GetHitResultUnderCursor虽然方便但有几个限制它默认忽略 UI可你有时候就是想点 UI 后面的东西它用的通道是固定的没法临时换射线长度默认到世界尽头远距离物体也会被命中。手动射线检测把这几步拆开你能完全控制。核心两步先用UGameplayStatics::DeprojectScreenPositionToWorld把鼠标屏幕坐标转成世界空间的起点和方向再用GetWorld()-LineTraceSingleByChannel发射射线。4.2 屏幕坐标转世界坐标// 获取鼠标当前位置 float MouseX, MouseY; if (!CachedPC-GetMousePosition(MouseX, MouseY)) return; FVector WorldOrigin, WorldDirection; // 屏幕坐标 - 世界坐标 const bool bDeprojected UGameplayStatics::DeprojectScreenPositionToWorld( MouseX, MouseY, WorldOrigin, WorldDirection); if (!bDeprojected) return;WorldOrigin是相机位置附近的世界坐标WorldDirection是归一化的方向向量。射线起点用WorldOrigin终点用WorldOrigin WorldDirection * TraceDistance。4.3 LineTraceSingleByChannel 完整实现void UClickPickComponent::OnLeftMousePressedManual() { if (!CachedPC) return; float MouseX, MouseY; if (!CachedPC-GetMousePosition(MouseX, MouseY)) return; FVector WorldOrigin, WorldDirection; if (!UGameplayStatics::DeprojectScreenPositionToWorld( MouseX, MouseY, WorldOrigin, WorldDirection)) return; const float TraceDistance 10000.f; // 自定义射线长度 const FVector TraceEnd WorldOrigin WorldDirection * TraceDistance; FCollisionQueryParams Params; Params.AddIgnoredActor(GetOwner()); // 忽略自己 Params.bTraceComplex false; FHitResult Hit; const bool bHit GetWorld()-LineTraceSingleByChannel( Hit, WorldOrigin, TraceEnd, TraceChannel, Params); if (bHit Hit.bBlockingHit) { UE_LOG(LogTemp, Warning, TEXT(手动射线命中: %s), *Hit.GetActor()-GetName()); } if (bDrawDebugLine) { DrawDebugLine(GetWorld(), WorldOrigin, TraceEnd, bHit ? FColor::Green : FColor::Red, false, 2.f, 0, 1.f); } }4.4 两种方法对照对比项GetHitResultUnderCursor手动 Deproject LineTrace代码量少一行调用多需自己拼起点终点通道控制只能传一个通道可动态切换射线长度默认到世界尽头完全自定义忽略列表内部固定可自由 AddIgnoredActorUI 处理默认排除需自己判断适用场景原型、简单交互复杂拾取、自定义规则5. 验证请求与成功结果5.1 日志与断点检查编译后运行在角色上挂好组件点击场景里的物体Output Log 里应该看到LogTemp: Warning: 命中 Actor: SM_Box_2, 位置: X320.000 Y-150.000 Z88.000如果走手动射线还会看到绿色 Debug 线从相机射向命中点。断点建议打在OnLeftMousePressed的第一行和bHit判断处检查Hit.GetActor()是否为空、Hit.ImpactPoint是否合理。5.2 用 TaoToken 辅助排查当射线返回空或者命中 Actor 不对时把报错日志、TraceChannel设置、碰撞预设一起丢给 TaoToken 的模型对话入口让它帮你分析是通道配错还是碰撞体没开。接入文档里有完整的请求参数说明照着填就行。长期做 UE 项目的话Coding Plan 更适合能把编码辅助和调试问答放在一个通道里。6. 本篇常见错排查点击没反应日志也不打先确认输入绑定生效。InputComponent在BeginPlay里绑定时CachedPC可能还是空因为 Controller 还没 Possess。改成在APawn::SetupPlayerInputComponent或APlayerController::SetupInputComponent里绑定更稳。bHit 为 true 但 Hit.GetActor() 是 None命中的可能是地形或碰撞体Actor 指针为空说明打到了没有 Owner 的组件。检查Hit.GetComponent()和碰撞预设。射线打不中物体物体的碰撞预设要响应你用的TraceChannel。默认ECC_Visibility下很多静态网格的碰撞是Block但如果你改成了Overlap或Ignore射线就穿过去了。在物体碰撞设置里确认 Trace 响应。UI 挡住了点击GetHitResultUnderCursor第二个参数传false表示不忽略 UI传true才忽略。手动射线的话得自己用WidgetBlueprintLibrary判断鼠标是否在 UI 上。屏幕坐标转世界坐标方向不对GetMousePosition返回的是视口坐标如果用了分屏或者自定义视口得先转换。单屏项目一般没问题。编译报错找不到 UGameplayStatics头文件加#include Kismet/GameplayStatics.hDrawDebugHelpers.h也要加否则DrawDebugLine报未定义。把这些点过一遍点击拾取基本就稳了。真机上跑之前记得在打包设置里确认输入映射没被裁剪掉。

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

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

免费获取报价 →
↑