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

这篇具有很好参考价值的文章主要介绍了UE4运用C++和框架开发坦克大战教程笔记(十一)(第34~36集)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

34. 协程宏定义分块

我们前面已经在一个类里面实现了一套可行的协程系统,接下来我们需要通过宏来将它们变得更加方便可用,不必每次都写这么多代码。

将 CoroActor 头文件里的委托声明语句以及两个结构体全复制到 DDTypes 下,改成通用的结构。下面只列出需要更改的代码。

DDTypes.h

#pragma region Coroutine

DECLARE_DELEGATE_RetVal(bool, FCoroCondition)

struct DDCoroNode	// 添加 DD 前缀
{
	
	DDCoroNode() : IsActive(false) {}
	
};

struct DDCoroTask	// 添加 DD 前缀
{

	TArray<DDCoroNode*> CoroStack;

	DDCoroTask(int32 CoroCount)
	{
		for (int i = 0; i <= CoroCount; ++i)
			CoroStack.Push(new DDCoroNode());
	}

	virtual ~DDCoroTask()
	{
	}
};

#pragma endregion

来到 CoroActor 的头文件,去掉原来声明的委托,并添加一个用于测试宏的测试方法三。

CoroActor.h

// 删掉这个委托声明,因为 DDTypes 里已经有了
//DECLARE_DELEGATE_RetVal(bool, FCoroCondition)

UCLASS()
class RACECARFRAME_API ACoroActor : public ADDActor
{
	GENERATED_BODY()

protected:

	DDCoroTask* CoroTestThree();
};

我们需要将先前的协程系统代码按功能来分块,然后再将它们逐个转换为宏定义。

下图截取自梁迪老师准备的 DataDriven 文档:

UE4运用C++和框架开发坦克大战教程笔记(十一)(第34~36集),UE4/5 的学习笔记,ue4,c++,笔记

目前我们先不创建 DDCoroBegin.h 和 DDYieldReady.h,留到下一节课再处理。本节课先理解如何分块以及块各自的职责。

CoroActor.cpp

DDCoroTask* ACoroActor::CoroTestThree()
{
// 协程参数区块
#pragma region DDCORO_PARAM
	struct DGCoroTask : public DDCoroTask
	{
		ACoroActor* D;
		DGCoroTask(ACoroActor* Data, int32 CoroCount) : DDCoroTask(CoroCount) { D = Data; }
#pragma endregion


// 此处用来定义类变量,需要保存状态字变量


#define DDYIELD_COUNT -1	// 保存挂起节点数

// Work 方法开头
#pragma region DDCORO_WORK_START
		virtual void Work(float DeltaTime) override
		{
			goto DDCORO_LABEL_PICKER;
		DDCORO_LABEL_START:
#pragma endregion

// 协程方法逻辑
#pragma region CoroFunCode

// 理论上有 n 个协程节点就需要写 3n + 1 行代码,这里只作为演示,只写两个协程节点的情况
#if DDYIELD_COUNT == -1
#define DDYIELD_COUNT 0
		DDCORO_LABEL_0: 
#elif DDYIELD_COUNT == 0
#define DDYIELD_COUNT 1
		DDCORO_LABEL_1:
#endif
		
			if (CoroStack[DDYIELD_COUNT]->UpdateOperate(&(D->IsCoroPause)))
				goto DDCORO_LABEL_END;

// 每个协程节点之前都要添加这一段
#if DDYIELD_COUNT == -1
#define DDYIELD_COUNT 0
		DDCORO_LABEL_0: 
#elif DDYIELD_COUNT == 0
#define DDYIELD_COUNT 1
		DDCORO_LABEL_1:
#endif
			if (CoroStack[DDYIELD_COUNT]->UpdateOperate(10))
				goto DDCORO_LABEL_END;

#pragma endregion

// Work 方法中间,END 承接前面协程方法逻辑,Picker 承接后面跳转逻辑
#pragma region DDCORO_WORK_MIDDLE
			goto DDCORO_LABEL_END;
		DDCORO_LABEL_PICKER:
#pragma endregion

// 协程条件跳转代码
#pragma region CoroPicker
// 理论上有 n 个节点就需要写 n(n+1)/2 行代码,此处为了演示只写两个协程节点的情况
#if DDYIELD_COUNT == 0
			if (CoroStack[0]->IsActive) goto DDCORO_LABEL_0;
			
#elif DDYIELD_COUNT == 1
			if (CoroStack[0]->IsActive) goto DDCORO_LABEL_0;
			if (CoroStack[1]->IsActive) goto DDCORO_LABEL_1;
#endif	
#pragma endregion

// Work 方法结尾
#pragma region DDCORO_WORK_END
			goto DDCORO_LABEL_START;
	
		DDCORO_LABEL_END:
			;
		}
	};

	return new DGCoroTask(this, DDYIELD_COUNT);
#pragma endregion
}

可见,一个 region 就是我们分的一个块,我们目前将协程系统分成了 6 个块,下节课会对这些块继续进行简化和封装。

35. 协程宏定义封装

我们首先在 DDDefine 里封装这 4 个块:

  1. 参数区域 DDCORO_PARAM
  2. Work方法开头 DDCORO_WORK_START
  3. Work方法中部 DDCORO_WORK_MIDDLE
  4. Work方法结尾 DDCORO_WORK_END

并且后面需要嵌套定义宏,所以我们还会创建三个头文件来存放这些嵌套定义宏。

DDDefine.h

// 参数区域宏定义
#define DDCORO_PARAM(UserClass); \
struct DGCoroTask : public DDCoroTask \
{ \
	UserClass* D; \
	DGCoroTask(UserClass* Data, int32 CoroCount) : DDCoroTask(CoroCount) { D = Data; }

// Work 方法开头
#define DDCORO_WORK_START \
virtual void Work(float DeltaTime) override \
{ \
	goto DDCORO_LABEL_PICKER; \
DDCORO_LABEL_START:

// Work 方法中间
#define DDCORO_WORK_MIDDLE \
	goto DDCORO_LABEL_END; \
DDCORO_LABEL_PICKER:

// Work 方法结尾
#define DDCORO_WORK_END \
		goto DDCORO_LABEL_START; \
	DDCORO_LABEL_END: \
		; \
	} \
}; \
return new DGCoroTask(this, DDYIELD_COUNT);

// 直接将头文件路径定义为宏,这样在用的时候可以直接通过 #include 来引入
#define DDCORO_BEGIN() "DataDriven/Public/DDCommon/DDCoroBegin.h" 

#define DDCORO_END() "DataDriven/Public/DDCommon/DDCoroEnd.h"

#define DDYIELD_READY() "DataDriven/Public/DDCommon/DDYieldReady.h"

直接在项目文件夹的 Plugins/DataDriven/Source/DataDriven/Public/DDCommon 路径下创建三个文本文档,分别取名为 DDCoroBegin.hDDCoroEnd.hDDYieldReady.h

随后在 VS 里对着同路径下的 DDCommon 文件夹右键,选择添加现有项,选中我们刚刚新建的三个头文件。

DDCoroBegin 包括了 DDYIELD_COUNT 的初始化以及 DDCORO_WORK_START (Work 方法开头)

DDCoroBegin.h

#define DDYIELD_COUNT -1
#ifdef DDCORO_WORK_START
	DDCORO_WORK_START
#endif

DDCoroEnd 则包括 DDCORO_WORK_MIDDLE、上节课的 region CoroPicker 里面的跳转逻辑(写到存在 64 个协程节点的情况)以及 DDCORO_WORK_END

跳转逻辑部分太多了,建议是在老师写好的 DDCoroEnd.h 里复制过来,此处写小部分用作示例

DDCoroEnd.h

#ifdef DDCORO_WORK_MIDDLE
DDCORO_WORK_MIDDLE
#endif

// 建议此处开始直接复制老师写好的内容
#ifdef DDYIELD_COUNT
#	if DDYIELD_COUNT == -1
#		error DDYIELD_COUNT Is Less Zero	// 不允许存在没有协程节点的情况
#if DDYIELD_COUNT == 0
			if (CoroStack[0]->IsActive) goto DDCORO_LABEL_0;
#elif DDYIELD_COUNT == 1
			if (CoroStack[0]->IsActive) goto DDCORO_LABEL_0;
			if (CoroStack[1]->IsActive) goto DDCORO_LABEL_1;
#elif DDYIELD_COUNT == 2
			if (CoroStack[0]->IsActive) goto DDCORO_LABEL_0;
			if (CoroStack[1]->IsActive) goto DDCORO_LABEL_1;
			if (CoroStack[2]->IsActive) goto DDCORO_LABEL_2;			
// ... 复制老师写好的内容到大概2153行

// 在这个 error 之前必须要已经引入协程系统的开头,所以此处的作用是确保协程系统的代码按顺序排列
#error No Include DDCORO_BEGIN()
#endif
#ifdef DDCORO_WORK_END
DDCORO_WORK_END
#endif
#undef DDYIELD_COUNT	// 起保险作用,毕竟前面已经到达协程系统的末尾了

DDYieldReady 包括上一节课 region CoroFunCode 内定义标签(写到存在 64 个协程节点的情况)。

这里也最好复制老师已经写好的部分过来,此处就写小部分用于示例

DDYieldReady.h

// 从此处开始复制
#ifdef DDYIELD_COUNT
#if DDYIELD_COUNT == -1
#define DDYIELD_COUNT 0
		DDCORO_LABEL_0: 
#elif DDYIELD_COUNT == 0
#define DDYIELD_COUNT 1
		DDCORO_LABEL_1:
// ...复制到这里是 195 行

#error No Include DDCORO_BEGIN()	// 确保协程系统的代码按顺序排列
#endif

本节课的最后,来到 CoroActor.cpp。先将原来的 CoroTestThree() 方法复制一份放到底下,用宏隐藏起来,后续可用于参考。

然后将之前写的协程系统相关内容用刚刚写好的嵌套宏来替换掉。

CoroActor.cpp

DDCoroTask* ACoroActor::CoroTestThree()
{
	// 协程参数区块
	DDCORO_PARAM(ACoroActor);

// 此处用来定义类变量,需要保存状态字变量

#include DDCORO_BEGIN()

// 协程方法逻辑
#pragma region CoroFunCode

#include DDYIELD_READY()
			if (CoroStack[DDYIELD_COUNT]->UpdateOperate(&(D->IsCoroPause)))
				goto DDCORO_LABEL_END;

#include DDYIELD_READY()
			if (CoroStack[DDYIELD_COUNT]->UpdateOperate(10))
				goto DDCORO_LABEL_END;

#pragma endregion

#include DDCORO_END()
}


#if 0

	// CoroTestThree() 原本的复制代码

#endif

可见,替换完毕后,整个协程系统分为了可读性比较好的 4 个部分。

笔者将老师在本集课程讲的内容按自己的理解进行了排列,实际上结合老师的课程视频一起看会更加易懂一些。

36. 整合协程到框架

挂起条件封装到宏定义

我们已经将协程系统的流程用宏定义封装好了,还剩下最后的挂起条件需要封装。(挂起条件对应第 33 集课程)

DDDefine.h

// 挂起帧
#define DDYIELD_RETURN_TICK(Tick); \
if (CoroStack[DDYIELD_COUNT]->UpdateOperate(Tick)) \
	goto DDCORO_LABEL_END;

// 挂起秒
#define DDYIELD_RETURN_SECOND(Time); \
if (CoroStack[DDYIELD_COUNT]->UpdateOperate(DeltaTime, Time)) \
	goto DDCORO_LABEL_END;

// 挂起到 Bool 指针为 false
#define DDYIELD_RETURN_BOOL(Param); \
if (CoroStack[DDYIELD_COUNT]->UpdateOperate(Param)) \
	goto DDCORO_LABEL_END;

// 挂起到函数指针返回 false
#define DDYIELD_RETURN_FUNC(UserClass, UserFunc); \
if (CoroStack[DDYIELD_COUNT]->UpdateOperate(UserClass, UserFunc)) \
	goto DDCORO_LABEL_END;

// 挂起到 Lambda 表达式返回 false
#define DDYIELD_RETURN_LAMB(Expression); \
if (CoroStack[DDYIELD_COUNT]->UpdateOperate([this](){ return Expression; })) \
	goto DDCORO_LABEL_END;

// 直接终止协程
#define DDYIELD_RETURN_STOP(); \
if (CoroStack[DDYIELD_COUNT]->UpdateOperate()) \
	goto DDCORO_LABEL_END;

然后我们来测试一下用宏封装完的挂起条件是否可以正常使用。

之前测试用的代码统统都要依葫芦画瓢地使用在 DDTypes.h 里写好的通用代码再写一遍。

CoroActor.h

protected:

	// 给原来的方法添加一个 Temp 前缀,原名会在以后整合的时候使用到
	void TempStartCoroutine(CoroTask* InTask);

	void DDStartCoroutine(DDCoroTask* InTask);

protected:

	TArray<DDCoroTask*> DDTaskList;

CoroActor.cpp

void ACoroActor::DDEnable()
{
	
	

	// 同步改名,不过不运行
	//TempStartCoroutine(CoroTestTwo());

	DDStartCoroutine(CoroTestThree());
}

void ACoroActor::DDTick(float DeltaSeconds)
{
	Super::DDTick(DeltaSeconds);

	// 注释原来的逻辑便于参考
	/*
	TArray<CoroTask*> TempTask;

	for (int i = 0; i < TaskList.Num(); ++i) {
		TaskList[i]->Work(DeltaSeconds);
		if (TaskList[i]->IsFinish())
			TempTask.Push(TaskList[i]);
	}
	for (int i = 0; i < TempTask.Num(); ++i) {
		TaskList.Remove(TempTask[i]);
		delete TempTask[i];
	}
	*/

	// 复制一份 DDCoroTask 专属的逻辑
	TArray<DDCoroTask*> TempTask;

	for (int i = 0; i < DDTaskList.Num(); ++i) {
		DDTaskList[i]->Work(DeltaSeconds);
		if (DDTaskList[i]->IsFinish())
			TempTask.Push(DDTaskList[i]);
	}
	for (int i = 0; i < TempTask.Num(); ++i) {
		DDTaskList.Remove(TempTask[i]);
		delete TempTask[i];
	}

	TimeCounter++;
	if (TimeCounter == 500)
		IsCoroPause = false;
}

DDCoroTask* ACoroActor::CoroTestThree()
{
	// 协程参数区
	DDCORO_PARAM(ACoroActor);


	// 协程方法主体开始
#include DDCORO_BEGIN()

	DDH::Debug() << 0 << DDH::Endl();

#include DDYIELD_READY()
	DDYIELD_RETURN_BOOL(&(D->IsCoroPause));		// bool 挂起

	DDH::Debug() << 1 << DDH::Endl();
			
#include DDYIELD_READY()
	DDYIELD_RETURN_TICK(300);		// 帧挂起

	DDH::Debug() << 2 << DDH::Endl();

#include DDYIELD_READY()
	DDYIELD_RETURN_SECOND(5.f);		// 秒挂起

	DDH::Debug() << 3 << DDH::Endl();

	// 协程方法主体结束
#include DDCORO_END()
}

// 同步改名
void ACoroActor::TempStartCoroutine(CoroTask* InTask)
{
	TaskList.Push(InTask);
}

void ACoroActor::DDStartCoroutine(DDCoroTask* InTask)
{
	DDTaskList.Push(InTask);
}

编译运行,左上角输出如下,由于帧挂起的时长取决于各人电脑的帧数,就不列出来了。此时说明协程系统用宏定义封装成功。

UE4运用C++和框架开发坦克大战教程笔记(十一)(第34~36集),UE4/5 的学习笔记,ue4,c++,笔记

整合到框架

依旧是将协程系统放到消息模块。

DDMessage.h

public:

	// 开启一个协程,返回 true 说明开启成功;返回 false 说明已经有同对象名同协程任务名的协程存在
	bool StartCoroutine(FName ObjectName, FName CoroName, DDCoroTask* CoroTask);

	// 停止一个协程,返回 true 说明停止协程成功;返回 false 说明协程早已停止
	bool StopCoroutine(FName ObjectName, FName CoroName);

	// 停止该对象的所有协程(老师把方法名字拼写错了)
	void StopAllCoroutine(FName ObjectName);
	
protected:

	// 协程序列,键 1 保存对象名,值的键 FName 对应的是协程任务的名字
	TMap<FName, TMap<FName, DDCoroTask*>> CoroStack;

DDMessage.cpp

void UDDMessage::MessageTick(float DeltaSeconds)
{
	// 处理协程
	TArray<FName> CompleteTask;		// 保存完成了的协程任务
	for (TMap<FName, TMap<FName, DDCoroTask*>>::TIterator It(CoroStack); It; ++It) {
		TArray<FName> CompleteNode;		// 保存完成了的协程名
		for (TMap<FName, DDCoroTask*>::TIterator Ih(It->Value); Ih; ++Ih) {
			Ih->Value->Work(DeltaSeconds);
			if (Ih->Value->IsFinish()) {
				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)
		CoroStack.Remove(CompleteTask[i]);
}

bool UDDMessage::StartCoroutine(FName ObjectName, FName CoroName, DDCoroTask* CoroTask)
{
	// 如果协程序列没有这个对象名,则添加一个
	if (!CoroStack.Contains(ObjectName)) {
		TMap<FName, DDCoroTask*> NewTaskStack;
		CoroStack.Add(ObjectName, NewTaskStack);
	}
	// 如果协程序列里面对应的对象名没有协程名字,就添加目前的这个协程名字和协程任务进去
	if (!(CoroStack.Find(ObjectName)->Contains(CoroName))) {
		CoroStack.Find(ObjectName)->Add(CoroName, CoroTask);
		return true;
	}
	delete CoroTask;
	return false;
}

bool UDDMessage::StopCoroutine(FName ObjectName, FName CoroName)
{
	if (CoroStack.Contains(ObjectName) && CoroStack.Find(ObjectName)->Find(CoroName)) {
		DDCoroTask* CoroTask = *(CoroStack.Find(ObjectName)->Find(CoroName));
		CoroStack.Find(ObjectName)->Remove(CoroName);
		if (CoroStack.Find(ObjectName)->Num() == 0)
			CoroStack.Remove(ObjectName);
		delete CoroTask;
		return true;
	}
	return false;
}

void UDDMessage::StopAllCoroutine(FName ObjectName)
{
	if (CoroStack.Contains(ObjectName)) {
		for (TMap<FName, DDCoroTask*>::TIterator It(*CoroStack.Find(ObjectName)); It; ++It) 
			delete It->Value;
		CoroStack.Remove(ObjectName);
	}
}

我们希望协程系统跟对象的交互路线是 DDMessage – DDModule – DDOO – 对象,所以在这条路线上要建立一条调用链。

DDModule.h

public:

	// 开启一个协程,返回 true 说明开启成功;返回 false 说明已经有同对象名同协程任务名的协程存在
	bool StartCoroutine(FName ObjectName, FName CoroName, DDCoroTask* CoroTask);

	// 停止一个协程,返回 true 说明停止协程成功;返回 false 说明协程早已停止
	bool StopCoroutine(FName ObjectName, FName CoroName);

	// 停止该对象的所有协程
	void StopAllCoroutine(FName ObjectName);

DDModule.cpp

bool UDDModule::StartCoroutine(FName ObjectName, FName CoroName, DDCoroTask* CoroTask)
{
	return Message->StartCoroutine(ObjectName, CoroName, CoroTask);
}

bool UDDModule::StopCoroutine(FName ObjectName, FName CoroName)
{
	return Message->StopCoroutine(ObjectName, CoroName);
}

void UDDModule::StopAllCoroutine(FName ObjectName)
{
	return Message->StopAllCoroutine(ObjectName);
}

DDOO 的代码稍有不同,因为 DDOO 本身就保存有 ObjectName,所以不需要通过参数传递。

DDOO.h

protected:

	// 开启一个协程,返回 true 说明开启成功;返回 false 说明已经有同对象名同协程任务名的协程存在
	bool StartCoroutine(FName CoroName, DDCoroTask* CoroTask);

	// 停止一个协程,返回 true 说明停止协程成功;返回 false 说明协程早已停止
	bool StopCoroutine(FName CoroName);

	// 停止该对象的所有协程
	void StopAllCoroutine();

DDOO.cpp

bool IDDOO::StartCoroutine(FName CoroName, DDCoroTask* CoroTask)
{
	return IModule->StartCoroutine(GetObjectName(), CoroName, CoroTask);
}

bool IDDOO::StopCoroutine(FName CoroName)
{
	return IModule->StopCoroutine(GetObjectName(), CoroName);
}

void IDDOO::StopAllCoroutine()
{
	return IModule->StopAllCoroutine(GetObjectName());
}

最后我们测试一下整合好的协程系统是否能正常使用。

CoroActor.cpp

void ACoroActor::DDEnable()
{


	// 修改原来的调用语句如下
	DDH::Debug() << "StartCoroutine --> " << StartCoroutine("CoroTestThree", CoroTestThree()) << DDH::Endl();
}

DDCoroTask* ACoroActor::CoroTestThree()
{
	
	DDCORO_PARAM(ACoroActor);

	// 定义
	int32 i;
	int32 j;

#include DDCORO_BEGIN()

	DDH::Debug() << 0 << DDH::Endl();

#include DDYIELD_READY()
	DDYIELD_RETURN_BOOL(&(D->IsCoroPause));	// bool 挂起

	DDH::Debug() << 1 << DDH::Endl();
			
#include DDYIELD_READY()
	DDYIELD_RETURN_LAMB(D->PauseLambda());	// Lambda 返回的 bool 挂起

	DDH::Debug() << 2 << DDH::Endl();

#include DDYIELD_READY()
	DDYIELD_RETURN_FUNC(D, &ACoroActor::PauseFunPtr);	// 委托方法返回的 bool 挂起

	DDH::Debug() << 3 << DDH::Endl();

	// 添加一段 for 循环	
	for (i = 0; i < 10; i++) {
		for (j = 0; j < 5; j++) {
#include DDYIELD_READY()
			DDYIELD_RETURN_TICK(20);	// 帧挂起

			DDH::Debug() << i << j << DDH::Endl();

			if (i * 10 + j > 30) {
#include DDYIELD_READY()
				DDYIELD_RETURN_STOP();	// 停止协程
			}
		}
	}

// 下面这里不会运行,因为上面存在停止协程的语句
#include DDYIELD_READY()
	DDYIELD_RETURN_SECOND(5.f);		// 秒挂起

	DDH::Debug() << 4 << DDH::Endl();

#include DDCORO_END()
}

编译后运行,左上角 Debug 语句输出如下:

UE4运用C++和框架开发坦克大战教程笔记(十一)(第34~36集),UE4/5 的学习笔记,ue4,c++,笔记

最后再简单地示范下如何使用这个协程系统。

CoroActor.h

protected:

	DDCoroTask* CoroFunc();

CoroActor.cpp文章来源地址https://www.toymoban.com/news/detail-768407.html

void ACoroActor::DDEnable()
{


	// 启动协程需要传入名字和指定协程方法
	//DDH::Debug() << "StartCoroutine --> " << StartCoroutine("CoroFunc", CoroFunc()) << DDH::Endl();
}

DDCoroTask* ACoroActor::CoroFunc()
{
	// 协程参数区
	DDCORO_PARAM(ACoroActor);

	// 协程方法变量

	// 协程方法主体开始
#include DDCORO_BEGIN()

	// 协程方法主体逻辑代码
#include DDYIELD_READY()
	DDYIELD_RETURN_SECOND(5.f);		// 挂起 5 秒

	// 协程方法主体结束
#include DDCORO_END()
}

到了这里,关于UE4运用C++和框架开发坦克大战教程笔记(十一)(第34~36集)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【UE4】蓝图转为C++官方教程部分笔记

    官方教学有将蓝图转为C++的教学视频,非常详细。 将蓝图转为C++ – Unreal Engine 为了方便查找知识点,特意在这里记了一部分笔记(图片都来自于本人的工程而非视频) 想要实现C++和蓝图的转换,首先就得知道蓝图和C++的变量对应关系 首先官方文档有的规定代码规范中,明确

    2023年04月19日
    浏览(56)
  • UE4 C++联网RPC教程笔记(二)(第5~7集)

    在前面的课程里,我们都是通过 Actor 的生成来看服务端与客户端是否同步。接下来我们研究下 Actor 的变量复制来实现变量同步。 下面文本截取自梁迪老师的 RPC 联网文档。 变量复制只有在服务端修改才会更新到服务端和所有客户端,在客户端修改只会更新所在客户端,对服

    2024年02月22日
    浏览(50)
  • UE4 C++联网RPC教程笔记(一)(第1~4集)

    本系列笔记将会对梁迪老师的《UE4C++联网RPC框架开发吃鸡》教程进行个人的知识点梳理与总结,此课程也像全反射零耦合框架的课程那样,已经超过报名截止时间了,无法通过正常方法观看。 笔者依旧是采取神奇的方法,通过手机浏览器(不同浏览器的效果有差别,有的会直

    2024年02月19日
    浏览(51)
  • UE4 C++联网RPC教程笔记(三)(第8~9集)完结

    前面我们通过蓝图节点实现了局域网连接的功能,实际上我们还可以给项目打包后生成的 .exe 文件创建一个快捷方式,然后修改这个快捷方式的属性中的目标就可以实现简易的联网功能。 下面内容截取自梁迪老师准备的 RPC 联网文档: 使用 .exe 后缀输入和 open IP 地址联网 注

    2024年02月22日
    浏览(50)
  • Stanford UE4 & UE5 C++ 开发 课程笔记(三)子弹物理碰撞与弹道校正

    Unreal中两个物体碰撞需要两方预设的碰撞通道中对方对应的类型都设置为 Block 。 在场景中设置一个cube,并将其进行适当拉伸: 选中放置好的cube,在 Collision 中将 Collision Presets 设为 Custom ,并将每一项置为 Block : 注意cube的默认类型是 WorldStatic 。 在 Project Setting - Engine - Co

    2024年02月15日
    浏览(60)
  • Java笔记037-坦克大战【3】

    目录 坦克大战【3】 坦克大战0.6 增加功能 思路分析 坦克大战【0.7】 增加功能 思路分析 坦克大战【3】(0.7) 运行结果 增加功能 防止敌人坦克重叠运动 记录玩家的成绩(累计击毁敌方坦克数),暂存盘【IO流】  记录当时的敌人坦克坐标/方向,存盘退出【IO流】 玩游戏时,可以

    2023年04月09日
    浏览(39)
  • Java游戏开发 —— 坦克大战

    坦克大战也是小时一个比较经典的游戏了,我在网上也是参考了韩顺平老师写的坦克大战,并做了一下完善,编写出来作为儿时的回忆吧! 创建主窗口,加载菜单及游戏面板。 在游戏面板中初始化各种参数,并建立各种功能组件。 利用线程固定刷新游戏界面。 处理各种碰撞

    2024年02月06日
    浏览(59)
  • 学习 Python 之 Pygame 开发坦克大战(二)

    坦克大战游戏包含很多个物体,现在要对这些物体进行总结 类名 包含的操作 包含的属性 敌方坦克类 射击,移动,显示 生命,速度,伤害,方向,类型 我方坦克类 射击,移动,显示 生命,速度,伤害,方向,装甲,等级 子弹类 移动,显示 方向,伤害,发射源,速度 墙壁

    2024年02月02日
    浏览(52)
  • 学习 Python 之 Pygame 开发坦克大战(一)

    Pygame是一组Python用于编写视频游戏的模块。Pygame在优秀的SDL库上添加了功能。可以让我们使用python语言创建功能齐全的游戏和多媒体程序,并且Pygame是高度可移植的,几乎可以在所有平台和操作系统上运行。 官方文档 函数名称 作用 返回值 pygame.display.init() 初始化展示模块

    2024年02月02日
    浏览(49)
  • 【UE4】多人联机教程(重点笔记)

    1. 创建房间、搜索房间功能 2. 根据指定IP和端口加入游戏 1. 新建一个第三人称角色模板工程 2. 创建一个空白关卡,这里命名为“InitMap” 3. 新建一个控件蓝图,这里命名为“UMG_ConnectMenu” 在关卡蓝图中显示该控件蓝图 打开“UMG_ConnectMenu”,添加如下控件 首先添加创建房间按

    2024年02月14日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包