78. UE5 RPG 创建技能数据并初始化技能ui

在上一篇文章里,我们创建了技能的UI,接下来,我们要考虑如何实现对技能UI的填充,肯定不能直接写死,需要有一些方法去实现技能的更新。我们期望能够创建一个技能数据,然后根据数据通过回调的方式实现数据的更新。
为了实现这个功能,我们会先创建一个结构体,用于存储技能的相关数据(Tag,使用的图片等),然后创建一个DataAsset,然后创建回调函数,在注册技能的时候,将技能相关的数据广播出去,在UI里接受,更新UI显示。

创建DataAsset

首先,我们基于DataAsset创建一个新的类,用于设置技能需要的相关配置
在这里插入图片描述
创建命名 AbilityInfo,技能数据
在这里插入图片描述
在类里面,我们首先创建一个结构体,用于设置技能所需哪些配置,如果需要,我们后续还可以继续添加,这里添加了四项数据

USTRUCT(BlueprintType)
struct FRPGAbilityInfo
{GENERATED_BODY()//技能标签UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)FGameplayTag AbilityTag = FGameplayTag();//技能输入映射标签UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)FGameplayTag InputTag = FGameplayTag();//技能图标UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)TObjectPtr<const UTexture2D> Icon = nullptr;//背景材质UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)TObjectPtr<UMaterialInterface> BackgroundMaterial = nullptr;
};

接着,我们在数据类里面增加一个参数,用于在蓝图中使用此类后,可以设置一个技能数据数组,并增加一个通过技能标签获取对应数据的方法

/*** */
UCLASS()
class RPG_API UAbilityInfo : public UDataAsset
{GENERATED_BODY()public:UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="AbilityInformation")TArray<FRPGAbilityInfo> AbilityInformation;//通过技能标签获取到技能相关数据FRPGAbilityInfo FindAbilityInfoForTag(const FGameplayTag& AbilityTag, bool bLogNotFound = false) const;
};

在获取技能数据的函数实现这里,我们直接遍历数组,查找到相同的技能标签返回,并增加一个参数,如果无法查询到,打印一个错误信息,方便后续调试

FRPGAbilityInfo UAbilityInfo::FindAbilityInfoForTag(const FGameplayTag& AbilityTag, const bool bLogNotFound) const
{for(const FRPGAbilityInfo& Info : AbilityInformation){if(Info.AbilityTag == AbilityTag){return Info;}}if(bLogNotFound){//如果获取不到数据,打印消息}return FRPGAbilityInfo();
}

实现日志分类

在打印这里,我们想实现对于技能设置不同的打印通道,和其它默认的区分开来,这样调试起来会更加的方便。为了实现这个功能,我们需要额外的创建一个.h 和 .cpp文件
直接项目文件夹上面,右键选择添加,文件
在这里插入图片描述
在弹出窗口这里写入需要创建的文件名称
在这里插入图片描述
我们将两个文件都创建出来
在这里插入图片描述
在.h文件中,我们设置#pragma once 可以实现一次编译,多次复用。然后引入基础头文件和打印相关的头文件,并通过宏定义了一个我们自定义的打印通道。

#pragma once#include "CoreMinimal.h"
#include "Logging/LogMacros.h"DECLARE_LOG_CATEGORY_EXTERN(LogRPG, Log, All);

然后在cpp文件中,使用DEFINE_LOG_CATEGORY对一个打印通道进行实例化,这个宏与DECLARE_LOG_CATEGORY_EXTERN宏一起使用来实现一个新的打印通道。

#include "RPGLogChannels.h"DEFINE_LOG_CATEGORY(LogRPG);

接着,我们可以在获取技能相关数据的函数中,引入此文件

#include "RPG/RPGLogChannels.h"

并在查询不到对应数据时,在我们自定义的日志分类中打印

	if(bLogNotFound){//如果获取不到数据,打印消息UE_LOG(LogRPG, Error, TEXT("无法通过技能标签[%s]在技能数据[%s]查找到对应的技能数据"), *AbilityTag.ToString(), *GetNameSafe(this));}

应用技能数据

我们将技能数据的DataAsset创建完成,接下来,要实现对其的应用,我们在蓝图中创建了技能数据,需要有一个地方去设置,并可以应用。这些数据是在在UI上使用的,我们将其设置在OverlayWidgetController里面,增加一个对齐配置的配置项。OverlayWidgetController是配置在HUD类上面的,在项目运行时,就会初始化OverlayWidgetController,并应用对OverlayWidget,覆盖屏幕的OverlayWidget就会从OverlayWidgetController中获取数据。

	//技能的表格数据UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Widget Data")TObjectPtr<UAbilityInfo> AbilityInfo;

我们现实现对数据的设置,编译代码,打开UE,创建一个数据资产
在这里插入图片描述
类就选择我们创建的数据资产的类
在这里插入图片描述
命名为DA_AbilityInfo
在这里插入图片描述
接着打开基于OverlayWidgetController创建的蓝图,将创建的数据资产设置上去,这样,我们就可以在后续UI更新中,使用此数据,并能够实现对数据的获取。
在这里插入图片描述

给数据资产添加第一条数据

有了数据资产,我们首先将玩家角色的第一个技能添加进去,就是普通攻击火球术,我们还没有其相关的技能标签
我们首先创建一个火球术的技能标签

FGameplayTag Abilities_Fire_FireBolt; //火球术技能标签

然后在cpp里面对其注册

	GameplayTags.Abilities_Fire_FireBolt = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("Abilities.Fire.FireBolt"),FString("火球术技能标签"));

然后编译,进行设置
在这里插入图片描述
这里考虑到有可能后续玩家会修改触发技能的按键,对于技能的输入tag设置,我们后续将修改为在程序中动态设置它,并且,后续将其在蓝图中设置的功能关闭。

	//技能输入映射标签UPROPERTY(BlueprintReadOnly)FGameplayTag InputTag = FGameplayTag();

重新编译
在这里插入图片描述

广播技能数据

有了技能数据,我们需要实现在ASC应用角色技能时,UI上也能够获取到应用通知,跟随更新数据。
为了实现这点,我们需要在ASC中增加委托,并在应用技能后,进行广播触发回调。
我们在自定义ASC中增加一个委托宏,这个宏用于在技能初始化应用完成后广播回调

DECLARE_MULTICAST_DELEGATE_OneParam(FAbilityGiven, URPGAbilitySystemComponent*) //技能初始化应用后的回调委托

使用宏创建一个委托

FAbilityGiven AbilityGivenDelegate; //技能初始化应用后的回调委托

由于我们无法确定运行起来后,是技能的初始化完成,还是UI的初始化完成,所以,我们通过一个变量来记录,在技能初始化应用完成后,将其设置为true

bool bStartupAbilitiesGiven = false; //初始化应用技能后,此值将被设置为true,用于记录当前是否被初始化完成

接着在初始化应用技能的函数里,将变量设置为true,并将委托广播出去

void URPGAbilitySystemComponent::AddCharacterAbilities(const TArray<TSubclassOf<UGameplayAbility>>& StartupAbilities)
{for(const TSubclassOf<UGameplayAbility> AbilityClass : StartupAbilities){FGameplayAbilitySpec AbilitySpec = FGameplayAbilitySpec(AbilityClass, 1);if(const URPGGameplayAbility* AbilityBase = Cast<URPGGameplayAbility>(AbilitySpec.Ability)){AbilitySpec.DynamicAbilityTags.AddTag(AbilityBase->StartupInputTag); //设置技能激活输入标签GiveAbility(AbilitySpec); //只应用不激活// GiveAbilityAndActivateOnce(AbilitySpec); //应用技能并激活一次}}bStartupAbilitiesGiven = true;AbilityGivenDelegate.Broadcast(this);
}

这样,我们就实现了技能初始化应用的委托,然后,我们在Overlay的Controller的类里面,绑定此委托的回调,完成和ASC的交互,我们在OverlayWidgetController里面创建一个回调函数

void OnInitializeStartupAbilities(URPGAbilitySystemComponent* RPGAbilitySystemComponent) const; //技能初始化应用后的回调

刚好里面有我们之前书写的绑定委托的函数,我们在里面对此委托进行绑定。
这里逻辑是,我们获取到使用的ASC,将其转换为自定义ASC,通过判断变量,如果变量值为true,代表当前技能初始化应用已经完成,我们可以直接调用回调。如果变量为false,初始化还未完成状态,我们就需要去绑定委托,在技能初始化应用完成后,也可以触发委托的回调。
通过这两步,不管谁先谁后,都可以成功触发我们写在OverlayWidgetController里面的回调。并且我们也成功的获取到了ASC,并进行下一步处理。

void UOverlayWidgetController::BindCallbacksToDependencies()
{...if(URPGAbilitySystemComponent* RPGASC = Cast<URPGAbilitySystemComponent>(AbilitySystemComponent)){if(RPGASC->bStartupAbilitiesGiven){//如果执行到此处时,技能的初始化工作已经完成,则直接调用初始化回调OnInitializeStartupAbilities(RPGASC);}else{//如果执行到此处,技能初始化还未完成,将通过绑定委托,监听广播的形式触发初始化完成回调RPGASC->AbilityGivenDelegate.AddUObject(this, &ThisClass::OnInitializeStartupAbilities);}//AddLambda 绑定匿名函数RPGASC->EffectAssetTags.AddLambda([this](const FGameplayTagContainer& AssetTags) //中括号添加this是为了保证内部能够获取类的对象{for(const FGameplayTag& Tag : AssetTags){//对标签进行检测,如果不是信息标签,将无法进行广播FGameplayTag MessageTag = FGameplayTag::RequestGameplayTag(FName("Message"));// "A.1".MatchesTag("A") will return True, "A".MatchesTag("A.1") will return Falseif(Tag.MatchesTag(MessageTag)){const FUIWidgetRow* Row = GetDataTableRowByTag<FUIWidgetRow>(MessageWidgetDataTable, Tag);MessageWidgetRowDelegate.Broadcast(*Row); //前面加*取消指针引用}//将tag广播给Widget Controller 测试代码// const FString Msg = FString::Printf(TEXT("GE Tag in Widget Controller: %s"), *Tag.ToString()); //获取Asset Tag// GEngine->AddOnScreenDebugMessage(-1, 8.f, FColor::Cyan, Msg); //打印到屏幕上 -1 不会被覆盖}});}
}

接下来,我们将实现技能的初始化回调的内容逻辑,它将实现对所有的应用的技能进行类型判断,并选出需要手动触发的技能,然后获取对应的技能UI数据,并通过Controller广播给用户控件的UI。虽然逻辑稍微复杂点,但是这种方式能够将逻辑拆分开来,不会造成代码之间的耦合度过高,造成报错问题。

实现UI技能委托

现在,当角色的技能初始化应用后,会触发UI的Controller里面的初始化回调。在回调里面,我接下来将实现的是,从里面获取到主动技能,然后获取其是否是需要按键激活的技能,然后通过技能Tag去获取数据,将数据广播出去。
接下来,我们实现技能初始化应用后的回调,在控制器里,初始化后,我们进行一次判断,当前技能是否初始化成功

if(!RPGAbilitySystemComponent->bStartupAbilitiesGiven) return; //判断当前技能初始化是否完成,触发回调时都已经完成

接下来,我们要遍历调用技能的实例,对技能进行处理,这里我们创建一个新的单播委托,它只能绑定一个回调函数

DECLARE_DELEGATE_OneParam(FForEachAbility, const FGameplayAbilitySpec&); //单播委托,只能绑定一个回调

在ASC中增加一个新的函数,用于遍历技能,并通过委托回调的形式广播出去,通过这种方式,降低了和OverlayWidgetController之间的耦合,即使你换一个其它的类,也可以调用。参数我们传入单播委托

void ForEachAbility(const FForEachAbility& Delegate); //遍历技能,并将技能广播出去

在函数实现这里,我们首先使用一次域锁,在执行下面的逻辑时,传入的内容内部的数据是无法被更改变动的。然后我们遍历所有可激活的技能,通过委托广播的形式调用。

void URPGAbilitySystemComponent::ForEachAbility(const FForEachAbility& Delegate)
{FScopedAbilityListLock ActiveScopeLock(*this); //使用域锁将此作用域this的内容锁定(无法修改),在遍历结束时解锁,保证线程安全for(const FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities()){if(!Delegate.ExecuteIfBound(AbilitySpec)) //运行绑定在技能实例上的委托,如果失败返回false{UE_LOG(LogRPG, Error, TEXT("在函数[%hs]运行委托失败"), __FUNCTION__);}}
}

有了这个函数,我们在OverlayWidgetController里面就可以实现对技能的遍历,而且还不需要类型转换等操作。上面的函数可以针对每个技能实例触发一次委托回调,所以,我们创建一个委托,并绑定回调函数,就可以实现对所有技能的处理。
具体实现如下,创建委托,绑定回调,然后通过函数调用,即可将技能实例进行遍历。

	//创建单播委托FForEachAbility BroadcastDelegate;//委托绑定回调匿名函数,委托广播时将会触发函数内部逻辑BroadcastDelegate.BindLambda([this](const FGameplayAbilitySpec& AbilitySpec){...});//遍历技能并触发委托回调RPGAbilitySystemComponent->ForEachAbility(BroadcastDelegate);

我们在回调函数中,将针对于每个技能实例进行处理,获取技能的Tag标签,判断它是否属于技能,并获取技能的输入标签设置给技能数据,通过委托,广播出去。我们在技能的用户控件实例里面,就可以通过监听相关委托来实现修改技能图标。
我们在OverlayWidgetController里面新创建一个委托,用于在蓝图中对其进行监听,委托返回一个参数,对应的技能的数据

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAbilityInfoSignature, const FRPGAbilityInfo, Info);

根据委托的类型创建一个参数,用于在蓝图中绑定监听

	UPROPERTY(BlueprintAssignable, Category="GAS|Messages")FAbilityInfoSignature AbilityInfoDelegate;

我们还需要实现两个函数,它们可以通过传入的技能实例,从技能实例里面获取到对应的技能标签和输入标签。这里我们直接创建两个静态函数

	static FGameplayTag GetAbilityTagFromSpec(const FGameplayAbilitySpec& AbilitySpec);static FGameplayTag GetInputTagFromSpec(const FGameplayAbilitySpec& AbilitySpec);

它们的实现是,我们技能标签直接从AbilityTags里面获取,由于它不只可以设置一个标签,我们需要遍历。

FGameplayTag URPGAbilitySystemComponent::GetAbilityTagFromSpec(const FGameplayAbilitySpec& AbilitySpec)
{if(AbilitySpec.Ability){for(FGameplayTag Tag : AbilitySpec.Ability.Get()->AbilityTags) //获取设置的所有的技能标签并遍历{if(Tag.MatchesTag(FGameplayTag::RequestGameplayTag(FName("Abilities")))) //判断当前标签是否包含"Abilities"名称{return Tag;}}}return FGameplayTag();
}FGameplayTag URPGAbilitySystemComponent::GetInputTagFromSpec(const FGameplayAbilitySpec& AbilitySpec)
{for(FGameplayTag Tag : AbilitySpec.DynamicAbilityTags) //从技能实例的动态标签容器中遍历所有标签{if(Tag.MatchesTag(FGameplayTag::RequestGameplayTag(FName("InputTag")))) //查找标签中是否设置以输入标签开头的标签{return Tag;}}return FGameplayTag();
}

MatchesTag的解释是A.1如果MatchesTag的A那么将返回true,相当于判断的它的标签下面的子级,我们的技能都是在"Abilities"下面
在这里插入图片描述
输入标签也是同理
在这里插入图片描述

输入标签的设置是我们在初始的时候设置在技能上的,我们可以通过蓝图设置它的输入
在这里插入图片描述
并在应用技能时,设置在技能实例的动态技能标签中,所以,我们要去技能标签中去获取判断,这种还可以实现如果玩家修改键位了,可以实现不同的键位,前提是你需要把之前默认的删除掉。
在这里插入图片描述
可以获取技能标签和输入标签,还有了蓝图可以绑定的回调,那么,我们就可以去实现回到的函数,首先获取技能标签,然后通过技能标签获取到技能对应的技能数据,并设置技能数据广播出去,完成整个逻辑。

//委托绑定回调匿名函数,委托广播时将会触发函数内部逻辑BroadcastDelegate.BindLambda([this](const FGameplayAbilitySpec& AbilitySpec){//通过静态函数获取到技能实例的技能标签,并通过标签获取到技能数据FRPGAbilityInfo Info = AbilityInfo->FindAbilityInfoForTag(URPGAbilitySystemComponent::GetAbilityTagFromSpec(AbilitySpec));//获取到技能的输入标签Info.InputTag = URPGAbilitySystemComponent::GetInputTagFromSpec(AbilitySpec);//广播技能数据AbilityInfoDelegate.Broadcast(Info); });

以下是初始化技能应用后的完整回调代码

void UOverlayWidgetController::OnInitializeStartupAbilities(URPGAbilitySystemComponent* RPGAbilitySystemComponent) const
{if(!RPGAbilitySystemComponent->bStartupAbilitiesGiven) return; //判断当前技能初始化是否完成,触发回调时都已经完成//创建单播委托FForEachAbility BroadcastDelegate;//委托绑定回调匿名函数,委托广播时将会触发函数内部逻辑BroadcastDelegate.BindLambda([this](const FGameplayAbilitySpec& AbilitySpec){//通过静态函数获取到技能实例的技能标签,并通过标签获取到技能数据FRPGAbilityInfo Info = AbilityInfo->FindAbilityInfoForTag(URPGAbilitySystemComponent::GetAbilityTagFromSpec(AbilitySpec));//获取到技能的输入标签Info.InputTag = URPGAbilitySystemComponent::GetInputTagFromSpec(AbilitySpec);//广播技能数据AbilityInfoDelegate.Broadcast(Info); });//遍历技能并触发委托回调RPGAbilitySystemComponent->ForEachAbility(BroadcastDelegate);
}

实现在技能UI上绑定委托回调

完成上面内容,我们可以编译代码打开UE,在UE里面对我们上一篇文章中制作的技能UI进行修改。
打开我们之前创建的WBP_SpellGlobe用户控件,我们需要添加一个标签,用于记录当前的UI需要显示哪个技能,因为我们输入的键位是固定的,需要在实例上面标识这个技能的出入标签。
在这里插入图片描述
接着我们添加逻辑,在控制器设置回调里面,去将控制器实例转换为目标类型,方便后续使用
在这里插入图片描述
然后绑定监听上面的委托回调,在目标委托广播后,将触发后续逻辑,返回一个技能相关数据,我们可以通过判断当前的输入标签和技能数据的输入标签是否一致,如果一致,使用技能数据的技能图标和背景材质更新当前的技能UI。
在这里插入图片描述
接着打开WBP_HealthManaSpells这个用户控件
在这里插入图片描述
我们首先需要在技能ui上面设置它的输入标签,按照对应的输入,设置对应的输入标签。
在这里插入图片描述
还需要一步,就是设置控件的控制器,这样就可以成功触发控制器设置回调
在这里插入图片描述
最后,我们在技能身上设置好对应的技能输入标签和技能标签
在这里插入图片描述
接着运行,查看是否能够在ui上面显示出来。
在这里插入图片描述
查看修改输入标签后是否也能够切换
在这里插入图片描述

处理多人玩法中的bug

如果我们开启两个玩家运行游戏,会发现第二个玩家的ui没有跟着更新。这个原因是因为技能初始化应用完成调用的广播是在服务器执行的,在客户端无法执行
在这里插入图片描述
我们查看源代码,可以看到在ASC里面,存储着当前激活的技能的容器,被修改后,会调用同步函数OnRep_ActivateAbilities
在这里插入图片描述
这个函数是一个虚函数,我们可以复写它,然后在里面调用,去初始化客户端的技能
在这里插入图片描述
我们在自定义的ASC中覆写它

virtual void OnRep_ActivateAbilities() override;

然后在函数内,判断当前是否已经初始化广播,如果没有,则调用广播

void URPGAbilitySystemComponent::OnRep_ActivateAbilities()
{Super::OnRep_ActivateAbilities();if(!bStartupAbilitiesGiven){bStartupAbilitiesGiven = true;AbilityGivenDelegate.Broadcast(this);}
}

然后发现在客户端也能够顺利初始化技能ui
在这里插入图片描述

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

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

相关文章

一键掌握天气动态 - 基于Vue和高德API的实时天气查询

前言 本文将学习如何使用Vue.js快速搭建天气预报界面,了解如何调用高德地图API获取所需的天气数据,并掌握如何将两者有机结合,实现一个功能丰富、体验出色的天气预报应用 无论您是前端新手还是有一定经验,相信这篇教程都能为您带来收获。让我们一起开始这段精彩的Vue.js 高德…

桌面悬浮备忘录哪个好?能在桌面悬浮使用的备忘app

备忘录是我们日常工作和生活中的常用工具&#xff0c;它帮助我们记录重要信息&#xff0c;提醒我们完成各项任务。而将备忘录悬浮在桌面上使用&#xff0c;无疑能进一步提高我们的工作效率。想象一下&#xff0c;在处理复杂的工作任务时&#xff0c;你能够随时在桌面上查看提醒…

JS获取本机ip地址方法

前端获取本机ip地址&#xff1b;使用第三方免费API <script>function ipJson(ipJson) {console.log(获取到的网络IP,ipJson);//可以把结果存在window上&#xff0c;方便调用window.ipJson ipJson;} </script> <script src"https://whois.pconline.com.cn/…

产品使用手册深度剖析:五步快速敲定产品手册策划思路

引言 在这个信息爆炸的时代&#xff0c;产品使用手册不仅是产品的“说明书”&#xff0c;更是品牌与用户之间建立情感连接的桥梁。一份优秀的手册&#xff0c;能够迅速吸引用户的注意力&#xff0c;引导他们轻松上手&#xff0c;并深入体验产品的魅力。那么&#xff0c;如何撰…

ruoyi项目swagger文档升级knife4j文档

注释admin模块中的swagger依赖加入knife4j依赖 <!-- swagger3--> <!-- <dependency>--> <!-- <groupId>io.springfox</groupId>--> <!-- <artifactId>springfox-boot-starter</artifactId>--…

IDEA常用技巧荟萃:精通开发利器的艺术

1 概述 在现代软件开发的快节奏环境中&#xff0c;掌握一款高效且功能全面的集成开发环境&#xff08;IDE&#xff09;是提升个人和团队生产力的关键。IntelliJ IDEA&#xff0c;作为Java开发者的首选工具之一&#xff0c;不仅提供了丰富的编码辅助功能&#xff0c;还拥有高度…

预算有限?如何挑选经济适用的安全管理系统?

如今&#xff0c;无论是信息安全、生产安全还是人员安全&#xff0c;都直接关系到企业的稳定运营和长远发展。然而&#xff0c;对于许多中小企业而言&#xff0c;高昂的安全管理系统投入往往成为一大难题。那么&#xff0c;在预算有限的情况下&#xff0c;如何挑选一款既经济适…

Github 2024-07-07php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-07-07统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2JavaScript项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数…

如何整合生成的人工智能?(GenAI)为你未来的工作增加动力

生成人工智能(GenAI)它发展迅速&#xff0c;以前所未有的速度取得了突破。人工智能将继续改变各行各业&#xff0c;预计2023年至2030年的年增长率将达到37.3%。由于一种新的知识工作者现在面临被取代的风险&#xff0c;生成式人工智能的惊人崛起进一步加剧了这种紧迫性。据《未…

嘎嘎详细的三维变换详细讲解,包括视图变换、投影变换等,超级通俗易懂!

前置二维空间的各种变换笔记&#xff1a;二维变换 三维空间中的齐次坐标 从二维变换开始引申&#xff0c;可得到三维中的一个点的表达方式为 ( x , y , z , 1 ) ⊤ (\mathbf{x}, \mathbf{y}, \mathbf{z}, 1)^{\top} (x,y,z,1)⊤&#xff0c;也就是w1&#xff0c;而三维的向量…

插入排序算法(C语言版)

直接插入排序 插入排序&#xff08;insert sort&#xff09;是一种简单的排序算法&#xff0c;它的工作原理与手动整理一副牌的过程非常相似。 具体来说&#xff0c;我们在未排序区间选择一个基准元素&#xff0c;将该元素与其左侧已排序区间的元素逐一比较大小&#xff0c;并…

(自用)gtest单元测试

gtest是Google的一套用于编写C测试的框架&#xff0c;可以运行在很多平台上&#xff08;包括Linux、Mac OS X、Windows、Cygwin等等&#xff09;。基于xUnit架构。支持很多好用的特性&#xff0c;包括自动识别测试、丰富的断言、断言自定义、死亡测试、非终止的失败、生成XML报…

Mybatis的优缺点及适用场景?

目录 一、什么是Mybatis&#xff1f; 二、Mybatis框架的特点 三、Mybatis框架的优点&#xff1f; 四、MyBatis 框架的缺点&#xff1f; 五、MyBatis 框架适用场合&#xff1f; 六、代码示例 1. 配置文件 mybatis-config.xml 2. 映射文件 UserMapper.xml 3. Java 代码…

QT TCP网络通信编程

学习目标&#xff1a; TCP网络通信编程 前置环境 运行环境:qt creator 4.12 学习内容 一、TCP 协议基础知识: TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 拥塞控制算法包括慢启动、拥塞避免、快速重传和快速恢复。TCP 通信需要建立连接,Qt 提供 QTcp…

利用Python的sympy包求解一元多次方程

一元1次方程 import sympy as sp # 导入sympy包 x sp.Symbol(x) # 定义符号变量 f 2*x -8 # 定义要求解的一元1次方程 x sp.solve(f) # 调用solve函数求解方程 x[4]一元2次方程 import sympy as sp # 导入sympy包 x sp.Symbol(x) # 定义符号变量 f …

网络安全合规建设

网络安全合规建设 一、法律安全需求基本合规&#xff08;1&#xff09;《网络安全法》重要节点等级保护政策核心变化 二、安全需求 业务刚需&#xff08;1&#xff09;内忧&#xff08;2&#xff09;外患 三、解决方法&#xff08;1&#xff09;总安全战略目标图&#xff08;2&…

广汇汽车:救得起来吗?

五折奔驰、六折宝马...BBA们“腰斩式”大降价后正在引发连锁反应。 国内第二大汽车经销商——广汇汽车&#xff0c;还好吗&#xff1f; 受新能源品牌冲击&#xff0c;近年来奔驰、宝马等豪华燃油品牌销量低迷&#xff0c;纷纷开启降价模式&#xff0c;首当其冲的就是以广汇汽车…

QT TCP多线程网络通信

学习目标&#xff1a; TCP网络通信编程 学习前置环境 运行环境:qt creator 4.12 QT TCP网络通信编程-CSDN博客 Qt 线程 QThread类详解-CSDN博客 学习内容 使用多线程技术实现服务端计数器 核心代码 客户端 客户端&#xff1a;负责连接服务端&#xff0c;每次连接次数1。…

从零开始做题:MP3

题目 给出一个mp3文件 解题 右键->selection->save selection->另存为xxx.png即可 8750d5109208213f E:\逐鹿\MISC\tools\MP3Stego_1_1_19\MP3Stego>.\decode -X cipher.mp3 MP3StegoEncoder 1.1.19 See README file for copyright info Input file cipher.mp3…

53-5 内网代理7 - CS上线不出网主机

靶场搭建: 这里就用之前内网代理的靶场,把web服务器这台虚拟机关闭掉,用剩下的3台加kali 各个虚拟机的网络情况 kali - 可以连接外网win2008(之前的FTP服务器) 可以连接外网 win 7(之前的办公电脑) 不出网主机 - 无法连接外网win2012 克隆机(之前的域控) - 无法连接…