Lyra学习笔记2 GFA_AddComponents与ULyraPlayerSpawningManagerComponent

article/2025/7/24 7:20:58

目录

  • 前言
  • GameFeatureAction_AddComponents
  • ULyraPlayerSpawningManagerComponent
    • 缓存所有PlayerStart位置
    • 选择位置

前言

1.以control模式为例
2.比较散,想单独拿出一篇梳理下Experience的流程

GameFeatureAction_AddComponents

这部分建议看
《InsideUE5》GameFeatures架构(五)AddComponents
这里写的内容相当于是我跟着走了一遍

Lyra中角色基本类ALyraCharacter类似于一个框架,负责转发事件给其组件,组件实现相关功能,所以ALyraCharacter甚至只有几百行(在这个不算小的项目里这真的算短了(我觉得))。

在研究生成角色流程之前,我想先研究一下GameFeatureAction中的AddComponents,帮助之后的理解。

在加载Experience的过程中,执行到ULyraExperienceManagerComponent::OnExperienceLoadComplete时会激活GameFeature,以AddComponents为例,一直到OnGameFeatureActivating的调用栈如图:
在这里插入图片描述
而在OnGameFeatureActivating中都会调用到AddToWorld
前面经过一些World,客户端服务端之类的判断后,执行逻辑在如下的一行:

void UGameFeatureAction_AddComponents::AddToWorld(const FWorldContext& WorldContext, FContextHandles& Handles) {... 
Handles.ComponentRequestHandles.Add(GFCM->AddComponentRequest(Entry.ActorClass, ComponentClass, static_cast<EGameFrameworkAddComponentFlags>(Entry.AdditionFlags))); 
... 
}

GFCM::AddComponentRequest

TSharedPtr<FComponentRequestHandle> UGameFrameworkComponentManager::AddComponentRequest(const TSoftClassPtr<AActor>& ReceiverClass, TSubclassOf<UActorComponent> ComponentClass, const EGameFrameworkAddComponentFlags AdditionFlags)
{// You must have a receiver and component class. The receiver cannot be AActor, that is too broad and would be bad for performance.if (!ensure(!ReceiverClass.IsNull()) || !ensure(ComponentClass) || !ensure(ReceiverClass.ToString() != TEXT("/Script/Engine.Actor"))){return nullptr;}//一些检查FComponentRequestReceiverClassPath ReceiverClassPath(ReceiverClass);UClass* ComponentClassPtr = ComponentClass.Get();FComponentRequest NewRequest;NewRequest.ReceiverClassPath = ReceiverClassPath;NewRequest.ComponentClass = ComponentClassPtr;// Add a request if there is not an already existing one. Note that it will only uses the receiver and component class to check for uniqueness, not the addition flags.int32& RequestCount = RequestTrackingMap.FindOrAdd(NewRequest);RequestCount++;if (RequestCount == 1)//第一次匹配{EGameFrameworkAddComponentResult Result = EGameFrameworkAddComponentResult::Failed;auto& RequestInfoSet = ReceiverClassToComponentClassMap.FindOrAdd(ReceiverClassPath);RequestInfoSet.Add({ ComponentClassPtr, AdditionFlags } );if (UClass* ReceiverClassPtr = ReceiverClass.Get()){UGameInstance* LocalGameInstance = GetGameInstance();if (ensure(LocalGameInstance)){UWorld* LocalWorld = LocalGameInstance->GetWorld();if (ensure(LocalWorld)){for (TActorIterator<AActor> ActorIt(LocalWorld, ReceiverClassPtr); ActorIt; ++ActorIt)//遍历场景中的Actor{if (ActorIt->IsActorInitialized())//调用过BeginPlay{
#if WITH_EDITORif (!ReceiverClassPtr->HasAllClassFlags(CLASS_Abstract)){ensureMsgf(AllReceivers.Contains(*ActorIt), TEXT("You may not add a component request for an actor class that does not call AddReceiver/RemoveReceiver in code! Class:%s"), *GetPathNameSafe(ReceiverClassPtr));}
#endifResult = CreateComponentOnInstance(*ActorIt, ComponentClass, AdditionFlags);//创建组件}}}}}else{// Actor class is not in memory, there will be no actor instances}return MakeShared<FComponentRequestHandle>(this, ReceiverClass, ComponentClass);}return nullptr;
}
EGameFrameworkAddComponentResult UGameFrameworkComponentManager::CreateComponentOnInstance(AActor* ActorInstance, TSubclassOf<UActorComponent> ComponentClass, const EGameFrameworkAddComponentFlags AdditionFlags)
{...UActorComponent* NewComp = NewObject<UActorComponent>(ActorInstance, ComponentClass, NewComponentName);...
}

一些细节原文写的很好《InsideUE5》GameFeatures架构(五)AddComponents

ULyraPlayerSpawningManagerComponent

总结一下目前为止的流程:

读取WordSetting中的Experience->
GameState中的ULyraExperienceManagerComponent加载Experience->
激活Experience中配置的GameFeature->
执行Actions

Control关卡中的B_LyraShooterGame_ControlPoints(Experience)的Actions中,
有添加组件的Action,其中配置了在LyraGameState中添加ULyraPlayerSpawningManagerComponent,这个组件负责了管理生成位置。

缓存所有PlayerStart位置

void ULyraPlayerSpawningManagerComponent::InitializeComponent()
{Super::InitializeComponent();UE_LOG(LogTemp,Warning,TEXT("LAPI: ULyraPlayerSpawningManagerComponent::InitializeComponent"));FWorldDelegates::LevelAddedToWorld.AddUObject(this, &ThisClass::OnLevelAdded);UWorld* World = GetWorld();World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &ThisClass::HandleOnActorSpawned));for (TActorIterator<ALyraPlayerStart> It(World); It; ++It){if (ALyraPlayerStart* PlayerStart = *It){CachedPlayerStarts.Add(PlayerStart);}}
}

直接看后半部分可知缓存了场景中所有的PlayerStart。
再来看OnLevelAdded这个函数:

void ULyraPlayerSpawningManagerComponent::OnLevelAdded(ULevel* InLevel, UWorld* InWorld)
{if (InWorld == GetWorld()){for (AActor* Actor : InLevel->Actors){if (ALyraPlayerStart* PlayerStart = Cast<ALyraPlayerStart>(Actor)){ensure(!CachedPlayerStarts.Contains(PlayerStart));CachedPlayerStarts.Add(PlayerStart);}}}
}

这里是新的Level被AddToWorld的时候调用的,以实现更新PlayerStart,从L_DefaultEditorOverview到L_Convolution_Blockout并不会触发,因为是加载组件之后才绑定的。
同理HandleOnActorSpawned是负责处理动态生成的PlayerStart的。

选择位置

加载完地图资源后,GameMode会将ChoosePlayerStart转到SpawningManagerComponent的ChoosePlayerStart函数:
在这里插入图片描述
Choose函数比较长

AActor* ULyraPlayerSpawningManagerComponent::ChoosePlayerStart(AController* Player)
{if (Player){
#if WITH_EDITORif (APlayerStart* PlayerStart = FindPlayFromHereStart(Player)){return PlayerStart;}
#endif//这部分处理编辑器中PlayFromHere的PlayerStartPIE的特殊情况TArray<ALyraPlayerStart*> StarterPoints;for (auto StartIt = CachedPlayerStarts.CreateIterator(); StartIt; ++StartIt){if (ALyraPlayerStart* Start = (*StartIt).Get()){StarterPoints.Add(Start);}else{StartIt.RemoveCurrent();}}//处理完后StarterPoints存的是安全的强引用if (APlayerState* PlayerState = Player->GetPlayerState<APlayerState>()){// start dedicated spectators at any random starting location, but they do not claim itif (PlayerState->IsOnlyASpectator()){if (!StarterPoints.IsEmpty()){return StarterPoints[FMath::RandRange(0, StarterPoints.Num() - 1)];}return nullptr;}}//若是Spectators在数组内随机一个位置返回AActor* PlayerStart = OnChoosePlayerStart(Player, StarterPoints);//返回nullptrif (!PlayerStart)//若为nullptr,暂时必然为nullptr{PlayerStart = GetFirstRandomUnoccupiedPlayerStart(Player, StarterPoints);//若有未占用,则在其中随机一个,若没有就在已经占用的随机一个}if (ALyraPlayerStart* LyraStart = Cast<ALyraPlayerStart>(PlayerStart)){LyraStart->TryClaim(Player);//尝试占用}return PlayerStart;}return nullptr;
}

待续…


http://www.hkcw.cn/article/twURGvnqym.shtml

相关文章

EDW2025|数据治理的神话破除——从误区到现实

在当今数据驱动的世界中&#xff0c;数据治理已成为企业成功的关键因素。然而&#xff0c;许多组织在实施数据治理时&#xff0c;常常被一些常见的误区所困扰。本文将逐一破除这些误区&#xff0c;揭示数据治理的真实面貌。 误区一&#xff1a;你需要一个大的预算&#xff01;…

(24)多租户 SaaS 平台设计

文章目录 2️⃣4️⃣ 多租户 SaaS 平台设计 &#x1f3e2;&#x1f510;&#x1f680; 多租户SaaS平台&#xff1a;打造云端共享公寓的隔离术&#xff01;&#x1f3d7;️ 多租户架构模型&#xff1a;三种共存方式1️⃣ 独立数据库模式2️⃣ 共享数据库&#xff0c;独立Schema模…

理想树图书:以科技赋能教育,开启AI时代自主学习新范式

深耕教育沃土 构建全场景教辅产品矩阵 自2013年创立以来&#xff0c;理想树始终以教育匠心回应时代命题。在教辅行业这片竞争激烈的领域&#xff0c;由专业教育工作者组成的理想树图书始终秉持“知识互映”理念&#xff0c;经过十余年的精耕细作&#xff0c;精心打造了小学同步…

26 C 语言函数深度解析:定义与调用、返回值要点、参数机制(值传递)、原型声明、文档注释

1 函数基础概念 1.1 引入函数的必要性 在《街霸》这类游戏中&#xff0c;实现出拳、出脚、跳跃等动作&#xff0c;每项通常需编写 50 - 80 行代码。若每次调用都重复编写这些代码&#xff0c;程序会变得臃肿不堪&#xff0c;代码可读性与维护性也会大打折扣。 为解决这一问题&…

网易 - 灵犀办公文档

一. 企业介绍 网易是中国领先的互联网技术公司&#xff0c;为用户提供免费邮箱、游戏、搜索引擎服务&#xff0c;通过开设新闻、娱乐、体育等30多个内容频道&#xff0c;以及博客、视频、论坛等互动交流&#xff0c;网聚人的力量。 为了给中小企业和个人打造一款综合性办公产…

2025年素养大赛编程赛项练习题

我用K克网盘分享了「最新素养大赛.zip」&#xff0c;点击链接即可保存。打开「K克App」&#xff0c;无需下载在线播放视频&#xff0c;畅享原画5倍速&#xff0c;支持电视投屏。 链接&#xff1a;https://pan.quark.cn/s/c2f85a297992 提取码&#xff1a;d4Uq

CSS3前端入门(第三天)2D转换 transform

转换&#xff08;transform&#xff09;是CSS3中具有颠覆性的特征之一&#xff0c;可以根据实现元素的位移、旋转、缩放等效果 移动&#xff1a;translate旋转&#xff1a;rorate缩放&#xff1a;scale 2D转换之移动translate 2D移动是2D转换里面的一种功能&#xff0c;可以…

深兰科技董事长陈海波受邀出席2025苏商高质量发展(常州)峰会,共话AI驱动产业升级

5月29日&#xff0c;2025苏商高质量发展峰会在常州隆重开幕。本次峰会聚焦新质生产力培育与产业创新转型&#xff0c;汇聚了众多江苏省内知名企业家、专家学者及政府代表。深兰科技创始人、董事长陈海波作为人工智能领域的领军企业代表&#xff0c;受邀出席盛会并参与重要活动环…

【软件】navicat 官方免费版

Navicat Premium Lite https://www.navicat.com.cn/download/navicat-premium-lite

AIGC与影视制作:技术革命、产业重构与未来图景

文章目录 一、AIGC技术全景&#xff1a;从算法突破到产业赋能1. **技术底座&#xff1a;多模态大模型的进化路径**2. **核心算法&#xff1a;从生成对抗网络到扩散模型的迭代** 二、AIGC在影视制作全流程中的深度应用1. **剧本创作&#xff1a;从“灵感枯竭”到“创意井喷”**2…

消防体能考核器材的智能化发展

消防员体能考核器材的智能化发展&#xff0c;在实际应用中带来了诸多益处。 一、精准计时与动作评判 高精度计时 &#xff1a;如400米障碍智能考官&#xff0c;依托高精度传感器和先进算法&#xff0c;能够精确到毫秒记录跑步用时&#xff0c;改变了传统人工计时易出现误差的…

大厂前端研发岗位设计的30道Webpack面试题及解析

文章目录 一、基础核心二、配置进阶三、性能优化四、Loader原理五、Plugin机制六、高级应用七、工程化实战八、原理深挖九、异常处理十、综合场景一、基础核心 Webpack的核心概念是什么? 解析:入口(entry)、输出(output)、加载器(loader)、插件(plugins)、模式(mode)。Loader…

展会聚焦丨漫途科技亮相2025西北水务博览会!

2025第三届西北水务数字化发展论坛暨供排水节水灌溉新技术设备博览会在兰州甘肃国际会展中心圆满落幕。本届展会以“科技赋能水资源&#xff0c;数智引领新动能”为主题&#xff0c;活动汇集水务集团、科研院所、技术供应商等全产业链参与者&#xff0c;旨在通过前沿技术展示与…

二、OpenCV图像处理-图像处理

目录 1、连通性 2、形态学操作 2.1腐蚀和膨胀 2.2开闭运算 2.3礼帽和黑帽 2.4总结 3、图像平滑 3.1图像噪声 3.2均值滤波 3.3高斯滤波 3.4中值滤波 3.5总结 4、直方图 4.1直方图的原理与显示 4.2掩膜的应用 4.3直方图均衡化 4.4自适应均衡化 4.5总结 5、边缘…

代码随想录算法训练营 Day60 图论Ⅹ Bellmen_ford 系列算法

图论 题目 94. 城市间货物运输 I Bellmen_ford 队列优化算法 SPFA 大家可以发现 Bellman_ford 算法每次松弛 都是对所有边进行松弛。 但真正有效的松弛&#xff0c;是基于已经计算过的节点在做的松弛。 本图中&#xff0c;对所有边进行松弛&#xff0c;真正有效的松弛&#…

CppCon 2014 学习:Making C++ Code Beautiful

你说的完全正确&#xff0c;也很好地总结了 C 这门语言在社区中的两种典型看法&#xff1a; C 的优点&#xff08;Praise&#xff09; 优点含义Powerful允许底层控制、系统编程、高性能计算、模板元编程、并发等多种用途Fast无运行时开销&#xff0c;接近汇编级别性能&#x…

手机照片太多了存哪里?

手机相册里塞满了旅行照片、生活碎片&#xff0c;每次清理都舍不得删&#xff1f;NAS——一款超实用的存储方案&#xff0c;让你的回忆安全又有序&#xff5e; 1️⃣自动备份解放双手 手机 / 电脑 / 相机照片全自动同步到 NAS&#xff0c;再也不用手动传文件 2️⃣远程访问像…

Java String的使用续 -- StringBuilder类和StringBuffer

文章目录 字符串的不可变性StringBuilder和StringBuffer函数使用 字符串的不可变性 字符串不可变是因为有private修饰&#xff0c;只能在类的内部使用不可以在类外使用&#xff0c;因此使用时是不可以修改字符串的 public class test {public static void main(String[] args…

关于xilinx pcie ip core管脚分配出现布局布线报错问题说明

一、问题说明 xilinx的pcie几个ip core选择的物理位置是固定的&#xff0c;那么相当于管脚就被指定了&#xff0c;但是这个可能和原理图的真实情况对不上 二、xilinx官方推荐 xilinx对pcie放置的位置是有推荐的&#xff0c;如果没有按照推荐的&#xff0c;是否有问题呢&#x…

全面了解DMEM培养基:功能、组成与应用

近年来&#xff0c;研究人员陆续报道了采用营养素、生长因子和激素取代血清&#xff0c;在无血清培养基中培养各种细胞系的方法。Mather和Sato&#xff08;BBRC, 1985&#xff09;报道在含有胰岛素、转铁蛋白、表皮生长因子、黄体生成素或促卵泡激素、生长调节素和生长激素的无…