关于古董商的寻宝之旅的单机游戏 从房间找东西开启地下室找到东西来到院子等一点点通过,有找船,出海钓鱼,拼地图等

当前位置:&>&
游戏介绍:
DearRED是一款精美的独立冒险解谜游戏,故事改编自一篇短篇小说,讲述了女主人公小红的凄惨身世,埋藏了复仇的心理,一起随主角来深入了解下剧情,来寻找真正的凶手吧!
游戏介绍:
《残酷的谎言2:遗产》是由开发过《神秘追踪者》系列的“Elephant Games”带来的最新作品。本作依旧采用了隐藏类冒险解谜形式,以机关谜题为主要进行方式,混合了单词寻物,和各种好玩的迷你游戏。你需要运用智慧和策略去解开巧妙的谜题。
评分:10分
游戏介绍:
进入《时间之谜2:古代幽灵》,开始挑战性的解谜游戏!阻止邪恶巫婆 Viviana,拯救Esther的家庭!Esther的遗产继承权收到了威胁。她必须去寻找古老的安布罗斯历史,穿越时空。开始不可思议的冒险,改变历史事件来解决难以置信的谜题!
评分:5.0分
游戏介绍:
《尼莫船长的秘密》是根据儒勒?凡尔纳的科幻冒险小说《海底两万里》改编的隐藏解谜游戏。继续一场愉快且令人兴奋的隐藏大冒险,去寻找尼莫船长传说中的鹦鹉螺号潜艇!跟随线索和尼莫船长亲自发来的提示,去追查船的下落,完成搜素你将被列入传奇!
评分:5.0分
游戏介绍:
《阿扎达4:元素》是由Bigfish Games发行的一款解谜游戏,是《阿扎达》系列的最新作品。《阿扎达》系列第一部堪称经典,玩家们期待这部新作会有怎样的表现。故事发生在一本被诅咒魔法书里,里面封印着提多,您必须解决阿扎达一系列的迷题,寻找隐藏的线索,才能释放他。
评分:5.0分
游戏介绍:
《救赎墓园:乌鸦的诅咒》是《救赎墓园》系列冒险解谜游戏的开山之作,游戏采用了隐藏类冒险解谜形式,以机关谜题为主要进行方式,混合了单词寻物,和各种好玩的迷你游戏。
评分:5.0分
游戏介绍:
木偶秀3:失落的小镇是《木偶秀戏》系列的第三部作品了,延续了前作玩法,游戏中不仅具备了经典的单词寻物,寻找物品图形玩法,巧妙的机关谜题也占据了很大比例。
评分:5.0分
游戏介绍:
《夜班人》是FlayFirst带来的一款神秘视线类解谜游戏,所有故事都从历史博物馆夜班人,主角Mike的一次值勤开始,他将遇到一位神秘的人物,会把他从芝加哥引领到神秘的希腊小岛,开始一场奇幻而危险的旅程。寻找隐藏的物品并拼合线索,打开富有挑战性的谜题,最后将发掘到一件失落的珍宝。
评分:5.0分
游戏介绍:
《营火传奇3:最终章》别名《营火传说3:最后一幕》,是一款冒险解谜类游戏。在游戏中,玩家需要先触发项目列表,再根据图形寻找,并按照逻辑关系在场景中还原并使用物品。同时,你还要完成具有策略性的迷你游戏,巧妙的机关设计使本作更为引人入胜,具有很高的可玩性。 你将享受到无限制的游戏乐趣,它包含了充足的场景和关卡等待你的探索。惊悚的场景描绘让人心惊胆颤,富有感染力的冒险气氛和优秀的解谜系统是本作的优势。
评分:5.0分
游戏介绍:
《音乐大师2: 生命乐章》是开发过《黑暗故事》和《木偶戏》系列的“ERS G-Studio”带来的最新作品。本作依旧采用了隐藏类冒险解谜形式,以机关谜题为主要进行方式,混合了单词寻物,和各种好玩的迷你游戏。
评分:5.0分
游戏介绍:
《祖鲁动物园》是Bigfish Games带来的隐藏类解谜游戏新作。游戏形式以单词寻物,同类收集,图形解谜等多种方式出现,虽然没有太多的创新,但可爱的动物主题会为你带来一份轻松与惬意。 帮助女主角Zulu获得100万美元奖金,确保动物园以一流的形象去感染审判官。找到动物给它们喂食,打扫栖息地,收集垃圾,对隐藏对象进行广泛探索,成功获得奖金。
评分:5.0分
游戏介绍:
失落的希律王古墓简体中文版包含了5个小游戏、16个背景图选自国家地理:失落的希律王古《National Geographic》的场景游戏中精致的剧情与画面、丰富的提示与画外音将使找图游戏不再枯燥。和同类解谜游戏相比,单词和图形并存的找东西的形式可以使家更为轻松上手,且更容易体验到游戏乐趣。除此之外,你还要利用不同的工具和仪器,来寻找隐藏在深处或被掩埋的物品。
评分:5.0分
游戏介绍:
《塞尔尼》是由Cylne游戏小组制作发行的最新解谜游戏,在游戏中你会看到各种离奇的风景和建筑,也有可能遇到奇怪的生命。玩家要探索和体验这个充满通灵环境的虚拟世界,围绕黑暗光环,去寻找事情的真相。
评分:5.0分
游戏介绍:
《神像帝国》是“Alawar”开发的一款以城市模拟建造为基本框架,结合了图形寻物、机关解谜、迷你游戏等众多解谜元素的创新型作品。在游戏中,玩家可以搜索四周的荒野收集资源,开展海上贸易,发展繁华的城市,最终建造一座庞大的太阳神赫利。
评分:5.0分
游戏介绍:
《快乐伙伴的捉鬼冒险》是一款以卡通女孩为主角的,带有灵异故事情节的寻找东西的解谜找图游戏。游戏的故事梗概是,“大胆儿”是一个穿着露脐装、总爱说些俏皮话的城市女孩。她在一家叫《在你身边》杂志社当记者,专门负责报道一些灵异事件。她的两个好朋友长毛儿和大个儿为了帮她,偷偷的制造了各种看似幽灵事件的恶作剧,你要帮助大胆通过各种小游戏来解开每个谜团,最终恍然大悟:世界上哪有什么鬼!该游戏有非洲的风格在里面。
评分:5.0分
游戏介绍:
《丽塔?詹姆斯:香格里拉大冒险》由Big Finish制作发行,并于日发布。丽塔詹姆斯香格里拉大冒险是一解谜题材的AVG游戏,游戏讲的是丽塔?詹姆斯和她的父亲一起去寻找传说的圣地香格里拉的故事。
评分:5.0分
游戏介绍:
《神秘黄昏之歌剧院的诅咒》是“VastStudios”带来的全新解谜游戏。本作在细节上有所突破,加入了更多的有趣内容。游戏模式不仅具备了基础的单词寻物,也将一些道具和机关谜题随着剧情巧妙融入。
评分:5.0分
游戏介绍:
《黑暗秘密:日蚀山》是“黑暗秘密”系列的第二作,在游戏中你要从黑暗恶魔的手中拯救你的朋友,跟随石头僧侣留下的线索,在泰国寻找隐藏的秘密,和黑暗魔法进行战斗,神奇的冒险之旅尽在《黑暗秘密:日蚀山》。
评分:10分
游戏介绍:
《福尔摩斯:神秘的波斯地毯》讲述的是大侦探夏洛克?福尔摩斯和好友华生医生居住在伦敦贝克街221b的一所公寓中。1896年,一个年轻的画家遭到杀害,被发现时,他蜷缩在奇怪的波斯地毯中,苏格兰场的警探们寻求紧急帮助。你需要侦察每一个场景寻找线索,锁定嫌疑人进行调查,揭示神秘的波斯地毯谜案。
评分:10分
游戏介绍:
在瓦莱丽波特与绯红丑闻游戏中,玩家将扮演女主角Valerie Porter,在上个世纪二十年代以男性为主导的社会中,一位年轻有抱负的独家新闻记者。在最近发生的谋杀和丑闻案件中,你将有机大展身手。发现隐藏的线索,寻找失踪的对象,并收集足够的证据,去侦破谋杀案。
评分:10分
游戏介绍:
《维多利亚之谜:白衣女郎》中的白衣女郎到底是谁以及她从哪儿来的? 她的秘密是什么? 这款隐藏物体冒险游戏的剧情是在英国推理小说家Wilkie Collins1860年写的侦探小说《维多利亚之谜:白衣女郎》的基础上编写而成的。您将旅行回到维多利亚时代,探索大宅,为寻找线索,发现新的秘密,并发现有关神秘的白衣女郎的真相。
评分:10分
游戏介绍:
《都市传奇之迷宫》是由开发过《神秘追踪者》系列的Elephant Games带来的最新冒险解谜游戏。本作有着充满悬念的故事线索,逼真的环境表现,耐人寻味的巧妙谜题,使游戏展示耐人寻味的趣味性。 《都市传奇之迷宫》依旧采用了隐藏寻物的解谜原理,并将有趣的迷你游戏和简单机关谜题巧妙融入。玩家可以欣赏着精彩的动画故事剧情,将线索物品进行还原,穿梭于不同的场景之间,运用智慧去解开谜题。
评分:10分
游戏介绍:
《纽约迷云:黑手党内幕》是一款将黑帮题材同超自然力量元素结合到一起的冒险解谜游戏。本作的画面非常精致,整体的画面比较昏暗,同时还融入了不错的音效。游戏中玩家要根据游戏的提示来寻找到隐藏的物品。
评分:10分
游戏介绍:
《凯尔特传说》一款最新的解谜游戏。和之前的作品相比,本作在形式上稍有不同,不仅有隐藏寻物环节,穿梭场景的设计使游戏更具备了冒险解谜的气氛。
评分:5.0分
游戏介绍:
《阿西利亚岛的秘密》是一款冒险解谜游戏,游戏讲述为了解开阿西利亚岛的秘密,随着你和你的父亲以及船员所起航的这次危险的远征之旅!在暴风雨带来的海难之后,你醒来发现自己身处一座荒岛,周围布满了奇怪的生物,并且被四个致命的诅咒困扰着。寻找你的父亲,对抗诅咒,阻止残暴的巫婆,这是你的责任!
评分:5.0分
游戏介绍:
《隐藏的秘密6:巴黎圣母院之谜》是经典解密游戏《隐藏的秘密》系列的第六部作品,在隐藏的秘密6:巴黎圣母院之谜中,您将扮演一位探长,通过寻找各种隐藏物体,通过各种小游戏, 解开巴黎圣母院之谜。
游戏介绍:
在游戏《爱语魔咒2:剑与玫瑰》中,消散由巫婆复仇而被邪恶诅咒的王国!穿越岛屿王国,寻找四处散落的魔法玫瑰花瓣,巫婆的诅咒已经使王国陷入了混乱,你需要去挽救当前的局面。收集花瓣,击败可怕的怪兽,对抗邪恶的巫婆,完成令人兴奋的隐藏寻物大冒险!
评分:5.0分
游戏介绍:
《皇后的故事:野兽与夜莺》是一款经典的魔幻题材解密故事,讲述了女儿为寻找在城堡中走失的父亲,展开了一段奇妙的探险之旅。
评分:5.0分
游戏介绍:
《葬》是一个解谜为主的冒险游戏,游戏没有战斗部分,所以是集中于解谜方面,谜题的难度还是比较高的,玩家需要仔细观察周围的环境来寻找线索,并根据线索推理出答案。游戏的故事背景设定比较阴暗,画风则是非常复古,好在解谜的方面做的很优秀,有兴趣的玩家来试试吧。若实在被难住了,不妨看看游戏文件夹里的攻略吧。
评分:5.0分
游戏介绍:
《惊天大盗:迪林格》是一款AVG冒险解谜游戏,虽然采用了基础的单词寻物玩法,但在细节上也具有一定创新,加入了更多有趣的元素。
评分:5.0分
游戏介绍:
《失踪帆船之谜》中讲述的是Jack Haniken和他的朋友Elizabeth使他们自己陷入一个非常危险的境地,你需要去解救他们!在发现了沉船宝藏的下落之后,Jack起身去寻找财富,但很快就失踪了。在游戏中,你要帮助Natasha和Thompson教授去追踪Jack的下落!
评分:5.0分
游戏介绍:
阿扎达是Bigfish Games带来的解谜大作。故事发生在一本被诅咒魔法书里,里面封印着提多,您必须解决阿扎达一系列的迷题,寻找隐藏的线索,才能释放他。有1.0和1.04两个版本,不过目前最新版本是2.0正式版。
评分:5.0分
游戏介绍:
《水晶传送门之谜2:天边外》讲述的是为了寻找失踪的父亲,尼可激活了地下室的水晶传送门。在那神秘的天边之外,尼可和她的小助手伊格开始了一段不可思议的冒险旅程。标准的寻找隐藏线索解谜游戏,画面精美细致,故事剧情引人入胜,有两种难度模式可供选择。
评分:5.0分
游戏介绍:
《心灵之眼:遗忘的秘密》是一款AVG冒险解谜小游戏,与普通的冒险解谜游戏类似,采用的是常见的看单词寻物的玩法,虽然简单,但比较考验玩家的观察力。除此之外还有许多有趣的小游戏,包含其中。
评分:10分
游戏介绍:
《永恒:被遗忘的小镇》是一款找图寻物类型的解谜游戏,剧情讲述的是,主角执意启动一个时光机器,被带到了一百年前的一个神秘小镇。在那里玩家将体验到阴谋与爱情,误解与原谅,信任与背叛,感情与权力。一场毫无预兆的分别,一场分隔百年的等待。那位善良美丽的新娘,是否会等来她的新郎?
评分:8.5分
游戏介绍:
在《寻宝人4:时机已到》中,汤尼的姐姐海伦神秘的失踪了,同时“图特卡瑞夫”再次返回人间,为了寻找姐姐,汤尼追踪各种线索,却发现了2012人类面临毁灭性灾难的惊天秘密,他能打败图特卡瑞夫拯救姐姐和整个人类世界吗?图特卡瑞夫真的是个恶魔吗?一切答案由你解开!
评分:10分
游戏介绍:
《隐藏的秘密6:巴黎圣母院之谜》是经典解密游戏《隐藏的秘密》系列的第六部作品,在隐藏的秘密6:巴黎圣母院之谜中,您将扮演一位探长,通过寻找各种隐藏物体,通过各种小游戏, 解开巴黎圣母院之谜。
游戏介绍:
真人秀致命一枪是大鱼游戏公司发行的一款解谜类游戏,游戏主要以图片寻物为主。玩家在游戏中会发行有一个小丑角色会一直出来干扰你并给你添加更多的麻烦
评分:5.0分
游戏介绍:
《远古冒险之宙斯的礼物》中,你将开始一段令人兴奋的远古冒险,前往埃及去寻找宙斯的神奇礼物。加入我们的女英雄,她将设法去了解雅典娜地下墓穴的秘密,并收集贵重的珍珠!
评分:5.0分
游戏介绍:
《魔镜谜踪》是Gogii Games开发的一款寻找图形结合启动机关的解谜游戏。一个年轻的家庭把车停在一幢废弃老屋前开始野餐,母亲享受着阳光开始午休,当她听到了关门声后意识到孩子不见了。进入神秘的老屋,在镜子的引导下,前往7个魔法世界进行探索。
评分:5.0分
游戏介绍:
《沉寂:死亡禁锢之屋》是BigFish在2011年发布的一款冒险解谜游戏,玩家需要在每个场景中找到有用的道具,来揭开各种难题。有一天你姐夫给你打电话,你得知你的姐姐面临着生命危险!于是你踏上了冒险之旅,寻找隐藏的种种神秘道具和失踪姐姐的线索,直到谜题解开,亲人团聚。
评分:4.3分
游戏介绍:
传奇之书跟随佐伊和查尔斯顿遵循线索去寻找被人遗忘的传奇之书,线索也将帮助他们搜寻世界上最为神秘的传说,圣剑真的存在吗?在找到传奇之书前,他们的敌人又在做些什么?解开其中之谜,将永远改变力量的平衡!但如果传奇之书落在坏人手中,也将造成毁灭性的灾难。
评分:5.0分
游戏介绍:
《孤岛:失落纹章》讲述了一位年轻的考古学家帕姆,带着来历不明的远古纹章远征回到家中。转天,她被绑架了,现在她的男朋友詹姆斯必须去追寻她的行踪。在游戏中,你将冒险至一座岛屿,去探寻帕姆失踪的秘密,并跟随纹章的指引穿越地球去寻找并拯救帕姆。
评分:10分
游戏介绍:
神秘侦探:吸血鬼之吻帮助Sarah寻找她失踪的朋友,她在一个黑暗的夜晚被带走!Sarah向一个神秘机构中的私家侦探求助来寻找Emilia,调查Emilia对于她的失踪责任重大。潜入黑暗的氛围中,帮助Sarah在时间耗尽前去解救她的朋友!
评分:5.0分
游戏介绍:
失落的王国:太阳公主的遗产中Alexia最近老是被脑海出现的神奇幻象所困扰,幻象中她看到了一个被大火吞噬的印加帝国。为了找寻真相,Alexia决定前往秘鲁,探寻这座神秘的古城所隐藏的秘密,她将会被一个神秘的幽灵所引导,寻找帝国失落的王冠。
评分:5.0分
游戏介绍:
光阴旅店发生古埃及的教正在寻找一件具有无限权力的神器,这件物品已被马克安东尼、亚瑟王等人隐藏了数百年之久,它具有可以征服世界的巨大能力。在游戏中,玩家将扮演来自纽约市警察局的 Bridget Brightstone探员,你需要要阻止邪恶情况的发生,这将是一次千载难逢的调查机会。
评分:10分
游戏介绍:
航行线索:加勒比冒险之旅此款游戏是以安吉丽娜为主角的FBI探员侦破珠宝大盗案件为主线。在游戏的过程中,不但可以欣赏轻松的音乐,并且可以身临其境的游览豪华邮轮,并且“迈阿密――加勒比”航线中的各个旅游胜地也作为侦破案件的背景,让人赏心悦目。在寻找物品的同时,也将游戏的主线代入,其中某些场景可要费一番周折才能破案哦。
游戏介绍:
《梦境游乐园》依旧采用了常见的解谜模式,各种迷你游戏和巧妙的机关谜题占据了很大比例。玩家需要通过简单的单词搜索寻找线索物品,切换场景将物品进行还原,运用智慧和策略去解开谜题。
游戏介绍:
  神秘黄昏:精神病院的阴谋是大鱼游戏公司与2010年8月发行的一款解谜冒险类游戏,该游戏是款以黑暗风格与寻图找物为主。玩家将会进入一家神秘的精神病院当中去寻找在这里失踪的外公。你将会揭开这座神秘精神病院背后所隐藏的秘密以及阴谋。
评分:10分
游戏介绍:
《水晶传送门之谜2:天边外》讲述的是为了寻找失踪的父亲,尼可激活了地下室的水晶传送门。在那神秘的天边之外,尼可和她的小助手伊格开始了一段不可思议的冒险旅程。标准的寻找隐藏线索解谜游戏,画面精美细致,故事剧情引人入胜,有两种难度模式可供选择。
评分:10分
游戏合集推荐
从2008年-2014年 快猴网一直在努力做到最好 Www.KuaiHou.Com
备案编号:粤ICP备号-1洞窟寻宝收藏重玩全屏缩小放大
推荐游戏游戏说明
方向键↓投掷夹子,↑使用物品。试试您的敏捷程度,开启一段寻宝之旅吧!
游戏介绍:光照是一个函数,对于局部光照来说,其参数是光照表面的位置/法线、观察方向、光源(及其可见性信息)数组、材质特性(双向反射分布函数/BRDF),返回观察方向的出射光线。&br&&br&纹理通常是一维、二维、三维数组,有时会加入向下采样的版本(mipmap)。贴图通常是指三维表面映射至二维纹理的坐标。对于网格(mesh)模型,会在每顶点储存二维坐标,然后使用特别的插值方法去采样每个表面采样点的值。&br&&br&烘焙是缓存光照函数的全部或部分因子至纹理贴图或顶点数据。例如烘焙光照贴图是指把观察方向无关的入射光积分预计算,缓存起来,在运行时与材质特性计算最终光照结果。&br&&br&不过我觉得用程序的角度不一定令你容易理解,你需要了解物理上光的传播方式,以至计算机图形学用哪些算法去近似化这些过程。
光照是一个函数,对于局部光照来说,其参数是光照表面的位置/法线、观察方向、光源(及其可见性信息)数组、材质特性(双向反射分布函数/BRDF),返回观察方向的出射光线。 纹理通常是一维、二维、三维数组,有时会加入向下采样的版本(mipmap)。贴图通常…
&figure&&img src=&https://pic2.zhimg.com/v2-a3a6b0c5cac09cd797e28942_b.jpg& data-rawwidth=&1000& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&https://pic2.zhimg.com/v2-a3a6b0c5cac09cd797e28942_r.jpg&&&/figure&&p&概述&/p&&p&本文就数种重要的Gameplay框架及插件,简述它们的原理,介绍这些Gameplay框架的适用场合,并进行对比。&br&本文假设读者有一定的游戏开发经验、Unity开发经验。&br&本文会写得比较随性和啰嗦。&/p&&h2&关于Gameplay&/h2&&p&&br&&/p&&blockquote&Wikipedia:&br&Gameplay is the pattern defined through the game rules.&/blockquote&&p&Gameplay,游戏性、玩法、游戏规则。&/p&&p&&br&&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-df8de704dc22c637a5dd5_b.jpg& data-caption=&& data-rawwidth=&520& data-rawheight=&346& class=&origin_image zh-lightbox-thumb& width=&520& data-original=&https://pic1.zhimg.com/v2-df8de704dc22c637a5dd5_r.jpg&&&/figure&&p&Mario & Luigi RPG&/p&&figure&&img src=&https://pic1.zhimg.com/v2-b03a2d3de36f4ef324ad3bb70dfcbc42_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&768& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic1.zhimg.com/v2-b03a2d3de36f4ef324ad3bb70dfcbc42_r.jpg&&&/figure&&p&Hearthstone&/p&&figure&&img src=&https://pic3.zhimg.com/v2-c72bb2aa0f_b.jpg& data-caption=&& data-rawwidth=&960& data-rawheight=&537& class=&origin_image zh-lightbox-thumb& width=&960& data-original=&https://pic3.zhimg.com/v2-c72bb2aa0f_r.jpg&&&/figure&&p&Overwatch&/p&&p&做游戏还是玩游戏,Gameplay都是最最最重要的因素之一。&br&玩家开始玩一款游戏的原因是多样的,表现、心流、炫耀、交友,但其中最有可能的是:好玩。&br&玩家停止玩一款游戏的原因也是多样的,难度、重复、劳累、孤独,但其中最有可能的是:乏味。&/p&&p&为了让我们的游戏不乏味,我们必须持续添加内容、更新规则,让玩家持续地感受到新意和有趣。&br&但项目组的人员是有限的、工作时间哪怕加班也是有限的、玩家的耐心也是有限的,如何能让项目组在有限资源的情况下,更好更快地进行游戏Gameplay迭代更新,是Gameplay框架的一大责任。&/p&&p&(另,可能一般不会太关注到的点是,我们也不能过度更改我们的游戏。一个游戏当前玩家是已经认可之前版本玩法设定的、受之前版本重重过滤后留下的玩家,如果玩家手上的版本本来是个RAC,我们下一个版本把它改成RTS,那玩家肯定都流失了。比如笔者之前负责过的一款游戏,个人觉得其2.0版本因为对战斗外体验更改过大,是造成2.0版本上线后数据滑落的重要原因之一。)&/p&&h2&Gameplay框架&/h2&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-d6a8d98cef782_b.jpg& data-caption=&& data-rawwidth=&1226& data-rawheight=&480& class=&origin_image zh-lightbox-thumb& width=&1226& data-original=&https://pic3.zhimg.com/v2-d6a8d98cef782_r.jpg&&&/figure&&p&开始实现各种各样Gameplay时,我们常会编写符合需求,却相对更hardcode的Gameplay代码。&/p&&p&这做法有一定好处,其在时间紧急的情况下,能在初期就立刻见成效。&br&随着时间推演,Gameplay需求越来越多、越来越复杂、越来越和自己之前所想不一样的时候,这些之前hardcode的代码就越来越难以维护。&br&此时我们需要重构,需要针对这些各种各样的Gameplay需求,进行归纳总结。&br&(换句话说,上述这种更hardcode的Gameplay代码还有一个好处:其的确能让我们更早地了解细节,更早地知道自己为何重构、如何重构,甚至给重构提供集成测试用例。)&/p&&p&世界万物都可被归纳、被总结。&br&我们不能拒绝归纳总结,否则解决一个问题后、再出现类似问题我们又得从零开始苦思冥想。归纳总结可以帮助人去理解并记住结论,让人有可能举一反三。&br&但过分的归纳总结是抽象、甚至可能是无用的、不严谨的。不存在万金油。(“&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Theory_of_everything& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ToE&/a&”也尚未被证实。:P)&/p&&p&框架也是。&br&框架是必须的,为了更好地提供服务解决某一类问题,我们搭建底层框架。&br&但&b&从我们写框架的第一行代码开始,给它带来功能的同时,也给它带来了限制&/b&。&br&即,没有万能的框架、只有适用的框架。&/p&&p&在游戏行业中,根据前人的实践、思考,已归纳总结出不错的几种重要Gameplay框架。&br&本文将讨论几种Gameplay框架,讨论它们是什么、&b&它们之间的联系和区别&/b&、它们各自的适用场合。它们是:&/p&&ul&&li&&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Entity_component_system& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&实体组件系统(Entity-Component-System)&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Visual_programming_language& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&节点可视化编程(Node-based Visual Scripting)&/a&&/li&&ul&&li&&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Finite-state_machine& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&状态机(Finite State Machine)&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Behavior_tree_%28artificial_intelligence%2C_robotics_and_control%29& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&行为树(Behavior Tree)&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Event-driven_programming& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&事件驱动可视化编程(Event Driven Visual Scripting)&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Non-linear_editing_system& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&非线性编辑(Non-linear editing)&/a&&/li&&/ul&&/ul&&p&&br&&/p&&p&并非说以上框架能满足一切Gameplay,但它们组合在一起,相信已能满足颇多需求。&br&这些框架是实用的。本文之所以会提到这几个框架,并非生硬地把它们堆砌在一起。恰恰相反,而是因为作者本人在游戏开发中遇到了实际问题,思考后发现,“这不是恰好可以用这种Gameplay框架来解决这个问题吗?”,通过实验和实践,才体会到这些框架的实用价值。&/p&&h2&实体组件系统(Entity-Component-System)&/h2&&figure&&img src=&https://pic1.zhimg.com/v2-f153bf48dbf094fc6373eb7_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&827& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic1.zhimg.com/v2-f153bf48dbf094fc6373eb7_r.jpg&&&/figure&&p&Unity的GameObject/Component是很好的Entity-Component System例子&/p&&p&之所以把实体组件系统(Entity-Component-System,以下简称ECS)放在最前面,是因为它是最最最重要的、同时也是我们最熟悉的、可能也是我们最容易忽略的。&/p&&p&ECS不复杂,本人亦曾2度写过ECS,分别是Flash游戏《弹道轨迹(TNT)》和一个开发中的Unity帧同步游戏。如果我必须做出N选一,我会放弃其他所有Gameplay框架而选择保留ECS。&br&另,从&a href=&https://link.zhihu.com/?target=http%3A//gameenginebook.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Game Engine Architecture》&/a&将ECS这个话题收编于其Runtime Gameplay Foundation Systems一章,重点着墨介绍,也能证明其与Gameplay的密切关系。&/p&&h2&Is-A转为Has-A&/h2&&p&ECS最核心的功能很简单:将传统继承的is-a换成了has-a,将Component保存于Entity的一个容器中,Entity提供API进行Component的查找访问。&br&因为针对任何一个事物进行有限的功能拆分必然是不完整的,选取任意一个维度将其作为基类,都是不那么严谨的。所以,将这些功能有限拆分后,与其不精确地必须选取一个作为基类,倒不如把它们公平地作为组件,公平地处于Entity里。&br&ECS能让我们更好地分解复杂的问题、整理复杂的关系。&/p&&p&狭义的ECS只包括上述这个功能,但一般,广义的ECS也会被修改成拥有以下几项重要功能。&/p&&h2&生存期&/h2&&p&ECS还可以提供API,进行Entity、Component的生存期管理,以及生存期相关事件的回调。&br&生存期以Unity的术语为例,一般指的是:&/p&&ul&&li&创建(Awake)&/li&&li&有效(OnEnable)&/li&&li&启动(Start)&/li&&li&轮转(Update)&/li&&li&无效(OnDisable)&/li&&li&销毁(OnDestroy)&/li&&/ul&&p&实现生存期的重难点在于:&/p&&ul&&li&如何确保“同时”创建的Entity的所有Start都发生在Awake之后。比如可以使用ms_gameObjectsWillStart列表实现。&/li&&li&如何确保创建销毁不会影响轮转阶段。每一次Tick()都会对组件列表进行遍历调用Update()。用户在Update()内调用创建或销毁后,如果ECS立刻将其从列表中添加或移除,这将可能影响遍历逻辑。所以ECS会在Tick的开始阶段或末尾阶段才真正将Entity、Component添加或移除到最终列表里。比如可以使用ms_gameObjectsWillStart列表和ms_gameObjectsWillDestroy队列实现。&/li&&li&如何确保高效的轮转。比如通过接口(Unity通过反射检测Update()等函数)让用户有权限规定某些自定义的Component是否接受Update。&/li&&/ul&&h2&通信&/h2&&p&Entity之间可以通信、Component之间也可以通信。通信的方式可以是多样的,包括:&/p&&ul&&li&事件(GameObject.SendMessage())&/li&&li&搜索并直接依赖(GameObject.Find()、GameObject.GetComponent())&/li&&li&也有一些做法,是将数据(黑板)也作为通信方式(GetProperty()、SetProperty()),但Unity并无此设计&/li&&/ul&&h2&父子从属关系&/h2&&p&Entity之间可以有父子从属关系,从而进一步拆分功能。&/p&&blockquote&比如人是一个Entity,它有Human这个Component;如果游戏需要重点关心心脏及其跳动次数,让Human提供GetHeartPumpCount()已不太合适,则可把心脏也作为一个Entity,作为人Entity的子Entity,同时心脏Entity有Heart这个Component,提供Heart.GetPumpCount()接口。&/blockquote&&p&但Unity的实现中,并不将此功能归于GameObject,而是归于Transform。这样子有其好处,即进行Transform世界空间坐标运算时,仅仅关心Transform这个组件本身就好了。但坏处是,为了表达父子层级关系,必须引入Transform、居然就被迫引入Position、Rotaiton、Scale这些可能没用的信息了。&/p&&h2&重要属性&/h2&&p&有一些重要的、通用的属性,也直接定义在Entity中,比如唯一ID。&br&Unity的GameObject,还有供(物理、渲染)引擎内部使用的Layer属性,供Gameplay使用的Tag属性。&/p&&p&&br&&/p&&h2&&b&Data-Oriented ECS&/b&&/h2&&p&以上,是典型的Object-Oriented ECS。&br&随着《守望先锋》的成功和他们在GDC分享&a href=&https://link.zhihu.com/?target=http%3A//schedule.gdconf.com/session/overwatch-gameplay-architecture-and-netcode& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《'Overwatch' Gameplay Architecture and Netcode》&/a&,&a href=&https://link.zhihu.com/?target=http%3A//t-machine.org/index.php//entity-systems-are-the-future-of-mmog-development-part-1/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Data-Oriented ECS&/a&成为了最近的话题焦点。&/p&&p&它的特点是Component只有数据没有方法、System只有方法而没有数据(Component has no method, and System has no field)。数据和行为分离,更加解耦。&/p&&p&同一种Component以Array的形式存储在一起。因为是struct-of-array,更加内存友好,性能效率会更快。&/p&&p&特定System只关心特定某几种Component(Group,守望先锋称为“Tuple”)。比如Render System只关心Transform和Renderer这两种Component,仅当一个Entity#12实例同时有这两种Component的实例Transform#98和Renderer#37时,Transform#98和Renderer#37就置于一个Tuple里,然后Render System就针对这包含Transform和Renderer的Tuple所组成的数组进行foreach执行逻辑。&/p&&p&另外很重要地,基于以上,DO ECS更加容易做到粗粒度的JobSystem多线程编程。这一方面可另外参阅&a href=&https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DAXUvnk7Jws4%26t%3D336s& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Unite Europe 2017 - C# job system & compiler》&/a&&/p&&p&既能解耦,也可能带来性能提升,这是Data-Oriented ECS最诱人之处。&/p&&p&&br&&/p&&p&&br&&/p&&p&从上面的例子可以看出,ECS的功能是如此基础和重要,所以才说是Gameplay的必备要素。&/p&&p&&br&&/p&&p&&br&&/p&&p&&br&&/p&&h2&节点可视化编程(Node-based Visual Scripting)&/h2&&figure&&img src=&https://pic3.zhimg.com/v2-053ee848dd45cdbe65721a_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&488& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic3.zhimg.com/v2-053ee848dd45cdbe65721a_r.jpg&&&/figure&&ul&&li&状态机(Finite State Machine)&/li&&li&行为树(Behavior Tree)&/li&&li&事件驱动可视化编程(Event Driven Visual Scripting)&/li&&li&非线性编辑(Non-linear editing)&/li&&/ul&&p&上面提到的Gameplay框架及插件都有共同的一点:&b&它们都可以以Node-based Visual Scripting的形式存在&/b&。&/p&&p&Visual Scripting&/p&&p&可能有人对Visual Scripting反感,直觉觉得它们的性能是低效的。Visual Scripting的Editor的UI复杂程度,是造成这种偏见的主要原因,但Editor的复杂度和它的Runtime运行性能完全不相关。理论上,一个语言的Front-end也可实现成Visual Scripting。比如,在&a href=&https://link.zhihu.com/?target=http%3A//gameprogrammingpatterns.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Game Programming Patterns》&/a&的&a href=&https://link.zhihu.com/?target=http%3A//gameprogrammingpatterns.com/bytecode.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Bytecode&/a&一章,如果为游戏开发一门语言,作者的确建议使用Visual Scripting作为Bytecode的一环,而并非使用文本文件,因为Visual Scripting中用户的每一个操作都是分开的,其机制忽略用户的每一个非法操作,但文本编程不同,用户是可以输入所有代码了之后才交给编译器编译,这将大幅提升实现编译器错误检测、错误提示的难度。&/p&&p&Node-based&/p&&p&至于Node-based,其思想就是封装和组合。&br&我们可以合理地考虑重用性,将功能拆分为非常通用、非常细小的Node,作为一个又一个Node。但这样有可能会造成Node过多,造成浏览、编写时的麻烦。&br&我们可以针对比较重要的一段逻辑进行归纳,将本由多个Node才能实现的重要逻辑,重新以1个Node的形式呈现。&br&这事实上是个何时进行重构的问题,也是个提取共性、保留异性的思想。&/p&&p&Blackboard&/p&&p&各个Node是相对独立解耦的,但各个Node有是有可能需要数据交互的。往往通过在主体中添加一个Blackboard(黑板)和SharedValue,来让这些Node进行数据交互。&br&&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-6b4dc81c6fbdcbffbec17d8_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&704& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic3.zhimg.com/v2-6b4dc81c6fbdcbffbec17d8_r.jpg&&&/figure&&p&利用Blackboard实现找寻Target、移动到Target、并进行Attack的行为树&/p&&p&以上图行为树作为Blackboard的例子。它实现的需求是&/p&&ol&&li&找寻玩家控制的Actor(FindLocalUserActor节点)&/li&&li&移动到该Actor到足够近(ActorMoveToTargetUntilDistance节点)&/li&&li&攻击(FunActorVKey节点)&/li&&/ol&&p&留意到,Blackboard定义了TargetTransform的一个ShanredValue。&br&我们再观察FindLocalUserActor节点和ActorMoveToTargetUntilDistance节点:&br&&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-394a7cedbc004b92d760289_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&774& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic2.zhimg.com/v2-394a7cedbc004b92d760289_r.jpg&&&/figure&&p&FindLocalUserActor节点定义了Transform这个SharedValue。FindLocalUserActor将找寻到的Transform通过Transform这个SharedValue设置给Blackboard的TargetTransform&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-dab3e5a8b4bb_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&733& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic2.zhimg.com/v2-dab3e5a8b4bb_r.jpg&&&/figure&&p&ActorMoveToTargetUntilDistance节点定义了TargetTransform这个SharedValue(原谅命名和Blackboard的TargetTransform同名了,请读者留意),它的值在这棵行为树里绑定的Value是Blackboard中的TargetTransform&/p&&p&从而,FindLocalUserActor节点找到的目标Transform,成功地通过Blackboard的TargetTransform,传递给了ActorMoveToTargetUntilDistance的TargetTransform,成功地通过Blackboard让两个相对解耦的节点又能合作起来。&br&Blackboard和SharedValue往往通过Dictionary来实现。各个节点仅仅保存了SharedValue的Key的字符串,取值的时候,都是携带这个Key去Blackboard中查Dictionary对应Key的Value。&/p&&p&总而言之,通过Node-based Visual Scripting,可以让程序、策划更加好地分工。&/p&&ul&&li&程序通过实现代码实现各种通用的Node、封装各种常用的Node,&/li&&li&策划通过这些Node,通过Visual Scrpting,在将这些Node“有机”地组合起来,即能实现各种不同的逻辑。&/li&&/ul&&p&虽然都是Node-based Visual Scripting,&b&&i&不同的Gameplay框架,有不同的具体机制和限制&/i&&/b&。下面将逐一介绍。&/p&&h2&状态机(Finite State Machine)&/h2&&figure&&img src=&https://pic2.zhimg.com/v2-44ae3e13cd4b_b.jpg& data-caption=&& data-rawwidth=&1076& data-rawheight=&658& class=&origin_image zh-lightbox-thumb& width=&1076& data-original=&https://pic2.zhimg.com/v2-44ae3e13cd4b_r.jpg&&&/figure&&p&PlayMaker&/p&&p&状态机也是我们非常熟悉的概念。在Unity中,我们常通过Mecanim或PlayMaker接触到状态机。&br&《Game Programming Patterns》的&a href=&https://link.zhihu.com/?target=http%3A//gameprogrammingpatterns.com/state.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《State》&/a&一章,非常直观地概况了状态机的用处。&br&其将以下&b&响应玩家输入事件&/b&的混乱代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&void Heroine::handleInput(Input input){
if (input == PRESS_B){
if (!isJumping_ && !isDucking_){
// Jump...
else if (input == PRESS_DOWN){
if (!isJumping_){
isDucking_ =
setGraphics(IMAGE_DUCK);
isJumping_ =
setGraphics(IMAGE_DIVE);
else if (input == RELEASE_DOWN{
if (isDucking_){
// Stand...
&/code&&/pre&&/div&&p&重构为:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-d28e4fb47b468c049d897a_b.jpg& data-caption=&& data-rawwidth=&1040& data-rawheight=&667& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic3.zhimg.com/v2-d28e4fb47b468c049d897a_r.jpg&&&/figure&&p&这么简单直观的“一幅图”。&/p&&p&状态机之所以能将其问题简化,是因为它框架符合需求地提供了(但也限定死了)以下基础功能:&/p&&ul&&li&一个状态机内部的各个状态是互斥的,一个状态机一个时刻只处于一个特定状态&br&(比如上图的“STANDING”、“JUMPING”等方框)&br&(当然如果你坚持hardcode,你也可以把isJumping_、isDucking_这些独立的变量变为一个枚举变量State来表达互斥,这的确能大幅优化上面代码的繁乱程度)&/li&&li&可以将不同的事件发送给状态机&br&(比如上图的“PRESS↓”、“RELEASE ↓”等事件)&/li&&li&如状态A能跳转到状态B,则它们俩间会有一个从A指向B的Transition,该Transition指定由什么事件触发,从而触发状态跳转&br&(比如上图“JUMPING”状态到“DIVING”状态之间有一个Transition,其指定由“PRESS↓”事件触发)&/li&&ul&&li&当状态机接受到新事件时,如该事件是&b&局部事件&/b&,则只有当前所在状态有该事件对应的Transition时,才进行跳转&br&(比如上图,假设状态机当前处于“JUMPING”状态,因其只包含一个响应“PRESS↓”事件的Transition,所以当状态机接受到“PRESS B”局部事件时,将不会进行跳转;当状态机接受到“PRESS↓”局部事件时,才会跳转到“DIVING”状态)&/li&&li&全局事件不管当前处于什么状态,都可以立刻进行状态跳转&br&(即类似于Mecanim中AnyState相连的Transition、或PlayMaker的Global Transition)&/li&&/ul&&/ul&&p&&br&&/p&&ul&&li&A状态可以设置成能跳转到A状态自己,也可以设置成不可以&/li&&li&状态有Enter()、Update()、Exit()三个阶段函数。&br&(比如上图“JUMPING”状态跳转到“DRIVING”状态的过程中,将会依次调用到“JUMPING”这个状态对象的Exit()、“ DRIVING”这个状态对象的Enter();如果会停留在“DRIVING”这个状态对象的话,将一直调用它的Update())&/li&&li&状态由用户自定义的脚本组成,分别都可以实现自己的Enter()、Update()、Exit()逻辑。脚本默认为串行执行,有些状态机也可以并行执行脚本。&/li&&li&状态机提供Tick()函数以驱动当前状态的当前脚本的Update()函数&/li&&li&状态机是张图&/li&&li&可以有多个状态机同时并行运行&/li&&/ul&&p&从状态机的特点触发,它适用于简单的、需要全局事件跳转的、有状态的逻辑。&br&但状态机不适用于复杂的逻辑,否则状态机即变成盘丝洞。&/p&&p&使用状态机的具体举例有:技能的逻辑或表现、Buff的逻辑或表现、有明显步骤的动画表现(炉石传说主要用PlayMaker做表现动画)。&/p&&p&通过多个状态机并行执行,可以把多种互不相关的状态结合起来实现一个复杂的角色动作逻辑。&br&比如一个角色按身体姿态分有moveLayer={stand|run|crouch},按动作分有actionLayer={idle|shoot|melee},按状态分有statusLayer={normal|weak|speedup}。&br&我们可以使用1个状态机去表达上述所有情况,这个状态机将包括:&/p&&ul&&li&s0={stand&idle&normal}, &/li&&li&s1={run&idle&normal}, &/li&&li&s2={crouch&idle&normal}, &/li&&li&s3={stand&shoot&normal} &/li&&li&s4={run&shoot&normal}&/li&&li&...等最大可能4*3*3=36种状态及其切换。&/li&&/ul&&p&我们也可将这3个相关性本就较小的状态用3个并行执行的状态机去表达,此时,我们只需要考虑4+3+3=10种状态切换就好。&br&注意到,要成功这样做,需要依赖于底层服务提供者(如控制move的组件、控制action的组件、控制status的组件)本就能互不相关地被设置。&/p&&h2&行为树(Behavior Tree)&/h2&&figure&&img src=&https://pic3.zhimg.com/v2-6b4dc81c6fbdcbffbec17d8_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&704& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic3.zhimg.com/v2-6b4dc81c6fbdcbffbec17d8_r.jpg&&&/figure&&p&Behavior Designer&/p&&p&行为树是诞生于游戏行业的一种重要的执行模型。&/p&&p&行为树的使用示例恰好在前面的&b&Blackboard&/b&一节有提到,故不赘述。&/p&&p&行为树因为是树状,所以比状态机能够更好地应付复杂的执行流程。通过不断地拆分子问题、重用行为树,来帮助设计者更轻松地、更少出现错误地解决复杂问题。&br&虽然行为树也能和状态机一样响应外界事件、也能被外界事件中断某棵子树而跳到另一棵子树。但行为树常不这样做,常用于受外界事件突发事件影响较少的场合,而是通过行为树内部不断拉去游戏世界的信息,进行自发的流程控制。&/p&&p&所以,行为树常用于AI设计、流程相对比较固定的关卡逻辑。&/p&&p&其内部实现机制可概括为:&/p&&ul&&li&行为树类似分层状态机(Hierarchical Finite State Machine, HFSM),注意和上面提到的多个并行状态机并不同。&/li&&li&以树状的形式存在,状态被改叫为Task&/li&&li&其每个Task可返回Success、Running、Failure的执行结果给父节点&/li&&li&组合节点(Composite)是一种Task,其有1个或多个孩子Task。根据孩子Task返回的执行结果,不同的组合节点有不同的响应逻辑,从而不同地决定下一个节点是哪一个孩子并返回Running状态,或者不再执行孩子而返回Success或Failure节点&/li&&li&修饰节点(Decorator)是一种Task,组合节点差不多,但其只能有1个孩子Task&/li&&li&行为节点(Action)是一种Task,它对游戏世界信息进行读写操作,其必然是行为树的叶子节点,因为它并不能包含孩子节点。&/li&&li&判断节点(Conditional)是一种Task,它和行为节点差不多,但我们口头约定好,判断节点只对游戏世界信息进行读操作来判断其执行结果、而不要对游戏世界信息进行写操作&/li&&li&行为树提供Tick()函数,从而驱动当前待执行的或正在Running的节点的Update()函数。可以通过&b&&i&一个执行栈的列表&/i&&/b&来记录当前正在执行哪些节点。具体为:&/li&&ul&&li&从Root点开始递归深度逐一遍历,&/li&&ul&&li&将刚刚遍历到的新节点(包括Root自己)Push到执行栈栈顶;&/li&&li&调用该节点的Update();&/li&&li&先假设该节点的Update()只返回Success或Failue状态,即代表其已经执行完毕,即可将其返回状态保存在自身、Pop出栈、并交由父节点对其孩子们进行状态判断,决定需否执行下一个子节点,如没,则父节点本身返回状态并Pop出栈&/li&&li&如果期间没有并行节点、所有节点都返回Success或Failue状态,则这1个Tick()内都可以执行整棵行为树&/li&&/ul&&/ul&&/ul&&p&&br&&/p&&ul&&ul&&li&如果一个执行栈执行过程中出现节点返回Running状态,则这次Tick()不再执行这个执行栈。而是下一次Tick()再执行这个执行栈的栈顶元素&/li&&li&如果遇到并行组合节点,则该并行组合节点为所有孩子节点都new一个新的执行栈来供孩子节点分别使用,从而实现并行执行。这个并行组合节点执行完毕时,可以销毁被它new出来的这些执行栈们&/li&&li&所有执行栈可以保存在一个执行栈列表中,在Tick()内就这个执行栈列表进行遍历执行&/li&&/ul&&/ul&&p&&br&&/p&&h2&事件驱动可视化编程(Event Driven Visual Scripting)&/h2&&figure&&img src=&https://pic1.zhimg.com/v2-9c0d4a016ab_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&716& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic1.zhimg.com/v2-9c0d4a016ab_r.jpg&&&/figure&&p&Flow Canvas&/p&&p&在前一个项目中,我们采用行为树作为关卡逻辑编辑。&br&在打算实现新项目关卡逻辑的时候,却发现有太多全局事件跳转,导致行为树出现各种interrupt节点,从这颗子树跳到另一棵毫不相干的子树,很是突兀和麻烦。才意识到之所以行为树能用于独立防线的关卡逻辑,是因为它的关卡逻辑需求是相对&b&比较线性&/b&的,都是按照现行剧本去挨个发生的。&br&这时我们也正常但不合理地联想到状态机也能响应全局事件,但由于状态机一次全局事件只能被一个状态捕获,所以是和我们的需求不一致的。&/p&&p&于是参考兄弟项目组的经验,我们将目光转移到了Starcraft2的Galaxy Editor的关卡编辑器上:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-f2f9b5edbcab2a46f3fb529_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&761& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic1.zhimg.com/v2-f2f9b5edbcab2a46f3fb529_r.jpg&&&/figure&&p&Starcraft2 Galaxy Editor - Trigger&/p&&ul&&li&视频:&a href=&https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DHsc3fBaYnjw& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Starcraft 2: Heart of the Swarm - Behind The Scenes - Galaxy Editor (HD)&/a&&/li&&li&文档:&a href=&https://link.zhihu.com/?target=http%3A//starcraft-2-galaxy-editor-tutorials.thehelper.net/tutorials.php%3Fview%3D168845& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Triggering for Dummies (the basics)&/a&&/li&&/ul&&p&从视频和上图可以看出,一个“Trigger”包括了&/p&&ul&&li&Event&/li&&li&Local Variables&/li&&li&Conditions&/li&&li&Action&/li&&/ul&&p&这个Trigger机制非常棒!某某Event在世界里发生了,策划配置好这个Event对应的Trigger们都会进行一系列Condition的判断,如果判断通过,则执行对应的一系列Action,过程中Trigger自己的局部状态通过Local Variables去记录,供Condition和Action读写。&/p&&p&重点是在Local Variables和Conditions。从视频中你会发现,策划不已经是在编写逻辑了吗?只不过编写逻辑是通过UI来进行而已。&br&但问题是,类似于Galaxy Editor中的Conditions的操作、UI,都显得比较繁琐不直观(比如上图中的一长串配置英文:“Number of &b&Living&/b& units in (&b&Any&/b& units in (&b&Entire map&/b&) owned by player &b&1&/b& matching &b&Excluded: Structure, Missile, Dead, Hidden&/b&, with at most &b&Any Amount&/b&) &b&==0&/b&”)。&/p&&p&这时,我立刻联想到了Unreal4唯一押宝的Gameplay框架:&a href=&https://link.zhihu.com/?target=https%3A//docs.unrealengine.com/latest/INT/Engine/Blueprints/index.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Blueprints&/a&(前Unreal3 Kismet)。&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-93cb3d529eefc2733cceef4_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&435& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic3.zhimg.com/v2-93cb3d529eefc2733cceef4_r.jpg&&&/figure&&p&Unreal4 Blueprints Visual Scripting&/p&&p&了解Blueprints后,发现Blueprints和Galaxy Editor的Trigger事实上都是属于Event-Driven。而且因为Blueprints是基于Visual Scripting的概念出发的,所以对于Variable、Condition的实现会显得更加灵活和强大。&/p&&p&然后,恰好,在Unity Assets Store里,有不错的一些EDVS插件,包括uScript、FlowCanvas等。考虑到我们的关卡逻辑需要进行AssetBundle更新,所以将EDVS翻译成C#脚本的uScript并不适合,最后再通过各种使用和性能评估,我们选定了FlowCanvas。&/p&&p&EDVS的特点是:&/p&&ul&&li&基于Event触发,事件产生了之后push才触发逻辑。这点和状态机一样,比行为树轮询pull检查的性能较好&/li&&li&默认一个Event发生后,对应的Flow都是同步执行完的。和状态机、行为树不同,默认未定义“状态”、“运行中”这些概念。你也可以实现自己的有“执行中”状态的节点,但需要自己定义同样的事件在这个状态下再发一次给你的这个节点,你的节点是什么行为&/li&&li&提供更加类似于编程语言的变量和流程控制,比状态机行为树的粒度能做到更细&/li&&/ul&&p&我们当前正将EDVS应用于关卡逻辑配置上。&/p&&h2&非线性编辑(Non-linear editing)&/h2&&figure&&img src=&https://pic2.zhimg.com/v2-97bb856e747eac02de100bd_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&386& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic2.zhimg.com/v2-97bb856e747eac02de100bd_r.jpg&&&/figure&&p&In-house Character Action Editor: FunAction editor&/p&&p&什么是“ &a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Non-linear_editing_system%23/search& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&非线性编辑(Non-linear editing,以下简称NLE)&/a&”?我们先通过图片搜索来找个直观感受。&br&&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-db32a76659cdad08bf8a461_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&344& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic1.zhimg.com/v2-db32a76659cdad08bf8a461_r.jpg&&&/figure&&p&Image search of Non-linear editing&/p&&p&NLE事实上就是老百姓口中的视频编辑,或者也可称为时间线(Timeline)编辑。&br&注意到“非线性”这个字眼和时间线本身比较“线性”这个感觉,比较矛盾。这是因为历史原因导致的。在上个世纪90年代,&a href=&https://link.zhihu.com/?target=https%3A//en.m.wikipedia.org/wiki/Linear_video_editing& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&线性编辑(Linear video editing)&/a&是主要的视频编辑方式,其弊端是,进行视频编辑的时候,源视频必须线性地进行访问(想象一下录像带),给编辑带来了极大不便。所以,非线性编辑的最大特点是视频编辑时,可以对源视频进行非破坏性的随机访问。&br&所以,非线性编辑器和线性编辑器的差别并非我们当前游戏开发的重点——因为我们现在对外存、内存的访问都是非破坏性、可随机访问的。非线性编辑和线性编辑,都属于时间线编辑。&/p&&p&在游戏中,NLE主要用在实时过场动画(Cut-scene)的制作。&br&其核心概况是:&/p&&ul&&li&多对象共存于时间线上,受NLE操作。NLE就好像导演,去控制摄影师、演员们、特效师们、音效师们什么时候该做什么事&/li&&li&和Unity的Animation有相似性,都是基于时间线进行“某些东西”的编辑,但Animation中每一帧所编辑的东西非常固定:对象的属性或一些简单参数的事件,这远远不能满足于Cut-scene制作&/li&&li&NLE在时间线的基础上,允许开发自定义各种行为节点,及对行为节点进行参数配置&/li&&li&节点在时间线上有明确的开始点、结束点,即形象地以“条状”表达一段持续的“事件”。这样将[开始帧,结束帧)的帧范围(Frame Span)封装成一段范围事件的好处是:&/li&&ul&&li&明确区分1个Track内的多个帧范围事件对象拼接组成,以帧范围事件对象为单位,单独配置、操作、执行。举例为:&/li&&ul&&li&给帧范围单独设置角色动画,即可以不修改原有动画文件的情况下,单独配置角色所播动画的范围、播放速度&/li&&li&给帧范围传入一组路径点数据,作为对象(角色、Camera等)的运动轨迹&/li&&/ul&&/ul&&/ul&&p&&br&&/p&&ul&&ul&&li&方便地单独调节一段事件的长短&/li&&li&方便地修改交换A事件和B事件的发生次序&/li&&/ul&&/ul&&p&&br&&/p&&p&NLE还可以用在角色动作编辑上。&br&一般游戏类型的角色动作,我们完全可以使用上面提到的状态机或行为树来配置实现动作。&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-c4b7e749aa062_b.jpg& data-caption=&& data-rawwidth=&480& data-rawheight=&270& class=&origin_image zh-lightbox-thumb& width=&480& data-original=&https://pic2.zhimg.com/v2-c4b7e749aa062_r.jpg&&&/figure&&p&Street Fighter 4: Hit and Hurt boxes&/p&&figure&&img src=&https://pic1.zhimg.com/v2-24cbf21b029c98914fca_b.jpg& data-caption=&& data-rawwidth=&565& data-rawheight=&250& class=&origin_image zh-lightbox-thumb& width=&565& data-original=&https://pic1.zhimg.com/v2-24cbf21b029c98914fca_r.jpg&&&/figure&&p&Street Fighter: frame by frame hurt boxes&/p&&p&但在类似于FTG、ACT这些游戏类型,角色的动作精度要求极高,高到必须按帧进行单独配置(如上图Ryu的蓝色受击框是逐帧进行配置的)。所以我们也会把NLE的概念用于进行这种帧级别精度要求的角色配置上。&/p&&p&本章开篇图为本人参考多款NLE编辑器所制作出来的FunAction动作编辑器。&br&有Unity Flux插件经验的人会感觉其与Flux长得非常像,的确Editor方面FunAction是参考Flux的,但两者除了长得像之外,内在思路却完全不一样。&br&FunAction的概况如下:&/p&&ul&&li&最重要的,Action提供Tick()函数,从而一帧一帧地驱动执行&/li&&li&任意角色模型可和任意Action运行时动态绑定。但一旦绑定,规定了1个角色对象有且只有1个Action,1个Action认定只操作1个角色对象&/li&&ul&&li&事实上这对传统NLE多对象共存于时间线上来说,是一种退化。但这种退化是满足角色动作编辑的需求的,是合理的。未来如果有时间,在不能给编辑器带来额外操作复杂度的前提下,是可以实现成允许多对象同时编辑的,即一个既可编辑cutscene、也可编辑角色动作的NLE编辑器&/li&&/ul&&/ul&&p&&br&&/p&&ul&&li&Action有多个Motion(动作,如idle、attack、hurt等),每个Motion有多个Track(轨道),每个Track和且只和一种BaseEvent的子类(事件类型,如PlayAniamation)绑定,Track可以出现其绑定的事件类型的任意个事件对象。BaseEvent可以让用户重载Enter()、Update(currentFrame)、Exit()等函数,从而实现各种千变万化的功能。&/li&&li&BaseEvent的子类除了DurationEvent(样子为长条状)外,还有子类InstantEvent(箭头状)。DurationEvent类似于传统NLE的时间轴对象,有明确的StartFrame、EndFrame;InstantEvent类似,但规定StartFrame和EndFrame必须相同。这是因为在动作游戏中,有许多事件的持续帧数是只有1帧(比如攻击检测等)、或持续帧数是不用限定无法限定的(比如播放特效、播放音效等)&/li&&li&Action提供SetMotion()函数,从而切换动作&/li&&li&可自定义序列化、反序列化方式。默认为&a href=&https://link.zhihu.com/?target=https%3A//github.com/mgravell/protobuf-net& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Protobuf-net&/a&,效率比Unity的各种XML、各种JSON序列化方式好多个数量级。开发使用的方式非常简单,以PlayAnimation为例,如下图&br&&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-6fb87efeae6d54973ae8eb_b.jpg& data-caption=&& data-rawwidth=&898& data-rawheight=&314& class=&origin_image zh-lightbox-thumb& width=&898& data-original=&https://pic3.zhimg.com/v2-6fb87efeae6d54973ae8eb_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&每个自定义的Event都可方便地再自定义Inspector的逻辑和画法。示例如下图(留意到PlayAnimation的Inspector自定义实现了自动寻找动画属性的逻辑)&br&&/li&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-944fd7fcdf4_b.jpg& data-caption=&& data-rawwidth=&764& data-rawheight=&752& class=&origin_image zh-lightbox-thumb& width=&764& data-original=&https://pic1.zhimg.com/v2-944fd7fcdf4_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&每个自定义的Event都可方便地再自定义在Editor场景绘制额外元素。示例如下图,为ActorHurtBody的受击Capsule(可从AABB/Capsule/OBB间选择),和ActorHitTest的攻击OBB&br&&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-bec516a4d05e_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&566& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic3.zhimg.com/v2-bec516a4d05e_r.jpg&&&/figure&&p&&br&&/p&&h2&第三方Gameplay插件&/h2&&figure&&img src=&https://pic2.zhimg.com/v2-ef5ef6dda39fa1049459_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&510& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic2.zhimg.com/v2-ef5ef6dda39fa1049459_r.jpg&&&/figure&&p&&br&&/p&&p&上面这些Gameplay框架的Runtime实现都并非困难。但实现起来,往往大量开发时间消耗在:&/p&&ul&&li&提供功能齐全、人性化的Editor和Inspector&/li&&li&实现性能高效、人性化的序列化反序列化&/li&&/ul&&p&一个好的游戏设计思路,是能让开发者&b&&i&可以&/i&&/b&重复造轮子、而不是让开发者&b&&i&必须&/i&&/b&重复造轮子。&br&让开发者必须重复造轮子是简单粗暴欠妥的,让开发者既能选择重复造轮子、也能选择采用已有第三方插件,反而需要更多对基础框架扩展性的思考。&/p&&p&在Unity Asset Store里有好一些比较不错的Gameplay框架具体实现插件。它们是:&/p&&ul&&li&状态机:&a href=&https://link.zhihu.com/?target=https%3A//www.assetstore.unity3d.com/en/%23%21/content/14914& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&NodeCanvas&/a&、&a href=&https://link.zhihu.com/?target=https%3A//www.assetstore.unity3d.com/en/%23%21/content/368& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&PlayMaker&/a&&/li&&li&行为树:&a href=&https://link.zhihu.com/?target=https%3A//www.assetstore.unity3d.com/en/%23%21/content/14914& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&NodeCanvas&/a&、&a href=&https://link.zhihu.com/?target=https%3A//www.assetstore.unity3d.com/en/%23%21/content/15277& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&BehaviorDesigner&/a&&/li&&li&事件驱动可视化编程:&a href=&https://link.zhihu.com/?target=https%3A//www.assetstore.unity3d.com/en/%23%21/content/33903& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&FlowCanvas&/a&&/li&&li&非线性编辑:&a href=&https://link.zhihu.com/?target=http%3A//unity3d.com/cn/unity/roadmap& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unity Director Sequencer&/a&(尚未发布)、&a href=&https://link.zhihu.com/?target=https%3A//www.assetstore.unity3d.com/en/%23%21/content/56558& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Slate&/a&、&a href=&https://link.zhihu.com/?target=https%3A//www.assetstore.unity3d.com/en/%23%21/content/18440& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Flux&/a&(出名但不好)&/li&&/ul&&p&开发者不能有因使用第三方插件而感到“技术性羞耻自卑”的心态。&br&相反,开发者应该发挥开发的能力去评估一款第三方插件是否优秀,评估的角度包括:&/p&&ul&&li&是否满足基本需求&/li&&li&是否开源(这很重要,因为代码即文档、文档不透彻更新不及时、二次修改的可能)&/li&&li&运行性能、反序列化性能&/li&&li&版本迭代、作者、社区是否活跃&/li&&li&UI、操作、体验&/li&&/ul&&p&如果决定应用第三方插件,我们不应该轻易修改它,而是优先去扩展它。&br&在Unity里,第三方插件(及其他项目无关的通用基础功能),建议都摆放在“Standard Assets”目录里,因其与其他文件夹的脚本是处于不同的两个dll,从而防止普通开发者错误地把具体项目业务逻辑感染进通用逻辑里。&br&这样子,我们可以通过继承、或者partial、或者extend、或者static function等途径进行第三方插件的扩展。&br&对于一些重要不紧急的插件修改,可以通过社区和作者进行交流,让其进行修改。比如本人就多次对FlowCanvas/NodeCanvas/BehaviorDesigner的作者交流讨论、提出多项建议(如&a href=&https://link.zhihu.com/?target=http%3A//flowcanvas.paradoxnotion.com/topic/flowcanvas-should-be-able-to-tickupdate-manually/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&1&/a&、&a href=&https://link.zhihu.com/?target=http%3A//flowcanvas.paradoxnotion.com/topic/custom-drawer-for-custom-data/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&2&/a&等),最后被采纳。&br&如果有必要,我们决定修改第三方插件,我们需要承担从此不能再轻易更新这些插件的后果。&br&如果我们已大幅修改第三方插件,此时我们可以反问自己:“这第三方插件是否已经太不满足需求了?我们是否应该开始重新造更适合我们的轮子了?”&/p&&h2&结语&/h2&&p&通过上述Gameplay框架的有机合理组合,能够实现丰富的Gameplay逻辑。&/p&&p&Gameplay框架工具也远不只这些,地形编辑器、Starcraft2的Unit编辑器、技能编辑器,是更进一步、更具体细分的Gameplay编辑器。&br&也能就上述Gameplay框架进行特例化修改,比如主要用于对话设计的&a href=&https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Dialog_tree& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dialog tree&/a&是状态机的一种重要特例化应用。&br&&a href=&https://link.zhihu.com/?target=http%3A//apexgametools.com/products/apex-utility-ai-2/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Utility AI&/a&是一种不错的AI思路。相比更“Rule-based”的FSM/BehaviorTree,Utility AI和&a href=&https://link.zhihu.com/?target=http%3A//alumni.media.mit.edu/%7Ejorkin/goap.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GOAP&/a&相似,更有“Plan-based”的感觉。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-0b3fe6191763ecec6bdfa_b.jpg& data-caption=&& data-rawwidth=&1240& data-rawheight=&666& class=&origin_image zh-lightbox-thumb& width=&1240& data-original=&https://pic4.zhimg.com/v2-0b3fe6191763ecec6bdfa_r.jpg&&&/figure&&p&Utility AI的Apex实现&/p&&p&如上图,程序写好评分的Node后,策略填填不同Node的分数(Score),就一个不同性格的AI就出来了。你是喜欢近战的路霸,就把“Proximity To Nearest Enemy”的Score调高,你是喜欢直线攻击的76,就把“Line Of Sight To Cloeset”的Score调高。&/p&&p&应注意,没必要为了用工具而用工具,要看需求有否用到。但也要考虑,需求是易变的、市场是易变的、方向是易变的、玩家是不耐心的。要为Gameplay的通用性、扩展性做好准备。&/p&
概述本文就数种重要的Gameplay框架及插件,简述它们的原理,介绍这些Gameplay框架的适用场合,并进行对比。 本文假设读者有一定的游戏开发经验、Unity开发经验。 本文会写得比较随性和啰嗦。关于Gameplay Wikipedia: Gameplay is the pattern defined throu…
谢邀。&br&&br&回答的前提是:使用OpenGL来渲染。&br&&br&分几个点来回答。&br&&br&&b&1. RGBA4444真的比RGBA8888占用的RAM要少&/b&&br&&br&其实这里说的RAM,是指的显存而非内存。OpenGL支持以这几种形式来使用纹理资源(via &a class=& external& href=&//link.zhihu.com/?target=http%3A//www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&khronos.org/opengles/sd&/span&&span class=&invisible&&k/docs/man/xhtml/glTexImage2D.xml&/span&&span class=&ellipsis&&&/span&&/a&):&br&&ul&&li&GL_UNSIGNED_BYTE(RGBA8888或RGB888)&/li&&li&GL_UNSIGNED_SHORT_5_6_5&/li&&li&GL_UNSIGNED_SHORT_4_4_4_4&/li&&li&GL_UNSIGNED_SHORT_5_5_5_1&/li&&/ul&&p&在程序将图片载入系统内存后,会根据你选择的形式(RGBA8888/RGB565 etc.)对其做一些处理(怎么处理后面说),然后就将这些纹理上传到显卡的显存,之后会把这些图片占用的内存删除掉。&/p&&p&也就是说,载入的图片在变成了显卡能处理的纹理之后,就根本不会保存在内存中,所以你可能看不到内存占用的变化。&/p&&p&当然,这个处理操作是由程序员自行控制(或者由你选择的框架来决定)的,你如果决定不删除它们而让它们留在内存中,那当然会占用系统内存。&/p&&br&&p&&b&2. TexturePackger导出的图片是个什么情况?&/b&&/p&&br&&p&你可以拿一张图片尝试,在导出为RGBA8888和RGBA4444的时候,它们的文件大小确实是不同的。请看下面的图片,并注意我加亮的部分:&/p&&br&&figure&&img data-rawheight=&501& data-rawwidth=&402& src=&https://pic2.zhimg.com/50/b8bee972cd4ce3c92f16463_b.jpg& class=&content_image& width=&402&&&/figure&&figure&&img data-rawheight=&501& data-rawwidth=&402& src=&https://pic3.zhimg.com/50/dbcd7bdc7c_b.jpg& class=&content_image& width=&402&&&/figure&&br&&p&对于同一张图片,在RGBA8888格式下,唯一颜色数是5864;而RGBA4444格式下,唯一颜色数是1454,文件大小减少了80KB左右。&/p&&p&至于图片信息中显示的 Original Colors依然是32Bit,这是因为在图像处理软件显示图像的时候,内部使用的色彩是8888的。&/p&&br&&p&&b&3. 保存的文件是个什么情况?&/b&&/p&&br&&p&上面的RGBA4444是否就真的使用的16bit(4x4)来保存每个像素呢?&/p&不是。&br&我们知道,PNG格式可以保存成 8bit 、 24bit 和 24bit(with alpha channel)=32bit 三种格式。而JPEG格式只能保存为24bit。RGBA4444有16bit,无论如何是不能使用8bit格式来保存的。&br&因此,这个RGBA4444是使用24bit(或者32bit)格式来保存的。&br&既然同为24bit or 32bit,&u&为什么RGBA4444的文件体积会比RGBA8888小呢?&/u&&br&注意上面的两个 Number of unique colors。在这里,压缩算法起了作用,将相同的颜色压缩了,总颜色数量少了,文件体积就变小了。&br&不过,即使是采用同样的色深(24bit or 32bit)保存,我仍然要说的是,&u&RGBA4444比RGBA8888的图像质量会差一些&/u&。&br&&br&&p&&b&4. 怎么做到的?&/b&&/p&&br&&p&要解释最后一个下划线内容,我们需要提出一个新的问题:&u&RGBA8888转换成RGBA4444,发生了什么变化?&/u&&/p&&p&先来看看它们分别代表什么:&br&&/p&&ul&&li&RGBA8888 : R 8bit + G 8bit + B 8bit + A 8bit&/li&&li&RGBA4444 : R 4bit + G 4bit + B 8bit + A 8bit&br&&/li&&/ul&&p&8bit 能代表的最大数字是256,也就是说每种颜色可以表达256个级别,那么8x3=24bit(不算A)就能表现&img src=&//www.zhihu.com/equation?tex=2%5E%7B24%7D+& alt=&2^{24} & eeimg=&1&&=种颜色。&/p&&p&同样的,RGBA4444能表现的颜色是&img src=&//www.zhihu.com/equation?tex=2%5E%7B12%7D+& alt=&2^{12} & eeimg=&1&&=4096种(不算A)。&br&&/p&&p&也就是说,进行这种转换,是一定会丢失颜色信息的。&/p&&br&&p&以 0xFFFFFFFF 这个RGBA8888颜色为例,转换成 RGBA4444可以这样做:&/p&&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&kt&&unsigned&/span& &span class=&kt&&int&/span& &span class=&n&&pixel32&/span& &span class=&o&&=&/span& &span class=&mh&&0xFFFFFFFF&/span&&span class=&p&&;&/span&
&span class=&kt&&unsigned&/span& &span class=&kt&&short&/span& &span class=&n&&pixel16&/span& &span class=&o&&=&/span&
&span class=&p&&((((&/span&&span class=&n&&pixel32&/span& &span class=&o&&&&&/span& &span class=&mi&&0&/span&&span class=&p&&)&/span& &span class=&o&&&&/span& &span class=&mh&&0xFF&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&mi&&4&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&mi&&12&/span&&span class=&p&&)&/span& &span class=&o&&|&/span& &span class=&c1&&// R&/span&
&span class=&p&&((((&/span&&span class=&n&&pixel32&/span& &span class=&o&&&&&/span& &span class=&mi&&8&/span&&span class=&p&&)&/span& &span class=&o&&&&/span& &span class=&mh&&0xFF&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&mi&&4&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span&
&span class=&mi&&8&/span&&span class=&p&&)&/span& &span class=&o&&|&/span& &span class=&c1&&// G&/span&
&span class=&p&&((((&/span&&span class=&n&&pixel32&/span& &span class=&o&&&&&/span& &span class=&mi&&16&/span&&span class=&p&&)&/span& &span class=&o&&&&/span& &span class=&mh&&0xFF&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&mi&&4&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&mi&&4&/span&&span class=&p&&)&/span& &span class=&o&&|&/span& &span class=&c1&&// B&/span&
&span class=&p&&((((&/span&&span class=&n&&pixel32&/span& &span class=&o&&&&&/span& &span class=&mi&&24&/span&&span class=&p&&)&/span& &span class=&o&&&&/span& &span class=&mh&&0xFF&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&mi&&4&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&mi&&0&/span&&span class=&p&&);&/span&
&span class=&c1&&// A&/span&
&/code&&/pre&&/div&&br&最终的结果是 0xFFFF。&br&&br&&b&5. iOS用什么?&/b&&br&&br&当然是用PVR格式。&br&&br&pvr是iOS设备的图形芯片 &a href=&//link.zhihu.com/?target=http%3A//www.imgtec.com/cn/powervr/powervr-graphics.asp& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&PowerVR 图形&/a& 支持的专用压缩纹理格式。它在PowerVR图形芯片中效率极高,占用显存也小。&br&性能对比可以看这里:&a class=& wrap external& href=&//link.zhihu.com/?target=http%3A//www.learn-cocos2d.com/2011/11/depth-ios-cocos2d-performance-analysis-test-project/%23image-formats& target=&_blank& rel=&nofollow noreferrer&&In Depth iOS & Cocos2D Performance Analysis with Test Project&/a&。&br&&br&&b&6. Android用什么?&/b&&br&&br&大部分的Android设备都支持ETC1格式,它也是受OpenGL ES 2.0标准支持的。&br&&br&ETC1不支持Alpha通道,但可以采用一些方法来处理:&a class=& wrap external& href=&//link.zhihu.com/?target=http%3A//malideveloper.arm.com/cn/develop-for-mali/sample-code/etcv1-texture-compression-and-alpha-channels/& target=&_blank& rel=&nofollow noreferrer&&ETC 纹理压缩和 Alpha 通道处理 <<
Mali 开发人员中心&/a&。&br&&br&ETC2支持Alpha通道,但需要 &a class=& wrap external& href=&//link.zhihu.com/?target=https%3A//www.khronos.org/news/press/khronos-releases-opengl-es-3.0-specification& target=&_blank& rel=&nofollow noreferrer&&OpenGL ES 3.0标准&/a& 支持。 &br&&br&但由于硬件平台不统一,每个厂商的GPU可能使用不同的纹理压缩格式,极少数的GPU甚至对ETC格式支持有问题。&br&&br&所以在Android设备上,目前可以采用ETC1或者PNG/JPEG格式。
谢邀。 回答的前提是:使用OpenGL来渲染。 分几个点来回答。 1. RGBA4444真的比RGBA8888占用的RAM要少 其实这里说的RAM,是指的显存而非内存。OpenGL支持以这几种形式来使用纹理资源(via ): GL_UNSIGNED_BYTE(RGBA8888或RGB888)GL_U…
前几天刚在知乎上喷过一次Chaos,然后昨天突然看到群里有人发一段文字,讲的是国内如何如何实现了Multi Thread Rendering,世界顶尖云云。我吓了一大跳,首先是怀疑,这可能吗?然后群友发了这个链接。&br&
作为一个成年人,为喷而喷从来不是应有的态度。不过,看到大神就各种阿谀,喷都不敢喷一下,那就耻为技术人了。我完整的看完了Chaos的文字,一时技痒,谈一下我自己的看法,讲得不对的地方,各位可以随便喷。在喷与被喷之间成长,一贯是我的风格。只要能有所提升,些许被喷完全不算什么:)。在这里,我应一些朋友之邀,讲一些图形学相关的背景知识。猪厂鹅厂的精英们可以略过这部分。&br&
首先说一些关于Multi Thread Rendering的理解。&br&
结论1:显卡不支持多线程渲染!这句话一出,估计口水无数。不要急,我无意搞什么文字游戏。我这里的结论是,显卡支持并行渲染,但真的没有多线程渲染这个概念。我试图用更清晰的语言来描述这个问题。首先从代码上,一个模型的渲染大概是这样子的:&br&
SetVertex();&br&
SetIndex();&br&
SetRendertarget();&br&
SetShaderResource();&br&
DrawIndex();&br& 请问,这部分代码,你能够同时在N个线程里面实现,并且同步渲染吗?显然不可能。&br& 那么问题来了,为什么显卡不支持多线程渲染?很简单,显卡已经做了并行渲染,这个并行渲染就是最好的多线程渲染,已经不需要再应用层来实现这个多线程渲染。每调用一个drawcall,本身就是N个线程同时渲染这个Mesh。&br&
结论2:多线程渲染理论上能提升渲染效率吗?答案是:能!这句话一出,估计立马又是口水无数。尼玛的刚才才说显卡不支持多线程渲染,现在又告诉我要实现多线程渲染,这是要找抽吗。其实这很正常,显卡和cpu是两个完全独立的硬件,我们所说的高效,是指最大限度的压榨硬件的资源,如果你的程序在无限循环Render();这部分代码,那么,你的显卡显然已经被压榨尽了。但是,在引擎里面,这可能吗?答案显然是不可能的。&br& 因此,如何尽可能的提升cpu跟gpu的利用率?答案是多线程。&br& 那么问题来了,如何实现这个多线程?先来看看传统的单线程渲染架构,大概如下:&br& while(1)&br& {&br&
update();&br&
render();&br& }&br& 这就是一个传统的最简单的架构,update里面处理各种更新,例如Animation计算,Skeleton计算,键盘鼠标消息,人物移动,攻击,网络消息……这里可能有各种设计,例如什么Tick设计,例如各种回调等等一大堆,但是归根到底,就是这么简单的设计。在这个设计里,大家都看到了,在update的时候,显卡是空闲的,在render的时候,cpu是比较空闲的。因此,其实很容想到,我能不能做两个线程或者多个线程,把update跟render分开?这样岂不是能提高利用率?答案是:可以。这是比较初级的,大家都能想到的多线程方案,假设一个游戏同屏人物过多,Skeleton计算消耗大量cpu的时候,这个优化意义很大。但是问题来了,对一些单机游戏什么的,同屏三几个人物,键盘鼠标消息处理消耗的时间可以忽略不计,这个update本身就是耗时极小的操作,这个优化还有那么大的意义吗?答案是:这个优化意义不大。&br& 综上所述,得出结论3:多线程优化,针对逻辑复杂的游戏,update处理比较耗时的游戏,如果你就做一个虚拟漫游什么的,做这类多线程优化基本就是做无用功。所以我的看法是:优化必须要针对特定场景。&br& (以上3点,全是我个人理解以及体会,有错的各位狂喷,让我认识到自己的不足)&br&&br&//----------------------------------------------------------------------------------------------------------------------------------------------------&br& 背景介绍完毕,华丽丽的分割线。&br&
假设现在要设计一款update需要处理很多复杂信息的游戏,如何做多线程优化是最合适的?做法其实很多,常见的做法是做一些background的资源加载,做一些Animation的计算,正如Chaos所说,其实这种优化到了一定程度,能看到底。因为这类做法,最终需要做很多的线程同步,这个同步过程,其实就是个等待,等待越多,效率越低,所以很多引擎的多线程老早就支持了,但是效率的提升并没有达到特别理想的效果。&br&
那么,还有没有其他更好的方式?老大哥MicroSoft提供了自己的方向:我在DX11和DX12里面都主打多线程渲染,大家拥抱我吧!&br&
老大哥的方式是什么呢?用一个叫做command list来缓存你的渲染,可以用deferred context来实现多线程!看着这部分文档,会有一种深深的延迟渲染的即视感。不过这确实不是延迟渲染。&br& 这个多线程的思路是什么呢?一般来说,场景需要实现比较complex的效果,会做各种rtt渲染。例如你做个实时阴影,你得实时渲染一个shadowmap吧?你做个水面反射,玻璃反射,你得做个rtt吧?更不要说在延迟渲染里面本身就是一个mrt。因此,把各种rtt分开渲染线程,这是老大哥的意思,也是一种可以想见的行之有效的多线程渲染方式。&br&
关于这种方式的详细描述,可以看老大哥的官方文档,这里不做太详细的描述。这种方式有效吗?肯定有效,我们要相信老大哥。这种方式是最高效的吗?显然不是,最高效的方式是:update and render everywhere!通俗来讲就是,让cpu跟gpu都不要停!我的理解是,Chaos号称他们实现了这个,所以他们是全球最顶尖最高效的引擎。&br&//----------------------------------------------------------------------------------------------------------------------------------------------------&br& 这里是华丽丽的分割线。这里对小包,对美术友好度,对编辑器的友好度,对画面的各种乱入PBR,HDR,DOF……全部不讨论,Chaos的原话,那些都没有技术含量,都是小儿科。其实我觉得那些还是有些技术含量的,不过相比这个牛逼的多线程架构,那些确实弱爆了,因此,这里也不讨论那些东西,只讨论最重要最根本的东西。这里首先有一个疑问:这么牛逼的东西,是Chaos独创吗?是别人都不知道或者想不到或者实现不了吗?答案Chaos都告诉我们了,不是,他认为是历史原因,需要这样大幅度的修改引擎架构,不合适,所以他们不搞这个,同时,我们也是世界上最顶尖的团队,因此我们做出了这个最顶尖的产品。&br& 这里,如果确定一定以及肯定Chaos他们做出来了这个东西,我真的衷心祝贺他们,毕竟能做出来这个东西非常的不易,而且确实有较高的水准。我很清醒的认识到自己的代码只是OGRE的水准,还在仰视UE,但是他们已经完成了超越,这是了不起的成绩。不过,我表示,这个是我都能想到的多线程方式,各路大神更加不可能想不到,别人不来实现,真的是因为历史原因或者是能力原因吗?这里,我的答案是:不是。我认为,这种实现方式作为一个通用引擎,有非常大的问题。&br& 1、首先,从实现上,假设你已经实现了update and render ererywhere,那么,我认为你的update跟render的耦合程度会非常高,需要无穷好的设计以及能力,才能让他们河水不犯井水,各自飞速在做各自的事情。&br& 2、其次,在传统的渲染里面,update();render();就是一帧,在一些需要做帧同步之类的游戏里,这样的架构是非常清晰并且清爽的,我不知道你这个架构还有没有“帧”这个概念,如果有,如何界定一帧?等待吗?如果没有,完全凭借时间轴来做帧,而不再考虑其他?这可能吗。举例:估计大家都玩过LOL,就算没玩过也该见过,这类游戏的技能,打斗,都有严格的帧同步,所以绝对不会造成看到打不到之类的坑爹效果。我一直认为,诸如Chaos号称的这个设计,最大限度的压榨硬件,会对这类游戏的设计上不友好。&br& 3、弯道超车,人人都想,在图形学的历史上,无数技术都是昙花一现,浪淘尽。对于UE这样的引擎来说,不确定某个技术大概率引领风骚的时候}

我要回帖

更多关于 寻宝网 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信