定制软件的网站,合肥 企业网站设计公司,天津建设工程信息网站,营销策划公司是干嘛的GameInstance这个类可以跨关卡存在#xff0c;它不会因为切换关卡或者切换游戏模式而被销毁。然而#xff0c;GameMode和PlayController就会再切换关卡或者游戏模式时被引擎销毁重置#xff0c;这样他们里面的状态就不能被保存。比如#xff0c;你想再下一个关卡中知道上一…GameInstance这个类可以跨关卡存在它不会因为切换关卡或者切换游戏模式而被销毁。然而GameMode和PlayController就会再切换关卡或者游戏模式时被引擎销毁重置这样他们里面的状态就不能被保存。比如你想再下一个关卡中知道上一个关卡游戏角色的位置这时就得在GameInstance中保存游戏角色在上一个关卡的位置。用户登录的账号信息也可以保存在GameInstance中。每一个关卡都可以对应不同的GameMode和PlayController 引言 上篇我们讲到了UE在World之上继续抽象出了Player的概念包含了本地的ULocalPlayer和网络的UNetConnection并以此创建出了World中的PlayerController从而实现了不同的玩家模式策略。一路向上依照设计里一个最朴素的原理自己是无法创建管理自身的所以Player也需要一个创建管理和存储的地方。另一方面上文提到Player固然可以负责一些跟玩家相关的业务逻辑但是对于World之上协调管理的逻辑却也仍然无处安放。如果是有一定的游戏开发实战经验的朋友也一定能体会到在自己开发的游戏中往往除了我们上文提到的Player类常常会创建一个Game类比如BattleGame、WarGame或HappyGame等等。Game之前的名词往往都是游戏的开发代号。这倒不是因为我们如此热衷创建各种Manager类而是确实需要一个大管家来干一些协调的活。一般的游戏引擎都只会暴露给你它自己引擎的管理类如DirectorEngine或Application之类的但是却不会主动在Game类的创建管理上为你提供方便。游戏引擎的出现最开始其实只是因为一些人发现游戏做着做着有一大部分功能是可以复用的于是就把它抽离了出来方便做下一款游戏。在那个时候人们对游戏还是处于开荒探索的阶段游戏引擎只是一大堆功能的复合体就像叮当猫的口袋一样互相比谁掏出的工具最强大。然而即使到了现代绝大部分的引擎的思想却还停留在上个世纪仍然执着于罗列Feature列表却忘了真正的游戏开发人员天天面对的游戏业务逻辑编写没有思考在那方面如何也下一番功夫去帮助开发者。人们对比UE和其他游戏引擎时也会常常说出的一句话是“别忘了Epic自己也是做游戏的”虚幻竞技场战争机器无尽之剑……。从这一点也可以看出UE很大的得益于Epic实战游戏开发的反哺这一方面Unity就有点吃亏了没有自己亲自下手干脏活累活就不懂得急人民群众之所急。所以如果一个游戏引擎能把GamePlay也做好了那就不止是口袋了而是知你懂你的叮当猫本身。 GameInstance 简单的事情就不用多讲了UE提供的方案是一以贯之的为我们提供了一个GameInstance类。为了受益于UObject的反射创建能力直接继承于UObject这样就可以依据一个Class直接动态创建出来具体的GameInstance子类。我并不想罗列所有的接口UGameInstance里的接口大概有4类 引擎的初始化加载Init和ShutDown等在引擎流程章节会详细叙述Player的创建如CreateLocalPlayerGetLocalPlayers之类的。GameMode的重载修改这是从4.14新增加进来改进本来你只能为特定的某个Map配置好GameModeClass但是现在GameInstance允许你重载它的PreloadContentForURL、CreateGameModeForURL和OverrideGameModeClass方法来hook改变这一流程。OnlineSession的管理这部分逻辑跟网络的机制有关到时候再详细介绍目前可以简单理解为有一个网络会话的管理辅助控制类。而GameInstance是在GameEngine里创建的先不谈UEditorEngine void UGameEngine::Init(IEngineLoop* InEngineLoop)
{//[...]// Create game instance. For GameEngine, this should be the only GameInstance that ever gets created.{FStringClassReference GameInstanceClassName GetDefaultUGameMapsSettings()-GameInstanceClass;UClass* GameInstanceClass (GameInstanceClassName.IsValid() ? LoadObjectUClass(NULL, *GameInstanceClassName.ToString()) : UGameInstance::StaticClass());if (GameInstanceClass nullptr){UE_LOG(LogEngine, Error, TEXT(Unable to load GameInstance Class %s. Falling back to generic UGameInstance.), *GameInstanceClassName.ToString());GameInstanceClass UGameInstance::StaticClass();}GameInstance NewObjectUGameInstance(this, GameInstanceClass);GameInstance-InitializeStandalone();}//[...]}
//在BaseEngine.ini或DefaultEngine.init里你可以配置GameInstanceClass
[/Script/EngineSettings.GameMapsSettings]
GameInstanceClass/Script/Engine.GameInstance 先从配置中取出GameInstanceClass然后动态创建一目了然。 思考GameInstance只有一个吗一般而言是的。对于我们自己开发的游戏而言我们始终只需要关注自己的一亩三分地那么你可以认为你子类化的那个GameInstance就像个单件一样全局唯一只有一个从游戏的开始到结束。但既然是本系列文章的读者自然也是不甘于只了解这么多的。正如把网络连接也当作Player这个概念一样我们此时也需要重新审视一下Game这个概念。什么是一个Game?对于玩家而言Game就是从打开到关闭的这整个过程说展现的内容。但是对于开发者来说这个概念就需要扩充一下了。假设有个引擎支持双击图标一下子开出4个窗口来让4个玩家独立运行你能说得清这是一个Game还是4个Game在运行吗哪一种说法都能自圆其说但关键是哪一种概念划分能更好的让我们管理组织结构。因此针对这种情况如果是这4个窗口一点都不互相关联或者只是单独的共用地图资源那么用4个Game的概念来管理就更为合适。如果这4个窗口里运行的内容实际上只是在同一个关卡里本地对战内存里互相直接通信那用一个Game加上4个Player的概念就会变得更合适。所以针对这点你可以把Game理解为就像进程一样进程可以在同一个exe上多开Game也可以在同一份游戏资源上开出多个运行实例进程之间可以互相通信协作Game的不同实例也可以互相沟通不管是内存中直接在Engine的协调下完成还是通过Socket通信。另一方面一般游戏引擎都只是服务于游戏本身而对于其配套的各种编辑器就像是对待外来的打工者一样编辑器往往只负责最终输出游戏资源。由于应用场景的不同编辑器的架构也常常根据相应平台而定五花八门有用QtMFCWPF等各种平台UI框架。而对于另一些有大志向的引擎比如Unity和UE其编辑器就是采用引擎自绘的方案其优劣暂不分析以后聊到UI框架再细说。所以游戏引擎这个时候就更加的拔高了一个层次就不再只是个“游戏”引擎了而是个“程序”引擎了。因此UE本身的这套框架不光要服务游戏还要服务编辑器甚至是另外一些辅助程序。所以Game的概念也就扩充到了更上层的“程序”变得更广义了。言归正传因为UE的这套Editor自绘机制还有PIEPlayInEditor进程里其实是可以同时有多个GameInstance的如正在编辑的EditorWorld所属于的和Play之后的World属于的。我想这也就是为何UE把它叫做GameInstance而不是简单的Game的含义其名字中就隐含了多个Instance的深意。我们现在再次回顾一下(GamePlay架构三WorldContextGameInstanceEngine)最后的结构图了解一下GameInstance又是被谁管理的当初我们是以数据的视角在考察WorldContext的从属的时候讨论过这个结构。现在以逻辑的角度明白了GameInstance也会被上层的Engine实例出来多个就会有更深的理解了。再扩充一下在Engine之下允许同时运行多个GameInstance还会有许多其他好处就像操作系统允许一份资源运行多个进程实例一样Engine就可以站在更高的层次上管理协调多个Game同时也能更加的深入到Game内部去得到更多的优化。比如未来要实现游戏本地的host多开并管理或者在Server同时Host一个Map的多个实例(现在只能一个……还是有很多工作要做啊)这对于开发MMO网游是非常需要的功能虽然目前UE在这一块的具体工作还有些薄弱但至少可扩展的可能性是已经保证了的动手能力强的高手可以在此基础上定制。一般而言间接多一层就多了一层的灵活性所以很多引擎其实就是把Game和Engine揉在了一块没有为了GamePlay框架而分开。 思考哪些逻辑应该放在GameInstance第二个惯例的问题是这一层应该写些什么逻辑。顾名思义既然是作为游戏中全局唯一的长者我们就应该给他全局的控制权。在逻辑层面GameInstance往下看是 WorldsLevel的切换实际发生地是Engine而GameInstance可以说是UE之神其下的唯一代言人所以GameInstance也可以代之管理World的切换等。我们可以在GameInstance里实现各种逻辑最后调用Engine的OpenLevel等接口。Players虽然一般来说我们直接控制Players的机会不多都是配置好了就行。但要是到了需要的时候GameInstance也实现了许多的接口可以让你动态的添加删除Players。UIUE的UI是另一套World之外的系统虽然同属于Viewport的显示之下但是控制结构跟Actor们并不一样。所以我们常常会需要控制UI各种切换的业务逻辑虽然在Widget的Graph里也可以写些简单的切换但是要想复用某些切换逻辑的时候在特定的Wdiget里就不合适了而GameMode一方面局限于Level另一方面又只存在于ServerPlayerController也是会切换掉的同时又只存在于World中所以最后比较合适的就剩下GameInstance了以后当然有可能了可能会扩展出个UI的业务逻辑Manger类不过那是后话了。全局的配置也常常需要根据平台改变一些游戏的配置Execute一些ConsoleCommandGameInstance也是这些命令的存放地。游戏的额外第三方逻辑如果你的游戏需要其他一些控制比如自己写的网络通信、自定义的配置文件或者自己的一些程序算法如果简单的话GameInstance也可以一放等复杂起来了也可以把GameInstance当作一个模块容器你可以在里面再扩展出来其他的子逻辑模块。当然如果是插件的话还是在自己的插件Module里面自行管理逻辑然后把协调工作交给GameInstance来做。而在数据层面上我们层层上来已经有了针对一个Player的Contoller的PlayerState也有了针对World的GameMode的GameState到了更全局之上自然的GameInstance就应该存储一些全局的状态数据。所以你可以在GameInstance的成员变量中添加一些全局的状态或者是那些想要在Level之外持续存在的对象。不过需要注意的一点是GameInstance成员变量中最好只保存那些“临时”的数据而对于那些想要持久序列化保存的数据我们就需要接下来的SaveGame了。把持久的数据直接放在SaveGame用的时候直接读取出来之后再直接在其上更新好处是只用维护一份省得要保存的时候还去想到底要选GameInstance的哪些成员变量中来保存一开始就设计选好以后就方便了。 SaveGame UE连玩家存档都帮你做了得益于UObject的序列化机制现在你只需要继承于USaveGame并添加你想要的那些属性字段然后这个结构就可以序列化保存下来的。玩家存档也是游戏中一个非常常见的功能差的引擎一般就只提供给你读写文件的接口好一点的会继续给你一些序列化机制而更好的则会服务得更加周到。UE为我们在蓝图里提供了SaveGame的统一接口让你只用关心想序列化的数据。USaveGame其实就是为了提供给UE一个UObject对象本身并不需要其他额外的控制所以它的类是如此的简单以至于我能直接把它的全部声明展示出来 UCLASS(abstract, Blueprintable, BlueprintType)
class ENGINE_API USaveGame : public UObject
{/*** see UGameplayStatics::CreateSaveGameObject* see UGameplayStatics::SaveGameToSlot* see UGameplayStatics::DoesSaveGameExist* see UGameplayStatics::LoadGameFromSlot* see UGameplayStatics::DeleteGameInSlot*/GENERATED_UCLASS_BODY()
}; 而UGameplayStatics作为暴露给蓝图的接口实现部分其内部的实现是先在内存中写入一些SavegameFileVersion之类的控制文件头然后再序列化USaveGame对象接着会找到ISaveGameSystem接口最后交于真正的子类实现文件的保存。目前的默认实现是FGenericSaveGameSystem其内部也只是转发到直接的文件读写接口上去。但你也可以实现自己的SaveGameSystem不管是写文件或者是网络传输保存到不同的地方去。或者是内部调用OnlineSubsystem的Storage接口直接把玩家存档保存到Steam云存储中也可以。因此可见单单是玩家存档这件边角的小事UE作为一个深受游戏开发淬炼过的引擎为了方便自己也同时造福我们广大开发者已经实现了这么一套完善的机制。关于存档数据关联的逻辑再重复几句对于那些需要直接在全局处理的数据逻辑也可以直接在SaveGame中写方法来实现。比如实现AddCoin接口对外隐藏实现对内可以自定义附加一些逻辑。USaveGame可以看作是一个全局持久数据的业务逻辑类。跟GameInstance里的数据区分就是GameInstance里面的是临时的数据SaveGame里是持久的。清晰这一点区分到时就不会纠结哪些属性放在哪里哪些方法实现在哪里了。注意一下SaveGameToSlot里的SlotName可以理解为存档的文件名UserIndex是用来标识是哪个玩家在存档。UserIndex是预留的在目前的UE实现里并没有用到只是预留给一些平台提供足够的信息。你也可以利用这个信息来为多个不同玩家生成不同的最后文件名什么的。而ISaveGameSystem是IPlatformFeaturesModule提供的模块接口关于模块的机制等引擎流程章节再说吧目前可以简单理解为一个单件对象里提供了一些平台相关的接口对象。 总结 至此我们可以说已经介绍完了GamePlay下半部分——逻辑控制。在蓝图层UE并不向BP直接暴露Engine概念即使在C层在实现GamePlay业务时也是很少需要真正直接操纵Engine的时候。如果GamePlay已经足够好那么Engine自然就可以隐居幕后了。UE用GameInstance实现了全局的控制并支持多GameInstance来实现编辑器最后在存档的时候还可以用到SaveGame的方便的接口。下篇就是GamePlay章节的最终章我们将会对GamePlay架构的一到九篇进行回顾归纳总结巩固以一个承上启下总览的眼光再来重新审视一下UE的整套GamePlay框架下个章节见。 引用 SaveGameUE4.14 作者的话GamePlay架构9篇下来我也在探索不同书写风格希望能够为后续的其他章节确定下来基调。对于文风、内容组织或其他问题还请各位能直言批评指教留言私信全都欢迎。目前也处在准备下个大章节UObject的阶段也希望能有更多建议多谢。 转载自https://www.cnblogs.com/fjz13/p/6109330.html转载于:https://www.cnblogs.com/timy/p/8689028.html