第一部分:打包
需求分析
项目的客户端主主体是App,其中包含了游戏功能;为了降低用户使用门槛,App主体只包含UE基本的功能(core),其余大部分游戏资源和游戏功能使用Pak的方式来进行下载和加载。游戏的DS的包包含全部资源,运营根据玩法来部署玩法DS:比如BallServer,ShootServer等等。
所以可以总结出打包脚本的需求和目标:
1.打包DSServer,包含当前游戏版本的全量资源,有几个玩法就打包几个DSServer。
2.打包客户端,包含当前版本的基本功能(core),分Android 和 IOS。
3.使用HotPatcher插件打包Pak包,包含每个游戏的游戏资源(空间包),基础资源包(包含公共游戏资源和Lua脚本)
其实项目依照现在的设计,还有如下问题:
如果有需求导致底层程序框架(c++.修改),那么App就需要更新So/framework或者main.obb中的资源发生了改变,App也需要升级,否则链接线上服务器会导致崩溃等错误。并且安卓很多渠道不支持强制版本更新。理想的情况下,App中应只包含一个通用的UE动态库/framework,这个动态库应该是经过抽象和总结的,包含UE与移动端相互通信的所有函数并且短期内不会更新(统一接口)
客户端,DS,Pak包的版本匹配等详细业务逻辑再次不一一赘述
修改Config文件
在脚本打包过程中,需要修改config文件中的配置来满足我们打包某端时进行特定的设置的的需求,例如:打包Server时根据打包的是哪个玩法的Server来设置DS默认地图,打包客户端时动态的将不需要打包的文件夹(玩法的资源)添加到NeverCook的设置中等。
我写了一个BlueprintLibrary的插件,来在打包脚本中调用插件的函数来操作UE的.ini文件,仅供参考:
https://github.com/BPXXX/UEScriptPackageHelper
打包脚本
有了上述插件之后,我们脚本打包的思路就梳理出来了,其实一共就两步:
1.根据打包的平台和Target设置ini文件的配置。
2.运行UAT命令进行打包
下面用打包客户端的代码举例:
打包客户端函数:
def PackageClientCore(pConfig,pPlatform) :
FixClientCoreConfig()
OutDir = OutputRoot + f"/ClientCore/"+pPlatform
LogDir = LogRoot + f"/Client/"+pPlatform + ".txt"
RunUATPackage("Client",pPlatform,pConfig,OutDir,LogDir)
修改打包配置
def FixClientCoreConfig():
#添加基础设置
for setting in BaseSettings:
unreal.ScriptPackageHelperBPLibrary.set_string_array(GameIniPath,setting["Section"],setting["Key"],setting["Paths"])
unreal.ScriptPackageHelperBPLibrary.add_array_item(GameIniPath,"/Script/UnrealEd.ProjectPackagingSettings","DirectoriesToNeverCook",MakeConfigFolderPath("/Game/sensen/Patches"))
#覆盖客户端的两个ServerUrl
unreal.ScriptPackageHelperBPLibrary.set_string_value(GameIniPath,"/Script/GameplayModule.SenSenGameInstance","ServerURL",IniConfig.ClientServiceUrl)
unreal.ScriptPackageHelperBPLibrary.set_string_value(GameIniPath,"/Script/ViewDynamicLoadRuntime.CharacterViewComponent","ServerURL",IniConfig.ClientServiceUrl)
运行UAT命令进行打包
def RunUATPackage(pTarget,pPlatform,pConfig,pOutDir,pLogDir):
ProjectDir = ProjectPath + "sensen_ue_mobile.uproject"
Platform = "Android"
PackageConfig = pConfig
Unrealexe = EditorExePath
EditorIOPort = "52540"
UATFileDir = UATPath
if pTarget == 'Client':
target = "sensen_ue_mobile"
Platform = pPlatform
cmd = ""
if Platform == "Android":
cmd = f"{UATFileDir} -ScriptsForProject={ProjectDir} Turnkey -command=VerifySdk -platform={Platform} -UpdateIfNeeded -EditorIO -EditorIOPort={EditorIOPort} -project={ProjectDir} BuildCookRun -nop4 -utf8output -nocompileeditor -skipbuildeditor -cook -project={ProjectDir} -target={target} -unrealexe={Unrealexe} -platform=Android -cookflavor=ASTC -stage -archive -package -build -pak -compressed -prereqs -archivedirectory={pOutDir} -manifests -clientconfig={PackageConfig} -nocompile -nocompileuat"
elif Platform == "IOS":
cmd = f"{UATFileDir} -ScriptsForProject={ProjectDir} Turnkey -command=VerifySdk -platform={Platform} -UpdateIfNeeded -EditorIO -EditorIOPort={EditorIOPort} -project={ProjectDir} BuildCookRun -nop4 -utf8output -nocompileeditor -skipbuildeditor -cook -project={ProjectDir} -target={target} -unrealexe={Unrealexe} -platform=IOS -stage -archive -package -build -pak -compressed -prereqs -archivedirectory={pOutDir} -manifests -clientconfig={PackageConfig} -nocompile -nocompileuat"
elif pTarget == 'Server':
target = "sensen_ue_mobileServer"
Platform = "Linux"
cmd = f"{UATFileDir} -ScriptsForProject={ProjectDir} Turnkey -command=VerifySdk -platform={Platform} -UpdateIfNeeded -EditorIO -EditorIOPort={EditorIOPort} -project={ProjectDir} BuildCookRun -nop4 -utf8output -nocompileeditor -skipbuildeditor -cook -project={ProjectDir} -target={target} -unrealexe={Unrealexe} -platform=Linux -stage -archive -package -build -pak -compressed -prereqs -archivedirectory={pOutDir} -manifests -server -noclient -serverconfig={PackageConfig} -nocompile -nocompileuat"
# 创建文件夹
output_folder = os.path.dirname(pLogDir)
os.makedirs(output_folder, exist_ok=True)
print(f"cmd: {cmd}")
cmd = f"{cmd} >> {pLogDir}"
#ret = os.system(cmd)
ret = subprocess.run(cmd, shell=True, check=True)
if ret.returncode == 0:
print(f"Package Success Platform:{Platform} PackageConfig:{PackageConfig}")
else:
print(f"Package Failed Platform:{Platform} PackageConfig:{PackageConfig}")
raise Exception("project build faild!")
打包产物
本项目的DSServer部署在Linux上的,具体的部署方案不在此赘述,下面重点讨论客户端。
IOS:
想要UE项目打包为framework,嵌入到移动端IOS工程里,需要勾选Build project as a framework。
打包后,会发现打包文件下有一个Wrapper文件夹,里面的xcode工程就是UE生成的默认工程
Android
第一部分:嵌入到移动项目中
IOS
IOS平台下,UE项目作为framework的一些坑,我都记录在另外一篇博客里:https://blog.csdn.net/weixin_43866011/article/details/136842291
下面简述一下工作流程中有用的信息。
设置路径:
可以修改烘焙内容或者framework的路径等等(如下图)
添加Include路径:
因为是混合开发的项目,所以会有OC端调用UE函数的情况,需要在Header Search Paths添加对应的.h文件。比如在我的项目里写了一个UIOSFunctionLibrary,里面实现了一些让OC端调用UE函数的函数,那么我就需要在路径里加上这个.h文件的路径就好了。需要注意的是UE作为framework这个功能是实验性的,起码我使用UE5.1生成出来的xcode工程,searchPaths是不全的,需要自己补全。
下面我们看看xcode工程里在Build的时候发生了什么:
打开xcode工程,点击Product -> Scheme ->Edit Scheme-> Build Phases
将烘焙好的UE资源拷贝到build目录里。
将framework拷贝并签名
所以每次IOS项目的UE的内容(资源或者framework)修改时,只需要替换对应文件夹下的内容重新Build打包就好了。
Android
安卓端的UE混合开发相较于IOS端就没有那么复杂,我写过一篇安卓的混合开发的博客,包括JNI调用的例子:
https://blog.csdn.net/weixin_43866011/article/details/129535092
先这样记录一下,后续针对一些细节再详细更新,如果有问题或者不够详细的地方请大家指正和提出。