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

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

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

37. 延时事件系统

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

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

DDTypes.h

#pragma region Invoke

DECLARE_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;
				else
					return 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 获取 Controller
	PlayerController = 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”。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于JAVA的考研专业课程管理系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高校教师管理模块2.4 考研专业模块2.5 考研政策模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 考研高校表3.2.2 高校教师表3.2.3 考研专业表3.2.4 考研政策表 四、系统展示五、核…

命令行创建Vue项目

Vue项目创建 1. 打开UI界面 在命令行中&#xff0c;执行如下指令&#xff1a; vue ui 2. 打开项目管理器 3. 创建项目 创建项目的过程&#xff0c;需要联网进行&#xff0c;这可能会耗时比较长的时间&#xff0c;请耐心等待。 windows的命令行&#xff0c;容易卡顿&#xff0c…

WPF 漂亮长方体、正文体简单实现方法 Path实现长方体 正方体方案 WPF快速实现长方体、正方体的方法源代码

这段XAML代码在WPF中实现了一个类似长方体视觉效果的图形 声明式绘制&#xff1a;通过Path、PathGeometry和PathFigure等元素组合&#xff0c;能够以声明方式精确描述长方体每个面的位置和形状&#xff0c;无需编写复杂的绘图逻辑&#xff0c;清晰直观。 层次结构与ZIndex控制…

RabbitMQ之快速入门、上手

前言 学习一样新技术、新框架&#xff0c;最重要的是学习其思想、原理。即原理性思维。 如果是因为工作原因&#xff0c;需要快速上手RabbitMQ&#xff0c;本篇或许适合你。 核心概念 Connection&#xff1a;publisher&#xff0f;consumer 和 broker 之间的 TCP 连接Channel…

Hadoop之Yarn 详细教程

1、yarn 的基本介绍和产生背景 YARN 是 Hadoop2 引入的通用的资源管理和任务调度的平台&#xff0c;可以在 YARN 上运行 MapReduce、Tez、Spark 等多种计算框架&#xff0c;只要计算框架实现了 YARN 所定义的 接口&#xff0c;都可以运行在这套通用的 Hadoop 资源管理和任务调…

nodejs+vue+微信小程序+python+PHP的冷链物流配送系统-计算机毕业设计推荐

对于冷链物流信息调度系统所牵扯的管理及数据保存都是非常多的&#xff0c;例如管理员&#xff1b;首页、用户管理&#xff08;管理员、客户、业务员、配送员&#xff09;客户管理&#xff08;货物信息、客户运输单、车辆信息、调度安排&#xff09;这给管理者的工作带来了巨大…

【机组期末速成】指令系统|机器指令概述|操作数类型与操作类型|寻址方式|指令格式

&#x1f3a5; 个人主页&#xff1a;深鱼~&#x1f525;收录专栏&#xff1a;计算机组成原理&#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 目录 前言&#xff1a; 一、本章考点总览 二、考点分析 1、以下有关指令系统的说法中错误的是&#xff08; &#xff09;。 2…

【电商项目实战】MD5登录加密及JSR303自定义注解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《电商项目实战》。&#x1f3af;&#x1f3af; &am…

mac安装k8s环境

安装kubectl brew install kubectl 确认一下安装的版本 kubectl version --client 如果想在本地运行kubernetes 需要安装minikube brew install minikube 需要注意安装minikube需要本地的docker服务是启动的 启动 默认连接的是google的仓库 minikube start 指定阿…

下载和安装AD14 - Altium Designer 14.3.20.54863

这个版本应该还支持XP 系统[doge]&#xff0c;总之就是想安装一下&#xff0c;没什么特别的意义。 下载 资源来自毛子网站&#xff1a;https://rutracker.net/forum/viewtopic.php?t5140739&#xff0c;带上个网页翻译插件就行。要用磁力链接下载&#xff0c;推荐用qbittorr…

远程网络唤醒家庭主机(openwrt设置)

远程网络唤醒家庭主机&#xff08;openwrt设置&#xff09; 前提&#xff1a; 1.配置好主板bios的网络唤醒功能(网络教程自己百度一下找) 2.电脑开启网络唤醒功能(网络教程自己百度一下找) 3.路由器通过ddns实现域名和动态IP绑定内网穿透方法汇总_不修改光猫进行内网穿透-C…

最新AI系统ChatGPT网站H5系统源码,支持AI绘画,GPT语音对话+ChatFile文档对话总结+DALL-E3文生图

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

代码质量评价及设计原则

1.评价代码质量的标准 1.1 可维护性 可维护性强的代码指的是: 在不去破坏原有的代码设计以及不引入新的BUG的前提下,能够快速的修改或者新增代码. 不易维护的代码指的是: 在添加或者修改一些功能逻辑的时候,存在极大的引入新的BUG的风险,并且需要花费的时间也很长. 代码可…

如何让python在手机上运行,python程序在手机上运行

大家好&#xff0c;给大家分享一下python怎么在手机上运行爱心代码&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 1. 写在前面的话 天天都在PC端运行Python代码的我&#xff0c;今天突然灵光一现&#xff0c;想着是不是能够在移动端运行P…

C++继承与派生——(6)派生类的析构函数

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 站在巨人的肩上&#xff0c;是为了超过…

初识智慧城市

文章目录 智慧家居 智慧社区 智慧交通 智慧医疗 智慧教育 智慧旅游 智慧农业 智慧安防 智慧家居 利用智能语音、智能交互等技术,实现用户对家居系统各设备的远程操控和能控制如开关窗帘(窗户)、操控家用电器和照明系统、打扫卫生等操作。利用计算机视觉等技术,对被照看…

Spring boot:3.X + Security OAuth2 自定义登录页面、登出后跳转到登录页

本文描述了基于 Spring Oauth2 的 code 模式&#xff0c;实现登陆同时授权、自定义登录界面、登出页面的功能。 1.Maven依赖&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactI…

SAP缓存 表缓存( Table Buffering)

本文主要介绍SAP中的表缓存在查询数据&#xff0c;更新数据时的工作情况以及对应概念。 SAP表缓存的工作 查询数据 更新数据 删除数据 表缓存的概念 表缓存技术设置属性 不允许缓冲&#xff1a; 允许缓冲&#xff0c;但已关闭&#xff1a; 缓冲已激活&#xff1a; 已…

笔记1:基于锚框(先验框)的目标检测

一、边缘框&#xff08;bounding box&#xff09; 1.1 定义 边缘框&#xff1a;真实标注的物体位置 2.1 表示方式 1、&#xff08;x1,y1)和(x2,y2) 2、&#xff08;x1,y1)和w,h 二、锚框(anchor box)/先验框&#xff08;prior bounding box&#xff09; 2.1 定义 对边缘…

webpack打包批量替换路径(string-replace-webpack-plugin插件)

string-replace-webpack-plugin 是一个用于在 webpack 打包后的文件中替换字符串的插件。它可以用于将特定字符串替换为其他字符串&#xff0c;例如将敏感信息从源代码中移除或对特定文本进行本地化处理。比如文件的html、css、js中的路径地址想批量更改一下 http://localhost:…