86. UE5 RPG 技能面板实现监听数据

在上一篇文章里,我们创建了技能面板的控制器,接下来,我们将实现通过控制器绑定委托,来更新显示内容。

更新技能面板应用的技能

我们首先更新技能面板上面已经应用的技能,让其和WBP_Overlay上面一样,可以更新显示。
由于我们当前面板使用的是升级技能的UI按钮,所以,我们需要创建一个新的按钮,还能够通过点击按钮取消应用的技能。
在这里插入图片描述
复制一个按钮用户控件
在这里插入图片描述
将应用技能UI里的对应节点替换掉
在这里插入图片描述
在WBP_SpellMenuWidget里面,设置应用控制器,它们共同使用技能面板控制器SpellMenuWidgetController
在这里插入图片描述
然后,设置初始化
在这里插入图片描述
接着,我们在应用技能用户控件里,对每个按钮进行设置控制器
在这里插入图片描述
在按钮里面,我们首先将其转换为技能面板控制器
在这里插入图片描述
然后增加一个标签,用于设置技能的输入标签
在这里插入图片描述
被动技能没有对应的输入标签,我们创建两个被动技能标签用于识别

	FGameplayTag InputTag_Passive_1; //被动技能1FGameplayTag InputTag_Passive_2; //被动技能2

注册

	GameplayTags.InputTag_Passive_1 = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("InputTag.Passive.1"),FString("被动技能1"));GameplayTags.InputTag_Passive_2 = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("InputTag.Passive.2"),FString("被动技能2"));

在应用技能的组件里,设置其对应的标签
在这里插入图片描述
然后,在按钮里绑定技能事件,通过判断数据标签是否一致,来设置显示的笔刷
在这里插入图片描述
在其未设置技能时,我们将图标和背景设置为透明
在这里插入图片描述
在初始化构建是,我们去调用此函数
在这里插入图片描述
由于设置了笔刷半透明,而之前回调更新图标和背景的节点都是更新笔刷的,所以,我们要修改设置,在应用时创建一个新的笔刷
在这里插入图片描述
运行查看技能应用是否正确。
在这里插入图片描述

设置技能状态

接下来,我们将实现技能相关功能,在实现功能之前,我们将创建每个技能的状态Ability Status,它将分为:

  1. Locked已锁定,技能图标显示锁的状态
  2. Eligible 可解锁,当前技能可以通过技能点解锁,但处于未解锁状态,将显示技能图标
  3. Unlocked 已解锁,当前技能已通过技能点解锁,背景将被替换掉
  4. Equipped 已装配,当前技能已经被装配到技能栏,样式和已解锁没有区别

相应的,我们将创建对应的标签,通过标签来识别当前技能所处的状态。

我们还需要对技能的类型Ability Type进行识别:
它可以是主动技能,被动技能,以及空状态(受击技能,死亡技能,没错,它们也可以做成技能)

我们首先创建对应的标签

	FGameplayTag Abilities_HitReact; //受击技能标签FGameplayTag Abilities_Status_Locked; //技能状态 已锁定FGameplayTag Abilities_Status_Eligible; //技能状态 可解锁FGameplayTag Abilities_Status_Unlocked; //技能状态 已解锁FGameplayTag Abilities_Status_Equipped; //技能状态 已装配FGameplayTag Abilities_Type_Offensive; //技能类型 主动技能FGameplayTag Abilities_Type_Passive; //技能类型 被动技能FGameplayTag Abilities_Type_None; //技能类型 空 受击等技能设置

然后在函数中注册到标签管理器

void FRPGGameplayTags::InitializeGAGameplayTags()
{/** Abilities 技能标签*/GameplayTags.Abilities_Attack = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Attack"),FString("攻击技能标签"));GameplayTags.Abilities_Summon = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Summon"),FString("召唤技能标签"));GameplayTags.Abilities_Fire_FireBolt = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Fire.FireBolt"),FString("火球术技能标签"));/** 空类型技能标签*/GameplayTags.Abilities_HitReact = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.HitReact"),FString("受击技能标签"));/** 当前技能状态标签*/GameplayTags.Abilities_Status_Locked = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Status.Locked"),FString("已锁定"));GameplayTags.Abilities_Status_Eligible = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Status.Eligible"),FString("可解锁"));GameplayTags.Abilities_Status_Unlocked = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Status.Unlocked"),FString("已解锁"));GameplayTags.Abilities_Status_Equipped = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Status.Equipped"),FString("已装配"));/** 当前技能类型标签*/GameplayTags.Abilities_Type_Offensive = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Type.Offensive"),FString("主动技能"));GameplayTags.Abilities_Type_Passive = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Type.Passive"),FString("被动技能"));GameplayTags.Abilities_Type_None = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Type.None"),FString("啥也不是"));}

还有一种添加标签的方式,就是使用宏的方式,我们在RPG.h文件里,来定义需要使用的标签

#define Abilities_Status_Locked FGameplayTag::RequestGameplayTag(FName("Abilities.Status.Locked"))
#define Abilities_Status_Equipped FGameplayTag::RequestGameplayTag(FName("Abilities.Status.Equipped"))

然后在使用时,直接使用定义的名称
在这里插入图片描述
接着,我们在ASC里增加一个用于获取技能的状态的函数

static FGameplayTag GetStatusTagFromSpec(const FGameplayAbilitySpec& AbilitySpec); //获取技能的状态标签

遍历动态标签容器,获取标签

FGameplayTag URPGAbilitySystemComponent::GetStatusTagFromSpec(const FGameplayAbilitySpec& AbilitySpec)
{for(FGameplayTag Tag : AbilitySpec.DynamicAbilityTags) //从技能实例的动态标签容器中遍历所有标签{if(Tag.MatchesTag(FGameplayTag::RequestGameplayTag(FName("Abilities.Status")))) //查找标签中是否设置以输入标签开头的标签{return Tag;}}return FGameplayTag();
}

给技能面板主动技能增加状态修改

我们上面增加了状态的对应的标签,接下来,我们将实现对技能的状态修改,并能够显示到技能框上面。
首先我们对技能按钮增加一个参数,可以设置当前按钮需要显示的技能
在这里插入图片描述
接着在WBP_OffensiveSpellTreel里对按钮设置应显示的技能标签
在这里插入图片描述
之前,我们已经对WBP_OffensiveSpellTree设置了控制器,在WBP_OffensiveSpellTree的事件里面,我们对每个按钮设置控制器
在这里插入图片描述
在WBP_SpellGlobe_Button里,我们在设置控制器回调后,将控制器转换为技能面板控制器,方便后续使用
在这里插入图片描述
由于技能数据里没有对应的数据状态标签,我们需要在结构体里增加对技能状态标签的设置
在这里插入图片描述
然后在广播时,我们通过代码获取它的状态标签,并设置到数据上
在这里插入图片描述
接下来,我们在WBP_SpellGlobe_Button里添加数据委托的监听,并调用函数
在这里插入图片描述
在数据处理函数里,我们首先判断技能标签和设置在按钮上的标签是否一致,然后根据状态去修改ui显示的图标和背景
在这里插入图片描述
设置状态笔刷的函数里面是将对应的传入的图片和材质应用
在这里插入图片描述
最后我们测试查看效果
在这里插入图片描述
在代码修改查看另外的效果
在这里插入图片描述

设置根据等级更新技能状态

我们实现了更新技能状态的UI,接下来,我们实现可以根据角色的等级去修改技能的状态

为了实现此功能,我们需要在AbilityInfo里增加两个配置项,一个是当前技能解锁角色等级,另一个是技能类

	//解锁技能所需角色的等级UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)int32 LevelRequirement;//当前技能使用的技能类UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)TSubclassOf<UGameplayAbility> Ability;

为了能够在服务器上获取到技能数据,我们在GameMode类里增加技能相关配置数据的参数
在这里插入图片描述
接着在蓝图函数库里增加一个获取技能数据的函数

	//获取玩家角色技能配置数据UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|CharacterClassDefaults", meta=(DefaultToSelf = "WorldContextObject"))static UAbilityInfo* GetAbilityInfo(const UObject* WorldContextObject);

实现是获取到当前客户端GameMode,并从中获取角色技能的配置项数据

UAbilityInfo* URPGAbilitySystemBlueprintLibrary::GetAbilityInfo(const UObject* WorldContextObject)
{//获取到当前关卡的GameMode实例const ARPGGameMode* GameMode = Cast<ARPGGameMode>(UGameplayStatics::GetGameMode(WorldContextObject));if(GameMode == nullptr) return nullptr;//返回关卡的角色的配置return  GameMode->AbilityInfo;
}

接下来编译,我们可以在技能的数据资产中设置对应的数据
在这里插入图片描述
然后在GameMode蓝图中设置对应的数据
在这里插入图片描述
为了方便测试,我们增加一个额外的技能,这个技能需要两级解锁,我们增加一个新的技能
在这里插入图片描述
这个技能是雷系的技能,雷击
在这里插入图片描述
在技能里,我们先链接调试代码,后续对其进行实现
在这里插入图片描述
技能还没有对应的技能标签,我们在代码中新增一个

FGameplayTag Abilities_Lightning_Electrocute; //雷击技能标签
	GameplayTags.Abilities_Lightning_Electrocute = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Lightning.Electrocute"),FString("电击技能标签"));

设置技能对应的内容
在这里插入图片描述
在技能面板的一个按钮上设置对应的技能标签
在这里插入图片描述
接下来,我们要实现根据角色等级更新,我们要遍历技能数据,并判断角色是否拥有。
所以我们先实现一个通过标签获取角色身上的技能实例

FGameplayAbilitySpec* GetSpecFromAbilityTag(const FGameplayTag& AbilityTag); //通过技能标签获取技能实例

在实现这里,我们需要先获取所有激活的技能实例,根据标签判断是否包含相同的,防止多线程出现问题,我们使用了域锁

FGameplayAbilitySpec* URPGAbilitySystemComponent::GetSpecFromAbilityTag(const FGameplayTag& AbilityTag)
{FScopedAbilityListLock ActiveScopeLoc(*this); //域锁//遍历已经应用的技能for(FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities()){for(FGameplayTag Tag : AbilitySpec.Ability.Get()->AbilityTags){if(Tag.MatchesTag(AbilityTag)){return &AbilitySpec;}}}return nullptr;
}

接着增加根据角色等级更新技能状态

void UpdateAbilityStatuses(int32 Level); //根据角色等级更新技能数据状态

然后在实现这里,我们对技能数据遍历,判断角色是否拥有此技能,如果未拥有,我们将创建一个技能实例应用到角色的ASC上。

void URPGAbilitySystemComponent::UpdateAbilityStatuses(const int32 Level)
{//从GameMode获取到技能配置数据UAbilityInfo* AbilityInfo = URPGAbilitySystemBlueprintLibrary::GetAbilityInfo(GetAvatarActor());for(const FRPGAbilityInfo& Info : AbilityInfo->AbilityInformation){if(!Info.AbilityTag.IsValid()) continue; //如果没有技能标签,取消执行if(Level < Info.LevelRequirement) continue; //如果当期等级未达到所需等级,取消执行//判断ASC中是否已存在当前技能实例if(GetSpecFromAbilityTag(Info.AbilityTag) == nullptr){//如果没有技能实例,将应用一个新的技能实例FGameplayAbilitySpec AbilitySpec = FGameplayAbilitySpec(Info.Ability, 1);AbilitySpec.DynamicAbilityTags.AddTag(FRPGGameplayTags::Get().Abilities_Status_Eligible);GiveAbility(AbilitySpec);MarkAbilitySpecDirty(AbilitySpec); //设置当前技能立即复制到每个客户端}}
}

接下来,我们将在角色升级时,去更新技能的状态,在玩家角色类里的提升等级函数中增加对技能数据状态的调用

void ARPGHero::AddToPlayerLevel_Implementation(int32 InPlayerLevel)
{ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();check(PlayerStateBase); //检测是否有效,无限会暂停游戏PlayerStateBase->AddToLevel(InPlayerLevel);if(URPGAbilitySystemComponent* RPGASC = Cast<URPGAbilitySystemComponent>(GetAbilitySystemComponent())){RPGASC->UpdateAbilityStatuses(PlayerStateBase->GetPlayerLevel());}
}

我们还要在自定义ASC增加一个委托用于广播新状态标签

DECLARE_MULTICAST_DELEGATE_TwoParams(FAbilityStatusChanged, const FGameplayTag& /*技能标签*/, const FGameplayTag& /*技能状态标签*/);

在ASC中增加委托参数

FAbilityStatusChanged AbilityStatusChanged; //技能状态更新委托

ASC是在服务器运行,我们再增加一个客户端执行的函数,用于广播到每个客户端

	UFUNCTION(Client, Reliable)void ClientUpdateAbilityStatus(const FGameplayTag& AbilityTag, const FGameplayTag& StatusTag); //技能状态更新后回调

在函数内对委托进行广播

void URPGAbilitySystemComponent::ClientUpdateAbilityStatus_Implementation(const FGameplayTag& AbilityTag, const FGameplayTag& StatusTag)
{AbilityStatusChanged.Broadcast(AbilityTag, StatusTag);
}

接着在之前更新技能数据状态的函数中,如果需要更新,那么,我们对数据进行广播
在这里插入图片描述
接着在技能面板控制器中,绑定对技能状态修改后的回调,并将数据广播给UI

void USpellMenuWidgetController::BindCallbacksToDependencies()
{//绑定技能状态修改后的委托回调GetRPGASC()->AbilityStatusChanged.AddLambda([this](const FGameplayTag& AbilityTag, const FGameplayTag& StatusTag){if(AbilityInfo){FRPGAbilityInfo Info = AbilityInfo->FindAbilityInfoForTag(AbilityTag); //获取到技能数据Info.StatusTag = StatusTag;AbilityInfoDelegate.Broadcast(Info);}});
}

运行查看效果。
在这里插入图片描述
如果在技能面板打开的情况下升级,那么技能将通过委托去更新状态数据,如果在关闭状态,升级时,那么技能的状态标签将会更新到技能实例的动态标签里,然后在开启技能面板时,通过技能标签去更新图标的状态。

更新所拥有的技能点显示

接下来,我们实现对技能点的显示更新,在技能面板中正确显示玩家角色所拥有的技能点数。
我们在控制器基类里有对玩家状态更新声明的宏
在这里插入图片描述
我们在技能面板控制器里,增加一个参数声明

	UPROPERTY(BlueprintAssignable)FOnPlayerStateChangedSignature SpellPointChanged;

在初始化时,广播技能点
在这里插入图片描述
接下来就是在变动时,监听更新变动,在PlayerState上,我们拥有了技能点数变动的委托
在这里插入图片描述

那么,在绑定更改函数里,我们绑定对变动后的监听即可,并在函数内广播给UI

//绑定技能点变动回调GetRPGPS()->OnSpellPointsChangedDelegate.AddLambda([this](const int32 SpellPoints){SpellPointChanged.Broadcast(SpellPoints); //广播拥有的技能点});

代码实现完毕,接着我们就是修改UI,将显示技能点的UI设置成变量
在这里插入图片描述
然后给控件增加一个函数,更新它的值
在这里插入图片描述
在调用初始化之前,我们监听变动,并调用函数更新值,这样在初始化时,就能准确获取到值进行更新。
在这里插入图片描述
接着运行测试,打开技能面板显示默认一点技能点
在这里插入图片描述
然后杀怪升到三级,奖励给了我们两点技能点
在这里插入图片描述

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

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

相关文章

ELK+filebeat

ELKfilebeat 一、filebeat概述 1、filebeat概念&#xff1a; filebeat日志收集工具和logstash相同 filebeat是一款轻量级的日志收集工具&#xff0c;可以在非JAVA环境下运行。 因此&#xff0c;filebeat常被用在非JAVAf的服务器上用于替代Logstash&#xff0c;收集日志信息。…

产品经理NPDP好考吗?

NPDP是新产品开发专业人员的资格认证&#xff0c;对于希望在产品管理领域取得认可的专业人士来说&#xff0c;NPDP认证是一项重要的资格。 那么&#xff0c;产品经理考取NPDP资格认证究竟难不难呢&#xff1f; 首先&#xff0c;NPDP考试的难易程度取决于考生的背景和准备情况…

谷粒商城实战笔记-105~107-全文检索-ElasticSearch-入门

文章目录 一&#xff0c;105-全文检索-ElasticSearch-入门-_cat二&#xff0c;106-全文检索-ElasticSearch-入门-put&post新增数据三&#xff0c;107-全文检索-ElasticSearch-入门-get查询数据&乐观锁字段1&#xff0c;过时的乐观锁-version2&#xff0c;Elasticsearch…

计算机网络知识点面试总结4

#来自ウルトラマンゼロ&#xff08;赛罗&#xff09; 1 传输层提供的服务 1.1 功能 传输层向它上面的应用层提供通信服务&#xff0c;它属于面向部分的最高层&#xff0c;同时也是用户功能中的最底层。 为运行在不同主机上的进程之间提供了逻辑通信。 传输层的功能&#xff1…

【教程】从零开始用QT简易实现modbus通信

前言&#xff1a;本文旨在让读者了解在qt6中实现modbus通信主要使用哪些函数&#xff0c;需要引用哪些库和头文件&#xff0c;不对modbus协议进行介绍&#xff0c;仅在代码层面简单实现一个modbus通信案例 实现效果&#xff1a;点击读取按钮可以读取从机中的十个寄存器&#x…

【QT】鼠标按键事件 - QMouseEvent QKeyEvent

qt 事件 事件1. 事件概念2. 事件的处理3. 按键事件&#xff08;1&#xff09;单个按键&#xff08;2&#xff09;组合按键 4. 鼠标事件&#xff08;1&#xff09;鼠标单击事件&#xff08;2&#xff09;鼠标释放事件&#xff08;3&#xff09;鼠标双击事件&#xff08;4&#x…

【数据分析】统计学基础及Python具体实现

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…

【Python】已解决:ERROR: Could not install packages due to an OSError: [WinError 5] 拒绝访问。: ‘e:\anaconda\in

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;ERROR: Could not install packages due to an OSError: [WinError 5] 拒绝访问。: ‘e:\anaconda\install_root\scripts\pip.exe’ Consider using the --user o…

C语言详解(结构体)

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#xff0c;在这里撰写成文一…

【MAVEN】如何解决“Error unmarshaling return header; nested exception is: java.io.EOFException“?

目录标题 异常现场分析解决Chat GPT出场一下增大【Build process heap size (Mbytes) 】试试&#x1f64f;增大【Maven->importing->VM options for importer】试试✅Idea的所有配置说明 异常现场 Error unmarshaling return header; nested exception is: java.io.EOFEx…

C++内存管理(区别C语言)深度对比

欢迎来到我的Blog&#xff0c;点击关注哦&#x1f495; 前言 前面已经介绍了类和对象&#xff0c;对C面向对象编程已经有了全面认识&#xff0c;接下来要学习对语言学习比较重要的是对内存的管理。 一、内存的分区 代码区&#xff1a;存放程序的机器指令&#xff0c;通常是可…

IntelliJ IDEA安装教程(超详细)

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a; IDEA的使用 IDEA的简单介绍IDEA的主要优势IDEA的卸载IDEA的安装第一个程序&#xff1a;HelloWorld结束语 IDEA的简单介绍…

JAVA (Springboot) i18n国际化语言配置

JAVA i18n国际化语言配置 一、简介二、功能三、Java配置国际化步骤四、Java国际化配置工具类五、Spring Boot配置六、测试 一、简介 在Java中&#xff0c;国际化&#xff08;Internationalization&#xff0c;通常简称为i18n&#xff09;是一个过程&#xff0c;它允许应用程序…

【C语言项目】实现一个通讯录,一步一步详细讲解,小白也能看

目录 设计思路 代码实现 代码改造1 代码改造2 完整代码 代码仓库 设计思路 1. 通讯录存放的信息 这个通讯录保存的信息包括&#xff1a;名字&#xff0c;年龄&#xff0c;性别&#xff0c;电话&#xff0c;住址。 2. 通讯录的功能 1. 通讯录可以存放100个人的信息。 2…

2024年 Java 面试八股文(20w字)

> &#x1f345;我是小宋&#xff0c; 一个只熬夜但不秃头的Java程序员。 > &#x1f345;关注我&#xff0c;带你**过面试,读源码**。提升简历亮点&#xff08;14个demo&#xff09; > &#x1f345;我的面试集已有12W 浏览量。 > &#x1f30f;号…

[C++] 深度剖析C_C++内存管理机制

文章目录 内存分布内存分布图解 C语言中动态内存管理方式malloc:callocrealloc C内存管理方式内置类型**自定义类型** operator new & operator deleteoperator new & operator delete函数operator newoperator delete **new T[N]** 与**delete[]** **定位new表达式(pl…

【C语言】指针由浅入深全方位详解!!!

目录 指针 野指针 二级指针 指针数组 字符指针 数组指针 数组参数&#xff0c;指针参数 函数指针 函数指针数组 回调函数 练习题 代码仓库 指针 1. 指针定义 1. 指针是内存中一个最小单元的编号&#xff0c;也就是地址。 2. 平时口语中说的指针&#xff…

【C++】如何巧妙运用C++命名空间:初学者必备指南

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01; 本篇将带领大家走进C的旅途&#xff0c;为了更好地学习C这门语言&#xff0c;我们需要了解它的前世今生。在了解完C如何诞生后&#xff0c;将开始我们C之旅第一站"命名空间"。(老早说是C/C博主&…

Java 集合框架:HashMap 的介绍、使用、原理与源码解析

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 020 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

零基础入门转录组数据分析——GO+KEGG富集分析

零基础入门转录组数据分析——GOKEGG富集分析 目录 零基础入门转录组数据分析——GOKEGG富集分析1. 富集分析基础知识2. GO富集分析&#xff08;Rstudio&#xff09;——代码实操3. KEGG富集分析&#xff08;Rstudio&#xff09;——代码实操注&#xff1a;配套资源只要改个路径…