UE4运用C++和框架开发坦克大战教程笔记(十二)(第37~39集)

UE4运用C++和框架开发坦克大战教程笔记(十二)(第37~39集)

  • 37. 延时事件系统
  • 38. 协程逻辑优化更新
  • 39. 普通按键绑定

37. 延时事件系统

由于梁迪老师是写 Unity 游戏出身的,所以即便 UE4 有自带的 TimeManager 这样的延时系统,老师还是重新写了一个符合 Unity 开发习惯的延时系统。

在 DDTypes 里定义延时任务结构体,以及它要用到的一个委托。

DDTypes.h

#pragma region InvokeDECLARE_DELEGATE(FDDInvokeEvent)struct DDInvokeTask
{// 延迟执行的时间float DelayTime;// 是否循环bool IsRepeat;// 循环时间间隔float RepeatTime;// 是否在循环阶段bool IsRepeatState;// 计时器float TimeCount;// 方法委托FDDInvokeEvent InvokeEvent;// 构造函数DDInvokeTask(float InDelayTime, bool InIsRepeat, float InRepeatTime){DelayTime = InDelayTime;IsRepeat = InIsRepeat;RepeatTime = InRepeatTime;IsRepeatState = false;TimeCount = 0.f;}// 帧更新操作函数bool UpdateOperate(float DeltaSeconds){TimeCount += DeltaSeconds;// 如果不循环的,到时间了执行一次就停止;否则执行一次后开启循环状态if (!IsRepeatState) {if (TimeCount >= DelayTime) {InvokeEvent.ExecuteIfBound();TimeCount = 0.f;if (IsRepeat)IsRepeatState = true;elsereturn true;}} else {if (TimeCount >= RepeatTime) {InvokeEvent.ExecuteIfBound();TimeCount = 0.f;}}return false;}
};#pragma endregion

我们依旧将延时系统放在 DDMessage 这里。

协程系统和延时系统的 3 个方法的逻辑几乎都是一样的,所以可以依葫芦画瓢地将代码复制一份过来然后更改。

DDMessage.h

public:// 开始一个延时方法,返回 true 说明成功;返回 false 说明已经存在同对象名同任务名的任务bool StartInvoke(FName ObjectName, FName InvokeName, DDInvokeTask* InvokeTask);// 停止一个延时bool StopInvoke(FName ObjectName, FName InvokeName);// 停止某对象下的所有延时方法void StopAllInvoke(FName ObjectName);protected:// 延时序列,第一个 FName 是对象名,第二个 FName 是延时任务名TMap<FName, TMap<FName, DDInvokeTask*>> InvokeStack;

DDMessage.cpp

void UDDMessage::MessageTick(float DeltaSeconds)
{// 处理延时系统CompleteTask.Empty();	// 跟协程系统共用这个名字数组,所以要先清空for (TMap<FName, TMap<FName, DDInvokeTask*>>::TIterator It(InvokeStack); It; ++It) {TArray<FName> CompleteNode;	// 保存完成的延时任务名字for (TMap<FName, DDInvokeTask*>::TIterator Ih(It->Value); Ih; ++Ih) {if (Ih->Value->UpdateOperate(DeltaSeconds)) {delete Ih->Value;CompleteNode.Push(Ih->Key);}}for (int i = 0; i < CompleteNode.Num(); ++i)It->Value.Remove(CompleteNode[i]);if (It->Value.Num() == 0)CompleteTask.Push(It->Key);}for (int i = 0; i < CompleteTask.Num(); ++i) InvokeStack.Remove(CompleteTask[i]);
}bool UDDMessage::StartInvoke(FName ObjectName, FName InvokeName, DDInvokeTask* InvokeTask)
{if (!InvokeStack.Contains(ObjectName)) {TMap<FName, DDInvokeTask*> NewTaskStack;InvokeStack.Add(ObjectName, NewTaskStack);}if (!(InvokeStack.Find(ObjectName)->Contains(InvokeName))) {InvokeStack.Find(ObjectName)->Add(InvokeName, InvokeTask);return true;}delete InvokeTask;return false;
}bool UDDMessage::StopInvoke(FName ObjectName, FName InvokeName)
{if (InvokeStack.Contains(ObjectName) && InvokeStack.Find(ObjectName)->Find(InvokeName)) {DDInvokeTask* InvokeTask = *(InvokeStack.Find(ObjectName)->Find(InvokeName));InvokeStack.Find(ObjectName)->Remove(InvokeName);if (InvokeStack.Find(ObjectName)->Num() == 0)InvokeStack.Remove(ObjectName);delete InvokeTask;return true;}return false;
}void UDDMessage::StopAllInvoke(FName ObjectName)
{if (InvokeStack.Contains(ObjectName)) {for (TMap<FName, DDInvokeTask*>::TIterator It(*InvokeStack.Find(ObjectName)); It; ++It)delete It->Value;InvokeStack.Remove(ObjectName);}
}

调用路线依旧是 DDMessage – DDModule – DDOO – 对象,所以补充完整这条调用链。

DDModule.h

public:// 开始一个延时方法,返回 true 说明成功;返回 false 说明已经存在同对象名同任务名的任务bool StartInvoke(FName ObjectName, FName InvokeName, DDInvokeTask* InvokeTask);// 停止一个延时bool StopInvoke(FName ObjectName, FName InvokeName);// 停止某对象下的所有延时方法void StopAllInvoke(FName ObjectName);

DDModule.cpp

bool UDDModule::StartInvoke(FName ObjectName, FName InvokeName, DDInvokeTask* InvokeTask)
{return Message->StartInvoke(ObjectName, InvokeName, InvokeTask);
}bool UDDModule::StopInvoke(FName ObjectName, FName InvokeName)
{return Message->StopInvoke(ObjectName, InvokeName);
}void UDDModule::StopAllInvoke(FName ObjectName)
{Message->StopAllInvoke(ObjectName);
}

在 DDOO 里,需要将延时运行和延时循环运行分为两个方法。其余方法则跟协程系统一样只传递调用即可。

DDOO.h

protected:// 延时运行template<class UserClass>bool InvokeDelay(FName InvokeName, float DelayTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod);// 延时循环运行,与上面这个方法的区别就是多传了一个循环间隔时长的 float 变量template<class UserClass>bool InvokeRepeat(FName InvokeName, float DelayTime, float RepeatTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod);// 关闭延时方法bool StopInvoke(FName InvokeName);// 关闭对象下所有延时方法void StopAllInvoke();
};template<class UserClass>
bool IDDOO::InvokeDelay(FName InvokeName, float DelayTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod)
{DDInvokeTask* InvokeTask = new DDInvokeTask(DelayTime, false, 0.f);InvokeTask->InvokeEvent.BindUObject(UserObj, InMethod);		// 绑定委托return IModule->StartInvoke(GetObjectName(), InvokeName, InvokeTask);
}template<class UserClass>
bool IDDOO::InvokeRepeat(FName InvokeName, float DelayTime, float RepeatTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod)
{DDInvokeTask* InvokeTask = new DDInvokeTask(DelayTime, true, RepeatTime);InvokeTask->InvokeEvent.BindUObject(UserObj, InMethod);return IModule->StartInvoke(GetObjectName(), InvokeName, InvokeTask);
}

DDOO.cpp

bool IDDOO::StopInvoke(FName InvokeName)
{return IModule->StopInvoke(GetObjectName(), InvokeName);
}void IDDOO::StopAllInvoke()
{IModule->StopAllInvoke(GetObjectName());
}

最后在 CoroActor.cpp 里调用循环延时方法进行测试。

CoroActor.cpp

void ACoroActor::DDEnable()
{// 测试完后记得注释掉InvokeRepeat("EchoInfo", 3.f, 2.f, this, &ACoroActor::EchoCoroInfo);// TempStartCoroutine(CoroTestTwo());//DDH::Debug() << "StartCoroutine --> " << StartCoroutine("CoroFunc", CoroFunc()) << DDH::Endl();
}

编译后运行,3 秒输出第一句,随后每 2 秒输出一次。

延时系统

38. 协程逻辑优化更新

之前写的协程系统还有一点 Bug 需要解决,我们先复现一下问题:

CoroActor.h

protected:DDCoroTask* CoroFixed();	// 使用协程的方法void StopCoro();	// 负责调用停止协程的方法

CoroActor.cpp

void ACoroActor::DDEnable()
{// 本节课结束后记得注释掉StartCoroutine("CoroFixed", CoroFixed());
}DDCoroTask* ACoroActor::CoroFixed()
{DDCORO_PARAM(ACoroActor);#include DDCORO_BEGIN()#include DDYIELD_READY()DDYIELD_RETURN_SECOND(5.f);		// 挂起 5 秒DDH::Debug() << "StopCoro" << DDH::Endl();D->StopCoro();		// 在第一次挂起时停止协程#include DDYIELD_READY()DDYIELD_RETURN_SECOND(3.f);		// 挂起 3 秒DDH::Debug() << "StopCoroComplete" << DDH::Endl();#include DDCORO_END()
}void ACoroActor::StopCoro()
{StopCoroutine("CoroFixed");
}

编译后运行,项目应该在输出那一瞬间就崩溃了。崩溃的原因是:DDMessage.cpp 的逻辑里,Work() 方法调用了停止协程方法 StopCoroutine(),将协程任务移出了容器;但是后续调用 IsFinish() 判断时依旧会访问这个协程任务原来在容器里的位置,导致访问到错误地址。

所以我们在协程任务结构体里面再添加一个 bool 值,保存协程任务实例是否被删除。这样在 StopCoroutine() 里就不再去进行 “将协程任务移除出容器” 的操作,而是直接更改这个 bool 值;实际的移除操作由 Tick() 全权负责。

DDTypes.h

struct DDCoroTask
{// 是否销毁(老师拼写错了)bool IsDestroy;DDCoroTask(int32 CoroCount){IsDestroy = false;}}

DDMessage.cpp

void UDDMessage::MessageTick(float DeltaSeconds)
{// ... 省略if (Ih->Value->IsFinish() || Ih->Value->IsDestroy) {	// 添加多一个判断delete Ih->Value;CompleteNode.Push(Ih->Key);}// ... 省略
}bool UDDMessage::StopCoroutine(FName ObjectName, FName CoroName)
{if (CoroStack.Contains(ObjectName) && CoroStack.Find(ObjectName)->Find(CoroName)) {// 修改如下(*(CoroStack.Find(ObjectName)->Find(CoroName)))->IsDestroy = true;return true;}return false;
}void UDDMessage::StopAllCoroutine(FName ObjectName)
{if (CoroStack.Contains(ObjectName)) {for (TMap<FName, DDCoroTask*>::TIterator It(*CoroStack.Find(ObjectName)); It; ++It)// 修改如下It->Value->IsDestroy = true;}
}

编译后运行,打印一条 “StopCoro” 语句后就不会打印下一条,游戏也没有崩溃,说明修改成功了。

依葫芦画瓢地改一下延时系统,因为延时系统是基本照搬协程系统的。

DDTypes.h

struct DDInvokeTask
{// 是否销毁bool IsDestroy;DDInvokeTask(float InDelayTime, bool InIsRepeat, float InRepeatTime){IsDestroy = false;}}

DDMessage.cpp

void UDDMessage::MessageTick(float DeltaSeconds)
{// ... 省略if (Ih->Value->UpdateOperate(DeltaSeconds) || Ih->Value->IsDestroy) {	// 添加多一个判断delete Ih->Value;CompleteNode.Push(Ih->Key);}// ... 省略
}bool UDDMessage::StopInvoke(FName ObjectName, FName InvokeName)
{if (InvokeStack.Contains(ObjectName) && InvokeStack.Find(ObjectName)->Find(InvokeName)) {(*(InvokeStack.Find(ObjectName)->Find(InvokeName)))->IsDestroy = true;return true;}return false;
}void UDDMessage::StopAllInvoke(FName ObjectName)
{if (InvokeStack.Contains(ObjectName)) {for (TMap<FName, DDInvokeTask*>::TIterator It(*InvokeStack.Find(ObjectName)); It; ++It)It->Value->IsDestroy = true;}
}

39. 普通按键绑定

下面这段文字截取自梁迪老师准备的 DataDriven 文档:

UE4的按键绑定需要调用 ACharactor 下的 SetupPlayerInputComponent(),或者 APlayerController 下的 SetupInputComponent() 进行按键事件的绑定,如果要实现 “按下 Esc 键弹出菜单” 的功能,就需要获取 UI 对象的指针与添加头文件来进行绑定,这样的话耦合程度较高。因此 DataDriven 框架提供一套自己的按键绑定系统,可以在任何对象下进行按键事件的绑定,并且提供多按键事件绑定功能。

按键绑定系统的功能包括:绑定 Axis 按键、触摸按键、单个按键和多个按键(同时按下)。

来到 DDMessage,它首先要获得玩家控制器,通过 UDDCommon 就可以获取。这样就可以通过玩家控制器访问绑定按钮的函数。

四个模板方法对应上面列出的 4 种绑定按键的类型。

DDMessage.h

#include "GameFramework/PlayerController.h"	// 引入头文件
#include "DDMessage.generated.h"UCLASS()
class DATADRIVEN_API UDDMessage : public UObject, public IDDMM
{GENERATED_BODY()public:// 绑定 Axis 按键事件template<class UserClass>FInputAxisBinding& BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName);// 绑定触摸事件template<class UserClass>FInputTouchBinding& BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent);// 绑定 Action 按键事件template<class UserClass>FInputActionBinding& BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent);// 绑定单个按键事件template<class UserClass>FInputKeyBinding& BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent);protected:// PlayerController 指针APlayerController* PlayerController;
};template<class UserClass>
FInputAxisBinding& UDDMessage::BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName)
{return PlayerController->InputComponent->BindAxis(AxisName, UserObj, InMethod);
}template<class UserClass>
FInputTouchBinding& UDDMessage::BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent)
{return PlayerController->InputComponent->BindTouch(KeyEvent, UserObj, InMethod);
}template<class UserClass>FInputActionBinding& UDDMessage::BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent)
{return PlayerController->InputComponent->BindAction(ActionName, KeyEvent, UserObj, InMethod);
}template<class UserClass>
FInputKeyBinding& UDDMessage::BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent)
{return PlayerController->InputComponent->BindKey(Key, KeyEvent, UserObj, InMethod);
}

DDMessage.cpp

void UDDMessage::MessageBeginPlay()
{// 从 UDDCommon 获取 ControllerPlayerController = UDDCommon::Get()->GetController();
}

依旧是建立 DDMessage – DDModule – DDOO – 对象 的调用链。

DDModule.h

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DATADRIVEN_API UDDModule : public USceneComponent
{GENERATED_BODY()public:// 绑定 Axis 按键事件template<class UserClass>FInputAxisBinding& BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName);// 绑定触摸事件template<class UserClass>FInputTouchBinding& BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent);// 绑定 Action 按键事件template<class UserClass>FInputActionBinding& BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent);// 绑定单个按键事件template<class UserClass>FInputKeyBinding& BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent);};template<class UserClass>
FInputAxisBinding& UDDModule::BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName)
{return Message->BindAxis(UserObj, InMethod, AxisName);
}template<class UserClass>
FInputTouchBinding& UDDModule::BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent)
{return Message->BindTouch(UserObj, InMethod, KeyEvent);
}template<class UserClass>
FInputActionBinding& UDDModule::BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent)
{return Message->BindAction(UserObj, InMethod, ActionName, KeyEvent);
}template<class UserClass>
FInputKeyBinding& UDDModule::BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent)
{return Message->BindInput(UserObj, InMethod, Key, KeyEvent);
}

DDOO.h

class DATADRIVEN_API IDDOO
{GENERATED_BODY()protected:// 绑定 Axis 按键事件template<class UserClass>FInputAxisBinding& BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName);// 绑定触摸事件template<class UserClass>FInputTouchBinding& BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent);// 绑定 Action 按键事件template<class UserClass>FInputActionBinding& BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent);// 绑定单个按键事件template<class UserClass>FInputKeyBinding& BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent);
};template<class UserClass>
FInputAxisBinding& IDDOO::BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName)
{return IModule->BindAxis(UserObj, InMethod, AxisName);
}template<class UserClass>
FInputTouchBinding& IDDOO::BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent)
{return IModule->BindTouch(UserObj, InMethod, KeyEvent);
}template<class UserClass>
FInputActionBinding& IDDOO::BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent)
{return IModule->BindAction(UserObj, InMethod, ActionName, KeyEvent);
}template<class UserClass>
FInputKeyBinding& IDDOO::BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent)
{return IModule->BindInput(UserObj, InMethod, Key, KeyEvent);
}

最后来简单测试一下绑定单个按键事件的方法。

CoroActor.h

protected:void BKeyEvent();

CoroActor.cpp

void ACoroActor::DDEnable()
{BindInput(this, &ACoroActor::BKeyEvent, EKeys::B, IE_Pressed);
}void ACoroActor::BKeyEvent()
{DDH::Debug() << "BKeyEvent" << DDH::Endl();
}

来到项目的 .Build.cs 文件,需要添加对 Slate 的依赖。

RaceCarFrame.Build.cs

		// 需要添加对 Slate 的依赖,否则会报错PrivateDependencyModuleNames.AddRange(new string[] {"Slate","SlateCore",});PublicDefinitions.Add("HMD_MODULE_INCLUDED=1");

编译后运行,此时按一次 B 键可以让左上角输出一次 “BKeyEvent”。

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

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

相关文章

AIGC开发:调用openai的API接口实现简单机器人

简介 开始进行最简单的使用&#xff1a;通过API调用openai的模型能力 OpenAI的能力如下图&#xff1a; 文本生成模型 OpenAI 的文本生成模型&#xff08;通常称为生成式预训练 Transformer 或大型语言模型&#xff09;经过训练可以理解自然语言、代码和图像。这些模型提供文…

RabbitMQ消息存储JSON格式反序列化

如果发送消息消息体为实体类对象数据&#xff0c;交换机接收消息经由路由键发送给队列。需要实现数据反序列化操作。实现JSON格式的反序列化操作 Rabbitmq的反序列化接口 MessageConverter&#xff0c;它的实现类有 Jackson2JsonMessageConverter的反序列化实现类&#xff0c…

HarmonyOS page生命周期函数讲解

下面 我们又要看一个比较重要的点了 页面生命周期 页面组件有三个生命周期 onPageShow 页面显示时触发 onPageHide 页面隐藏时触发 onBackPress 页面返回时触发 这里 我们准备两个组件 首先是 index.ets 参考代码如下 import router from ohos.router Entry Component struc…

小米电脑管家 - 手机平板电脑家居互联

系列文章目录 前言 联想电脑安装小米电脑管家实现设备互联 如图&#xff0c;将 小米平板 5 Pro 作为联想笔记本 GeekPro 5000 &#xff08;这垃圾电脑&#xff09;的副屏。 可以在小米平板控制笔记本&#xff0c;如图所示 一、官方使用手册 参考&#xff1a;小米电脑管家帮助 …

最新GPT4教程,GPT语音对话使用,Midjourney绘画,ChatFile文档对话总结+DALL-E3文生图教程工具

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画&#xff0c;文档对话总结DALL-E3文生图&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和…

【前端面经】即时设计

目录 前言一面git 常见命令跨窗口通信vue 响应式原理发布订阅模式翻转二叉树Promise.all()扁平化数组面试官建议 二面Event Loop 原理Promise 相关css 描边方式requestAnimationReact 18 新特性JSX 相关react 输出两次函数式编程React 批处理机制http请求头有哪些本地存储性能优…

Java核心技术卷接口的实现与继承多态知识梳理总结

Java核心技术卷接口的实现与继承多态知识梳理总结 接口的概念 在Java程序设计语言中&#xff0c;接口不是类&#xff0c;而是对希望符合这个接口的类的一组需求。 form&#xff1a; Java核心技术卷 I&#xff08;原书第11版&#xff09; 基础知识 by 凯 S.霍斯特曼 在Java中&a…

Spring Boot整合GraphQL

RPC选型入门测试系列文章 GraphQL是一种用于API开发的查询语言和运行时环境。它由Facebook开发并于2015年开源。GraphQL的主要目标是提供一种更高效、灵活和易于使用的方式来获取和操作数据。与传统的RESTful API相比&#xff0c;GraphQL允许客户端精确地指定需要的数据&#…

Unity中Shader裁剪空间推导(在Shader中使用)

文章目录 前言一、在Shader中使用转化矩阵1、在顶点着色器中定义转化矩阵2、用 UNITY_NEAR_CLIP_VALUE 区分平台矩阵3、定义一个枚举用于区分当前是处于什么相机 二、我们在DirectX平台下&#xff0c;看看效果1、正交相机下2、透视相机下3、最终代码 前言 在上一篇文章中&…

高光回眸:阿里云容器服务如何全面助力精彩亚运

作者&#xff1a;刘佳旭 谢乘胜 贤维 引言 2023 年&#xff0c;第 19 届杭州亚运会在杭州成功举办。在亚运之光和科技之光的交相辉映下&#xff0c;这届亚运会成为亚运史上首届“云上亚运”&#xff0c;用云计算创造了历史&#xff0c;赛事核心系统和转播全面上云&#xff0c…

[足式机器人]Part2 Dr. CAN学习笔记-自动控制原理Ch1-6根轨迹Root locus

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-自动控制原理Ch1-6根轨迹Root locus 1. 根的作用2. 手绘技巧3. 分离点/汇合点&根轨迹的几何性质 1. 根的作用 G ( s ) s 3 s 2 2 s 4 G\left( s \right) \frac{s3}{s^22s4} G(s)s22s4s3​…

Qt Quick 用cmake怎么玩子项目

以下内容为本人的著作&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/o-_aGqreuQda-ZmKktvxwA 以往在公司开发众多的项目中&#xff0c;都会出现要求本项目里部分功能模块代码需要具备保密性。如果需要对外输出demo工程&…

如何拍摄好VR全景图片,VR全景图片后期处理有什么技巧

引言&#xff1a; VR全景图片是一种以全景视角呈现场景的图片&#xff0c;通过VR技术可以将用户带入虚拟的环境中&#xff0c;给人一种身临其境的感觉&#xff0c;那么如何才能更好的制作让人满意的全景图片呢&#xff1f; 一&#xff0e;如何拍摄好VR全景图片 1.选择合适的拍…

vue中常见的指令

简单介绍一下常见的vue中用到的指令 v-on 指定当前的事件&#xff0c;语法糖为&#xff0c;如例子所示&#xff0c;指定按钮的事件为addCounter&#xff0c;点击会使变量counter 1 <!DOCTYPE html> <html><head><meta charset"utf-8" />…

Unity JSON编码解码之LitJson 深度剖析

把LitJson的代码库放入到项目中&#xff0c;如图所示:JSON在游戏开发中是一种序列化/反序列化常用的技术&#xff0c;把游戏相关的数据,如地图组成,通过JSON编码&#xff0c;序列化成JSON文本&#xff0c;传输或存储, 要使用的时候再通过JSON技术把文本解析成数据对象&#xff…

2023-12-29 服务器开发-centos-安装php8

摘要: 2023-12-29 服务器开发-centos-安装php8 centos-安装php8 必备条件 Minimal CentOS 8 / RHEL 8User with sudo rightsInternet Connection (1) 更新系统 更新系统 $ sudo dnf update $ sudo dnf upgrade 重启系统 $ sudo reboot (2) 启用 EPEL & Remi 软件库…

【教学类-43-03】20231229 N宫格数独3.0(n=1、2、3、4、6、8、9) (ChatGPT AI对话大师生成 回溯算法)

作品展示&#xff1a; 背景需求&#xff1a; 大4班20号说&#xff1a;我不会做这种&#xff08;九宫格&#xff09;&#xff0c;我做的是小格子的&#xff0c; 他把手工纸翻过来&#xff0c;在反面自己画了矩阵格子。向我展示&#xff1a;“我会做这种&#xff01;” 原来他会…

Android笔记(二十三):Paging3分页加载库结合Compose的实现分层数据源访问

在Android笔记&#xff08;二十二&#xff09;&#xff1a;Paging3分页加载库结合Compose的实现网络单一数据源访问一文中&#xff0c;实现了单一数据源的访问。在实际运行中&#xff0c;往往希望不是单纯地访问网络数据&#xff0c;更希望将访问的网络数据保存到移动终端的SQL…

GitHub Copilot 终极详细介绍

编写代码通常是一项乏味且耗时的任务。现代开发人员一直在寻找新的方法来提高编程的生产力、准确性和效率。 像 GitHub Copilot 这样的自动代码生成工具可以使这成为可能。 GitHub Copilot 到底是什么&#xff1f; GitHub Copilot 于 2021 年 10 月推出&#xff0c;是 GitHub 的…

【无标题】《巴黎图书馆》,又发现一本书

我喜愛看的书(https://img-blog.csdnimg.cn/8cd84d33e6724f09a46831f75abe6464.jpg)在这里插入图片描述