首页 > 编程语言 >斯坦福 UE4 C++ ActionRoguelike游戏实例教程 15.创建持续效果BUFF

斯坦福 UE4 C++ ActionRoguelike游戏实例教程 15.创建持续效果BUFF

时间:2023-04-24 20:24:42浏览次数:54  
标签:触发 Instigator 15 效果 void C++ 实例教程 我们 BUFF

斯坦福课程 UE4 C++ ActionRoguelike游戏实例教程 0.绪论

概述

本篇文章对应Lecture 18 – Creating Buffs, World Interaction, 71、72节。将会基于之前实现的SurAction能力系统,教你如何定义和创建拥有持续效果的BUFF,例如许多游戏常见的灼烧、中毒效果。

目录

  1. 分析
  2. 创建BUFF基类
  3. 新增燃烧BUFF
  4. 子弹附魔
  5. 总结

需求分析

不得不说,持续BUFF是所有RPG游戏中十分重要的一个设定。常见的BUFF有中毒、烧伤等持续掉血的负面BUFF,同样也有速度提升、攻击力提升等正面BUFF。除此以外,我们还可以使用BUFF为角色附加各种标签,比如如果敌方被标记为“易伤”,那么所有攻击对于他都会得到一个增幅的效果。那么这些丰富多彩的BUFF是如何实现的呢?本篇文章将会基于前几节课实现的Action能力系统,构建一个基础的BUFF系统。

首先让我们拆解一下持续型的BUFF需要拥有哪些特点:

  1. 拥有一个持续时间
  2. 在持续时间内,每隔一段时间触发一次效果
  3. 在拥有BUFF的时候马上触发
  4. 持续时间结束后自动销毁。

OK,目前这些就足够了,具体该如何实现,让我们边做边说。

创建BUFF基类

这里才是基类。我们将其命名为SurActionEffect,继承于USurAction。

值得一提的是,我们在USurAction新增了一个bool成员变量bAutoStart,将其暴露为public,当一个Action被创建并添加的时候,可以根据这个变量来判断是否自动开始。

另外,我们重载了StartAction函数,在能力开始的时候,会自动调用定时器,以保证BUFF能自动结束和触发效果。

同理,重载了StopAction函数,当BUFF的持续时间结束时,会自动调用StopAction。注意一下,在调用父类的StopAction之前,会先判断当前是否有周期效果要触发。因为周期触发效果可能会用到Action里面的各种tag,因此我们希望先触发周期效果,再调用父类的StopAction。这里用到了KINDA_SMALL_NUMBER宏来判断是否同时触发。

//.h
class FPSPROJECT_API USurActionEffect : public USurAction
{
	GENERATED_BODY()
public:
	USurActionEffect();
	
	void StartAction_Implementation(AActor* Instigator) override;

	void StopAction_Implementation(AActor* Instigator) override;

	
protected:
	//BUFF的持续时间。我们不希望在蓝图中修改持续时间,因此使用BlueprintReadOnly
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Effect")
	float Duration;

	//触发BUFF的周期
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Effect")
	float Period;

	FTimerHandle PeriodHandle;
	FTimerHandle DurationHandle;

	// BUFF效果,在周期定时器触发时执行
	UFUNCTION(BlueprintNativeEvent, Category = "Effect")
	void ExecutePeriodEffect(AActor* Instigator);
};


//.cpp
USurActionEffect::USurActionEffect()
{
	bAutoStart = true;
}


void USurActionEffect::StartAction_Implementation(AActor* Instigator)
{
   Super::StartAction_Implementation(Instigator);
   //启动计时器
   if(Duration > 0.f)
   {
      FTimerDelegate Delegate;
      Delegate.BindUFunction(this, "StopAction", Instigator);
      GetWorld()->GetTimerManager().SetTimer(DurationHandle, Delegate, Duration, false);
   }

   if(Period > 0.f)
   {
      FTimerDelegate Delegate;
      Delegate.BindUFunction(this, "ExecutePeriodEffect", Instigator);
      GetWorld()->GetTimerManager().SetTimer(PeriodHandle, Delegate, Period, true);
   }
}

void USurActionEffect::StopAction_Implementation(AActor* Instigator)
{
   //检查周期效果是否会同时触发,如果时间近乎一样,那么先触发周期效果,再停止Action
   if(GetWorld()->GetTimerManager().GetTimerRemaining(PeriodHandle) < KINDA_SMALL_NUMBER)
   {
      ExecutePeriodEffect(Instigator);
   }
   Super::StopAction_Implementation(Instigator);

   GetWorld()->GetTimerManager().ClearTimer(DurationHandle);
   GetWorld()->GetTimerManager().ClearTimer(PeriodHandle);

   USurActionComponent* Comp = GetOwningComponent();
   if(Comp)
   {
      Comp->RemoveAction(this);
   }
}

当然,需要为ActionComponent作出相应的修改。

为了让BUFF能够被移除,这里我们为ActionComponent增加一个移除Action的功能。

void USurActionComponent::RemoveAction(USurAction* ActionToRemove)
{
   if(ensure(ActionToRemove && !ActionToRemove->IsRunning()))
   {
      return ;
   }
   Actions.Remove(ActionToRemove);
}

顺带一提,得亏UE4那魔法一般的垃圾回收机制,才得以让我们不用担心各种内存泄漏的问题。在这段函数的Actions.Remove(ActionToRemove)仅仅只会将数组中的指针给删除掉,指针所指向的内存空间并不会被销毁。笔者以前也深受C++内存管理的苦痛已久,通常这种情况会导致严重的内存泄漏,因为往往这时候我们就永远失去了访问这段内存空间的方式。所幸UE实现了C++下的垃圾回收机制,当检测到没有指针指向这段内存空间时,UE会自动释放这段内存空间。

虽然使用UObject并不需要担心内存泄漏的问题,笔者仍不希望在习惯于UE的便利性中逐渐失去对内存管理的敏感度,因为作为开发者,我们仍然有可能不小心写出了拥有内存安全问题的代码,请务必小心小心再小心。


我们还修改了AddAction的参数列表。引进了BUFF系统后,由于BUFF很有可能是其他角色为你施加的,因此需要为AddAction添加一个Instigator参数。

void USurActionComponent::AddAction(AActor* Instigator, TSubclassOf<USurAction> ActionClass)
{
   if(!ensure(ActionClass))
   {
      return;
   }
   USurAction* NewAction = NewObject<USurAction>(this, ActionClass);
   if(ensure(NewAction))
   {
      Actions.Add(NewAction);

      if(NewAction->bAutoStart && NewAction->CanStart(Instigator))
      {
         NewAction->StartAction(Instigator);
      }
   }
}

当然,其他用到了AddAction的地方也应该做适当的修改。

新增燃烧BUFF

新建一个继承于SurActionEffect的蓝图类,我们将其取名为Effect_Buring,我们将在蓝图中实现这个新的BUFF。

在我们的设想中,燃烧BUFF应该每隔一段时间就给予BUFF的拥有者一定伤害,直到持续时间结束。因此我们需要为其定义伤害(DamageAmount)属性。

接着修改蓝图。重载ExecutePeriodEffect函数,这是我们定义的周期效果函数,每隔一定时间(Period)就会触发一次。在重载的函数中,我们调用定义在GamePlayFunctionLibrary的ApplyDamage函数,如图所示

image-20230423134601787

ExecutePeriodEffect函数蓝图

值得一提的是,UE中自带了一个ApplyDamage函数,好巧不巧我们的函数与其同名,使用的时候请注意这个问题。为了规避这个问题,我们可以使用元描述符(Category)来修改ApplyDamage的类别,实在不行换个名字也成。

image-20230423134223480

注意这是我们定义在GamePlayFunctionLibrary的函数

简单修改一下燃烧BUFF的默认值。持续时间5s,每隔1s给予BUFF的拥有者1点伤害。

值得一提的是,这里我们为燃烧BUFF添加了一个Status.Buring标签,这意味着BUFF被添加到角色身上时,会为角色加上一个Status.Buring的标签。我们可以通过检测这个标签的方式,为角色加上燃烧的特效,或者添加类似遇水蒸发的效果,任君想象。

image-20230423134542954

修改Effect_Buring默认值

为子弹附魔「火焰附加」

没什么复杂的东西,实际上就在在子弹击中敌人并成功判定后,获取敌人身上的Action组件,并为其添加指定的BUFF即可。

为此,我们需要为子弹添加一个EffectActionClass成员。

void ASurMagicProjectile::OnOverlapBegin(UPrimitiveComponent* HitComp, AActor* OtherActor,
                                         UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
   ...
      if(USurGameplayFunctionLibrary::ApplyDirectionalDamage(GetInstigator(), OtherActor, DamageAmount, SweepResult))
      {
         Explode();
         //附加燃烧效果
         if(ActionComp)
         {
            ActionComp->AddAction(GetInstigator(), EffectActionClass);
         }
      }
   }
}

image-20230423135440217

进入游戏看看效果,当我们击中敌人后,敌人身上会不断冒出-1的数字,表示我们成功为敌人加上了燃烧的BUFF。

还有一个有趣的点,当前设置的燃烧BUFF是可以在短时间内无限叠加的。也就是说,如果角色在短时间内收到了10次子弹攻击,那么他身上就会带有十个燃烧BUFF,意味着他每秒钟会受到10点伤害。有时候我们希望角色只能受到一层BUFF,至于怎么修改,就当是我留给读者的课后作业吧(

总结

本篇文章我们创建了SurActionEffect类作为各种BUFF的基类,他拥有持续触发效果的功能,能够自动开始执行,并自动移除自身在ActionComponent的引用。

之后我们以此基类构建了燃烧BUFF,并为我们的魔发子弹添加了燃烧“附魔”,以此抛砖引玉,希望能够激发大家的想象力。

标签:触发,Instigator,15,效果,void,C++,实例教程,我们,BUFF
From: https://www.cnblogs.com/Qiu-Bai/p/17350748.html

相关文章

  • 斯坦福 UE4 C++ ActionRoguelike游戏实例教程 16.优化交互,实现看到物体时出现交互提
    斯坦福课程UE4C++ActionRoguelike游戏实例教程0.绪论概述本篇文章对应Lecture18–CreatingBuffs,WorldInteraction,73节。本文将会重构以前实现过的SurInteractionComponent,实现在玩家注释可交互物体时,可以出现可交互提示,效果如下:在文章的最后,我会放出所有相关的代......
  • AT1504
    题意翻译:\(N\)个坐标,给定\(M\)个区间覆盖,求最后有多少区间被覆盖的次数不是\(1\)。无脑选手选择先差分区间修改,再线段树维护区间是否存在\(1\)。代码以及一些注释:#include<bits/stdc++.h>#defineFfirst#defineSsecondusingnamespacestd;constexprintN=3e5+......
  • C/C++服务端客户端通讯程序[2023-04-24]
    C/C++服务端客户端通讯程序[2023-04-24]Socket通讯程序..服务器端).pptx任务:Socket通讯程序开发·基本要求(80分)∶完成一对一的Socket客户端与服务器程序·进阶要求(90分)∶在完成基本要求基础上,将服务器端程序改为多线程程序·高级要求(100分)︰将客户端和服务器端都改为多......
  • AtCoder Beginner Contest 158
    AtCoderBeginnerContest158https://atcoder.jp/contests/abc158基础不牢,地动山摇D-StringFormation一个小小的STL应用#include<bits/stdc++.h>#definelllonglongusingnamespacestd;strings;intq,t,f;charc;intmain(){cin>>s>>q......
  • 面试最常问的数组转树,树转数组 c++ web框架paozhu实现
    刚毕业同学,找工作常被问二维数组转树,树转二维数组需要支持无限层级实现,如果你了解这个语言那么实现起来还要一番思考c++web框架paozhu使用需要实现数据库表数据到前台菜单实现,就是这种功能二维数组转树,树转二维数组保存时候树二维数组,展示时候树树状。这个技术难点在于无......
  • 初学者代码训练Day7(c/c++)
    兔子产子问题要求 流程图  代码1#include<iostream>2usingnamespacestd;34intmain()5{inta=1,b=1,sum=0,y;6printf("%d\n%d\n",a,b);7for(y=3;y<=30;y++)8{sum=a+b;9printf("%d\n",sum);10a=b;1......
  • 【c&c++】[Error] iostream.h: No such file or directory的解决办法
    直接上错误代码实例#include<iostream.h>intmain(){print('hello,world\n')return0;}编译通不过,直接出错 这是C语言转C++的两条经典错误C++中是没有iostream.h这个东西的(或者一般不会这么使用),正确用法是:#include<iostream>用了iostream还不......
  • 【c&c++】VScode报错error: ‘::main‘ must return ‘int‘ void main()
    在运行指针时终端出现error:‘::main’mustreturn‘int’voidmain()错误。源代码如下:#include<stdio.h>voidmain(){inta,*p,b,c,d,e;a=100;p=&a;/*(*&a)先进行&a运算,得a的地址,再进行*运算,即变量a的值*/b=*&a;printf("a=%d\n",a);......
  • 【c&c++】C++ 关于编译出现“undefined reference to `std::cout‘“的问题
    1、问题概述        在使用gcc编译c++代码时会出现undefinedreferenceto`std::cout',如编译如下代码:#include<iostream>usingnamespacestd;intmain(){cout<<"Helloworld!";return0;}然而,gcc下编译出现的问题是: 2、解决方法使用g++编译,g++......
  • 开心档之C++ 类 & 对象
    C++类&对象C++在C语言的基础上增加了面向对象编程,C++支持面向对象程序设计。类是C++的核心特性,通常被称为用户定义的类型。类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。C++类定义定......