怎么把百度地图放到网站上,建设银行湖南省分行官方网站,网站备案用户名,专注苏州网站优化UE4运用C和框架开发坦克大战教程笔记#xff08;十二#xff09;#xff08;第37~39集#xff09; 37. 延时事件系统38. 协程逻辑优化更新39. 普通按键绑定 37. 延时事件系统
由于梁迪老师是写 Unity 游戏出身的#xff0c;所以即便 UE4 有自带的 TimeManager 这样的延时… 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 是延时任务名TMapFName, TMapFName, DDInvokeTask* InvokeStack;DDMessage.cpp
void UDDMessage::MessageTick(float DeltaSeconds)
{// 处理延时系统CompleteTask.Empty(); // 跟协程系统共用这个名字数组所以要先清空for (TMapFName, TMapFName, DDInvokeTask*::TIterator It(InvokeStack); It; It) {TArrayFName CompleteNode; // 保存完成的延时任务名字for (TMapFName, 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)) {TMapFName, 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 (TMapFName, 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:// 延时运行templateclass UserClassbool InvokeDelay(FName InvokeName, float DelayTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegateUserClass::FMethodPtr InMethod);// 延时循环运行与上面这个方法的区别就是多传了一个循环间隔时长的 float 变量templateclass UserClassbool InvokeRepeat(FName InvokeName, float DelayTime, float RepeatTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegateUserClass::FMethodPtr InMethod);// 关闭延时方法bool StopInvoke(FName InvokeName);// 关闭对象下所有延时方法void StopAllInvoke();
};templateclass UserClass
bool IDDOO::InvokeDelay(FName InvokeName, float DelayTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegateUserClass::FMethodPtr InMethod)
{DDInvokeTask* InvokeTask new DDInvokeTask(DelayTime, false, 0.f);InvokeTask-InvokeEvent.BindUObject(UserObj, InMethod); // 绑定委托return IModule-StartInvoke(GetObjectName(), InvokeName, InvokeTask);
}templateclass UserClass
bool IDDOO::InvokeRepeat(FName InvokeName, float DelayTime, float RepeatTime, UserClass* UserObj, typename FDDInvokeEvent::TUObjectMethodDelegateUserClass::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 (TMapFName, 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 (TMapFName, 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.hUCLASS()
class DATADRIVEN_API UDDMessage : public UObject, public IDDMM
{GENERATED_BODY()public:// 绑定 Axis 按键事件templateclass UserClassFInputAxisBinding BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName AxisName);// 绑定触摸事件templateclass UserClassFInputTouchBinding BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const EInputEvent KeyEvent);// 绑定 Action 按键事件templateclass UserClassFInputActionBinding BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent);// 绑定单个按键事件templateclass UserClassFInputKeyBinding BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent);protected:// PlayerController 指针APlayerController* PlayerController;
};templateclass UserClass
FInputAxisBinding UDDMessage::BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName AxisName)
{return PlayerController-InputComponent-BindAxis(AxisName, UserObj, InMethod);
}templateclass UserClass
FInputTouchBinding UDDMessage::BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const EInputEvent KeyEvent)
{return PlayerController-InputComponent-BindTouch(KeyEvent, UserObj, InMethod);
}templateclass UserClassFInputActionBinding UDDMessage::BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent)
{return PlayerController-InputComponent-BindAction(ActionName, KeyEvent, UserObj, InMethod);
}templateclass UserClass
FInputKeyBinding UDDMessage::BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::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 按键事件templateclass UserClassFInputAxisBinding BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName AxisName);// 绑定触摸事件templateclass UserClassFInputTouchBinding BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const EInputEvent KeyEvent);// 绑定 Action 按键事件templateclass UserClassFInputActionBinding BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent);// 绑定单个按键事件templateclass UserClassFInputKeyBinding BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent);};templateclass UserClass
FInputAxisBinding UDDModule::BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName AxisName)
{return Message-BindAxis(UserObj, InMethod, AxisName);
}templateclass UserClass
FInputTouchBinding UDDModule::BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const EInputEvent KeyEvent)
{return Message-BindTouch(UserObj, InMethod, KeyEvent);
}templateclass UserClass
FInputActionBinding UDDModule::BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent)
{return Message-BindAction(UserObj, InMethod, ActionName, KeyEvent);
}templateclass UserClass
FInputKeyBinding UDDModule::BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::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 按键事件templateclass UserClassFInputAxisBinding BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName AxisName);// 绑定触摸事件templateclass UserClassFInputTouchBinding BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const EInputEvent KeyEvent);// 绑定 Action 按键事件templateclass UserClassFInputActionBinding BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent);// 绑定单个按键事件templateclass UserClassFInputKeyBinding BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent);
};templateclass UserClass
FInputAxisBinding IDDOO::BindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName AxisName)
{return IModule-BindAxis(UserObj, InMethod, AxisName);
}templateclass UserClass
FInputTouchBinding IDDOO::BindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const EInputEvent KeyEvent)
{return IModule-BindTouch(UserObj, InMethod, KeyEvent);
}templateclass UserClass
FInputActionBinding IDDOO::BindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent)
{return IModule-BindAction(UserObj, InMethod, ActionName, KeyEvent);
}templateclass UserClass
FInputKeyBinding IDDOO::BindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegateUserClass::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_INCLUDED1);编译后运行此时按一次 B 键可以让左上角输出一次 “BKeyEvent”。