UE4 uses C++ and framework to develop tank battle tutorial notes (12) (Episodes 37~39)

UE4 uses C++ and framework to develop tank battle tutorial notes (12) (Episodes 37~39)

37. Delayed event system

Since Mr. Liang Di has a background in writing Unity games, even though UE4 has its own delay system such as TimeManager, the teacher still re-wrote a delay system that conforms to Unity development habits.

Define the deferred task structure in DDTypes and a delegate it uses.

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

We still put the delay system here in DDMessage.

The logic of the three methods of the coroutine system and the delay system is almost the same, so you can copy the code and then change it.

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);
	}
}

The calling route is still DDMessage – DDModule – DDOO – object, so this calling chain is completed.

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);
}

In DDOO, delayed running and delayed loop running need to be divided into two methods. The rest of the methods just pass the call just like the coroutine system.

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());
}

Finally, call the loop delay method in CoroActor.cpp for testing.

CoroActor.cpp

void ACoroActor::DDEnable()
{
    
    
	
	
	// 测试完后记得注释掉
	InvokeRepeat("EchoInfo", 3.f, 2.f, this, &ACoroActor::EchoCoroInfo);

	// TempStartCoroutine(CoroTestTwo());
	
	//DDH::Debug() << "StartCoroutine --> " << StartCoroutine("CoroFunc", CoroFunc()) << DDH::Endl();
}

After compilation and running, the first sentence will be output in 3 seconds, and then every 2 seconds.

delay system

38. Coroutine logic optimization update

There are still some bugs in the previously written coroutine system that need to be resolved. Let’s reproduce the problem first:

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");
}

After compiling and running, the project should crash the moment it is output. The reason for the crash is: in the logic of DDMessage.cpp, Work()the method calls the stop coroutine method StopCoroutine()and moves the coroutine task out of the container; however, subsequent calls to IsFinish()judge will still access the original location of the coroutine task in the container, resulting in access to Wrong address.

So we add another bool value to the coroutine task structure to save whether the coroutine task instance is deleted. In this way, StopCoroutine()you no longer perform the operation of "removing the coroutine task from the container", but directly change the bool value; the actual removal operation is solely Tick()responsible for it.

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;
	}
}

After compiling and running, after printing a "StopCoro" statement, the next one will not be printed, and the game will not crash, indicating that the modification is successful.

Let’s change the delay system according to the pattern, because the delay system basically copies the coroutine system.

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. Normal key bindings

The following text is excerpted from the DataDriven document prepared by Teacher Liang Di:

The key binding of UE4 needs to call ACharactorthe following SetupPlayerInputComponent(), or the key event binding APlayerControllerunder SetupInputComponent()In this case, the degree of coupling is higher. Therefore, the DataDriven framework provides its own key binding system, which can bind key events under any object and provides multiple key event binding functions.

Functions of the key binding system include: binding Axis keys, touch keys, single keys and multiple keys (pressed simultaneously).

Coming to DDMessage, it first needs to obtain the player controller, which can be obtained through UDDCommon. This allows the button-bound functions to be accessed through the player controller.

The four template methods correspond to the four types of bound keys listed above.

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();
}

The calling chain of DDMessage – DDModule – DDOO – objects is still established.

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);
}

Finally, let’s briefly test the method of binding a single key event.

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();
}

Go to the .Build.cs file of the project and you need to add a dependency on Slate.

RaceCarFrame.Build.cs

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

		PublicDefinitions.Add("HMD_MODULE_INCLUDED=1");

After compiling and running, pressing the B key once will cause "BKeyEvent" to be output in the upper left corner.

Guess you like

Origin blog.csdn.net/q446013924/article/details/134801421