37. UE5 RPG创建自定义的Ability Task

news/2024/5/17 15:59:07

在前面的文章中,我们实现了一个火球术的一些基本功能,火球术技能的释放,在技能释放后,播放释放动画,在动画播放到需要释放火球术的位置时,将触发动画通知,在动画通知中触发标签事件,然后再技能中监听事件完成火球术的创建。接下来,我们将继续优化火球术技能,并研究点新的东西。
在上一篇文章中,我们使用了PlayMontageAndWait节点实现蒙太奇的播放,这个节点实现是基于Ability Task(AT)实现的,它的主要特点是可以在技能蓝图中使用异步,我们要通过Ability Task类实现一个存储鼠标点击位置信息的Task,并且可以实现将数据传递到服务器,实现服务器同步播放动画。

创建TargetDataUnderMouse

我们首先实现一个AbilityTask(AT),用于保存触发技能时鼠标拾取的数据。
在这里插入图片描述
将类名称设置为TargetDataUnderMouse
在这里插入图片描述
首先在类里面增加一个静态函数,这个函数用来创建类的实例,也就是我们将节点添加到蓝图,它就会创建一个实例。参数配置我在上一片文章使用播放蒙太奇的节点时也介绍了一下,这里在解释一下。
DisplayName 为在蓝图搜索时,可以直接搜索这个名称,节点上也会显示这个名称。
HidePin 是隐藏一个参数的引脚,设置以后,在蓝图中无法设置它的属性。
DefaultToSelf 将类或者蓝图实例作为默认参数 这两项一起使用,我们就不需要设置OwningAbility的值了,默认设置了蓝图实例。
BlueprintInternalUseOnly 设置了此函数只能在蓝图中使用。

	UFUNCTION(BlueprintCallable, Category="Ability|Tasks", meta=(DisplayName = "TargetDataUnderMouse", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"))static UTargetDataUnderMouse* CreateTargetDataUnderMouse(UGameplayAbility* OwningAbility);

这个静态函数的视线,我们就直接创建一个实例返回

UTargetDataUnderMouse* UTargetDataUnderMouse::CreateTargetDataUnderMouse(UGameplayAbility* OwningAbility)
{UTargetDataUnderMouse* MyObj = NewAbilityTask<UTargetDataUnderMouse>(OwningAbility);return MyObj;
}

这就是一个最简单的AbilityTask(AT)实现,编译后,我们在技能蓝图中搜索名称,效果如下,右上角的时钟图标代表它是一个异步节点,这是一个没有任何功能的AT,只实现创建,没有其它功能。
在这里插入图片描述
接着,在类里面添加一个委托,看看效果,首先增加一个委托宏,返回一个向量

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMouseTargetDataSignature, const FVector&, Data);

接着定义一个变量 ,作为蓝图可调用的委托

	UPROPERTY(BlueprintAssignable)FMouseTargetDataSignature ValidData;

运行打开,会发现右侧多了两个引脚,一个是回调广播后可以执行引脚,另一个则是数据引脚。
在这里插入图片描述
接下来,我们要实现这个委托的广播,并获取到鼠标拾取点位的位置传递给技能实例。为了实现这个功能,我们将覆盖默认的执行函数,这函数将在触发左键引脚时,执行内部的内容。

private:virtual void Activate() override;

在实现这里,你不需要调用它的父调用,因为它在父类里面只做了打印,没有执行其它逻辑,但是你如果需要调式的时候可以调用。
在函数中,我们首先要获取到它的PlayerController,因为在PC上面可以去拾取点击位置的坐标,然后使用PC上面的坐标拾取函数去获取结果,然后直接广播出去坐标。

void UTargetDataUnderMouse::Activate()
{APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();FHitResult CursorHit;PC->GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);ValidData.Broadcast(CursorHit.Location);
}

编译,打开UE,修改技能蓝图,在委托回调里接收到广播以后,绘制调试球体,然后结束技能
在这里插入图片描述
会发现每点击一次,敌人身上生成一个调试模型。
在这里插入图片描述
那么重点来了,如果我们开启两个客户端,一个运行在服务器端,另一个运行在单独客户端
在这里插入图片描述
你会发现,在客户端自己的位置是正确的,但是服务器端显示效果位置不正确,这是因为当前广播的数据没有上传到服务器,所以出现了数据在原点显示的bug,接下来,我们将实现从客户端将数据上传到服务器,解决这个bug。
在这里插入图片描述

使用TargetData传递数据到服务器

首先我们分析一下,为什么出现这个问题,在服务器端的位置为0,在技能被激活后,触发我们制作的Task实例,然后在内部进行鼠标拾取位置,并广播出来。需要同步的数据是在AT激活和返回的拾取坐标,由于是需要客户端提交到服务器端,有网络的延迟,你无法确定哪个数据先被提交成功,比如当前问题就是AT激活时,坐标位置还没有提交到服务器,所以在服务器向后运行时是没有坐标值的。
好在,GAS系统里面想到了这一点,它内置一套TargetData的系统(FGameplayAbilityTargetData)帮助我们实现从客户端提交数据并实现了异步等待数据提交完成再向后执行的逻辑。通过ServerSetReplicatedTargetData()函数将数据上传到服务器,并在服务器生成FAbilityTargetDataSetDelegate委托,并将数据广播出去,在技能里面,通过在AbilityTargetDataMap(Key是技能实例,value是TargetData)获取到实际数据,来运行后续的逻辑。
在服务器端,我们首先绑定TargetSet的委托,这样,如果激活逻辑先上传到服务器,我们可以通过委托的广播来获取数据。如果是数据先到的服务器,在激活时无法获取到委托的广播,我们可以使用CallReplicatedTargetDataDelegateIfSet()函数获取。
双击Shift键,在文件GameplayAbilityTargetTypes.h中,我们可以看到内置TargetData给我们定义了多个格式,每个格式传递的内容也不相同。
在这里插入图片描述
UE的GAS为我们派生三种类型:

  1. FGameplayAbilityTargetData_LocationInfo 这个类用于表示基于位置的目标数据。例如,一个技能可能需要在某个特定的地点释放效果,而不是针对某个特定的角色。FGameplayAbilityTargetData_LocationInfo 包含了这样的位置信息,如世界坐标或其他与位置相关的数据。
  2. FGameplayAbilityTargetData_ActorArray 这个类用于表示基于一组角色的目标数据。如果一个技能需要影响多个角色,那么可以使用这个类。它包含了一个角色数组(通常是 AActor 或其派生类的实例),这样技能就可以对数组中的每个角色应用效果。
  3. FGameplayAbilityTargetData_SingleTargetHit 这个类用于表示单个角色作为目标的数据。当一个技能只影响一个角色时,可以使用这个类。它通常包含了关于这个单一目标的信息,比如该角色的位置、健康状态或其他相关数据。

下面,我就实现客户端的数据提交并实现服务器端的数据接收并处理。
增加一个私有函数,用于内部用于实现数据从客户端提交到服务器端

	//客户端向服务器端提交数据void SendMouseCursorData();

在函数内顶部,我们添加一个预测窗口

	//创建一个预测窗口,该窗口允许客户端在不确定服务器响应的情况下,对游戏状态进行预测性更新。FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent.Get(), true);

接下来,我们需要创建一个提交的TargetData,这里选择讲一个单一的目标上传,所以创建一个FGameplayAbilityTargetData_SingleTargetHit变量,并将我们从鼠标拾取的结果设置给Data

	//获取鼠标拾取结果APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();FHitResult CursorHit;PC->GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);//创建需要上传服务器端的TargetDataFGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();Data->HitResult = CursorHit;

将TargetData上传至服务器端需要它的句柄,我们将创建一个FGameplayAbilityTargetDataHandle 用于存储Data

	//创建TargetData句柄,上传到服务器端需要上传句柄FGameplayAbilityTargetDataHandle DataHandle;DataHandle.Add(Data);

接着调用上传函数ASC的ServerSetReplicatedTargetData

	//将TargetData上传至服务器端AbilitySystemComponent->ServerSetReplicatedTargetData(GetAbilitySpecHandle(),GetActivationPredictionKey(),DataHandle,FGameplayTag(),AbilitySystemComponent->ScopedPredictionKey);

由于上传的是DataHandle,我们将委托宏的值也修改为DataHandle

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMouseTargetDataSignature, const FGameplayAbilityTargetDataHandle&, DataHandle);

判断当前是否可以触发委托函数,这个触发需要在服务器端通过验证后触发,由于网络限制,这时候都没有执行完成

	//判断服务器端是否通过验证if(ShouldBroadcastAbilityTaskDelegates()){ValidData.Broadcast(DataHandle);}

上面,我们实现在客户端数据的发送,接下来,我们将修改Task的Activate()函数,在函数内,我们首先判断当前task是否由本地玩家控制

	//是否由客户端控制const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();

如果是由客户端控制,那么我们将执行数据提交服务器端的逻辑。

	if(bIsLocallyControlled){//如果是客户端控制器控制,实现将数据发射到服务器端SendMouseCursorData();}

如果不是本地控制,那肯定当前是在服务器端运行,我们在服务器端监听数据提交成功。
首先创建两个值,用于实现委托SpecHandle 为当前技能的标示,ActivationPredictionKey 为预测键,用于同步客户端和服务器之间的预测性操作

const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();  
const FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();

接着设置委托,当服务器端接收到目标数据时,这个回调函数会被触发。

AbilitySystemComponent.Get()->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(this, &UTargetDataUnderMouse::OnTargetDataReplicatedCallback);

通过调用CallReplicatedTargetDataDelegatesIfSet函数来检查是否已经为特定的SpecHandle和ActivationPredictionKey调用了委托。如果已经调用过,bCalledDelegate将为true。

		//判断在服务器端,上面的委托是否已经广播过const bool bCalledDelegate = AbilitySystemComponent.Get()->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);

如果当前委托还未广播,我们将调用SetWaitingOnRemotePlayerData()让服务器端正在等待客户端上传目标数据。

		if(!bCalledDelegate){//设置服务器端等待PlayerData数据的上传SetWaitingOnRemotePlayerData();}

接下来就是委托函数的视线,我们先定义一个函数,委托会返回两个值,数据的句柄和激活的标签

	//当数据提交到服务器端后的委托回调void OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag);

在回调函数中,我们首先将数据应用到本地客户端,并将缓存的数据清除掉,如果数据通过了验证,则广播数据。

void UTargetDataUnderMouse::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag)
{//通知客户端 服务器端已经接收并处理了从客户端复制的目标数据(将服务器的TargetData应用到客户端,并清除掉缓存)AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());//判断服务器端是否通过验证if(ShouldBroadcastAbilityTaskDelegates()){ValidData.Broadcast(DataHandle);}
}

还有重要的一项,如果我们要使用TargetData,UE默认是不开启此功能的,我们需要代码开启,在资源管理器StartInitialLoading()函数中开启。

void UMyAssetManager::StartInitialLoading()
{Super::StartInitialLoading();FMyGameplayTags::InitializeNativeGameplayTags();//如果使用TargetData,必须开启此项UAbilitySystemGlobals::Get().InitGlobalData();
}

接着,我们打开技能蓝图,修改蓝图,由于Task返回的内容在代码里面修改掉了,现在返回的是TargetDataHandle了,我们通过Get Hit Result from Target Data 节点来获取设置的数据,从里面找到目标的位置数据绘制调试球体。
在这里插入图片描述
修改一下网络模式,并设置一下接口,以监听服务器运行,主窗口将为服务器端,其它窗口为客户端
在这里插入图片描述
然后在客户端上面点击哥布林,发现服务器可以获取到相关的数据了。
在这里插入图片描述
接下来,我把制作的AT的代码放在下面
TargetDataUnderMouse.h

// 版权归暮志未晚所有。#pragma once#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "TargetDataUnderMouse.generated.h"DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMouseTargetDataSignature, const FGameplayAbilityTargetDataHandle&, DataHandle);/*** */
UCLASS()
class AURA_API UTargetDataUnderMouse : public UAbilityTask
{GENERATED_BODY()public:UFUNCTION(BlueprintCallable, Category="Ability|Tasks", meta=(DisplayName = "TargetDataUnderMouse", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"))static UTargetDataUnderMouse* CreateTargetDataUnderMouse(UGameplayAbility* OwningAbility);UPROPERTY(BlueprintAssignable)FMouseTargetDataSignature ValidData;private:virtual void Activate() override;//客户端向服务器端提交数据void SendMouseCursorData();//当数据提交到服务器端后的委托回调void OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag);
};

TargetDataUnderMouse.cpp

// 版权归暮志未晚所有。#include "AbilitySystem/AbilityTasks/TargetDataUnderMouse.h"#include "AbilitySystemComponent.h"UTargetDataUnderMouse* UTargetDataUnderMouse::CreateTargetDataUnderMouse(UGameplayAbility* OwningAbility)
{UTargetDataUnderMouse* MyObj = NewAbilityTask<UTargetDataUnderMouse>(OwningAbility);return MyObj;
}void UTargetDataUnderMouse::Activate()
{//是否由客户端控制const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();if(bIsLocallyControlled){//如果是客户端控制器控制,实现将数据发射到服务器端SendMouseCursorData();}else{const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();const FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();AbilitySystemComponent.Get()->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(this, &UTargetDataUnderMouse::OnTargetDataReplicatedCallback);//判断在服务器端,上面的委托是否已经广播过const bool bCalledDelegate = AbilitySystemComponent.Get()->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);if(!bCalledDelegate){//设置服务器端等待PlayerData数据的上传SetWaitingOnRemotePlayerData();}}
}void UTargetDataUnderMouse::SendMouseCursorData()
{//创建一个预测窗口,该窗口允许客户端在不确定服务器响应的情况下,对游戏状态进行预测性更新。FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent.Get(), true);//获取鼠标拾取结果APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();FHitResult CursorHit;PC->GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);//创建需要上传服务器端的TargetDataFGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();Data->HitResult = CursorHit;//创建TargetData句柄,上传到服务器端需要上传句柄FGameplayAbilityTargetDataHandle DataHandle;DataHandle.Add(Data);//将TargetData上传至服务器端AbilitySystemComponent->ServerSetReplicatedTargetData(GetAbilitySpecHandle(),GetActivationPredictionKey(),DataHandle,FGameplayTag(),AbilitySystemComponent->ScopedPredictionKey);//判断服务器端是否通过验证if(ShouldBroadcastAbilityTaskDelegates()){ValidData.Broadcast(DataHandle);}
}void UTargetDataUnderMouse::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag)
{//通知客户端 服务器端已经接收并处理了从客户端复制的目标数据(将服务器的TargetData应用到客户端,并清除掉缓存)AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());//判断服务器端是否通过验证if(ShouldBroadcastAbilityTaskDelegates()){ValidData.Broadcast(DataHandle);}
}

http://www.mrgr.cn/p/57588055

相关文章

RT-Thread时钟管理

操作系统需要通过时间来规范其任务,主要介绍时钟节拍和基于时钟节拍的定时器。 时钟节拍 任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。 RT-Thread 中,时钟节拍的长度可以根据 RT_TICK_P…

搭建HBase2.x完全分布式集群(CentOS 9 + Hadoop3.x)

Apache HBase™是一个分布式、可扩展、大数据存储的Hadoop数据库。 当我们需要对大数据进行随机、实时的读/写访问时&#xff0c;可以使用HBase。这个项目的目标是在通用硬件集群上托管非常大的表——数十亿行X数百万列。Apache HBase是一个开源、分布式、版本化的非关系数据库…

虚拟机磁盘剩余空间不足

VMware 弹出提示&#xff1a; 对文件“E:\Virtual Machine\CentOS 7 1810 的克隆 (2)\CentOS 7 1810-cl1.vmdk”的操作失败。 如果该文件位于远程文件系统上&#xff0c;请确保网络连接以及该磁盘所在的服务器正常工作。如果该文件位于可移动介质中&#xff0c;请重新连接该介…

HarmonyOS NEXT应用开发之多层嵌套类对象监听

介绍 本示例介绍使用@Observed装饰器和@ObjectLink装饰器来实现多层嵌套类对象属性变化的监听。 效果图预览使用说明加载完成后显示商品列表,点击刷新按钮可以刷新商品图片和价格。实现思路创建FistGoodsModel类,类对象是用@Observed修饰的类SecondGoodsItemList,SecondGood…

基于信息安全的软测工具链解决方案

伴随着汽车与外界的交互手段不断丰富,车联网相关设备、系统间的数据交互更加频繁,万物互联下的网络攻击也逐渐渗透延伸到车联网的领域。汽车行业面临着重大的信息安全挑战。此外,UNECE WP.29 R155和ISO/SAE 21434标准也对汽车的信息安全提出了规范化的要求,旨在产品全生命周…

Linux 网络编程项目--简易ftp

主要代码 config.h #define LS 0 #define GET 1 #define PWD 2#define IFGO 3#define LCD 4 #define LLS 5 #define CD 6 #define PUT 7#define QUIT 8 #define DOFILE 9struct Msg {int type;char data[1024];char secondBuf[128]; }; 服务器: #i…

GIS融合之路(六)-Cesium的雨雪风雷电效果

终于来到系列第六篇了,也来到大家最喜闻乐见天气效果 系列传送门: 山海鲸可视化:GIS融合之路(一)技术选型CesiumJS/loaders.gl/iTowns? 山海鲸可视化:GIS融合之路(二)CesiumJS和ThreeJS深度缓冲区整合 山海鲸可视化:GIS融合之路(三)CesiumJS和ThreeJS相机同步 山海…

WPF项目使用日志

提问 WPF项目如何使用日志 回答引入nuget log4net加入配置特性[assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located //(used if a resource is not found in the page, // or application resource dictionarie…

编程入门(四)【计算机网络基础(由一根网线连接两个电脑开始)】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言两个电脑如何互连呢&#xff1f;集线器、交换机与路由器总结 前言 当你有…

实验一——椰子视频app原型设计

一、对比分析墨刀、Axure、Mockplus等原型设计工具的各自的适用领域及优缺点。 (1)Axure的优缺点 1.主要优点 Axure作为老牌的原型图工具,功能最齐全,交互最多样,基本任何想要的效果都可以实现,尤其在制作PC端原型图上有优势。 2.主要缺点 Axure缺点同样也相当明显,Axure的…

Docker操作容器打包(commit),压缩(save),挂载(load)

文章目录 前言一、容器打包二、将镜像压缩成tar包三、将tar包挂载为镜像结束 前言 将容器打包成镜像时&#xff0c;你正在将应用程序及其所有依赖项、文件和配置文件捆绑到一个可移植的、独立的单元中。这样做可以确保您的应用程序在不同环境中具有一致的运行方式&#xff0c;…

SQL server跨库链接服务器

SQL server进阶技能篇:SQL的跨库查询与链接服务器 - 知乎 (zhihu.com)各位小伙伴们,关于MSSQL的基本技能篇前面一共写了10篇,也基本上算是告一段落,接下来将开始介绍进阶技能篇。在构思这个进阶技能篇的时候,一直在考虑先写哪个,其实到看到这部分内容能理解的人,基本上对…

cesium 解决带高度的polygon 材质不能透明的问题

问题描述&#xff1a;创建一个带高度的polygon&#xff0c;用一个带透明度的图片做材质&#xff0c;画出来的多边形没有透明效果&#xff0c;图片的透明通道没有用上。 一、创建不带高度polygon 不带高度的polygon&#xff0c;使用带透明度的图片是有效果的&#xff0c;但是不…

8-01. 逻辑调整及补充内容

实现音量修改 修改 AudioManager修改 UIManager实现结束游戏 修改 EventHandler修改 UIManager修改 Player修改 NPCMovement修改 TimeManager修改 AudioManager修改 SaveLoadManager修改 TransitionManager解决游戏刚开始时NPC有移动的问题 修改 NPCMovement增加初始箱子解决箱…

年龄与疾病c++

题目描述 某医院想统计一下某项疾病的获得与否与年龄是否有关&#xff0c;需要对以前的诊断记录进行整理&#xff0c;按照0-18岁、19-35岁、36-60岁、61以上&#xff08;含61&#xff09;四个年龄段统计的患病人数以及占总患病人数的比例。 输入 共2行&#xff0c;第一行为过…

(学习)godot4.2 Android调试

1 勾选远程部署 2 安装 Android 构建模板 3 cmd 运行以下命令获取 debug.keystorekeytool -keyalg RSA -genkeypair -alias androiddebugkey -keypass android -keystore debug.keystore -storepass android -dname "CN=Android Debug,O=Android,C=US" -validity 99…

NLP问答系统:使用 Deepset SQUAD 和 SQuAD v2 度量评估

目录 一、说明 二、Deepset SQUAD是个啥&#xff1f; 三、问答系统&#xff08;QA系统&#xff09;&#xff0c;QA系统在各行业的应用及基本原理 3.1 医疗 3.2 金融 3.3 顾客服务 3.4 教育 3.5 制造业 3.6 法律 3.7 媒体 3.8 政府 四、在不同行业使用QA系统的基本原理 五、关于…

visual studio连接ubuntu不成功原因(SSH问题)及解决办法

原因1&#xff1a; 网络没有互通&#xff08;一般VMware&#xff09; 使用ping来看网络是不是可以互通&#xff0c;例如&#xff1a; //这里的ip是ubuntu的ip&#xff0c;也可以从ubuntu的客户端ping一下当前主机 ping 192.168.1.101原因2&#xff1a; SSH没有密钥&#xf…

机器学习在安全领域的应用:从大数据中识别潜在安全威胁

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…