TomLooman_ActionRoguelike_第四章接口和碰撞查询

2023-08-09 18:37:04 来源:哔哩哔哩

打印 放大 缩小

该专栏用于保存对TomLooman的ActionRoguelike项目的学习笔记,学习过程中的思考与记录不一定准确。

教程参考:/tomlooman/ActionRoguelike

基于的项目实现:/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial


(相关资料图)

2023_07_30

接口与碰撞查询:C++接口(与Actor互动),ActorComponent和碰撞痕迹,动画和计时器(改进攻击)

UE中的接口

官方大致解释:接口能让一组不相关的类实现一组通用的函数。某些游戏功能可能被大量复杂且不相关的类共享,这就是接口的出场之时。

例如,在游戏中,玩家进入一个trigger区域后,陷阱会伤害玩家,敌人会做出反应,夺旗点会给予玩家点数奖励。它们都共享同一个功能“玩家进入trigger区域,执行某个动作”。陷阱派生自AActor,敌人派生自ACharacter,奖励点数派生自UDataAsset,它们的唯一公共父类是UObject,但我们无法修改UObject,所以常用的通过在公共父类中声明虚函数,在子类中对虚函数进行覆盖,实现动态绑定的做法行不通。在这种情况下,推荐使用接口。(实际上,接口的基本原理也类似于覆盖公共父类的虚函数,但这个公共父类不是本来就有的,而是我们后来加上去的)

因为我们要做宝箱和医疗包,两者都要实现玩家与其互动后执行某个动作的功能,所以我们要先构建一个Interface类,来让宝箱和医疗包的类继承这个Interface类。

接口类的声明如下

UINTERFACE(MinimalAPI, Blueprintable)    classUReactToTriggerInterface : publicUInterface    {        GENERATED_BODY()    };     classIReactToTriggerInterface    {        GENERATED_BODY()     public:        /** 在此处添加接口函数声明 */    };

发现与我们之前声明的C++不太一样的是,Interface类有两个类声明,它们类名相同,但前缀不同,以此做出区分。

"前缀为U(U-prefixed)"的类是个空白类,不需要构造函数或任何其他函数,创建后不应被修改。它并不是实际接口,只是向UE的反射系统确保可见性。

"前缀为I(I-prefixed)"的类是实际接口,将包含所有接口函数,且此类实际上将被你的其他类继承。我们的工作在这个类中进行。

我们在ISGameplayInterface中声明了接口函数如下

UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void Interact(APawn *InstigatorPawn);

但我们在源文件中并没有相应的实现,所以目前Interface类的源文件是个空文件。根据自带的注释,我们可以发现Interface类实际上就类似于我们创造的,实现相同功能的类的公共父类,而如果我们不对Interface类的任何成员函数给予定义的话,该公共父类是个抽象基类。

根据覆盖Interface类的成员函数(接口函数)位置(C++/蓝图),UFUNCTION中应使用不同的specifier。从而对该接口函数是否是虚拟函数,以及如何覆盖该函数提出不同要求。

在我们的声明中,BlueprintCallable指该接口函数可被蓝图调用,同时要求再使用BlueprintNativeEvent或BlueprintImplementableEvent,且该函数不能是虚函数。(一开始认为Interface类就是自己创建的公共父类,通过派生类覆盖基类虚函数,来实现不同功能。但这么一看可能不是这样,只是想法有些类似。)(但是在仅C++的情况下,接口函数又必须是虚函数。)

BlueprintImplementableEvent指该函数只能在继承该Interface的蓝图类中被覆盖,BlueprintNativeEvent指该函数可以在C++中被覆盖,但是覆盖该函数的函数要在函数名末尾加后缀“_Implementation”,在我们的项目中如下。

(Interface类中的声明)

(Interface类的派生类中的覆盖)

我们通过继承Interface类并覆盖接口函数来使用接口。我们的宝箱类的声明如下

注意这里继承的是I开头的Interface类,而不是U开头的。而且由于接口函数UNFUNCTION中的Specifier是BlueprintNativeEvent,所以覆盖时函数名要加后缀”_Implementation”。

覆盖的接口函数如下

在这个函数中我们没有使用到形参,只是进行了UStaticMesh类的LidMesh的一个相对旋转。

到目前为止,虽然我们定义了接口,也定义了使用接口的类,但是这个接口对应的功能并不能被执行,因为没有东西来触发它。

我们直接可以想到的是,在ASCharacter类中定义一个函数,这个函数在我们按下某个按键时执行,触发使用接口的类中的接口函数。

但是这样的做法在长期看来会导致ASCharacter越来越臃肿。一个好的解决方法是自定义ActorComponent,就像UE自带的碰撞组件、相机组件那样。通过将可复用的模块定义为组件,能实现系统模块之间的解耦合。

之前我们创建了名为SGameplayInterface的Interface类,所以对应的,我们创建名为SInteractionComponent的UActorComponent类,并在这个组件类中执行接口函数。

该类的声明如下,

ActorComponent是添加到Actor上的实现各种功能的组件的基类,其中带有Transform的被称为SceneComponent,可以渲染Actor的被称为PrimitiveComponent。

UClass的Specifier中的ClassGroup控制该类在UE编辑器的浏览器中属于的类别,ClassGroup的值不能随意指定。Specifier中的meta是元数据说明符,表示类与引擎、编辑器的相处方式,它只存在于编辑器中,我们不能编写能访问到meta的游戏逻辑,其中的BlueprintSpawnableComponent说明该类可由蓝图生成,如下,

ActorComponent类与Actor类有一点不同是,Actor类中有一个Tick函数,而ActorComponent中的函数叫TickComponent。因为这里并没有使用,所以后面再讲。

在SInteractionComponent类中声明并定义如下函数

在这个函数中,我们从SCharacter射出一道射线,在第一个碰撞到的物体上执行接口函数。因此包含两个主要步骤,检测第一个碰撞物体,在碰撞物体上执行接口函数。

检测碰撞物体:

核心函数是GetWorld()->LineTraceSingleByObjectType(Hit, Start, End, ObjectQueryParams)。

GetWorld返回UWorld类的指针,UWorld类是一个Map中的顶级对象,Actor和Component都存在于其中。

LineTraceSingleByObjectType射出一道射线并返回第一个阻挡射线的对象,阻挡的依据是对象的类型。但是这里显式的返回值为bool变量,表示是否有阻挡对象,真正的阻挡对象信息保存在参数中(C++中通过引用类型的参数能增加返回值的个数)。

Hit是FHitResult类的对象,在这里作为返回值。FHitResult类保存了一次hit的信息,包括hit的对象,hit的位置等。

Start和End是FVector类的对象,表示射线的开始和结束位置,其中,GetOwner返回拥有该ActorComponent的Actor的指针,GetActorEyesViewPoint用引用形参的形式返回Actor的“眼睛”(或者说视角)(注意不是摄像机的视角)的location和rotation。

ObjectQueryParams属于FCollisionObjectQueryParams类,保存碰撞查询中涉及的对象类型,这里我们执行AddObjectTypesToQuery(ECC_WorldDynamic)

,表示碰撞中查询WorldDynamic类型的对象。

在碰撞物体上执行接口函数:

其核心是ISGameplayInterface::Execute_Interact(HitActor, MyPawn);

先通过AActor *HitActor = ();获得hit到的Actor对象,然后进行两重判断,保证该函数指针非空且实现了接口函数。

if (HitActor)判断是否有hit到对象,这里特指WorldDynamic对象。

if (HitActor->Implements<USGameplayInterface>())

判断HitActor是否实现了接口函数,这里的接口函数就是我们在SGameplayInterface里声明的Interact。需要注意的是,这里用的是SGameplayInterface声明中U开头的类,而不是I开头的类,因为U开头的类才是Interface中实现UE反射的部分。(这里无需先判断HitActor是否继承了Interface类,再判断是否实现了接口函数)

因为我们的接口函数有BlueprintNativeEvent的Specifier,所以执行时调用ISGameplayInterface::Execute_Interact,如果该接口函数只在C++中被覆盖,则可以直接调用原函数名,不用加前缀“Execute_”(覆盖时也不用加后缀“_Implement”)。

函数的参数包括(1)实现接口函数的对象(因为上面经过了if判断,所以就是HitActor,比如我们的宝箱或医疗包)(2)接口函数声明中的其它参数(这里是触发接口函数的对象,也就是拥有该ActorComponent的Actor,也就是我们的SCharacter对象)

因为接口函数声明中,形参是表示触发接口函数的APawn类对象,所以我们要把SCharacter类型的MyOwner对象进行类型转换。类型转换通过Cast实现,函数接口如下(这里执行时只给出了模板参数中的To和形参Src,模板参数中的From由形参推导得到)

和原生C++不同,在UE中,这样的转换是安全的。

上面的碰撞物体检测使用的是射线,如果我们想要交互的物体很小的话,用射线就不合理。我们可以做出如下改进,用圆柱体检测碰撞物体(也可以看作是很粗的射线)。

这里使用的是SweepMultiByObjectType,需要注意的是此时除了检测范围变大外,函数不再返回第一个碰撞到的对象,而是返回所有碰撞到的对象,因此这里的返回结果变成了一个数组(TArray)。

SweepMultiByObjectType的形参与LineTraceSingleByObjectType类似,不同之处在于要指定检测范围的形状(FCollisionShape Shape)和该形状的旋转(FQuat::Identity,不旋转,FQuat是UE中的四元组类)。

因为我们仍想要只执行第一个碰撞到的对象中的接口函数,所以在遇到实现该接口的对象,并执行接口函数后,就break跳出循环。

这里还有两个用于Debug的可视化函数,

DrawDebugSphere(GetWorld(), Hit.ImpactPoint, Radius, 32, LineColor, false, );

DrawDebugLine(GetWorld(), Start, End, LineColor, false, , 0, );

效果如下,

这里第一个方块不是WorldDynamic,第二个方块是WorldDynamic,宝箱上没有第二个方块那样的球体是因为DrawDebugSphere在break之后。

最终我们要将上面实现的ActorComponent添加到SCharacter中,以下是声明和构造函数中的实例化,

在ASCharacter::SetupPlayerInputComponent中将触发接口函数的成员函数与某个事件绑定,并声明、实现该函数

在该函数的实现中,我们直接调用ActorComponent中执行接口函数的成员函数。

总结一下通过Interface和ActorComponent实现Character与Actor交互的过程:

(1)定义一个Interface类,并在该Interface中声明一个接口函数

(2)定义一个Actor类并继承Interface类,并在该Actor类中覆盖Interface类中的接口函数

(3)定义一个ActorComponent类,并在该ActorComponent类中定义一个函数,该函数涉及(a)判断某个对象是否实现了某个Interface类(2)执行该对象上Interface类部分的某个接口函数

(4)定义一个Character类,并定义一个ActorComponent类的对象为成员变量,绑定外部输入、事件和该类的某个成员函数,在该成员函数中调用ActorComponent类成员变量的特定函数

发生交互的过程如下:

(1)外部输入触发Character的某个事件,执行与该事件绑定的成员函数,在该成员函数中调用ActorComponent成员变量的某个函数

(2)在ActorComponent对象的成员函数中,判断Character的交互对象是否实现了Interface类,若实现了,则调用该交互对象实现的Interface类的接口函数

(3)调用交互对象覆盖的Interface类的接口函数

虽然动画通常在蓝图中完成,但是在C++中也可以。我们首先在SCharacter中创建UAnimMontage类的成员变量如下,为了在编辑器使用方便,我们将飞弹动画归为同一类,

在内容浏览器中筛选AnimationMontage类型,将AnimationMontage对象赋给Character的成员变量。

我们在PrimaryAttack中调用该对象如下

其中,PlayAnimMontage在Character的Mesh上播放Montage动画,返回动画的时间。

加入攻击抬手的动画后,如果不做其它调整,我们会发现,由于魔法飞弹spawn在我们按键时Character手的位置,而不是动画中Character伸手的位置,所以会出现角色抬手,但飞弹在下面生成的情况,如下

因此我们要对PrimaryAttack增加延时,这可以通过设置定时器实现。

用下面的语句给原本的PrimaryAttack增加了延时,

GetWorldTimerManager().SetTimer(TimerHandle_PrimaryAttack, this, &ASCharacter::PrimaryAttack_TimeElapsed, );

其中,GetTimerManager() 获取 World 中保存的定时器的管理器 TimerManager。SetTimer设置一个定时器,每个一段时间调用给定函数。TimerHandle_PrimaryAttack是FTimerHandle (定时器句柄)类型的成员变量,在头文件中声明如下

this是调用执行函数的对象。&ASCharacter::PrimaryAttack_TimeElapsed

是待执行的函数,在这里也就是我们原本的PrimaryAttack成员函数。是函数执行的时间间隔。

关键词:

责任编辑:ERM523

相关阅读

精彩推送

TomLooman_ActionRoguelike_第四章接口和碰撞查询 1000万成立酒业运营管理公司 海南椰岛要“喝酒”?
人身险业进入“3%时代” 多家险企推出了各类新产品 人身险市场未来展望 杨鸥:大多数蓝海都基于竞争的求异 不意味每方面都领先对手 | 博鳌快讯
四川雅安一河道涨水致7人遇难,官方:不要擅自进入野外河道等危险区域 申请美国高中留学需要具备什么条件?
网红地涨水致7死 当地曾发山洪预警详情曝光河道快速涨水很多人来不及撤离 具体是啥状况呢 火山引擎首发“会表演”的有声内容创作平台
苹果将 Vision Pro 头显的外接电池称为“Magic Battery” 别人找我们推荐车子的时候,怎么推荐比较好
助力智慧旅游 《旅游大数据研究报告》出版 上半年黑龙江省跨境人民币业务实际收付299.9亿元 同比增长87.2%
北京蓝天救援队:731暴雨救援行动共投入队员2364人 完成转移人员35997人 健康??_健康问问
内蒙古大兴安岭林区发生森林火灾 情况变得越发危机 民爆光电(301362.SZ):公司产品下游覆盖工业照明、商业照明、植物照明和应急照明等领域,可应用于药材的生长
8月9日24时起国内汽、柴油价格每吨分别提高240元、230元 临泽:争分夺秒抢进度 项目建设如火如荼
广西一土匪被判死刑,狱中交出秘方,救治数万名志愿军性命 成功树立“信用南通”标杆 南通高位创成信用示范区
内容创作平台分为哪三类?_内容创作平台 TS-AI642登场:威联通®科技打造的AI NAS专用机助力企业数据保障
机床行业点评报告:机床税收优惠政策落地,鼓励加大研发投入&相关机床企业业绩将持续改善 数字经济产业园高质量发展对话会在昆明盘龙区云上数字赋能产业园举行
暑期亲子经济热力十足 广西柳州:百米水彩画卷展现大苗山民族风情
普法按下“快进键” 法治宣传在路上——建行银川开发区支行扎实开展民法典宣传 最美两江人|重庆市高新工程勘察设计院任佳:吹响预警“前哨” 筑牢地灾防治“生命线”
贷款没钱被起诉会怎么样 中国联通:20223 上半年营收、净利润再次刷新上市以来纪录
中国联通:上半年净利润同比增长13.7% 房山区教委:对受灾学生开启绿色通道,确保如期开学
中国成功发射环境减灾二号06星 企业养老保险待遇的一般规定是什么
上海市宝山区顾村科技园学校是公办吗 宋九朝编年备要 卷九
鹤岗市兴山区总工会:文化“惠”民生 凝聚“工”力量 东方钽业(000962)8月9日主力资金净买入98.87万元
思明区启动老年营养改善行动 winsockfix无法修复winsock怎么办? winsock损坏修复工具
一夜之间,10家银行评级被下调!美银行股集体下跌 创纪录!N盟固利上市首日盘中涨3699%,中一签可赚近10万
大额存单额度紧张,购买还要“配货”?记者实探广州多家银行 山西鹏飞集团6项氢能领域创新成果获国家专利
浪花男子成员西畑大吾穿女装约会揭秘:爱情的巧思与隐匿 公摊谁创造的(公摊是谁提出的)
台湾面板“双虎”友达、群创公告7月合并营收,同比均大增 QQ创号器(qq创号)
成大生物: 公司十分重视合规经营,目前公司的研发、生产、销售等生产经营活动都在按计划有序开展, “脱钩断链”挡不住各国发展诉求
10部门联合发文加强论坛活动规范管理:重点打击以论坛活动名义进行诈骗敛财等违法违规行为 促进合理膳食 助力乡村振兴 2023年维爱公益行动再启程
郎酒汪博炜率队走进长沙市场,强调确保商家合理利润,让商家敢于发展 滇池白鲢鱼集体“跳龙门”?云南地震局回应! 具体是什么情况?
长安逸达畅享版上市 售价8.39万元 江口怒溪:生态蛋鸡“孵出”强村富民大产业
上海将设立绿色航运示范区 江西13岁女童遭陌生男子强奸致死案二审维持原判:施暴者死刑
台铃集团、协氢新能源发布一款风冷氢能共享两轮车 中国电信与科大讯飞签署战略合作协议
超频三(300647)8月9日主力资金净买入1897.34万元 涪陵消防化身“蜘蛛侠” 22楼高空紧急救援
“卡努”又来了!辽宁将迎来局部大雨和9级阵风 一家8口自驾被困荒漠,其中4个老人2个孩子!网友怒了:怎么想的?
江苏新潮村:三代村居“同框”,道出振兴“密码” 车评头条:试驾梅赛德斯-奔驰V260L 把S级搬进MPV
Maruti Suzuki Vitara Brezza于2020年2月投产 腾讯、字节跳动、大北农三家企业各捐赠1亿元现金物资驰援京津冀救灾重建
复古做旧,卡宾旗舰店男士真皮工装靴139元新低(200元券) 海信5G荣耀家营造清爽一夏美好生活
莱音珠宝黄金价格今天多少一克(2023年08月09日) 英特尔携手视源股份、德晟达联手打造新一代OPS实施标准
双胞胎暴雨中降生被起名“定海” “神针” 红豆集团周海江:积极弘扬企业家精神 坚定不移走好高质量发展之路
中国民办教育协会:“数学花园探秘”(大师赛)为违规竞赛 金沙古酒500亿估值靠什么支撑?宣称五年上市,品牌商标纠纷不断
余杭“凉脖冰冰圈”为户外工作者实力“制冷” 博德之门3第三章戈塔什公爵具体位置
生意社:国内成品油零售价或实现“四连涨” 供应紧张+需求乐观支撑油市保持偏强走势 《童话》登岛宣传片:十位成人夜间登陆 陈伟霆扬言好喜欢星守村
命运方舟2023ChinaJoyshowgirl高度还原 车辆价格一降再降,车企利润真有这么高?
“挂羊肉卖猪鸭肉”,张亮麻辣烫竟“甩锅”加盟店? 强调隐私保护 国家网信办为“刷脸”划红线
呼叫杭州,您的“萌兽”大熊猫今日已从成都启动运输,请注意查收! 业绩堪忧!IPO被终止
为何光速只适用于光子? 动力煤:下游按需采购 产地市场止跌暂稳
韩国“杀人预告”发帖者动机多为引起关注或酒后玩笑,超一半为未成年人 马上评|高排片点映是不是“孤注一掷”?
华西证券给予上汽集团买入评级,系列点评六十五:合作奥迪技术输出电动智能转型加速 能建尽建,塔牌集团上半年厂内光伏收入109.96万 同比大涨146.04%
SMM快讯 乐惠国际8月9日加速下跌
远兴能源董秘回复:阿拉善天然碱项目正按计划推进中 省地矿局物化探院为国家水利工程白蚁防治提供技术支撑
胜利管道(01080.HK)将于8月19日举行董事会会议以审批中期业绩 成功发射!全天候应急遥感监测能力进一步增强
新茶饮品牌,迎来“上市潮”? 文旅部、工信部启动“5G+智慧旅游”应用试点项目申报工作