跳转到正文

子弹系统

作

作者

作者

创建时间: 2025年4月15日更新时间: 2026年10月20日

目录

概述

子弹系统,是GCS中,用于驱动远程战斗,法术,Projectile的系统。

我是艾尔登法环的重度玩家,我希望我的子弹系统能像艾尔登法环的子弹系统一样优秀。因此我通过很多资料学习艾尔登法环中的法术如何工作,并在UE中实现类似的。 你在这里可以看到艾尔登法环的子弹系统是多么优秀,而我的版本会越来越接近它:https://www.youtube.com/watch?v=rIDQSJ39JUM

在GCS中,你可以快速添加各种各样的子弹,以实现不同的效果。

子弹定义

真实项目中,你可能会定义成千上万个子弹,所以,子弹由DataTable进行管理,且所有对象的引用都采用的软对象引用,因此你不用担心我写的代码使你的项目爆炸。

你通过创建结构体类型为GCS_BulletDefinition的DataTable资产,来管理和编辑你项目中的所有子弹。

每一行代表一个子弹定义,每一个定义中你可以配置很多参数,其中最重要的参数如下(其余的参数拥有注释,不再这里赘述):

BulletActorClass:指定了该子弹定义对应的子弹ActorClass,你可以选择默认提供的,或者针对特殊的子弹,定义特殊的子弹实例。

BulletCount:这个用于控制使用该定义生成子弹时,实际会产生多少个子弹实例,配合发射配置(LaunchConfiguration),你可以实现很多有趣的子弹效果。

AttackDefinition:你通过这个选项来选择不同的攻击定义,在子弹击中目标后,会根据攻击定义中指定的信息为目标应用游戏效果。

子弹实例

所有子弹实例都以 C++ 基类 AGCS_BulletInstance 为基础;示例内容提供了蓝图子类 BP_GCS_Projectile。组件通过对象池复用实例,因此自定义子类时应把每次启用和回收时都需要执行的逻辑放在 Bullet 生命周期回调中,而不要依赖 Actor 的 BeginPlay / EndPlay 每次触发。

默认实现足够通用,通常情况下你不需要再创建新的蓝图子类。

通过动画触发子弹

请查看战斗攻防流程,了解动画通知、近战 AbilityTask 与子弹请求之间的流程。

通过蓝图触发子弹

当前运行时入口是 UGCS_BulletSystemComponent。通过其 RequestSpawnBullet 接口提交 UGCS_AttackRequest_Bullet,组件会创建并管理子弹批次与各个实例;批次 Entry 会通过 Fast Array 复制。UGCS_BulletSubsystem 仅作为旧兼容层保留,其公开接口已弃用。若现有蓝图仍调用该子系统,请迁移到所属 Actor 上的 BulletSystemComponent。

子弹实例通过 GCS_EffectCauser 接口承载效果。BulletSystemComponent 从子弹定义的 AttackDefinition 构建目标 GameplayEffect 或 EffectContainer,并把 AttackDefinition 句柄和 BulletInstance 写入 EffectContext。命中后只有服务器权威路径会应用这些效果;属性回调随后进入 CombatFlow 产出攻击结果。不要在 Bullet 层绕过 CombatFlow 自行复制攻击结果。

AttackRequest是一个默认实例化的对象,因此当你在任意蓝图建立一个AttackRequest类型的变量时,你是在创建一个AttackRequest的实例,而不是引用一个已经存在的AttackRequest对象。 实例化的Request会跟随所在的蓝图进行保存。

子弹对象池

所有创建的子弹实例,都通过对象池进行管理。简单来说,一个子弹完成它的作用后,功能会被禁用且缓存起来,每当需要创建新的子弹时,会优先从已经缓存的子弹找到可用的子弹,然后重新启用功能。这样可以极大的缓解频繁生成/销毁子弹所带来的性能消耗。

因此,当你自定义你的子弹Actor时,你需要注意:

子弹实例的BeginPlay和Endplay通常只会调用一次,但是OnBulletBeginplay和OnBulletEndplay会反复调用。

子弹客户端预测

客户端预测目前只覆盖玩家主动发射的第一批子弹,并且只预测弹道和视觉表现;该本地表现由蓝图实现。组件会用 EntryId 与服务器复制来的批次进行确认或否决,以完成预测调和。预测子弹不会应用 GameplayEffect,也不负责权威命中结算。命中效果始终由服务器应用;命中派生的链式子弹和 Emitter 子弹也只由服务器生成。

运行时状态与后续子弹

三层状态

层

职责

网络

FGCS_BulletEntry

一批开火的定义行、来源请求、父批次、是否预测

FastArray 复制

FGCS_BulletState

单发位置、速度、已飞距离、Trace 句柄、爆炸截止

本机运行时

FGCS_BulletHitState

对某个 Actor 的命中次数、穿透次数、冷却时间

本机历史

对外入口是 RequestSpawnBullet / RequestSpawnBulletWithEntryId。组件校验 UGCS_AttackRequest_Bullet 后,本地玩家且无权威时走预测:先本地 TrySpawnBulletEntry,再 Server_SpawnBullet。权威端或 AI 直接生成。IsAIAttack 为真时(来源不是本地玩家控制)跳过本地预测。

权威命中走 ApplyGameplayEffects,内部再调实例上的 Effect Causer。无游戏权限时这条路径直接返回,所以预测弹打到人不会改属性。

预测确认与清理

契约写在组件头文件里:只预测玩家主动发射的第一批轨迹和视觉;预测弹不得应用 GE;Chain 与 Emitter 仅服务端。客户端用 Client_PredictedBulletResult 确认或否决,超时由 PredictionTimeout 清理。迟到复制的批次会按已记录时长快进实例,用来藏复制延迟。

穿透、链式与 Emitter

子弹定义上的穿透开关决定命中后是否继续飞:bPenetrateCharacter / MaxPenetrationCount 管 Pawn,bPenetrateMap 管几何。bUseSharedHitList 让同一 Entry 下的多发共享命中历史,避免霰弹重复打同一人。DamageHitDuration 是对同一 Pawn 的命中冷却。

命中后若配置了 HitBulletDefinition,权威端 RequestSpawnBulletChain 再开一批。发射条件读原生标签命名空间 GCS_BulletLaunch(Always / DidNotHitPawn / HitPawn);生成姿态读 GCS_BulletChain(FromHitResult、FromSourceRequest、HitLocWithOwnerFwdDir 等)。

带 EmitterBulletDefinition 的弹会按间隔 EmitBullet。来源类型读 GCS_BulletEmitter(Default 复用父变换、ShooterWithRandomRadius、TargetingSystem)。Emitter 与 Chain 的公开函数带 BlueprintAuthorityOnly。

实例契约

对象池取出的 Actor 必须实现 IGCS_BulletInterface:系统分配 BulletId / EntryId / 定义句柄,再按 OnBulletBeginPlay → OnBulletLaunch → OnBulletHit → OnBulletEndPlay → OnBulletReset 周转。AGCS_BulletInstance 是带投射物移动和 Niagara/Cascade 的抽象实现,GameplayEffect 仍由系统在权威端应用。AGCS_SphereBulletInstance 加上球形碰撞,适合作为可蓝图派生的默认弹体。

定义里的 TraceDefinitions 会在发射时登记到拥有者的 Trace 组件,覆盖实例类上的默认形状。ProjectileFX / ImpactFX 由实例在激活和结算时创建。

一个具体场景

玩家在客户端开火,RequestSpawnBullet 立刻从池里取出一发 AGCS_SphereBulletInstance,本地看到拖尾。服务器确认同一 EntryId 后,权威弹继续飞;预测弹在确认时被跳过复制添加。打中第一个敌人后,定义允许穿透一名 Pawn,HitState 记一笔穿透,权威端应用攻击定义上的 GE。第二下命中墙壁,Chain 标签 FromHitResult 在落点再生成一发爆炸子弹。AI 射手在服务器上开火,整条路径跳过预测。