《InsideUE4》GamePlay架构学习

  • Level和World
    • 前话
    • Unity To UE
    • 思考
      • 为什么AWorldSettings[0]的位置,而ALevelScriptActor不用?
      • 既然ALevelScriptActor也继承于AActor,为何关卡蓝图不设计能添加Component?
      • 为何要有主PersistentLevel?
      • Levels们的Actors和World有直接关系吗?
      • 为什么要在Level里保存Actors,而不是把所有Map的Actors配置都生成在World一个总Actors里?

Level和World

前话

本次系列是关于知乎InsideUE系列的学习记录。原作链接如下:原文链接

Unity To UE

上一章谈到了Unity和UE里有了给各种各样的功能以及进行功能的对象。这一章谈谈两者是怎么将这些组织起来的。

首先,把所有物体同时放在一个世界可能会导致性能方面的问题,其次,玩家的活动范围是有限的,因此我们可以采取更细一点的粒度来规划这之间的关系。

在Unity中,存在一个 World(世界),它可能是3D的也可能是2D的,这是运行的基础。Unity将物体放在Scene(场景)中,这也就是不同的物体在不同的场景在同一世界下工作。我们可以通过SceneManager切换场景来实现不同功能,比如从开始UI界面场景跳到游戏主场景,不必要的物体通过切换场景来销毁释放,需要的物体保存下来,然后加载我们新场景需要的资源。

在UE中,对应Scene功能的就是Level(ULevel),而根据UE规则,Ulevel自然也是派生自UObject。

如图,Level带有一个ALevelScriptActor,这个组件允许我们在关卡里编写脚本,可以通过名字管理关卡中的所有Actor;另外也有一个继承自AInfo的AWorldSettings来记录本Level的各种规则属性,UE能直接访问这些数据。如下图:
如果这个Level的Settings是主PersistentLevel,它就是被当作整个World的WorldSettings。

World是一种Level的集合,提供一种Level动态加载的方法,但是在world加载的时候总是会有一个PersistentLevel来初始化世界的。

注意,Level里的Actors里也保存着AWorldSettings和ALevelScriptActor的指针,所以Actors实际上确实是保存了所有Actor。

有了Levels,我们把这些关卡放到World进行统一管理,一个World里有多个Level,我们可以设置这些Level在什么位置,是在一开始就加载进来,还是Streaming运行时加载。

UE里每个World支持一个PersistentLevel和多个其他Level:

Persistent的意思是一开始就加载进World,Streaming是后续动态加载的意思。Levels里保存有所有的当前已经加载的Level,StreamingLevels保存整个World的Levels配置列表。PersistentLevel和CurrentLevel只是个快速引用。在编辑器里编辑的时候,CurrentLevel可以指向其他Level,但运行时CurrentLevel只能是指向PersistentLevel

思考

为什么AWorldSettings[0]的位置,而ALevelScriptActor不用?

void ULevel::SortActorList()
{QUICK_SCOPE_CYCLE_COUNTER(STAT_Level_SortActorList);if (Actors.Num() == 0){// No need to sort an empty listreturn;}LLM_REALLOC_SCOPE(Actors.GetData());TArray<AActor*> NewActors;TArray<AActor*> NewNetActors;NewActors.Reserve(Actors.Num());NewNetActors.Reserve(Actors.Num());if (WorldSettings){// The WorldSettings tries to stay at index 0NewActors.Add(WorldSettings);if (OwningWorld != nullptr){OwningWorld->AddNetworkActor(WorldSettings);}}// Add non-net actors to the NewActors immediately, cache off the net actors to Append afterfor (AActor* Actor : Actors){if (Actor != nullptr && Actor != WorldSettings && !Actor->IsPendingKill()){if (IsNetActor(Actor)){NewNetActors.Add(Actor);if (OwningWorld != nullptr){OwningWorld->AddNetworkActor(Actor);}}else{NewActors.Add(Actor);}}}//iFirstNetRelevantActor = NewActors.Num(); UE4.26.2 DeleteNewActors.Append(MoveTemp(NewNetActors));// Replace with sorted list.Actors = MoveTemp(NewActors);
}

通过代码逻辑可知,在Actors不为空的情况下,首先会试着将WorldSettings放在NewActors的第一个,然后将非网络的Actors放在WorldSettings的后面,再将网络有关的放在最后。

原文所说的iFirstNetRelevantActor记录网络Actors所在的坐标在UE4.26.2版本被删除了。

一直放在第一个的话,也能同时把AWorldSettings和其他的前列Actor们再度区分开,在需要的时候也能加速判断。ALevelScriptActor因为是代表关卡蓝图,是允许携带“复制”变量函数的,所以也有可能被排序到后列。

既然ALevelScriptActor也继承于AActor,为何关卡蓝图不设计能添加Component?

观察到,平常我们在创建Actor的时候,我们蓝图界面是可以创建Component的。
那为什么在关卡蓝图里,却不能这么做(没有提供该界面功能)?
我虽然在图里标出了Level中拥有ModelComponents,但那其实只是针对BSP应用的一个子集。通过源码发现,其实UE自己也是在C++里往ALevelScriptActor添加UInputComponent来实现关卡蓝图可以响应事件。

void ALevelScriptActor::PreInitializeComponents()
{if (UInputDelegateBinding::SupportsInputDelegate(GetClass()) && !InputComponent){// create an InputComponent object so that the level script actor can bind key eventsInputComponent = NewObject<UInputComponent>(this, UInputSettings::GetDefaultInputComponentClass());InputComponent->RegisterComponent();UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent);}Super::PreInitializeComponents();
}

其实既然ALevelScriptActor是个Actor,那意味着我们当然可以为它添加组件,实际上也确实可以这么做。比如你可以在关卡蓝图里这么干:

而如果你实际意识到关卡蓝图本身就是一个看不见的Actor,你就可以在上面用Actor的各种操作:

在关卡蓝图里的self其实也是个Actor!虽然一般这么干也没什么毛用。
那么好好想想,为啥UE要给你这么一个关卡蓝图界面呢?

在此,我也只能进行一番猜测,ALevelScriptActor作为一个特化的Actor,却把Components列表界面给隐藏了,说明UE其实是不希望我们去复杂化关卡构成的。
假设说UE开放了关卡Component,那么我们在创建组件时就必然要考虑一个问题:哪些是ActorComponent,哪些是LevelComponent,再怎么ALevelScriptActor本质是个Actor,但Level的概念还是要突出,ALevelScriptActor的Actor本质是要隐藏的。所以用户就会多一些心智负担,可能混淆。而如果像这样不开放,大家的思路就都转向先创建个Actor,然后再往之上添加component,思路会比较统一清晰。
再之,从游戏逻辑的组织上来说,Level其实更应该表现为一个Actor的容器。UE其实也是不鼓励在Level里编写太复杂的逻辑的。所以才接着会有了之后的GameMode,Controller那些真正的逻辑控制类(后续会再细讨论)。
所以游戏引擎也并不是说最大化的暴露一切功能给你就是最好的,有时候选择太多了反而容易出错。在这一点上,我觉得UE很好的保持了克制,为我们提供了一个优秀的清晰的不易出错的框架,同时也对高阶用户保留了灵活性。

为何要有主PersistentLevel?

最基础的,当World初始化时,需要Level进行初始化。

首先,World至少得有一个Level,就像你也得先出生在一块大陆上才可以继续谈起去探索别的新大陆。所以这块玩家出生的大陆就是主Level了。

但再考虑到另一问题:Levels拼接进World一起之后,各自有各自的worldsetting,那整个World的配置应该以谁的为主?

AWorldSettings* UWorld::GetWorldSettings( const bool bCheckStreamingPersistent, const bool bChecked ) const
{checkSlow(!IsInActualRenderingThread());AWorldSettings* WorldSettings = nullptr;if (PersistentLevel){WorldSettings = PersistentLevel->GetWorldSettings(bChecked);if( bCheckStreamingPersistent ){if( StreamingLevels.Num() > 0 &&StreamingLevels[0] &&StreamingLevels[0]->IsA<ULevelStreamingPersistent>()) {ULevel* Level = StreamingLevels[0]->GetLoadedLevel();if (Level != nullptr){WorldSettings = Level->GetWorldSettings(bChecked);}}}}return WorldSettings;
}

可以看出,World的Settings也是以PersistentLevel为主的,但这也并不以为着其他Level的Settings就完全没有作用了。

Levels们的Actors和World有直接关系吗?

World除了保存当前场景内的Controllers和Pawns引用之外,调用其他的Actors实际上还是遍历Levels来的。而Levels共享World中的PhysicsScene,所以Actors运用World的物理碰撞。

为什么要在Level里保存Actors,而不是把所有Map的Actors配置都生成在World一个总Actors里?

这肯定也是一种实现方式,好处是把整个World看成一个整体,所有的actors都从属于world,这样就不存在Level边界,可以更整体的处理Actors的作用范围和判定问题,实现上也少了拼接导航等步骤。当然坏处也是模糊了Level边界,这样在加载进一个Level之后,之后再动态释放,就需要再重新再从整体中抽离出部分来释放,这个筛选过程也会产生比较大的损耗。试着去理解UE的权衡,应该是尽量的把损耗平摊(这里是把Level加载释放的损耗尽量减小),才不会产生比较大的帧率波动,让玩家感觉到卡帧。

《InsideUE4》GamePlay架构学习_Level和World相关推荐

  1. UE4 GamePlay架构学习篇

    本帖为原创文章,转载请注明出处. 现在UE4刚免费不久,网上的资料还很少,有一些UE3的大佬出了一些学习的帖子.通过参考前辈的文章+通过查阅官方文档和官方的模板案例测试得出如下结论,供学习参考: 1& ...

  2. 《InsideUE4》GamePlay 架构(二)Level 和 World

    我发现了新大陆! 引言 上文谈到Actor和Component的关系,UE利用Actor的概念组成一片游戏对象森林,并利用Component组装扩展Actor的能力,让世界里拥有了形形色色的Actor ...

  3. 《InsideUE4》GamePlay架构(十)总结

    世界那么大,我想去看看 引言 通过对前九篇的介绍,至此我们已经了解了UE里的游戏世界组织方式和游戏业务逻辑的控制.行百里者半九十,前述的篇章里我们的目光往往专注在于特定一个类或者对象,一方面固然可以让 ...

  4. javaweb k8s_K8S微服务核心架构学习指南 ASP.NET Core微服务基于K8S 架构师必备Kubernetes教程...

    K8S微服务核心架构学习指南 ASP.NET Core微服务基于K8S 架构师必备Kubernetes教程 课程内容是关于Kubernetes微服务架构学习课程,基于K8S开展ASP.NET核心进行微 ...

  5. ZT Android4.2蓝牙基础架构学习

    Android4.2蓝牙基础架构学习 分类: Jellybean Bluetooth Bluetooth 2013-10-13 23:58 863人阅读 评论(3) 收藏 举报 androidblue ...

  6. 时序图 分支_BOOM微架构学习(1)——取指单元与分支预测

    之前在RISC-V的"Demo"级项目--Rocket-chip一文中曾经简介过BOOM处理器的流水线,这次我们开始一个系列,深入学习一下BOOM的微架构,这样对于乱序执行的超标量 ...

  7. bpmn2.0业务过程模型和符号_IT帮业务架构学习小组学习内容

    关于学习内容,担心大家学完的可能不多,所以考虑是先选择一小部分来学.但考虑到业务架构属于组织级学科,本身就要求体系全面,所以还是决定把全套内容放入到本期学习.下面我列举一下我们这8个月在业务架构自主学 ...

  8. SpringCloud微服务架构学习(二)常见的微服务架构

    SpringCloud微服务架构学习(二)常见的微服务架构 1.Dubbo 阿里开源微服务框架 官网地址:http://dubbo.apache.org/en-us/ 简介: Dubbo是阿里巴巴SO ...

  9. UE4 GamePlay架构

    UE4 GamePlay架构 前言 GamePlay架构_1_Actor和Component GamePlay架构_2_Level和World GamePlay架构_3_GameInstance Ga ...

最新文章

  1. 机器学习训练中常见的问题和挑战!
  2. 基于深度神经网络的图像缺损修复方法综述
  3. TensorFlow CIFAR-10数据集
  4. DP(01背包) UESTC 1218 Pick The Sticks (15CCPC C)
  5. win7 ghost 纯净版最新系统下载
  6. 千字14图--Python慎用assert语句阻止代码执行
  7. html css 命名规范,浅谈css命名规则(新手必看)
  8. 让旧 Windows 电脑一键变成 Chromebook,Google 收购 Neverware!
  9. Rotate List leetcode
  10. 从.net复制源代码中国农历阵列,必要做日历
  11. centos7进程限制、打开文件限制等
  12. 【精简操作】Mathtype安装出现错误“53”/未找到.wll文件/选项卡灰色等问题
  13. 计算机五年计划个人,教师个人五年发展规划
  14. JS ASCII码转换代码
  15. java怎么判断field类型_java技巧:反射判断field类型方法
  16. 什么是“富人思维”?
  17. 2014年校园招聘新签约工资待遇
  18. python自定义cmap_python自定义cmap_Python matplotlib的使用并自定义colormap的方法
  19. Idea中git进行回滚版本操作和查看修改记录
  20. linux 的gz命令详解,linux tar.gz压缩解压命令详解

热门文章

  1. linux7配置dns服务,RHEL7 DNS配置
  2. 2020蚂蚁集团招股说明书.pdf
  3. PM如何写好产品需求文档
  4. java 图文验证码
  5. 通达信日线数据用转换为excel、csv和feather格式
  6. 社群运营的用户画像分析有哪些方法?
  7. 前淘宝工程师:12306几乎是一个奇迹
  8. GScoolink GSV2201 TypeC/DP to HDMI2.0
  9. 关于递归删除链表结点时为什么不会出现断链问题
  10. org.git.mm.mysql_异常:java.lang.ClassNotFoundException: org.gjt.mm.mysql.Driver