《游戏:资源从美术到真机》

#《游戏:资源从美术到真机》

上一节,讲了游戏从启动到登录页,经历的过程。
这一节,讲下我们在游戏界面的那些元素,从美术打开PS开始,是一步步怎么样出现到我们的游玩界面中的。

美术资源有很多种类型,游戏资源类型, ui、音效、模型、shader、icon、字体、场景、特效

先简介一下它们分别是什么:


ui:

即UserInterface, 是玩家在游戏中看到的一个个界面模块。
比如背包界面、登录界面、角色界面、技能界面等等,也是在游戏开发中改动、更新比较频繁的模块。
在打包时,
也是比较复杂的模块。要处理依赖关系等。
格式:prefab,依赖png

音效:

即玩家在游戏中主动触发的交互声音,及背景音乐等。
比如进游戏后,需要播放背景音乐。 点击按钮时,播放点击音效。放技能时,播放技能音效等。
通常声音分为两种类型进行处理。一种是即时音效。就是交互一次,播放一次的类型,这种声音的特点是时长短。另一种是背景音乐。需要常驻在内存中,进行循环播放。
在打包时,
比较简单,没有依赖关系。
格式:mp3\wav

模型:

通常是指3d游戏中,那些角色、npc、怪物等模型。
在mmo中用得最多,很多任务等,都要推进对应的npc,击杀对应的怪物,这些都需要和模型支撑。
在打包时,
模型资源身上的依赖,如材质、贴图、fbx、模型动画等,都需要计算。
格式:prefab,依赖png,fbx,mat,animation

shader:

着色器。可以用来做很多视觉效果,特效中用得比较多。
shader的过程,是属于gpu模块。其中每个在屏幕上显示的元素,都需要经过shader的函数渲染。所以可以通过调整shader来达到各种特殊的视觉艺术效果。如镜面反射、半透明、被遮挡后的描边等。
shader的打包,
也比较简单,因为没有依赖到其它信息,且本身占用很小,所以通常可以在游戏开始时,加载常驻到内存。
格式:shader

icon图片:

在界面上,会看到各种图片,人物图片、物品图片、按钮图片等。
图片是游戏中最常见的元素之一。
icon图片的打包,
也比较简单,因为没有依赖到其它资源。但有一点要注意,因为icon会被频繁的展示与隐藏,所以需要做引用计数,当长时间不用某icon时,才将其卸载。以免频繁加载卸载导致内存压力。
格式:png

字体:

在界面上会看到很多文字,介绍、对话之类的文字信息。
文字需要字体才能显示的,在unity字体相当于一张材质球,该材质中的贴图,该贴图中记录了大部分常用文字。
当需要显示某个文字时,在该贴图找到对应的图片信息部分显示出来,就有了游戏中的文字。
打包时,
字体本身没有依赖到其它的信息,所以打包也比较简单。
格式:ttf

场景:

场景的概念,类似关卡。也就是玩家所处的一个场景空间。
当游戏进行到某种情况下,需要进入另外的故事,就需要跳转场景。
场景本身会依赖很多种信息,最主要的是关照信息。需要提前烘焙好。
在加载场景时,去加载烘焙信息。
打包时,
需要处理依赖,比较复杂,过程也比较久。
格式:scene

特效:

特效是游戏中那些通常来说比较漂亮的效果,比如发光的球体,闪烁的提示,物品获得时的跳动效果等。
特效通常依赖也比较多,比如材质、贴图、shader、动画文件等。
打包时,需要处理上述依赖。
格式:prefab


动画资源、材质球资源

上面介绍了常见的需要打包的游戏资源,某些资源也是有用到,却没有单独提出来打,比如动画资源、材质球等。

这是有原因的。

先说下动画资源为什么没打包
动画资源通常是和模型或者特效资源一对一绑定的,也就是说,一个动画文件并不会被用于多个对象中。
所以也用拆分开来打,在打包特效、或者动画时,连同它们一起打包即可。
在加载时,也一样,加载该特效包后,先加载其中的动画,再加载其中特效即可。

再说下材质球为什么没打包。
因为材质球本身是对shader提供的参数支持,类似一个配置文件。它需要用到哪个贴图,应用哪些参数,进行一个怎样的着色过程。这样的一个描述。
所以既然已经单独打包了shader和贴图,那么材质球可以转换成一份配置文件,也就是json文本。
当模型或者特效打包时,这份json文本也随之进行打包到对应的目录。
当加载时,也从这里面去读取配置,并创建材质球去应用这些配置。

相比较而言,字体、shader、音效等不依赖其它资源而存在资源,会被进行打包,是因为可能会被多个其它资源引用到。
并不便于随依赖它的资源一起打包。否则造成打包了多份相同的资源,造成冗余。


各岗位工具流(策划、美术、程序)

上面,我们讲完游戏中有哪些常用资源。
游戏并不是一个部门完成的,通常至少需要三种岗位——策划、美术、程序。

策划在策划的工具流中提出需求,需要哪些美术资源,及配置游戏资源,决定哪里需要用到哪些资源
工具流中通常有excel配置表、mysql调参数、后台该参数等

美术在美术流工具中创造游戏资源
工具流中通常有ps做贴图、unity做特效和场景、maya做模型等

程序在程序工具中,用代码实现资源显示在游戏中
工具流中通常有unity搭建ui界面、lua实现客户端功能、c++实现服务器功能等

所以,游戏资源最开始是从美术人员,先开始创作的。让我们从这里开始。


美术岗位细分

美术人员按资源细分,又分为场景、ui贴图、原画、模型、特效、动作等。

如果是2D游戏,修改比较多的是,如ui界面、以及界面上需要的图集、icon、2D特效需要替换。
如果是3D游戏,可能模型、场景、3D特效之类的,也比较多。
再有就是一些通用的,字体、shader、音效这些那些,美术制作的资源。


资源流程五大步

它们都是经过了怎么样的流程,最终得以在游戏界面上显示的?
这也是一个比较复杂的过程。先说四个的思路过程:
A、同步:各种资源,是在美术的工作目录中制作的,需要先将同步美术资源至程序员的目录下,让程序员才能用起来。
B、打包:各种资源,根据类型的不同、依赖的不同,打包资源的方式也不同
C、发布:各种资源,根据需要发布的平台不同,需要打成不同平台资源,几个平台就打几份,因为每个平台读取的资源格式不一样
D、加载:在运行游戏时,需要调用对应的加载方法,将资源加载出来,才能在游戏中展示。


资源流程五大步——同步(美术资源至程序目录)

同步前言说明
!注意:虽然大体上都是这四步,但不同类型的资源走的过程会有区别。
美术与程序的产出环境也不同。
美术是在美术目录提交,
程序生产资料是在程序目录提交(通常是游戏引擎目录下)。
所以美术的资源做好后并不会直接能让程序使用。需要有专门的”同步“操作,将美术资源同步至程序目录。

而根据美术资源的不同,如icon、原画等资源,是在ps中制作的,无须用到游戏引擎。
这类资源在原理上并不复杂,没有资源的依赖,通常拿来就能使用,不会存在ps中能显示,但导入Unity后就显示异常的效果。
不需要经过特殊检查。

所以这类不需要引擎工具去同步的资源,
包括:字体、icon、shader、音效。
这类资源,手动由程序员去拷贝至程序项目路径。

这里共有四种资源,
还剩下另外四种,其中的ui资源,是由程序在引擎中制作,所以也不存在同步。
于是剩下场景、特效、模型,需要经过特殊同步。

同步具体过程
同步并不是简单的将从美术的资源目录,拷贝至程序的资源目录。
需要根据资源的类型,进行该类型的检查。

A拿场景同步举例:
A美术做好场景资源,提交美术svn
A 在后台web界面,点击”同步场景资源“

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VpFEEfOW-1651915156021)(en-resource://database/5083:1)]

A web发送php指令,在数据库插入”场景待打包“的一条新数据,状态设置为”0“,表示待打包

$sql = "insert into foreign_asset_build_list ( add_time, type, system, language, comment, status) values ( now(), '" . $type . "', '" . $system . "', '" . $language . "', '" . $comment . " ', 0);";

A web后台Python服务,每5秒,定时轮询,查询该数据库中,是否有状态为”0“的待打包的数据。

A将其状态改为”1“,表示执行中

update_sql = f"update {db_table} set status = 1 , begin_build_time =  {nowtime}  "\
                             f"where id = {async_event_context.event_id}"

A从数据库查询该数据的人完整信息,如平台类型、资源类型等。
A先对资源初始化, svn clean Up, svn revert -R, svn up

        await shell_command(f"svn cleanup {self.project_path}/Assets/_Resource")
        await shell_command(f"svn revert -R {self.project_path}/Assets/_Resource")
        await shell_command(f"svn up --force --accept theirs-conflict {self.project_path}/Assets/_Resource")

A根据资源的类型,定义需要拷贝的源路径和目标路径,也就是artPath和programPath

            copy_art_path = f"{self.game_art_path}/Assets/{self.asset_types['scene']}/{self.resource_path}/components"
            copy_program_path = f"{self.project_path}/Assets/_Resource/components"

A然后shell指令,先删除清空目标路径

        cmd = f"rd /s/q {target}"
        await shell_command(cmd)
        
subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)

A再拷贝路径文件

        cmd = f"xcopy /e/y/i/d/r {source} {target}"
        await shell_command(cmd)

A再使用子进程,调用Unity中自定义的检查资源的函数

        check_command = f"{UNITY_PATH} -executeMethod AssetsUpgradeCheck.CheckMissSceneRes -quit -batchmode " \
                        f"-logFile {scene_check_log_file} -projectPath {self.project_path}"
                        
    child = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = child.communicate()

A 在该函数中,Unity将读取场景组件中每一个材质球,进行检查

读取材质球
Material mat_file = AssetDatabase.LoadAssetAtPath(file, typeof(Material)) as Material;
检查看它的材质球是否为空
if (mat_file == null)
检查是否包含了错误ErrorShader
else if (mat_file.shader.name.Contains("ErrorShader"))
检查贴图是否为空
if (mat_file.mainTexture == null)

序列化资源
SerializedObject psSource = new SerializedObject(mat_file);
根据参数,获取其属性值
SerializedProperty emissionProperty = psSource.FindProperty("m_SavedProperties");

来判断
texture2d = (Texture2D)psSource.objectReferenceValue;

A再将上面如果有错误的信息(如没有贴图),写入到错误文件中

CreateTargetFolder

FileStream prefab_file = new FileStream("scene_check_file/miss_res.log", 
FileMode.Create);

        StreamWriter prefab_stream = new StreamWriter(prefab_file, Encoding.UTF8);
        Debug.Log("Count:" + miss_list.Count);
        string context = stringBuilder.ToString();
        prefab_stream.Write(context);
        prefab_stream.Flush();
        prefab_stream.Close();
        prefab_file.Close();


AUnity中执行就结束了,现在python中检查刚才的文件,如果内容为空,说明没有错误。否则就将错误打印出来给用户。

miss_log = open(program_check_miss_data, 'r', encoding=self.coding_rule).read()

A如果检查都没有错误,再执行上面的”同步资源“的过程。

A然后再subprocess.Popen,执行svn的提交

await shell_command(f"svn st {self.project_path}/Assets/_Resource | for /f \"tokens=1,2\" %i in ('findstr /B \"?\"') do svn del %j")
        await svn_add(f"{self.project_path}/Assets/_Resource")
        await svn_submit_files(f"{self.project_path}/Assets/_Resource", f"{self.async_event_context.event_parameters['language']} "
f"sync {self.async_event_context.event_parameters['type']} "
f"sync id {self.async_event_context.event_id}")

到这里,美术资源就从美术工作目录同步至了程序目录。


资源流程五大步——打包(美术资源至程序目录)

下一步,程序将资源打包成.unity3d后缀的assetBundle资源,使之可以热更新。

这里拿音效资源举例:
@@音效相关人员,找到需要的音效资源,在对应svn目录提交,
通常格式mp3、wav两种程序员更新资源下来,并统一放在音效目录调用引擎工具,打包音效的命令在自定义的编辑器工具界面中, 
程序更新下来,点击”打包sound资源“
读取路径 Assets/Resource/sound“FileHelper.FindFileBySuffix, 
通过后缀,检索其下的.mp3格式的全部文件,对资源的信息,生成md5。 
并读取资源旧的信息中记录的md5。
进行对比,(为了减少打包需要的时间,只对有改动的部分打包)
如果相同,则该资源跳过打包。
如果不同,则说明是新的资源,需对其设置ab名及后缀名。

  • 根据资源路径获取其Importer:
AssetImporter assetImporter = AssetImporter.GetAtPath(sound); 
  • 设置abName:
assetImporter.assetBundleName = AssetsBuildTool.OsType + "/" + sound.Replace("Assets/Resource/", "").Replace(".mp3", "") + NewLastName; 
  • 设置ab后缀:
assetImporter.assetBundleVariant = BuildStaticCommon.ASSETS_END_EXTEND;
  • 调用unity统一打包API的命令:
BuildPipeline.BuildAssetBundles(outPath, BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.DeterministicAssetBundle, EditorUserBuildSettings.activeBuildTarget);
  • 通过manifest获取全部的AB(二维数组):
string [] assetBundleManifestAssetBundles = assetBundleManifest.GetAllAssetBundles();

manifest是unity打包时,生成的记录资源依赖关系的文件因为有些资源比较复杂,会引用到其它的资源。需要先加载出其它资源,再最后加载出本体,才正常。所以需要依赖文件,当加载某个资源时,先加载出其依赖的资源。

  • 通过assetBundleManifest,传入上一步获取的ab名,得到该ab的依赖的资源信息(二维数组)
string[] assetBundleManifestDependencies = assetBundleManifest.GetAllDependencies(assetBundleManifestAssetBundles[i]);
  • 将这些依赖资源与主资源,添加到自定义的依赖类中,
  • 并将依赖类转成json格式,
  • 再将json写入本地文本文件xxx_Setting.txt占。
  • 删除*.manifest,以便下次打包
  • 将所有的ab名设置为""(空)
  • 再调用API清理AB名:AssetDatabase.RemoveUnusedAssetBundleNames();
  • 刷新资源:AssetDatabase.Refresh();
  • 上传提交到svn,包含资源ab文件,及版本文件(md5,资源size,资源版本号,资源路径)

资源流程五大步——发布(将打包的资源发布到服务器成为最新版本)
  • 再ftp同步至alpha版本(内部开发测试环境)

  • 再从alpha版本更新至center(内部多分支环境)

  • 再从center发布至外网(对外部玩家的最终,这个需要运营在更新时让后台进行发布)

  • 发布的版本,还分客户端和服务端。可分别发布。

到这里发布就算完成了。


资源流程五大步——更新(资源从服务器热更下载到本地)

如果玩家是之前下载的手机包,有了新版本后,有了新版本后,
即可通过热更新的方式,只更新那一部分数据,而不用重新安装应用包。即:热更。

那此时,最新的游戏数据,还在服务器上。
当玩家启动游戏时,就会执行检查,(可参考我的上一个视频从启动到登录)。
通过platform的平台配置,用其中的php向服务端发送请求,
根据玩家本地的资源版本,与服务器的资源进行比对,如果版本不一,则执行更新流程。
将有差异的文件下载到本地,替换原来的资源。
即完成资源更新。


资源流程五大步——加载(资源本地加载到游戏界面)
  • 此处以加载ui界面为例:
  • 游戏启动时,即会将依赖信息(_setting.txt)提前读到内存。
  • 玩家打开游戏,功能逻辑也随之运行(如lua客户端)
  • 比如,当玩家点某个按钮,此时,该按钮已经提前绑定了函数事件,会调用打开A界面。
  • lua即开始调用打开该界面的方法。
  • 传递要打开的界面的名字、参数等。
  • 调用基类base_ui_panel中的OpenView()方法。
  • 封装panel成为luaTable的属性,传递并调用c#中GameUIManager的ShowWindow方法
  • c#中先检查该面板是否已被打开,如果已开,则回调lua OnShow方法
  • 如果没开,则UIResourceManager中的LoadWindow方法。
  • 先将图集依赖信息取出,再将icon依赖也取出
  • 从依赖中取得路径后,使用WWW加载依赖的资源,同时也加载主资源(ui)的路径
  • 使用UnityApi
this.mAssetBundle = 
AssetBundle.LoadFromFile(resLoadInfo.GetPathForLoadfromfile());
  • // 资源下载完毕,开始初始化窗口,先实例化一个窗口模板,InitWindow,对其属性进行设置
  • 再使用bundle.LoadAsset(name, type);从ab中加载出图集和icon,并设置到ui上。(此时图片已经都加载到游戏界面ui了,可显示了)
  • 再回调到lua中的show方法、__OnDisplay、__UpdateControls等等方法,使lua客户端,可以在界面打开时,初始化获取信息。

结束


遗留问题:
只讲到了音效的打包过程,ui的加载过程。
但其它的资源如场景、模型、特效等等资源的打包与加载的过程没讲。
各种资源的方式其它都是不同的
那下一篇我们再细讲这个。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值