2.3 物理碰撞与输入事件


文档摘要

2.3 物理碰撞与输入事件 本节摘要:物理系统给世界定碰撞规则,输入系统把玩家的手指动作翻译成游戏事件,两者是"世界如何回应"的入口。本节讲清碰撞预设与通道、动态刚体的开销、事件与轮询两种输入范式,并用增强输入完成一条完整的点击交互链路。 世界的两条边界线 上一节的渲染管线负责"世界看起来如何",本节补上"世界摸起来如何"与"玩家怎么说话"。这两件事在帧预算里性格迥异:渲染是每帧必付的固定账单,物理与输入是事件驱动的不定账单——没有东西掉落、没有输入时它们近乎免费,一旦几百个刚体同时醒来,账单能在几毫秒内翻倍。理解"什么时候开始花钱"比记住参数更重要。 本节承接 2.1 的组件模型(碰撞体就是组件的一种),并为第七章 Chaos 破坏系统与第三章的输入代码打底。

2.3 物理碰撞与输入事件

本节摘要:物理系统给世界定碰撞规则,输入系统把玩家的手指动作翻译成游戏事件,两者是"世界如何回应"的入口。本节讲清碰撞预设与通道、动态刚体的开销、事件与轮询两种输入范式,并用增强输入完成一条完整的点击交互链路。

世界的两条边界线

上一节的渲染管线负责"世界看起来如何",本节补上"世界摸起来如何"与"玩家怎么说话"。这两件事在帧预算里性格迥异:渲染是每帧必付的固定账单,物理与输入是事件驱动的不定账单——没有东西掉落、没有输入时它们近乎免费,一旦几百个刚体同时醒来,账单能在几毫秒内翻倍。理解"什么时候开始花钱"比记住参数更重要。

本节承接 2.1 的组件模型(碰撞体就是组件的一种),并为第七章 Chaos 破坏系统与第三章的输入代码打底。学完后,第一章案例里"箱子推不动"的钩子就有了答案。

碰撞:通道、预设与响应矩阵

引擎把碰撞抽象成两个问题:这个物体算什么类别(通道),它对别的类别是什么态度(忽略、重叠、阻挡)。默认通道有世界静态、世界动态、实体物理等若干类,每个碰撞体属于一个通道;对象之间"谁挡谁、谁只报告、谁互不相干"由响应矩阵决定。编辑器把这些常用组合打包成预设——无碰撞、阻挡所有、物体物理、触发器等,日常开发九成场景直接选预设就够。

两个工程习惯值得第一天养成。其一,自定义项目别滥用默认通道,为玩法语义建专用通道(比如"玩家子弹""友军""侦察视线"),响应矩阵按语义管理,后期做穿透、友军伤害这类需求时改动范围可控。其二,碰撞体复杂度分三档:最简单的盒体球体胶囊、由网格生成的凸包、逐三角面的复杂碰撞。前两档便宜,第三档贵且只该用于静态不动物体——动态物体配复杂碰撞是物理开销失控的经典原因。外观精细与碰撞便宜可以兼得:用简单碰撞体代表轮廓,外观交给渲染。

物理模拟的稳定与开销都受子步影响。默认设置下物理每帧走一步,帧率高时结果精细,帧率一低,快速运动的物体可能互相穿过。开启子步后引擎把一帧切成多个小步分别求解,穿透概率下降,代价是物理耗时成倍上涨。开关与否的判断标准很实际:只有高速小物体(子弹、弹珠)出现穿墙时才开,并且限制子步数。

输入:从手指到事件

输入系统的任务是把硬件信号变成语义事件。老式绑定直接把按键映射到函数,简单但换设备就要重写;第五代引擎主推的增强输入做了两层抽象:输入动作定义语义(跳跃、射击、移动),输入映射上下文把设备信号绑定到动作(键盘空格与手柄按钮都可绑到"跳跃"),上下文还能按场景叠加切换——进载具换一套映射、打开菜单暂停另一套,这正是它比老系统省账的地方。

事件的传播链路值得看一眼,理解"按键到响应之间发生了什么":

两个设计词汇随之而来:轮询与事件。轮询是每帧主动问"这个键按着吗",适合持续行为(移动方向);事件是按下、松开瞬间触发一次,适合离散行为(跳跃、开火)。选错范式直接浪费预算——把移动做成按键事件,松键就停,手感僵硬;把跳跃做成轮询,每帧空转检查。事件驱动优先,是输入侧最省预算的原则。

案例:一条完整的点击拾取链路

背景:俯视角小游戏需要"鼠标点哪里,角色走过去;点到宝箱,宝箱高亮"。这条链路把射线检测(物理查询)、增强输入与事件响应串成一整条线,是玩法交互的最小完整样本。

操作:先建射线查询。用增强输入建一个"点击"输入动作,绑定鼠标左键,在角色或控制器类里响应它。响应函数里做一次射线检测:从屏幕上的鼠标位置反投影成世界射线,检测距离设几千单位,查询通道选可见性,开启对复杂碰撞的精确命中。检测返回命中结果后,尝试把命中的组件转成宝箱接口,成功则调用它的高亮方法,否则把命中点作为寻路目标。

void AMyPlayerController::OnClickPressed(const FInputActionValue& Value) { // 1. 取鼠标屏幕坐标,反投影成世界空间射线 float MouseX = 0.f, MouseY = 0.f; GetMousePosition(MouseX, MouseY); FVector WorldOrigin, WorldDir; DeprojectScreenPositionToWorld(MouseX, MouseY, WorldOrigin, WorldDir); // 2. 射线检测:只报告命中,不推动物体(查询而非模拟) FHitResult Hit; FCollisionQueryParams Params; Params.AddIgnoredActor(this); // 忽略自己 Params.bTraceComplex = true; // 逐三角面精确命中 const float TraceDistance = 50000.f; bool bHit = GetWorld()->LineTraceSingleByChannel( Hit, WorldOrigin, WorldOrigin + WorldDir * TraceDistance, ECC_Visibility, Params); if (!bHit) return; // 3. 命中对象若是宝箱(实现接口)则高亮,否则设为移动目标 AActor* HitActor = Hit.GetActor(); if (HitActor && HitActor->Implements<UPickupableInterface>()) { IPickupableInterface::Execute_Highlight(HitActor, true); } else { MoveToLocation(Hit.ImpactPoint); } }

结果:点击地面角色寻路走过去,点击宝箱宝箱发光。按下 Play 全程无卡顿。

解读:三个细节是这笔交互的账单明细。射线检测走的是物理查询而非物理模拟——查询只在按下瞬间花一次钱,模拟才是持续账单,所以"点击检测"对帧率几乎零影响。bTraceComplex 开了逐三角面精确命中,单次调用没问题,但若每帧对一堆物体都开,开销立刻上去——精确查询同样是钱。接口判断让逻辑解耦:控制器不需要认识宝箱类,只认接口,之后加"可拾取药物""可开门"都无需改这段代码。变式一:把单次点击改成按住拖动,需要在动作上改触发器类型并降低采样频率。变式二:做敌人"视线能否看到玩家",把射线通道从可见性换成自建通道,让墙体阻挡、草丛忽略——通道语义化的价值在这里兑现。

常见坑:给动态物体逐三角面碰撞后再开模拟。碰撞检测的三角面参与求解,性能与稳定双输。动态物体一律简单碰撞。

本节要点回顾

  • 碰撞等于通道加响应矩阵,语义化自建通道比堆默认预设更可维护。
  • 碰撞体三档复杂度:图元、凸包便宜,逐三角面昂贵且只配静态物体。
  • 子步是穿透保险不是默认项,只为高速小物体开启并限制步数。
  • 增强输入的两层抽象:动作定语义、上下文管绑定,场景切换靠叠加上下文。
  • 事件驱动优先于轮询,持续行为轮询、离散行为事件,范式选对预算自然省。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U