UE官方示例项目Lyra Game拆解(二)

在之前的逻辑中,Lyra通过在初始地图中放置了B_ExperienceList3D蓝图实例来创建了不同的游戏入口也就是B_TeleportToUserFacingExperience蓝图实例。该蓝图作为子游戏的入口,我们继续来看该蓝图的内部逻辑。
完整的蓝图事件图表如下:
在这里插入图片描述
该蓝图共有三条逻辑分支。

  • 分支一
    在这里插入图片描述
    该分支在控件被初始化时,生命周期函数BeginPlay调用时执行。它按顺序执行了两条逻辑,第一条逻辑为:
    1.获取该蓝图的子控件并转换为W_ExperienceTile类型
    2.调用W_ExperienceTile的SetUserFacingExperiecne函数
    并将UserFacingExperienceToLoad变量的值传递进去,该变量的值是在上一篇博客中通过AssetManager加载并传递的。SetUserFacingExperiecne函数内部实现如下:
    在这里插入图片描述
    主要内容就是设置在场景中显示的世界UI上的文本内容和图标。

  • 分支二
    在这里插入图片描述
    该分支处理了触发器触发事件逻辑,主要逻辑如下:
    1.当有Actor进入触发范围时,将该Actor转换为Pawn类型
    同时获取控制器。
    2.验证获取的Pawn是否有效,如果有效则进入条件判断节点。
    3.判断当前获取的控制器是否是本地玩家控制器,如果是则将获取的Pawn转换为PlayerController作为参数传递给一个自定事件,从而触发分支三的逻辑。

  • 分之三
    在这里插入图片描述
    1.验证UserFacingExperienceToLoad变量是否有效
    2.调用PushContentToLayerForPlayer函数,该函数主要用来向指定玩家显示对应的UI界面,这里是
    在这里插入图片描述
    3.调用UserFacingExperienceToLoad的CreateHostingRequest函数创建一个用于主机会话的请求对象,该函数实现在LyraUserFacingExperienceDefinition.cpp文件中,具体逻辑如下:

//创建一个用于主机会话的请求对象 UCommonSession_HostSessionRequest,
//并对该请求对象的各种属性进行初始化设置
UCommonSession_HostSessionRequest* ULyraUserFacingExperienceDefinition::CreateHostingRequest(const UObject* WorldContextObject) const
{
	const FString ExperienceName = ExperienceID.PrimaryAssetName.ToString();//将 ExperienceID 的主要资产名称转换为字符串
	const FString UserFacingExperienceName = GetPrimaryAssetId().PrimaryAssetName.ToString();//将当前对象的主要资产 ID 的主要资产名称转换为字符串。

	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);//获取世界对象,如果获取失败则返回 nullptr
	UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr;//获取游戏实例
	UCommonSession_HostSessionRequest* Result = nullptr;//存储最终的主机会话请求对象指针
	
	if (UCommonSessionSubsystem* Subsystem = GameInstance ? GameInstance->GetSubsystem<UCommonSessionSubsystem>() : nullptr)
	{
		Result = Subsystem->CreateOnlineHostSessionRequest();//通过子系统创建主机会话请求
	}
	//如果子系统创建失败,则手动创建请求对象
	if (!Result)
	{
		Result = NewObject<UCommonSession_HostSessionRequest>();//手动创建一个 UCommonSession_HostSessionRequest 对象
		Result->OnlineMode = ECommonSessionOnlineMode::Online;//设置会话的在线模
		Result->bUseLobbies = true;//设置是否使用大厅
		Result->bUseLobbiesVoiceChat = false;//是否使用大厅语音聊天
		//设置是否启用会话的存在状态,根据是否运行专用服务器来决定,非专用服务器则启用
		Result->bUsePresence = !IsRunningDedicatedServer();
	}
	Result->MapID = MapID;//设置地图 ID
	Result->ModeNameForAdvertisement = UserFacingExperienceName;//设置用于广告宣传的模式名称
	Result->ExtraArgs = ExtraArgs;//设置额外参数
	Result->ExtraArgs.Add(TEXT("Experience"), ExperienceName);//添加一个键值对 "Experience" 和 ExperienceName
	Result->MaxPlayerCount = MaxPlayerCount;//设置最大玩家数量
	//根据平台支持情况添加录像参数
	if (ULyraReplaySubsystem::DoesPlatformSupportReplays())
	{
		if (bRecordReplay)
		{
			Result->ExtraArgs.Add(TEXT("DemoRec"), FString());//添加一个键值对 "DemoRec" 和空字符串,表示要记录录像
		}
	}
	return Result;
}

4.设置在线模式为Offline,然后调用UCommonSessionSubsystem的HostSession函数。
以上就是全部蓝图逻辑。剩下的就是代码成面的逻辑了。

UCommonSessionSubsystem是一个用于管理多人游戏会话的核心子系统,负责处理会话创建、加入、参数传递以及与在线服务(如Steam、EOS等)的交互。
UCommonSessionSubsystem的HostSession函数的具体实现如下:

void UCommonSessionSubsystem::HostSession(APlayerController* HostingPlayer, UCommonSession_HostSessionRequest* Request)
{
	if (Request == nullptr)
	{
		SetCreateSessionError(NSLOCTEXT("NetworkErrors", "InvalidRequest", "HostSession passed an invalid request."));
		OnCreateSessionComplete(NAME_None, false);
		return;
	}

	ULocalPlayer* LocalPlayer = (HostingPlayer != nullptr) ? HostingPlayer->GetLocalPlayer() : nullptr;
	if (LocalPlayer == nullptr && !bIsDedicatedServer)
	{
		SetCreateSessionError(NSLOCTEXT("NetworkErrors", "InvalidHostingPlayer", "HostingPlayer is invalid."));
		OnCreateSessionComplete(NAME_None, false);
		return;
	}

	FText OutError;
	if (!Request->ValidateAndLogErrors(OutError))
	{
		SetCreateSessionError(OutError);
		OnCreateSessionComplete(NAME_None, false);
		return;
	}

	if (Request->OnlineMode == ECommonSessionOnlineMode::Offline)
	{
		if (GetWorld()->GetNetMode() == NM_Client)
		{
			SetCreateSessionError(NSLOCTEXT("NetworkErrors", "CannotHostAsClient", "Cannot host offline game as client."));
			OnCreateSessionComplete(NAME_None, false);
			return;
		}
		else
		{
			// Offline so travel to the specified match URL immediately
			GetWorld()->ServerTravel(Request->ConstructTravelURL());
		}
	}
	else
	{
		CreateOnlineSessionInternal(LocalPlayer, Request);
	}

	NotifySessionInformationUpdated(ECommonSessionInformationState::InGame, Request->ModeNameForAdvertisement, Request->GetMapName());
}

由于我们之前参数传递中使用的是离线模式,所以他会执行到这个分支下的逻辑:
在这里插入图片描述
调用UWorld的ServerTravel函数在服务器端触发地图跳转。

bool UWorld::ServerTravel(const FString& FURL, bool bAbsolute, bool bShouldSkipGameNotify)
{
	AGameModeBase* GameMode = GetAuthGameMode();
	
	if (GameMode != nullptr && !GameMode->CanServerTravel(FURL, bAbsolute))
	{
		return false;
	}

	//设置跳转类型
	NextTravelType = bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative;

	// if we're not already in a level change, start one now
	// If the bShouldSkipGameNotify is there, then don't worry about seamless travel recursion
	// and accept that we really want to travel
	if (NextURL.IsEmpty() && (!IsInSeamlessTravel() || bShouldSkipGameNotify))
	{
		NextURL = FURL;//设置目标URL
		if (GameMode != NULL)
		{
			// Skip notifying clients if requested
			if (!bShouldSkipGameNotify)
			{
				GameMode->ProcessServerTravel(FURL, bAbsolute);//通知客户端
			}
		}
		else
		{
			NextSwitchCountdown = 0;//无GameMode 触发硬切换
		}
	}

	return true;
}

继续进入ProcessServerTravel函数查看:


void AGameModeBase::ProcessServerTravel(const FString& URL, bool bAbsolute)
{
#if WITH_SERVER_CODE
	StartToLeaveMap();//触发地图离开前的逻辑
	UE_LOG(LogGameMode, Log, TEXT("ProcessServerTravel: %s"), *URL);
	//环境初始化
	UWorld* World = GetWorld();
	check(World);
	FWorldContext& WorldContext = GEngine->GetWorldContextFromWorldChecked(World);
	//无缝切换条件判断
	// Use game mode setting but default to full load screen if the server has been up for a long time so that TimeSeconds doesn't overflow and break everything
	bool bSeamless = (bUseSeamlessTravel && GetWorld()->TimeSeconds < 172800.0f); // 172800 seconds == 48 hours

	// Compute the next URL, and pull the map out of it. This handles short->long package name conversion
	FURL NextURL = FURL(&WorldContext.LastURL, *URL, bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative);

	// Override based on URL parameters
	//支持通过URL参数?SeamlessTravel或?NoSeamlessTravel动态覆盖默认设置
	if (NextURL.HasOption(TEXT("SeamlessTravel")))
	{
		bSeamless = true;
	}
	else if (NextURL.HasOption(TEXT("NoSeamlessTravel")))
	{
		bSeamless = false;
	}

	// There are some issues with seamless travel in PIE, so fall back to hard travel unless it is supported
	//PIE环境特殊处理
	if (World->WorldType == EWorldType::PIE && bSeamless && !FParse::Param(FCommandLine::Get(), TEXT("MultiprocessOSS")))
	{
		if (!UE::GameModeBase::Private::bAllowPIESeamlessTravel)
		{
			UE_LOG(LogGameMode, Warning, TEXT("ProcessServerTravel: Seamless travel is disabled in PIE, set net.AllowPIESeamlessTravel=1 to enable."));
			bSeamless = false;
		}
	}

	//通知客户端
	// Notify clients we're switching level and give them time to receive.
	FString URLMod = NextURL.ToString();
	APlayerController* LocalPlayer = ProcessClientTravel(URLMod, bSeamless, bAbsolute);

	World->NextURL = URLMod;//设置下一地图的URL路径
	ENetMode NetMode = GetNetMode();

	//执行切换逻辑
	if (bSeamless)
	{
		World->SeamlessTravel(World->NextURL, bAbsolute);
		World->NextURL = TEXT("");
	}
	else
	{
		// Switch immediately if not networking.
		if (NetMode != NM_DedicatedServer && NetMode != NM_ListenServer)
		{
			World->NextSwitchCountdown = 0.0f;
		}

		GEngine->IncrementGlobalNetTravelCount();
		GEngine->SaveConfig();
	}
#endif // WITH_SERVER_CODE
}

以上基本上就是进入子游戏的全部逻辑了,具体如何通知到客户端,感兴趣的朋友可以继续依次点进对应的函数去看,不在此处啰嗦了。

<think>好的,我现在需要帮助用户解决在UE5.5的Lyra示例项目中集成UnLua的问题。首先,我需要了解UnLua是什么,以及它在UE项目中的作用。UnLua是一个用于Unreal Engine的Lua脚本插件,允许开发者使用Lua语言进行游戏逻辑的编写,提高开发效率和灵活性。 接下来,我需要考虑Lyra项目的结构。根据用户提供的引用,Lyra项目采用了模块化设计,各个功能模块独立存在,比如InputConfig、GAS(Gameplay Ability System)等。用户之前的问题中提到的引用[1]和[2]提到了输入配置和角色配置,这些可能与集成UnLua时需要处理的部分有关。 用户想将UnLua集成到Lyra中,可能需要修改项目的构建文件(如.Build.cs),添加UnLua的模块依赖。引用[4]中提到Lyra项目的结构是头文件和cpp文件混放在一起,并且Build.cs中的路径代码有限,所以需要确保UnLua的路径正确包含在项目中。 另外,UnLua需要与UE的蓝图和C++代码交互,可能需要修改Lyra的启动模块,初始化Lua环境。例如,在LyraGame模块的启动函数中初始化UnLua,并设置Lua文件的加载路径。同时,需要处理Lyra原有的GameplayTag、InputAction等系统与Lua脚本的绑定,可能需要通过UnLua的接口将蓝图中的逻辑替换为Lua脚本。 还需要考虑动画系统的集成,引用[3]提到Lyra的动画蓝图较为复杂,如果用户希望用Lua控制动画逻辑,可能需要将动画蓝图中的部分功能迁移到Lua脚本中,这涉及到动画蓝图和Lua之间的通信。 在测试阶段,需要确保所有修改后的模块能够正确编译,并且Lua脚本能够被正确加载和执行。可能遇到的问题包括模块依赖缺失、路径配置错误、或Lua与C++之间的数据类型转换问题。此时需要参考UnLua的官方文档,检查每一步是否正确配置,并利用日志输出调试信息。 总结步骤大致为:获取UnLua插件并放入项目目录,修改项目构建文件,配置启动模块初始化Lua,调整输入和游戏逻辑绑定,处理动画系统,最后测试并调试。需要提醒用户注意版本兼容性,尤其是UE5.5和UnLua的版本是否匹配,以及遵循Lyra的模块化设计原则,避免破坏原有结构。</think>### UE5.5 Lyra示例项目集成UnLua的步骤指南 #### 1. **环境准备** - 确保已下载UnLua插件(兼容UE5.5版本),可从GitHub仓库或UnLua官网获取。 - 将UnLua插件放置在Lyra项目的`Plugins`目录下(若没有则手动创建)。 #### 2. **修改项目构建配置** - 在Lyra项目的`.Build.cs`文件中添加UnLua模块依赖: ```csharp // LyraGame.Build.cs PublicDependencyModuleNames.AddRange(new string[] { "UnLua", // 添加UnLua模块 "LyraGame", "GameplayAbilities", ... }); ``` - 引用[4]提到Lyra的Build.cs路径配置较为简单,需确认插件路径已正确包含。 #### 3. **初始化Lua环境** - 在Lyra的**启动模块**(如`LyraGame`)中添加UnLua初始化代码: ```cpp // LyraGame.cpp #include "UnLua.h" void FLyraGameModule::StartupModule() { FUnLuaModule::Get().Startup(); // 初始化UnLua } ``` - 设置Lua脚本默认加载路径(如`Content/Script`)。 #### 4. **绑定输入与GAS到Lua** - **输入映射**:根据引用[1],Lyra使用`InputConfig`结构体关联输入与GameplayTag。可在Lua中重写输入响应逻辑: ```lua -- Input.lua local function OnInputAction(PlayerController, InputAction) if InputAction == "Jump" then -- Lua处理跳跃逻辑 end end BindInput(OnInputAction) ``` - **GAS集成**:引用[2]提到Lyra通过`AbilitySystemComponent`管理技能。通过UnLua覆写Ability激活逻辑: ```lua -- Ability.lua function ActivateAbility(Ability) if Ability:GetTag() == "Attack" then -- Lua实现攻击逻辑 end end ``` #### 5. **动画系统适配** - 根据引用[3],Lyra的动画系统通过动画蓝图控制。可将部分动画逻辑迁移到Lua: ```lua -- AnimBP.lua function UpdateAnimParams(Speed, Direction) AnimGraph:SetFloatParam("Speed", Speed) AnimGraph:SetFloatParam("Direction", Direction) end ``` #### 6. **测试与调试** - 在编辑器中启用UnLua插件(`Edit > Plugins > UnLua`)。 - 创建测试Lua脚本(如`Content/Script/Test.lua`),并在关卡蓝图中调用: ```lua Print("UnLua Integration Success!") ``` - 检查日志输出,确保无`Lua_State`初始化错误或路径问题。 #### 7. **注意事项** - **版本兼容性**:确认UnLua插件支持UE5.5,否则需从源码编译适配版本。 - **模块化设计**:避免直接修改Lyra核心模块,建议新建`LyraLua`模块管理集成代码[^2][^4]。 - **性能优化**:高频逻辑(如Tick事件)建议保留C++实现,Lua用于业务逻辑。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值