UE5.1.1 C++从0开始(17.GAS游戏能力系统)

教程的链接:https://www.bilibili.com/video/BV1nU4y1X7iQ

教程内的老师没用GAS的插件,而是自己写了一个。这一篇文章只是开头,还有很多的内容没有往里面写。

新增了一个object类,新增了一个使用这个类的组件。然后把这个组件用在了我们的SCharacter上面,重构了我们的攻击功能。

新增类:SAction

SAction.h

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "SAction.generated.h"//此处的UCLASS宏内需要填入Blueprintable,否则我们不能在创建蓝图的时候从所有类中找到我们的SAction类
UCLASS(Blueprintable)
class ACTIONROGUELIKE_API USAction : public UObject
{GENERATED_BODY()public://行为名字来开始或停止UPROPERTY(EditDefaultsOnly,Category="Action")FName ActionName;//宏内的BlueprintNativeEvent意味着蓝图可重写UFUNCTION(BlueprintNativeEvent,Category="Action")void StartAction(AActor* Instigator);UFUNCTION(BlueprintNativeEvent, Category = "Action")void StopAction(AActor* Instigator);class UWorld* GetWorld() const override;
};

SAction.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "SAction.h"
#include "Logging/LogMacros.h"void USAction::StartAction_Implementation(AActor* Instigator)
{UE_LOG(LogTemp, Log, TEXT("Running: % s"), *GetNameSafe(this));
}void USAction::StopAction_Implementation(AActor* Instigator)
{UE_LOG(LogTemp, Log, TEXT("Stopped: % s"), *GetNameSafe(this));
}//重写的GetWorld()方法,防止子类调用GetWorld()方法出现错误
class UWorld* USAction::GetWorld() const
{UActorComponent* Comp = Cast<UActorComponent>(GetOuter());if (Comp){return Comp->GetWorld();}return nullptr;
}

SActionComponent.h

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SActionComponent.generated.h"class USAction;UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ACTIONROGUELIKE_API USActionComponent : public UActorComponent
{GENERATED_BODY()protected://行为列表UPROPERTY()TArray<USAction*> Actions;//蓝图内填写的行为列表UPROPERTY(EditAnywhere, Category = "Actions")TArray<TSubclassOf<USAction>> DefaultActions;public://增加行为函数UFUNCTION(BlueprintCallable,Category="Actions")void AddAction(TSubclassOf<USAction>ActionClass);//通过名字来获取Action类,然后执行ActionUFUNCTION(BlueprintCallable, Category = "Actions")bool StartActionByName(AActor* Instigator, FName ActionName);//通过名字来获取Action类,然后停止ActionUFUNCTION(BlueprintCallable, Category = "Actions")bool StopActionByName(AActor* Instigator, FName ActionName);public:	// Sets default values for this component's propertiesUSActionComponent();protected:// Called when the game startsvirtual void BeginPlay() override;public:	// Called every framevirtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;};

SActionComponent.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "SActionComponent.h"
#include "SAction.h"void USActionComponent::AddAction(TSubclassOf<USAction>ActionClass)
{if (!ensure(ActionClass)){return;}USAction* NewAction =  NewObject<USAction>(this,ActionClass);if (NewAction){Actions.Add(NewAction);}
}bool USActionComponent::StartActionByName(AActor* Instigator, FName ActionName)
{for (USAction *Action : Actions){if (Action&&Action->ActionName==ActionName){//调用开始事件Action->StartAction(Instigator);return true;}}return false;
}bool USActionComponent::StopActionByName(AActor* Instigator, FName ActionName)
{for (USAction* Action : Actions){if (Action && Action->ActionName == ActionName){//调用停止事件Action->StopAction(Instigator);return true;}}return false;
}// Sets default values for this component's properties
USActionComponent::USActionComponent()
{PrimaryComponentTick.bCanEverTick = true;
}// Called when the game starts
void USActionComponent::BeginPlay()
{Super::BeginPlay();//将我们在蓝图内填入的Action类丢到我们的类列表内for (TSubclassOf<USAction> ActionClass : DefaultActions){AddAction(ActionClass);}}// Called every frame
void USActionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{Super::TickComponent(DeltaTime, TickType, ThisTickFunction);}

写完这两个类后,我们可以先进行一个尝试。我们会分别用蓝图和C++来演示如何使用Action这个类,以及如何运行这些Action类的子类。

首先是冲刺的功能,当我们摁住Shift的时候人物移速会加快,而我们放开Shift的时候人物移速会回到平常的样子。我们在蓝图内实现这个功能。以SAction为父类创建一个蓝图子类,在子类里面进行函数的重写。

请添加图片描述

请添加图片描述

蓝图内的默认增长值和事件名字如图所示。

同时我们给SCharacter增加一个事件按键绑定,这一步和教程不一样(因为UE5.1.1版本的缘故)我们需要按照先前的方法进行绑定,然后在绑定函数那里写上

请添加图片描述

然后进入蓝图编辑器的PlayerCharacter内的ActionComp内,在默认事件内把我们的蓝图给填上去。

在这些工作完成之后我们就实现了Shift进行加速的功能。之后便是把我们进行攻击的功能的函数给迁移到我们的Action内,用我们的ActionComp去实现它而不是在我们的SCharacter类里面实现它。

为此我们需要写一个SAction的子类,SAction_ProjectileAttack。

SAction_ProjectileAttack.h

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "SAction.h"
#include "SAction_ProjectileAttack.generated.h"/*** */
UCLASS()
class ACTIONROGUELIKE_API USAction_ProjectileAttack : public USAction
{GENERATED_BODY()protected://魔法子弹类UPROPERTY(EditAnywhere, Category="Attack")class TSubclassOf<AActor> ProjectileClass;//手部插槽名字UPROPERTY(VisibleAnywhere, Category="Effects")FName HandSocketName;//timer的delay时间长UPROPERTY(EditDefaultsOnly, Category="Attack")float AttackAnimDelay;//动画UPROPERTY(EditAnywhere, Category="Attack")class UAnimMontage* AttackAnim;//手部生成效果UPROPERTY(EditAnywhere, Category="Attack")UParticleSystem* CastingEffect;//timer结束时触发的函数UFUNCTION()void AttackDelay_Elapsed(ACharacter* InstigatorCharacter);public://开始事件函数重写virtual void StartAction_Implementation(AActor* Instigator) override;USAction_ProjectileAttack();
};

各位会发现,我们把部分SCharacter类里面的部分UPROPERTY给拿了过来,在Action内去进行使用。

SAction_ProjectileAttack.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "SAction_ProjectileAttack.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
#include "SCharacter.h"void USAction_ProjectileAttack::StartAction_Implementation(AActor* Instigator)
{Super::StartAction_Implementation(Instigator);ACharacter* Character = Cast<ACharacter>(Instigator);if (Character){Character->PlayAnimMontage(AttackAnim);UGameplayStatics::SpawnEmitterAttached(CastingEffect, Character->GetMesh(), HandSocketName, FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::SnapToTarget);FTimerHandle TimerHandel_AttackDelay;FTimerDelegate Delegate;Delegate.BindUFunction(this, "AttackDelay_Elapsed", Character);GetWorld()->GetTimerManager().SetTimer(TimerHandel_AttackDelay, Delegate, AttackAnimDelay, false);}}void USAction_ProjectileAttack::AttackDelay_Elapsed(ACharacter* InstigatorCharacter)
{if (ensureAlways(ProjectileClass)){FVector HandLocation = InstigatorCharacter->GetMesh()->GetSocketLocation(HandSocketName);//生成参数设置FActorSpawnParameters SpawnParams;SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;SpawnParams.Instigator = InstigatorCharacter;//检测的球体半径设置FCollisionShape Shape;Shape.SetSphere(20.0f);//增加忽视碰撞的对象FCollisionQueryParams Params;Params.AddIgnoredActor(InstigatorCharacter);//增加检测的通道FCollisionObjectQueryParams ObjParams;ObjParams.AddObjectTypesToQuery(ECC_WorldDynamic);ObjParams.AddObjectTypesToQuery(ECC_WorldStatic);ObjParams.AddObjectTypesToQuery(ECC_Pawn);ASCharacter* PlayerCharacter = Cast<ASCharacter>(InstigatorCharacter);FVector TraceStart = PlayerCharacter->GetPawnViewLocation();FVector TraceEnd = TraceStart + (InstigatorCharacter->GetControlRotation().Vector() * 5000);FHitResult Hit;if (GetWorld()->SweepSingleByObjectType(Hit,TraceStart,TraceEnd,FQuat::Identity,ObjParams,Shape,Params)){TraceEnd = Hit.ImpactPoint;}//老师的求旋转方法FRotator ProjRotation = FRotationMatrix::MakeFromX(TraceEnd - HandLocation).Rotator();//我的求旋转方法//FRotator ProjRotation = (TraceEnd - HandLocation).Rotation();FTransform SpawnTM = FTransform(ProjRotation, HandLocation);GetWorld()->SpawnActor<AActor>(ProjectileClass, SpawnTM, SpawnParams);}StopAction(InstigatorCharacter);
}USAction_ProjectileAttack::USAction_ProjectileAttack()
{AttackAnimDelay = 0.2f;HandSocketName = "Muzzle_01";
}

在我看视频的时候,发现这一部分我的思路和老师的思路大体相同但还是有些细节上是不太一样的(汗)我用的是LineTracting,而老师用的是ObjType,很难说哪个好哪个不好,我觉得更像是一个需求的两个不同实现罢了。包括后面的求Rotation的方法,我是直接进行一个减法,在我的概念内,Rotation实际上是一个方向,也就是一个向量,而向量的长短对我来说是无所谓的东西。而老师用的方法我去网上查看了下,然后看了下源码,大概理解是这样:FRotationMatrix中存放物体相对于世界坐标系的旋转角度信息,具体的我在源码内做个解析

TMatrix<T> TRotationMatrix<T>::MakeFromX(TVector<T> const& XAxis)
{//获得一个参数的副本TVector<T> const NewX = XAxis.GetSafeNormal();// try to use up if possible//UE_KINDA_SMALL_NUMBER的大小为1.e-4f就是0.0001f,如果Z方向上的向量小于0.9999那么UpVector赋前值,如果大于,那么赋后值TVector<T> const UpVector = (FMath::Abs(NewX.Z) < (1.f - UE_KINDA_SMALL_NUMBER)) ? TVector<T>(0, 0, 1.f) : TVector<T>(1.f, 0, 0);//UpVector ^ NewX 我对于^符号的理解是进行异或运算,但是我没想明白向量做异或运算实际上是怎么做的const TVector<T> NewY = (UpVector ^ NewX).GetSafeNormal();const TVector<T> NewZ = NewX ^ NewY;//返回一个矩阵return TMatrix<T>(NewX, NewY, NewZ, TVector<T>::ZeroVector);
}

然后调用Rotator()函数,将矩阵变成我们的Rotation进行赋值。

除此之外的内容我的方法与教程给出的方法大同小异,我的方法内会缺一个在进行发射动画的时候手部会生成一个附着的特效,这个是之前的作业,我在之前的文档里面写过没看懂作业要求,这里做个补充说明。

创建完子类之后,我们在蓝图编辑器内去新建我们的MagicProjectile的蓝图类,我这里只用我们的普通魔法子弹做示范:

请添加图片描述

我们填入这些参数之后,在我们的SCharacter类内部进行修改,我们可以将我们攻击相关的绝大多数函数都给删掉了,只保留下一个和我们按键绑定的那一个函数,然后在函数内部写上

请添加图片描述

然后我们就可以重现我们的攻击功能了!同时如果以后我们想要往里面增加更多的魔法子弹相关技能的话,都可以通过这个MagicProjectile去创建,不用像我们以前那样一个函数写很多遍了(其实也可以只写一遍,在我们已经写好的函数内增加一个魔法子弹类,然后按生成参数的方法去生成我们的魔法子弹,但是所有代码都加到SCharacter类内,会让我们的代码显得太过于冗长,对我们的后期维护不友好)。而且对于SCharacter来说,这些功能都是封装起来的,它只不过是调用而已,我们后期维护也更加方便了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/8584.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring高手之路4——深度解析Spring内置作用域及其在实践中的应用

文章目录 1. Spring的内置作用域2. singleton作用域2.1 singleton作用域的定义和用途2.2 singleton作用域线程安全问题 3. prototype作用域3.1 prototype作用域的定义和用途3.2 prototype作用域在开发中的例子 4. request作用域&#xff08;了解&#xff09;5. session作用域&a…

NGINX+Tomcat负载均衡、动静分离集群

目录 前言 一、NGINX正向代理与反向代理 1.1、NGINX正向代理 1.2、NGINX反向代理 1. 2.1Nginx配置反向代理的主要参数 二、负载均衡 三、NGINX动静分离集群 3.1动静分离原理 四、NginxTomcat动静分离 4.1搭建nginx代理服务器192.168.14.100 4.1.1安装 NGINX依赖环境 …

阿里云国际站:云原生数据库2.0时代,阿里云如何将云原生进行到底?

【猎云网上海】11月3日报道&#xff08;文/孙媛&#xff09; “PolarDB将云原生进行到底&#xff01;” 在2021年云栖大会上&#xff0c;阿里巴巴集团副总裁、阿里云智能数据库事业部总负责人李飞飞宣布了PolarDB实现三层解耦的重磅升级以及引领云原生数据库技术持续创新的态…

【python】数据处理

1.按照间隔生成时间戳,并保存为csv文件 ##### 按照间隔生成时间戳,并保存为csv文件 import pandas as pd from datetime import datetime, time, timedelta times [] ts datetime(2023, 6, 17, 9, 10, 0) while ts < datetime(2023, 6, 17, 9, 26, 40):# times.append(t…

博客相关推荐在线排序学习实践

现有固定槽位的填充召回策略在相关线上推荐服务中缺乏有效的相关性排序&#xff0c;存在较硬的排列顺序&#xff0c;各个策略之间互相影响&#xff0c;导致线上基于规则的拓扑图比较复杂&#xff0c;因此设计在线推理服务&#xff0c;通过学习用户行为完成在线排序。 1. 博客相…

数通王国历险记之TCP协议下的三大协议的验证实验

系列文章目录 数通王国历险记&#xff08;1&#xff09; 前言 一&#xff0c;我们要先知道PDU是什么&#xff1f; 二、TCP协议下的三大协议的验证实验 1.FTP的验证实验 1&#xff0c;拓扑图 2.将lsw4配置一下 3&#xff0c;FTP服务器端开启FTP服务&#xff1a; 4&#x…

本地新项目推送至gitlab仓库

1. gitlab上新建一个空白项目 gitlab上点击new project按钮&#xff0c;新建一个项目 新建空白项目 项目名称与本地新建项目名称相同&#xff0c;其余根据具体需要选择 2. 初始化本地仓库并commit项目 进入本地项目根目录下&#xff0c;右击 git bash here打开命令窗口 初始化…

机器学习21:机器学习工程落地注意事项-I

目录 1.静态训练与动态训练 1.1 如何选择训练方式&#xff1f; 2.静态与动态推理 2.1 离线推理的优缺点 2.2 在线推理的优缺点 3.数据依赖性 3.1 可靠性 3.2 版本控制 3.3 必要性 3.4 相关性 3.5 反馈回路 4.参考文献 到目前为止&#xff0c;【机器学习1&#xff5e…

小型中文版聊天机器人

入门小菜鸟&#xff0c;希望像做笔记记录自己学的东西&#xff0c;也希望能帮助到同样入门的人&#xff0c;更希望大佬们帮忙纠错啦~侵权立删。 目录 一、简单介绍与参考鸣谢 二、数据集介绍 三、数据预处理 1、重复标点符号表达 2、英文标点符号变为中文标点符号 3、繁…

中北大学 - 信息对抗大三下学习课程设计(爬取招标网站,进行招标分析,数据保存execl中)

文章目录 1. 题目描述2. 项目细节分析定时爬取任务思路避免多次爬取数据重复问题网站结构根据爬取信息确认招标地区 3. 项目代码4. 运行截图 1. 题目描述 中北大学信息安全技术爬虫课程设计 题目 5&#xff1a;招投标信息分析系统 &#xff08;20050441 2005031113&#xff09…

在 7 月 4 日,PoseiSwap 治理通证 $POSE 上线了 BNB Chain 上的头部

在 7 月 4 日&#xff0c;PoseiSwap 治理通证 $POSE 上线了 BNB Chain 上的头部 DEX PancakeSwap&#xff08;POSE/ZBC 交易对&#xff09;&#xff0c;在 $POSE 开盘交易的 10 分钟内&#xff0c;其最高涨幅达到了 2169.22%&#xff0c;所有的早期投资者基本都从中获得了不菲的…

Kafka入门, 消费者工作流程(十八)

kafka消费方式 pull(拉)模式&#xff1a; consumer采用从broker中主动拉取数据。 Kafka采用这种方式。 push(推)模式&#xff1a; Kafka没有采用这种方式&#xff0c;因为由broker决定消息发送速率&#xff0c;很难适应所有消费者的速率。例如推送速度是50m/s&#xff0c;consu…