UE源代码——径向爆炸伤害计算函数(Grenade手雷爆炸功能)
当我们想设计实现一个爆炸功能时,应该怎么办? 如何考虑服务端与客户端优先权的问题?如何设计出一个合理的功能。
UE源代码中给我们提供了一个相当细节爆炸实现函数,里面写了详细的注释。应该如何使用这个函数,注意事项.
声明与解释:
代码前导声明标记,可以在什么地方使用。 UE 中的meta 为自定义类型,意思就是怎么实现看你自己,提供更广阔的施展空间(具体可以看看UE中meta的用法)。所以这个函数,是UE 手动自己造的一个。
UFUNCTION(BlueprintCallable, // 可在蓝图中调用BlueprintAuthorityOnly, // 仅在拥有网络权限的服务器上执行Category="Game|Damage", // 在蓝图中的分类路径meta=(WorldContext="WorldContextObject", // 指定世界上下文参数AutoCreateRefTerm="IgnoreActors" // 自动创建引用参数)
)
1. BlueprintCallable
- 作用:允许在蓝图图表中调用此函数
- 效果:
- 函数会出现在蓝图的 "调用函数" 节点列表中
- 可通过蓝图可视化编程直接调用 C++ 逻辑
- 适用场景:需要在蓝图中实现的核心游戏逻辑(如伤害计算)
2. BlueprintAuthorityOnly
- 作用:确保函数仅在拥有网络权限的服务器上执行
- 网络逻辑:
- 客户端调用此函数时,实际执行会转发到服务器
- 防止客户端作弊修改伤害计算逻辑
- 注意事项:
- 函数执行结果需要通过网络同步回客户端
- 纯单机游戏中此参数无效
3. Category="Game|Damage"
- 作用:在蓝图编辑器中对函数进行分类
- 显示效果:
- 函数会出现在蓝图节点面板的 "Game → Damage" 类别下
- 多层级分类使用竖线 (|) 分隔
- 最佳实践:
- 按功能模块组织函数(如 "Gameplay/Combat"、"UI/Navigation")
- 保持分类结构简洁清晰
4. meta 参数详解
WorldContext="WorldContextObject"
- 作用:指定哪个参数是用于获取游戏世界的上下文对象
- 技术实现:
- 虚幻引擎通过该参数获取 UWorld 实例
- 确保函数在正确的游戏世界上下文中执行
- 对应代码参数:
cpp
const UObject* WorldContextObject
AutoCreateRefTerm="IgnoreActors"
- 作用:自动为引用参数创建可修改的输入项
- 参数要求:
- 针对类型为
TArray<AActor*>&
的引用参数 - 允许在蓝图中动态修改传入的 Actor 数组
- 针对类型为
- 蓝图表现:
- 在蓝图节点中会显示为可编辑的数组输入框
函数在蓝图中的使用示例
当这个 C++ 函数被正确暴露后,在蓝图中可以这样使用:
- 在蓝图图表中添加 "调用函数" 节点
- 在 "Game|Damage" 类别下找到此函数
- 连接各输入参数:
- WorldContextObject:通常连接 "Self" 引用
- BaseDamage/MinimumDamage:设置数值
- Origin:连接位置向量(如技能释放点)
- IgnoreActors:添加需要忽略的 Actor 列表
- 处理返回值(是否成功应用伤害)
与网络同步相关的注意事项
由于标记了BlueprintAuthorityOnly
,此函数的网络执行流程为:
- 客户端蓝图调用此函数
- 请求通过网络发送到服务器
- 服务器执行实际的伤害计算逻辑
- 服务器通过网络通知客户端伤害结果
- 客户端播放伤害反馈效果(如特效、音效)
这种设计确保了多人游戏中伤害计算的一致性和安全性。
源代码: 所有细节
关键参数说明
- BaseDamage:中心点的最大伤害值。
- MinimumDamage:范围外边界的最小伤害值。
- Origin:伤害范围的球心位置。
- DamageInnerRadius:伤害保持最大值的内半径。
- DamageOuterRadius:伤害衰减的外半径。
- DamageFalloff:伤害衰减曲线控制参数(指数衰减)。
- IgnoreActors:忽略的伤害目标列表。
实现流程
- 碰撞检测初始化 使用
FCollisionQueryParams
配置碰撞检测参数,包括忽略特定 Actor
TArray<FOverlapResult> Overlaps;
World->OverlapMultiByObjectType(Overlaps, Origin, FQuat::Identity, FCollisionObjectQueryParams::InitType::AllDynamicObjects, FCollisionShape::MakeSphere(DamageOuterRadius), SphereParams);
2.球形范围检测 通过 OverlapMultiByObjectType
检测范围内所有动态对象:
TArray<FOverlapResult> Overlaps;
World->OverlapMultiByObjectType(Overlaps, Origin, FQuat::Identity, FCollisionObjectQueryParams::InitType::AllDynamicObjects, FCollisionShape::MakeSphere(DamageOuterRadius), SphereParams);
3. 筛选可伤害目标 遍历检测结果,过滤不可伤害或需忽略的 Actor:
if (OverlapActor && OverlapActor->CanBeDamaged() && OverlapActor != DamageCauser && Overlap.Component.IsValid())
4. 伤害事件构造 创建 FRadialDamageEvent
并配置伤害参数(包括衰减参数):
FRadialDamageEvent DmgEvent;
DmgEvent.Params = FRadialDamageParams(BaseDamage, MinimumDamage, DamageInnerRadius, DamageOuterRadius, DamageFalloff);
5.应用伤害 对每个有效目标调用 TakeDamage
方法:
TakeDamage 需要我们重载自定义的重新实现一下。因为这里重新实现了,伤害的计算方式。
Victim->TakeDamage(BaseDamage, DmgEvent, InstigatedByController, DamageCauser);
所以,一个完整的,带有衰减,距离检测,持续时间,爆炸范围的函数应该怎么用呢? 如下,
拿过来,只需要我们调用ApplyRadialDamageWithFalloff 此函数,并填好对应的参数就可以了
TArray<AActor*> IgnoredActors;
UGameplayStatics::ApplyRadialDamageWithFalloff(GetWorld(), 50.0f, // BaseDamage10.0f, // MinimumDamageExplosionLocation, 300.0f, // InnerRadius600.0f, // OuterRadius2.0f, // FalloffUDamageType::StaticClass(), IgnoredActors,this, // DamageCausernullptr // InstigatedByController
);