UE4 C++联网RPC教程笔记(三)(第8~9集)完结

news2025/4/7 14:53:15

UE4 C++联网RPC教程笔记(三)(第8~9集)完结

  • 8. exe 后缀实现监听服务器
  • 9. C++ 实现监听服务器

8. exe 后缀实现监听服务器

前面我们通过蓝图节点实现了局域网连接的功能,实际上我们还可以给项目打包后生成的 .exe 文件创建一个快捷方式,然后修改这个快捷方式的属性中的目标就可以实现简易的联网功能。

下面内容截取自梁迪老师准备的 RPC 联网文档:

使用 .exe 后缀输入和 open IP 地址联网

注意:这里只讨论 NM_Standalone、NM_ListenServer 以及 NM_Client 的情况

  1. NM_Standalone:打包出 exe 后,不创建快捷方式直接运行 exe,执行 GetNetMode() 返回 NM_Standalone 类型,在这个状态下打印 GetWorld()->IsServer() 以及 HasAuthority() 都是 true,如果在存在监听服务器的情况下,运行命令行 open 127.0.0.1,该单独端就会链接上监听服务器,执行 GetNetMode() 返回 NM_Client 类型,打印 GetWorld()->IsServer() 以及 HasAuthority() 都是 false,如果再次运行命令行 open 127.0.0.1,该端会先断开服务器,然后再链接一次
  2. NM_ListenServer:打包出来的 exe 生成快捷方式,并且在快捷方式的属性下在 exe 结尾添加 (空格)?listen,如:RPCProject.exe ?listen,运行该快捷方式就会运行监听服务器端,执行 GetNetMode() 返回 NM_ListenServer 类型,打印 GetWorld()->IsServer() 以及 HasAuthority() 都是true,如果运行命令行 open 127.0.0.1,该监听服务器端就会变成单独端 NM_Standalone
  3. NM_Client:打包出来的 exe 生成快捷方式,并且在快捷方式的属性下在 exe 结尾添加 (空格)127.0.0.1 -game,如:RPCProject.exe 127.0.0.1 -game,运行该快捷方式,如果存在监听端,就会链接上监听服务器,成为客户端,执行 GetNetMode() 返回 NM_Client 类型,打印 GetWorld()->IsServer() 以及 HasAuthority() 都是 false

接下来我们在 GameMap 里用到的玩家控制器中添加一些打印当前端的逻辑。

RPCController.h

protected:.

	void EchoNetMode();

RPCController.cpp

// 引入头文件
#include "RPCHelper.h"

void ARPCController::BeginPlay()
{
	Super::BeginPlay();

	// 限定打开的窗口尺寸。此处老师将变量名拼写错成 “Src”
	FString ScreenCommand = FString("r.setres 1280x720w");
	ConsoleCommand(ScreenCommand);

	bShowMouseCursor = false;
	FInputModeGameOnly InputMode;
	SetInputMode(InputMode);

	// 打印当前的端
	EchoNetMode();
}

void ARPCController::EchoNetMode()
{
	ENetMode NetMode = GetNetMode();
	switch (NetMode)
	{
	case NM_Standalone:
		DDH::Debug() << "NM_Standalone" << DDH::Endl();
		break;
	case NM_DedicatedServer:
		DDH::Debug() << "NM_DedicatedServer" << DDH::Endl();
		break;
	case NM_ListenServer:
		DDH::Debug() << "NM_ListenServer" << DDH::Endl();
		break;
	case NM_Client:
		DDH::Debug() << "NM_Client" << DDH::Endl();
		break;
	case NM_MAX:
		DDH::Debug() << "NM_MAX" << DDH::Endl();
		break;
	}
}

编译后,将默认关卡设置为 GameMap。随后开始打包。

在这里插入图片描述
打包成功后,运行 .exe 文件,可以看到左上角打印了当前的端名以及控制器名。

在这里插入图片描述
创建 .exe 文件的一个快捷方式,命名为 RPCCourseServer (保留 .exe 后缀),随后修改该文件的属性。

在这里插入图片描述
运行 RPCCourseServer.exe,可以看到左上角已经变成了聆听服务器。

在这里插入图片描述
再创建一个快捷方式,命名为 RPCCourseClient,这次给属性里的目标添加后缀 (空格)127.0.0.1 -game。先运行 RPCCourseServer,然后再运行 RPCCourseClient.exe,可以看到后者的窗口里左上角显示是客户端。

在这里插入图片描述
此时在服务端按下 J 键,只有在客户端能看到角色处生成了红色的数字 1。说明联网方法和变量都是可以用的。

关掉客户端,服务端左上角会显示客户端的控制器登出了。

在这里插入图片描述
保持服务端开启,运行 RPCCourse.exe,按 ~ 键(波浪符)呼出控制台,输入 open 127.0.0.1。可以看到独立端加入了服务端,并且原独立端左上角输出了当前为客户端。

在这里插入图片描述
此时在服务端按下 J 键,也是只有原独立端可以看到自己角色位置生成了一个红色的数字 1。

在服务端呼出控制台然后输入 open 127.0.0.1,服务器会关闭。

在这里插入图片描述

9. C++ 实现监听服务器

前面我们用蓝图和快捷方式实现聆听服务器联机,接下来我们尝试下用 C++ 来实现同样的效果。下面内容截取自梁迪老师准备的 RPC 联网文档:

创建寻找加入会话 C++ 模式(这里只实现局域网)

C++ 联网步骤和蓝图基本相同,但是中间多了一个 StartSession() 方法需要调用,主要可以参考

  1. UCreateSessionCallbackProxy
  2. UStartSessionCallbackProxy
  3. UFindSessionsCallbackProxy
  4. UJoinSessionCallbackProxy
  5. UDestroySessionCallbackProxy

这些类的实现,具体实现参考项目里的 URPCInstance 类。

UE4 官方推荐将联网的逻辑放在 GameInstance 下处理,GameInstance 在整个游戏所有关卡中都存在,用来传递关卡数据,在保存数据方面起作用,联网数据放在 GameInstance 下方便在任何关卡去操作联网

接下来开始实操。在默认路径下创建一个 C++ 的 Instance 类,命名为 RPCInstance

将默认地图设置成 MenuMap。

来到主界面 UI 类,声明一个 URPCInstance 的指针和相应的注册方法,用来保存对 GameInstance 的引用,并且声明两个蓝图可调用的方法用于接入主界面的按钮点击事件。

MenuWidget.h

// 提前声明
class URPCInstance;

UCLASS()
class RPCCOURSE_API UMenuWidget : public UUserWidget
{
	GENERATED_BODY()
	
public:

	void AssignRPCInstance(URPCInstance* InInstance);

	UFUNCTION(BlueprintCallable)
	void LANServerEvent();

	UFUNCTION(BlueprintCallable)
	void LANClientEvent();
	
public:

	URPCInstance* RPCInstance;
};

MenuWidget.cpp

// 引入头文件
#include "RPCInstance.h"

void UMenuWidget::AssignRPCInstance(URPCInstance* InInstance)
{
	RPCInstance = InInstance;
}

void UMenuWidget::LANServerEvent()
{
	RPCInstance->HostSession();
}

void UMenuWidget::LANClientEvent()
{
	RPCInstance->ClientSession();
}

RPCInstance 里承载着联网相关的逻辑,主要都是调用网络模块的 API 和利用委托绑定回调函数。

RPCInstance.h

// 引入头文件
#include "Interfaces/OnlineSessionInterface.h"	// 如果这个不行就用下面这句
//#include "../Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h"	
#include "Delegates/IDelegateInstance.h"

#include "RPCInstance.generated.h"

// 提前声明
class IOnlineSubsystem;
class APlayerController;

UCLASS()
class RPCCOURSE_API URPCInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:

	URPCInstance();

	// 注册玩家控制器,并且获取联网系统和端 ID
	void AssignPlayerController(APlayerController* InController);

	// 创建会话
	void HostSession();

	// 寻找会话
	void ClientSession();

	// 销毁会话
	void DestroySession();

protected:

	// 开启服务器回调函数
	void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
	void OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful);

	// 加入服务器回调函数
	void OnFindSessionsComplete(bool bWasSuccessful);
	void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);

	// 销毁会话回调函数
	void OnDestroySessionComplete(FName SessionName, bool bWAsSuccessful);

protected:

	APlayerController* PlayerController;

	// 开启服务器委托与句柄
	FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
	FOnStartSessionCompleteDelegate OnStartSessionCompleteDelegate;

	FDelegateHandle OnCreateSessionCompleteDelegateHandle;
	FDelegateHandle OnStartSessionCompleteDelegateHandle;

	// 加入服务器委托与句柄
	FOnFindSessionsCompleteDelegate OnFindSessionsCompleteDelegate;
	FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate;

	FDelegateHandle OnFindSessionsCompleteDelegateHandle;
	FDelegateHandle OnJoinSessionCompleteDelegateHandle;

	// 销毁会话委托与句柄
	FOnDestroySessionCompleteDelegate OnDestroySessionCompleteDelegate;
	FDelegateHandle OnDestroySessionCompleteDelegateHandle;

	// 联网系统
	IOnlineSubsystem* OnlineSub;

	// 端的 ID
	TSharedPtr<const FUniqueNetId> UserID;

	// 保存寻找到的 Sessions
	TSharedPtr<FOnlineSessionSearch> SearchObject;
};

RPCInstance.cpp

// 引入头文件
#include "../Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystem.h"
#include "../Plugins/Online/OnlineSubsystem/Source/Public/OnlineSessionSettings.h"
#include "../Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h"
#include "../Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h"
#include "GameFramework/PlayerController.h"
#include "RPCHelper.h"
#include "Kismet/GameplayStatics.h"

URPCInstance::URPCInstance()
{
	// 绑定回调函数
	OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnCreateSessionComplete);
	OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnStartOnlineGameComplete);
	OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &URPCInstance::OnFindSessionsComplete);
	OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnJoinSessionComplete);
	OnDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnDestroySessionComplete);
}

void URPCInstance::AssignPlayerController(APlayerController* InController)
{
	PlayerController = InController;
	
	// 获取 OnlineSub
	// 获取方式一:Online::GetSubsystem(GetWorld(), NAME_None),推荐方式
	// 获取方式二:使用 IOnlineSubsystem::Get(),直接获取可以 CreateSession 但是 JoinSession 后客户端没有跳转场景
	OnlineSub = Online::GetSubsystem(PlayerController->GetWorld(), NAME_None);

	// 获取 UserID
	// 获取方式一:UGameplayStatics::GetGameInstance(GetWorld())->GetLocalPlayers()[0]->GetPreferredUniqueNetId()
	if (GetLocalPlayers().Num() == 0) {
		DDH::Debug() << "No LocalPlayer Exists, Can't Get UserID" << DDH::Endl();
	}
	else {
		UserID = (*GetLocalPlayers()[0]->GetPreferredUniqueNetId()).AsShared();
	}

#if 0
	// 获取方式二:使用 PlayerState 获取,该方式在打包成 exe 运行无问题,但是在编辑器模式下运行多个窗口,就会找不到 PlayerState
	if (PlayerController->PlayerState)
		UserID = PlayerController->PlayerState->UniqueId.GetUniqueNetId();
	else
		DDH::Debug() << "No PlayerState Exists, Can't Get UserID" << DDH::Endl();
#endif

	// 如果在这里直接获取 Session 运行时会报错,生命周期的问题
}

void URPCInstance::HostSession()
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 会话设置
			FOnlineSessionSettings Settings;
			// 连接数
			Settings.NumPublicConnections = 10;
			Settings.bShouldAdvertise = true;
			Settings.bAllowJoinInProgress = true;
			// 使用局域网
			Settings.bIsLANMatch = true;
			Settings.bUsesPresence = true;
			Settings.bAllowJoinViaPresence = true;
			// 绑定委托
			OnCreateSessionCompleteDelegateHandle = Session->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
			// 创建会话
			Session->CreateSession(*UserID, NAME_GameSession, Settings);
		}
	}
}

void URPCInstance::ClientSession()
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 实例化搜索结果指针并且设定参数
			SearchObject = MakeShareable(new FOnlineSessionSearch);
			// 返回结果数
			SearchObject->MaxSearchResults = 10;
			// 是否是局域网,就是 IsLAN
			SearchObject->bIsLanQuery = true;
			SearchObject->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
			// 绑定寻找会话委托
			OnFindSessionsCompleteDelegateHandle = Session->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);
			// 进行会话寻找
			Session->FindSessions(*UserID, SearchObject.ToSharedRef());
		}
	}
}

void URPCInstance::DestroySession()
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 绑定销毁会话委托
			OnDestroySessionCompleteDelegateHandle = Session->AddOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegate);
			// 执行销毁会话
			Session->DestroySession(NAME_GameSession);
		}
	}
}

void URPCInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 解绑创建会话完成回调函数
			Session->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
			// 判断创建会话是否成功
			if (bWasSuccessful) {
				DDH::Debug() << "CreateSession Succeed" << DDH::Endl();

				// 绑定开启会话委托
				OnStartSessionCompleteDelegateHandle = Session->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);
				// 执行开启会话
				Session->StartSession(NAME_GameSession);
			}
			else
				DDH::Debug() << "CreateSession Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 注销开启会话委托绑定
			Session->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
			if (bWasSuccessful) {
				DDH::Debug() << "StartSession Succeed" << DDH::Endl();
				// 服务端跳转场景
				UGameplayStatics::OpenLevel(PlayerController->GetWorld(), FName("GameMap"), true, FString("listen"));
			}
			else
				DDH::Debug() << "StartSession Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnFindSessionsComplete(bool bWasSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 取消寻找会话委托绑定
			Session->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
			if (bWasSuccessful) {
				// 如果收集的结果存在并且大于 1
				if (SearchObject.IsValid() && SearchObject->SearchResults.Num() > 0) {
					DDH::Debug() << "Find Sessions Succeed" << DDH::Endl();
					// 绑定加入 Session 委托
					OnJoinSessionCompleteDelegateHandle = Session->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
					// 执行加入 Session
					Session->JoinSession(*UserID, NAME_GameSession, SearchObject->SearchResults[0]);
				}
				else
					DDH::Debug() << "Find Sessions Succeed But Num == 0" << DDH::Endl();
			}
			else
				DDH::Debug() << "Find Sessions Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 取消加入会话委托绑定
			Session->ClearOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegateHandle);
			// 如果加入成功
			if (Result == EOnJoinSessionCompleteResult::Success) {
				// 传送玩家到新地图
				FString ConnectString;
				if (Session->GetResolvedConnectString(NAME_GameSession, ConnectString)) {
					DDH::Debug() << "Join Sessions Succeed" << DDH::Endl();
					// 客户端切换到服务器的关卡
					PlayerController->ClientTravel(ConnectString, TRAVEL_Absolute);
				}
			}
			else 
				DDH::Debug() << "Join Sessions Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnDestroySessionComplete(FName SessionName, bool bWAsSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 注销销毁会话委托
			Session->ClearOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegateHandle);
			// 其他逻辑...
		}
	}
}

读者可能会发现上面的代码中,绑定委托后直接就通过 Session 执行相应逻辑了,然后在接下来的逻辑里解绑委托。这里笔者倾向于将这一过程理解为 装弹 —> 发射 —> 退弹壳。UE4 已经将网络模块的细枝末节都为我们封装好了,我们只需要知道如何使用就够了,当然,喜欢探索的读者也可以查阅源码去深入理解。

在主界面控制器里注册自己到 RPCInstance,并且将 RPCInstance 注册到主界面 UI。

MenuController.cpp

// 引入头文件
#include "Kismet/GameplayStatics.h"
#include "RPCInstance.h"

void AMenuController::BeginPlay()
{


	// 获取 GameInstance
	URPCInstance* RPCInstance = Cast<URPCInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
	RPCInstance->AssignPlayerController(this);
	
	UClass* MenuWidgetClass = LoadClass<UMenuWidget>(NULL, TEXT("WidgetBlueprint'/Game/Blueprint/MenuWidget_BP.MenuWidget_BP_C'"));
	UMenuWidget* MenuWidget = CreateWidget<UMenuWidget>(GetWorld(), MenuWidgetClass);
	MenuWidget->AddToViewport();
	MenuWidget->AssignRPCInstance(RPCInstance);	// 注册 RPCInstance 到 MenuWidget
}

最后添加一些网络模块相关的依赖。

RPCCourse.Build.cs

public RPCCourse(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "Slate", "UMG", 
		"HeadMountedDisplay", "OnlineSubsystem", "OnlineSubsystemUtils" });	// 添加这三个模块依赖

		// 添加动态加载模组
		DynamicallyLoadedModuleNames.AddRange(
			new string[] {
				"OnlineSubsystemNull",
			}
		);
	}

编译后,在项目设置里将默认的 GameInstance 设置为 RPCInstance。

来到 MenuWidget_BP 的图表,重新调整两个按钮点击事件的连接节点如下:

在这里插入图片描述
此时运行玩家数应该是 3。运行后,在服务端创建服务器,创建成功;让另外两个客户端加入服务器,也能进入成功。并且在服务端按 J 键,另外两个客户端各自能看到自己角色处生成红色数字。

不过如果在客户端创建服务器,另外一个客户端可以加入,但是服务端加入会显示找到会话和加入成功,但不会跳转到 GameMap。所以必须要让服务端创建服务器才能正常运作。

至此,梁迪老师的 RPC 课程到这里就结束了,衷心感谢梁迪老师提供的优质课程 : )

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

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

相关文章

【优选算法专栏】专题九:链表--------两两交换链表中的节点

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

华清远见作业第四十天——Qt(第二天)

思维导图&#xff1a; 编程&#xff1a; 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为…

智慧园区软件解决方案,园区运营管理系统(源码)

一、楼栋管理 1、园区信息&#xff1a;描述园区信息&#xff0c;有些规模较大的产业园区会存在A区&#xff0c;B区&#xff0c;C区。 2、楼栋信息&#xff1a;管理园区所有的楼栋&#xff0c;设有楼栋编号&#xff0c;所属园区&#xff0c;楼栋地址等。 3、房源信息&#xf…

JAVA并发编程之原子性、可见性与有序性

并发编程-原子性、可见性与有序性 一、CPU的可见性 1.1 缓存一致性问题的出现 CPU处理器在处理速度上&#xff0c;远胜于内存&#xff0c;主内存执行一次内存的读写操作&#xff0c;所需要的时间足够处理器去处理上百条指令。 为了弥补处理器与主内存处理能力之间的差距&am…

计算机网络基础之计算机网络组成与分类

计算机网络基础 计算机网络是计算机技术与通信技术发展相结合的产物&#xff0c;并在用户需求的促进下得到进一步的发展。通信技术为计算机之间的数据传输和交换提供了必需的手段&#xff0c;而计算机技术又渗透到了通信领域&#xff0c;提高了通信网络的性能。 计算机网络的…

书生·浦语大模型实战营-第六课笔记

1.评测追魂夺命三连问 2.主流大拿有话说-评测框架 3.友商最棒儿子最亲&#xff0c;好瓜都是王婆的 4.真枪实弹上战场 为了给平台省点电&#xff0c;我用了自家的电和自家的电脑进行评测。评测的模型也是之前在自己电脑上跑了3轮花费30多个小时的第四课作业微调的法律大模型。s…

Kotlin学习 6

1.接口 interface Movable {var maxSpeed: Intvar wheels: Intfun move(movable: Movable): String}class Car(var name: String, override var wheels: Int 4, _maxSpeed: Int) : Movable {override var maxSpeed: Int _maxSpeedget() fieldset(value) {field value}overr…

相机图像质量研究(40)常见问题总结:显示器对成像的影响--画面泛白

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

过减速带控制效果优化

一、忽略纵向和横向的影响 如上图所示&#xff0c;车辆以40km/h过减速带时&#xff0c;质心垂向加速度突然变化的同时&#xff0c;纵向加速度与侧向加速度也会引起突变。 我们在之前文章里提到&#xff0c;侧向控制与纵向控制是根据侧向加速度与纵向加速度来做的&#xff0c;因…

十三:集合

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 01、Java 集合框架概述1.1、集合框架与数组的对比及概述1.2、集合框架涉及到的API 02、Collection接口方法2.1、Collection接口中的常用方法12.2、Collection接口中…

Git详解及 github与gitlab使用

目录 1.1 关于版本控制 1.1.1 本地版本控制 1.1.2 集中化的版本控制系统 1.1.3 分布式版本控制系统 1.2 Git简介 1.2.1 Git历史 1.3 安装git 1.3.1 环境说明 1.3.2 Yum安装Git 1.3.3 编译安装 1.4 初次运行 Git 前的配置 1.4.1 配置git 1.4.2 获取帮助 1.5 获取 G…

k8s-hpa控制器 16

hpa可通过metrics-server所提供pod的cpu或者内存的负载情况&#xff0c;从而动态拉伸控制器的副本数&#xff0c;从而达到后端的自动弹缩 官网&#xff1a;https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscalewalkthrough/ 上传镜像 创建hpa实例 …

连续字母长度 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 给定一个字符串&#xff0c;只包含大写字母&#xff0c;求在包含同一字母的子串中&#xff0c;长度第 k 长的子串的长度&#xff0c;相同字母只取最长的那个子串。…

【蓝桥杯单片机入门记录】独立按键

目录 一、键盘、微动开关概述 二、按键工作原理 &#xff08;1&#xff09;按键构成 &#xff08;2&#xff09;&#xff08;蓝桥杯开发板&#xff09;独立按键电路图&#xff08;非实际&#xff0c;参考理解&#xff09; &#xff08;3&#xff09;独立按键工作原理 三、…

java程序流程控制

java程序有哪些流程控制、以及Java提供了哪些方案来控制程序的执行顺序&#xff1f; 程序的流程控制一般分为3种&#xff1a;顺序结构、分支结构、循环结构 顺序结构&#xff1a;就是不加任何控制&#xff0c;代码从main方法开始自上而下执行 分支结构&#xff1a;就是根据条…

高光谱图像降噪方法(2D Wavelet, 3D Wavelet, FORPDN, HyRes等方法)

近年来&#xff0c;随着遥感应用的不断深入&#xff0c;高光谱图像研究已经成为遥感领域发展最迅速的技术之一。与其他传统成像技术相比&#xff0c;高光谱图像具有更多优势&#xff1a;更丰富的信息量、纳米级的光谱分辨率以及范围更广且连续的光谱。因此&#xff0c;在农业、…

学习SpringMVC第二天

第一种方法与springmvc无关 , 用的是tomcat的东西在web.xml里配置 第二种方法 : 用SpringMVC解决 ,设置静态资源映射匹配 , 在Spring-mvc.xml里配置 第三种方法: 还是用SpringMVC解决 , 直接加一个 <mvc:default-servlet-handler/>, 在spring-mvc.xml里配置 第二种方法…

虹科方案 | 释放总线潜力:汽车总线离线模拟解决方案

来源&#xff1a;虹科汽车智能互联 虹科方案 | 释放总线潜力&#xff1a;汽车总线离线模拟解决方案 原文链接&#xff1a;https://mp.weixin.qq.com/s/KGv2ZOuQMLIXlOiivvY6aQ 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; #汽车总线 #ECU #汽车网关 导读 传统的…

【RPG Maker MV 仿新仙剑 战斗场景UI (一)】

RPG Maker MV 仿新仙剑 战斗场景UI 一 战斗场景制作原版仙剑战斗UI原版RPG Maker MV战斗UI启航战斗菜单 战斗场景制作 RPG Maker 中战斗场景的UI是比较经典的日式RPG的UI布局&#xff0c;现在尝试将它变成仙剑这样的布局看看。。。 原版仙剑战斗UI 这里只截图了开始的战斗UI…

Git笔记——2

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、撤销修改__情况一 二、撤销修改__情况二 三、撤销修改__情况三 四、删除文件 五、理解分支 六、创建、切换和合并分支初体验 七、删除分支 八、合并冲突 总…