(派趣游戏)——哪个王校长打职业趣图好王校长打职业趣图选择攻略

热门搜索:
王与异界骑士好玩么 游戏背景内容与特色玩法一览
发表时间:&&出处:本站整理 &&责任编辑:萤火虫&&标签:
王与异界骑士好玩么,王与异界骑士是一款女性向卡牌游戏,那么王与异界骑士好玩么?今天289掌游网小编就给大家带来王与异界骑士背景内容与特色玩法一览。
游戏介绍:
王与异界骑士官网安卓版是派趣科技最新推出的一款女性向游戏,专为广大妹子们打造了多个美型男子形象,古风大侠也乱入现代战场,唯美人设华丽非常。该作沿用了《刀剑乱舞》的声优阵容,各种耳控福利等你来拿哦!
游戏特色:
1、美型人设的卡牌类游戏;
2、回合制自动战斗,摆脱繁琐操作;
3、特殊失败机制,堵在墙角无法逃脱;
4、三大角色属性,自由养成强化。
游戏玩法:
王与异界骑士的玩法和战舰少女差别不大。游戏的角色职业划分为力量、智力、魅力三大分类,技能体系分伤宦、DOT、增益、减益、治疗、防御等多种类型,为迎合女性玩家,
游戏加入了一些特别设定&&当HP为0时,会出现CP玩法,壁咚。
以上就是今天289掌游网小编给大家带来的王与异界骑士好玩么 游戏背景内容与特色玩法一览,更多关于王与异界骑士的精彩内容,请继续关注。
微信扫一扫,免费秒领礼包
点击查询最新礼包,限量礼包免费领取、独家礼包、福利好礼,轻松获得。
公众号查询:zhangyouwang289 (扫我) QQ群号:
责编:萤火虫289掌游网在手 ,礼包资讯应有尽有。
近期热门游戏
关注最强手游神器
[ 尽享独家礼包 ]
友情链接:&&&&&&&&&&&&&&&&&&其实发展到现在,炉石以前那种显著的职业特色已经被各种新卡以及平衡补丁削弱了,现在的主流牌组其实挺少见到各职业特色关键词了。&br&比如说德鲁伊,特色是跳费,缺点是贫弱的过牌能力和几乎没有的解场能力,可是终极赖皮出来以后,设计师为了不削弱这张大过牌,强行砍了德鲁伊的跳费,星界德无辜躺枪。而且在冰封王座和狗头人版本,德鲁伊获得了巨量的叠甲牌,虽然是符合wow熊德的设定,但是在炉石中,这也算是抢走了部分战士的职业特色。&br&叠甲从上古版本开始就是战士永恒的梦想,甚至很多防战不惜少下两个怪也要“叠个甲”。除了叠甲以外,战士惯常使用的伎俩还有激怒,通过捅兄弟两刀来激发兄弟的怒火,使他们获得更强的战斗力。为了激怒兄弟,战士拥有着不错的aoe,而在aoe砸完以后,更是能用斩杀和盾猛这种单体去除完成清场。同时,战士不仅能让随从上阵冲杀,自己也经常提着武器上场,无数铁头战最终都是死在了冲锋的路上。&br&与捅兄弟两刀的战士不同,信仰圣光的牧师们十分注重保护自己的友军,同时也希望度化误入歧途的对手,因而牧师拥有的是卓越的治疗和buff能力,以及不错的精神法术。他们少有进攻的手段,而擅长利用沉默,精神控制来稳住场面,再用保护友军,即使友军阵亡,也能通过复活术来使他们重返战场。相对应的,上古牧师的缺点就是缺少伤害手段,以及缺乏直接aoe,只能通过奥金尼,炎术士来完成清场。但是在近几个版本的加持下,牧师获得了大量aoe手段,还得到了足量的直伤能力,也算是矫枉过正吧。&br&但是无论如何,论到法术直伤,是没有人能比过法师的。法师是少有的不依赖随从的职业,依靠各种华丽的法术完成清场,斩杀,召唤,过牌等。各种法术间的灵巧配合,呼啸而来的巨额伤害,都是法师手中常见的把戏。在近几个版本,法师开始往随机性的路线发展,从秘法宝典到远古雕文,从红宝石到惊奇套牌,法师甚至自己都不知道自己会在什么时候用出什么法术来。&br&其实随机性应该算是萨满的特色。从闪电风暴到元素毁灭,从声控图腾到连环爆裂,从进化到不稳定的异变,无不彰显着人品的重要性。一个老萨满应该与自己的套牌心灵相通,说法术打几就打几,说法强图腾就法强图腾,说进化出末日就进化出末日。除开随机性,萨满还有一个显著的特色就是过载,通过提前透支下回合的法力获取本回合的强大输出,尤其是在熔岩震鸡和哨卫的加持下,萨满还能欠债不还,实在是……太刺激了。&br&而与萨满的赌人品不同,圣骑士应该算是最稳扎稳打的职业了,几乎没有什么特别看人品的牌,就老老实实的铺场,控场,打死对手。圣骑的特色就是圣盾,buff以及各种软解。以三个城管为代表的圣骑专属解牌,都没有任何直接的去除能力,而是通过改变数值这种方式来使对面的大哥变得温和,达成控场的效果。&br&相反的,盗贼就不屑于使用这种温和的方式,他们擅长通过偷袭来达成回手和直接去除的效果。他们拥有游戏中最华丽的战斗技巧,通过大量低费随从,低费法术和高速过牌来完成一套又一套的组合技。而为了使组合技达成,盗贼必须通过连击这一关键词,将所有卡牌通过连招而不是随心所欲的方式打出。灵活的盗贼们还擅长使敌我的随从反复回手,以达成重复利用我方低费随从以及浪费敌方法力值重复使用大随从的效果。&br&也许是华丽的技巧太难以掌握,猎人们不愿去学习,转而寻求外部的能力来战斗。猎人们擅长与野兽沟通,通过训练野兽,猎人们获得了强大的战斗能力。他们不需要任何战斗技巧,只需要用凶狠的攻势打烂对面的狗头。猎人们拥有着充足的制造伤害的能力,他们的对手随时随地都面临着突然死亡的风险。&br&相对于猎人那随时随地带走对面的能力,术士更应该担心的是自己那随时随地带走自己的能力。术士擅长做交易,用生命,手牌,法力水晶,随从等一切资源来获取力量。经典卡牌力量的代价就很好的诠释了这一点,为了力量术士可以不惜一切手段。因此他们是最擅长自残的一群人。而如此邪恶的交易,也只有恶魔能接受,因此术士最亲密的伙伴就是各式各样的恶魔,关键时刻术士们甚至不惜付出生命的代价让恶魔代替自己接着战斗。这种不怕死的精神使术士获得了游戏中最重要的过牌能力,通过卖血,术士可以快速过牌,囤积手牌资源或者快速铺场。
其实发展到现在,炉石以前那种显著的职业特色已经被各种新卡以及平衡补丁削弱了,现在的主流牌组其实挺少见到各职业特色关键词了。 比如说德鲁伊,特色是跳费,缺点是贫弱的过牌能力和几乎没有的解场能力,可是终极赖皮出来以后,设计师为了不削弱这张大过…
&p&重点不在于“故事”,而在于“意义”&/p&&p&对于有些游戏,由于其规则本身就具有足够的深度和乐趣,完全可以不需要故事或回答玩家其游戏行为的意义——例如黑白棋/围棋,或强反应向的动作游戏(如超级六边形/几何战争)&/p&&p&因为玩家在玩这些游戏的时候,游戏规则的策略复杂度或者对实时响应能力的极高要求基本上榨干了玩家的思考余力,玩家实在没啥空余脑力来思考它们做的事情到底有什么意义&/p&&p&但是这世上绝大部分的游戏并不是这样的,很多时候你不得不在游戏里做一些其实并没有多少趣味也并不怎么金贵的操作(典型比如各类RPG游戏),这时候玩家不免会产生很多问题,比如我是谁,我为什么要玩这个游戏,我接下来要干什么……这时候游戏必须要为玩家提供答案,不然玩家就很容易陷入迷茫状态,而玩家一旦迷茫了嘛,那距离流失自然也就不远了对吧?而背景故事是一种很便利的提供“意义”的设计手段。&/p&&p&&br&&/p&&p&就拿Diablo这游戏来说,如果你把这个设计手段完全剥离掉,那么游戏是这样的:&/p&&p&屏幕中心有个矩形,点击矩形周围的区域可以让矩形移动,你会见到其他矩形,点击其他矩形可以让你的矩形进入一个新状态,其他矩形在靠近你的矩形时也会进入这个状态,在这个状态下它们可以互相交换一些数值,当这些数值满足一定条件后,其中一个矩形会被销毁,并创造出一些新的矩形,你可以操作自己的矩形将这些新矩形转移到自身的某一个数据条目槽上……&/p&&p&你猜这么个玩意的留存率大概是多少捏?&/p&&p&现在我们把背景故事这个设计手段加上,那么上面一段就会变成这样:&/p&&p&屏幕中心是你控制的英雄,周围是一片阴暗危险的陆地,点击周围的地面可以让你的英雄移动,移动中你会见到一些怪物,点击怪物后你的英雄会攻击它们,而怪物也会攻击你的英雄;当怪物的生命值降到0时,怪物会死亡并掉落装备,你的英雄可以捡起并穿上这些装备……&/p&&p&是不是好多了?&/p&&p&&br&&/p&&p&上面是个很极端的对比例子,因为第一段的矩形描述中严格地剥离掉了所有的背景故事元素,而现在游戏更多的不是“没有背景故事”,而是“没有合格的背景故事表现”:例如背景设定翻来覆去就那么点烂大街的东西,或者背景设定还不错但和游戏玩法系统完全搭不起来(例如让玩家扮演后宫嫔妃和其他妃子组队刷皇上掉材料升级紫薇天罡元气战魂)。烂俗的故事和低劣的实现导致的结果是一样的,那就是玩家游戏意义的消解,在最糟糕的情况下,游戏在玩家的认知中会退化为那个纯粹矩形般的样子,玩家与游戏之间的一切情感连接都会被打断,而玩家则会自然圆润的从游戏中流失。&/p&&p&&br&&/p&&p&说到这里,就该明白了,游戏设计背景故事并不是单纯为了讲述一个故事——毕竟单纯想看故事的话,比游戏好的故事载体多了去了。游戏背景故事最大的意义,在于为玩家提供游戏意义,并为设计师提供一个整体的设计指导——对于不靠核心游戏玩法乐趣取胜的复合型游戏,或者需要挤占玩家大量时间的游戏来说,背景故事以及其对游戏设计的整体指导意义就显得尤其重要。&/p&&p&&br&&/p&&p&毕竟,人是需要意义诠释才能生存下去的动物。&/p&
重点不在于“故事”,而在于“意义”对于有些游戏,由于其规则本身就具有足够的深度和乐趣,完全可以不需要故事或回答玩家其游戏行为的意义——例如黑白棋/围棋,或强反应向的动作游戏(如超级六边形/几何战争)因为玩家在玩这些游戏的时候,游戏规则的策略…
&p&下面这些图片由博主收集自reddit社区的“natureismetal”版块。它们展现了大自然令人超乎想象的一面。&/p&&figure&&img data-rawheight=&863& src=&https://pic1.zhimg.com/v2-03a9b345ec_b.jpg& data-rawwidth=&605& class=&origin_image zh-lightbox-thumb& width=&605& data-original=&https://pic1.zhimg.com/v2-03a9b345ec_r.jpg&&&/figure&↑ 名为“章鱼鬼笔”的菌类,像科幻片中的怪物卵。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&578& src=&https://pic4.zhimg.com/v2-12b95a67eddca2a39dbf88fb_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic4.zhimg.com/v2-12b95a67eddca2a39dbf88fb_r.jpg&&&/figure&↑ 猫头鹰的巢。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&535& src=&https://pic3.zhimg.com/v2-b9a7b2b8cffdd8443375ce_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic3.zhimg.com/v2-b9a7b2b8cffdd8443375ce_r.jpg&&&/figure&↑ 雪崩时被树枝插死的驼鹿尸体。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&720& src=&https://pic3.zhimg.com/v2-5ba4d3a6baddef2_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic3.zhimg.com/v2-5ba4d3a6baddef2_r.jpg&&&/figure&↑ 缩头鱼虱通过寄生在鱼的舌头上吸食鱼的血液,直到鱼的舌头萎缩。然后将自己的尾部与已经萎缩的鱼舌连接起来代替鱼舌工作。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&688& src=&https://pic2.zhimg.com/v2-8bd08ba2a925_b.jpg& data-rawwidth=&605& class=&origin_image zh-lightbox-thumb& width=&605& data-original=&https://pic2.zhimg.com/v2-8bd08ba2a925_r.jpg&&&/figure&↑ “椰子蟹(一种寄居蟹)”找到了一个玩具娃娃头当壳。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&605& src=&https://pic1.zhimg.com/v2-6f096b2b415324cab4f5a648c88ba0d0_b.jpg& data-rawwidth=&605& class=&origin_image zh-lightbox-thumb& width=&605& data-original=&https://pic1.zhimg.com/v2-6f096b2b415324cab4f5a648c88ba0d0_r.jpg&&&/figure&↑ 黄蜂把巢筑在残破的娃娃上。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&494& src=&https://pic4.zhimg.com/v2-0e1f7a2c642ff1d63d8cb96a631b6f07_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic4.zhimg.com/v2-0e1f7a2c642ff1d63d8cb96a631b6f07_r.jpg&&&/figure&↑ 外形残酷的非洲“盔甲蟋蟀”,正在残食同类。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&562& src=&https://pic2.zhimg.com/v2-2cef1dbe93efbc3a4da6e9d_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic2.zhimg.com/v2-2cef1dbe93efbc3a4da6e9d_r.jpg&&&/figure&&figure&&img data-rawheight=&540& src=&https://pic2.zhimg.com/v2-4fe97f28ae2e8ad9056129d_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic2.zhimg.com/v2-4fe97f28ae2e8ad9056129d_r.jpg&&&/figure&↑ 驯鹿的角正在褪皮。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&484& src=&https://pic4.zhimg.com/v2-412c1d1c323cc4001953_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic4.zhimg.com/v2-412c1d1c323cc4001953_r.jpg&&&/figure&↑ 自然褪下的鹿茸对驼鹿来说是不错的营养品。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&588& src=&https://pic4.zhimg.com/v2-b7c466177eef5a6d92abbf_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic4.zhimg.com/v2-b7c466177eef5a6d92abbf_r.jpg&&&/figure&↑ 红火蚁组成浮岛在水中自救。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&720& src=&https://pic3.zhimg.com/v2-4dc990dbae192f132ab49bca54b1111e_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic3.zhimg.com/v2-4dc990dbae192f132ab49bca54b1111e_r.jpg&&&/figure&↑ 齿鲸的牙。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&900& src=&https://pic1.zhimg.com/v2-ef7d756dce0941fd5adf0_b.jpg& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic1.zhimg.com/v2-ef7d756dce0941fd5adf0_r.jpg&&&/figure&↑ 石化的动物尸体。位於坦桑尼亚的纳特龙湖,水温高达60度,水质逼近强碱标准,动物接触后就会死亡,尸体会钙化成石雕一般。图为摄影师 Nick Brandt 用湖边捡到的石化动物尸体摆拍。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&648& src=&https://pic2.zhimg.com/v2-a7cbbdd5e8a9d11e28fcfe1_b.jpg& data-rawwidth=&484& class=&origin_image zh-lightbox-thumb& width=&484& data-original=&https://pic2.zhimg.com/v2-a7cbbdd5e8a9d11e28fcfe1_r.jpg&&&/figure&↑ 澳大利亚一户居民的窗前,一只蜘蛛正在捕食蛇。&p&&br&&/p&&p&&br&&/p&&figure&&img data-rawheight=&768& src=&https://pic4.zhimg.com/v2-7a268c9d2b81eb9fcd49b7_b.jpg& data-rawwidth=&576& class=&origin_image zh-lightbox-thumb& width=&576& data-original=&https://pic4.zhimg.com/v2-7a268c9d2b81eb9fcd49b7_r.jpg&&&/figure&↑ 在墨西哥,一条响尾蛇长到出奇的大。
下面这些图片由博主收集自reddit社区的“natureismetal”版块。它们展现了大自然令人超乎想象的一面。↑ 名为“章鱼鬼笔”的菌类,像科幻片中的怪物卵。 ↑ 猫头鹰的巢。 ↑ 雪崩时被树枝插死的驼鹿尸体。 ↑ 缩头鱼虱通过寄生在鱼的舌头上吸食鱼的血液,直…
&figure&&img src=&https://pic1.zhimg.com/v2-bf78c5ee9d7287db5ceabb_b.jpg& data-rawwidth=&2880& data-rawheight=&1800& class=&origin_image zh-lightbox-thumb& width=&2880& data-original=&https://pic1.zhimg.com/v2-bf78c5ee9d7287db5ceabb_r.jpg&&&/figure&&p&上一篇我们实现了在macOS上面的编译。但是我们采用的并不是macOS的原生接口,因此获得的OpenGL Context所支持的API版本也被限制到2.1版本。这显然是不够的。&/p&&p&因此,接下来我们来尝试使用macOS的原生图形接口 —— Cocoa,来完成OpenGL Context的创建工作。&/p&&p&Cocoa是基于Objective-C的一套Apple的独有系统。Objective-C是一种比较古老的语言,是C语言的超集,但是与C++语言又非常不同。作者在写这篇文章之前并没有接触过Objective-C语言,所以这两天查阅了大量的资料来“临时抱佛脚”。这里需要特别感谢 &a class=&member_mention& href=&https://www.zhihu.com/people/01ecc4f385d78dd67d08& data-hash=&01ecc4f385d78dd67d08& data-hovercard=&p$b$01ecc4f385d78dd67d08&&@袁全伟&/a& 分享的相关资料整理。我把这些资料会同我自己查阅到的资料都列在了参考引用当中。&/p&&p&实际写下来,感觉Cocoa类似Windows平台上的MFC,或者.NET的感觉,封装还是比较彻底的。好处当然是非常简单易用,但是同时也就意味着很多的细节被隐藏。在我早年学习MFC的时候,想要实现同时期Office所提供的一些很酷的控件效果,就发现非常的不容易。后来学习了Win32 API,发现就能很方便的实现了。这两天对Cocoa的突击学习又让我感觉似乎一下子回到了20多年前,找到了那种有力无处使的感觉。&/p&&p&但是到目前为止我还没有能够找到在macOS上比Cocoa更为底层的API接口。所以我们就将就着用吧。&/p&&p&Cocoa里面至少包含了两个Kit:AppKit和UIKit。AppKit提供了应用程序的框架结构,而UIKit提供了UI组件。最为方便地了解Cocoa的方法是使用XCode生成一个基于Cocoa的应用程序项目,这个过程与使用Visual Studio的向导创建项目非常类似,仅仅需要点击几次鼠标,输入应用程序名什么的,就能够生成一个基本的应用程序(含窗体)的基本结构。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-2f33df6b5afe23d3df058b51181c5ecf_b.jpg& data-rawwidth=&1672& data-rawheight=&1008& class=&origin_image zh-lightbox-thumb& width=&1672& data-original=&https://pic2.zhimg.com/v2-2f33df6b5afe23d3df058b51181c5ecf_r.jpg&&&figcaption&Xcode启动页面。选择Create a new Xcode project&/figcaption&&/figure&&figure&&img src=&https://pic3.zhimg.com/v2-9b41eabcba8eb9e22ca2e7_b.jpg& data-rawwidth=&1472& data-rawheight=&1062& class=&origin_image zh-lightbox-thumb& width=&1472& data-original=&https://pic3.zhimg.com/v2-9b41eabcba8eb9e22ca2e7_r.jpg&&&figcaption&应用程序向导界面。先选择macOS,然后选择Cocoa App&/figcaption&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-b5befd9d9d71f72c56df966_b.jpg& data-rawwidth=&1466& data-rawheight=&1058& class=&origin_image zh-lightbox-thumb& width=&1466& data-original=&https://pic4.zhimg.com/v2-b5befd9d9d71f72c56df966_r.jpg&&&figcaption&输入产品名Hello Cocoa,Language选Objective-C,取消所有勾选了的复选框,保持项目为最简状态&/figcaption&&/figure&&p&这样一个基本的Cocoa应用(含窗体)项目就生成好了。目录结构大致如下:&/p&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&.
├── Hello&span class=&se&&\ &/span&Cocoa
├── AppDelegate.h
├── AppDelegate.m
├── Assets.xcassets
└── AppIcon.appiconset
└── Contents.json
├── Base.lproj
└── MainMenu.xib
├── Hello_Cocoa.entitlements
├── Info.plist
└── main.m
└── Hello&span class=&se&&\ &/span&Cocoa.xcodeproj
├── project.pbxproj
└── project.xcworkspace
└── contents.xcworkspacedata
&/code&&/pre&&/div&&p&程序的主入口是 main.m,AppDelegate.h和AppDelegate.m定义了应用程序代理类,也就是可以对标准的Cocoa Application进行扩展的地方。Assets.xcassets当中是应用程序的一些资源文件,比如图标什么的。Base.lproj当中是被称为InterfaceBuilder工具的界面定义文件,就是所谓的WYSIWYG(What You See Is What You Get,所见即所得)的图形界面编辑器文件。Hello_Cocoa.entitlements和Info.plist,这两个都是XML格式的类似manifest的文件,用来向操作系统或者是APN等提供程序相关的meta data的。而Hello Cocoa.xcodeproj目录当中的则是Xcode的项目文件。&/p&&p&我们可以继续在Xcode当中编译这个项目并执行。如果采用命令行,那么编译的命令如下(假设我们在项目根目录):&/p&&div class=&highlight&&&pre&&code class=&language-console&&&span&&/span&&span class=&go&&chenwenlideMBP:Hello Cocoa chenwenli$ xcodebuild -project Hello\ Cocoa.xcodeproj/ build&/span&
&/code&&/pre&&/div&&p&然后执行&/p&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&chenwenlideMBP:Hello Cocoa chenwenli$ build/Release/Hello&span class=&se&&\ &/span&Cocoa.app/Contents/MacOS/Hello&span class=&se&&\ &/span&Cocoa
&/code&&/pre&&/div&&p&就可以看到我们的第一个Cocoa窗口了:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-19dab270b2a6dbcfd0ff_b.jpg& data-caption=&& data-rawwidth=&1184& data-rawheight=&988& class=&origin_image zh-lightbox-thumb& width=&1184& data-original=&https://pic3.zhimg.com/v2-19dab270b2a6dbcfd0ff_r.jpg&&&/figure&&p&然而,我们需要如何将它和我们之前的代码结合到一起呢?有没有可能将Objective-C和C++混合进行编程呢?&/p&&p&答案是可以的。Objective-C在经过编译之后,生成的二进制文件是与C语言有着同样的二进制接口的。也就是有二进制兼容性(Swift也是一样)。只不过,Object-C的面向对象模型与C++不同,不能简单地将两者等同起来(也就是运行时间库是不一样的)。在macOS全面采用clang作为编译工具之后,由于llvm中间层的存在,Objective-C与C++的互换变得更为方便,甚至出现了Objective-C++这种可以将两者同时写在一个文件当中的“编程语言”(新版本gcc也支持)。不过这里要注意,实际上Objective-C++并不是一种真正的新的语言,书写在源代码当中的Objective-C代码和C++代码其实是相对独立的被编译处理之后又链接在一起的。&/p&&p&好了,那么接下来让我们基于Xcode所生成的模版,按照我们自己所写引擎的架构和逻辑,编写Cocoa版本的Application和OpenGL Context创建代码。(将Objective-C代码嵌入到我们的C++代码当中)&/p&&p&首先我们将XcbApplication.{hpp,cpp}分别拷贝并改名为CocoaApplication.{h,mm}。mm是Objective-C++源代码的后缀。然后编辑如下:&/p&&p&CocoaApplication.h&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span&&span class=&cp&&#include&/span& &span class=&cpf&&&BaseApplication.hpp&&/span&&span class=&cp&&&/span&
&span class=&cp&&#include&/span& &span class=&cpf&&&Cocoa/Cocoa.h&&/span&&span class=&cp&&&/span&
&span class=&k&&namespace&/span& &span class=&n&&My&/span& &span class=&p&&{&/span&
&span class=&k&&class&/span& &span class=&nc&&CocoaApplication&/span& &span class=&o&&:&/span& &span class=&k&&public&/span& &span class=&n&&BaseApplication&/span&
&span class=&p&&{&/span&
&span class=&k&&public&/span&&span class=&o&&:&/span&
&span class=&n&&CocoaApplication&/span&&span class=&p&&(&/span&&span class=&n&&GfxConfiguration&/span&&span class=&o&&&&/span& &span class=&n&&config&/span&&span class=&p&&)&/span&
&span class=&o&&:&/span& &span class=&n&&BaseApplication&/span&&span class=&p&&(&/span&&span class=&n&&config&/span&&span class=&p&&)&/span& &span class=&p&&{};&/span&
&span class=&k&&virtual&/span& &span class=&kt&&int&/span& &span class=&nf&&Initialize&/span&&span class=&p&&();&/span&
&span class=&k&&virtual&/span& &span class=&kt&&void&/span& &span class=&nf&&Finalize&/span&&span class=&p&&();&/span&
&span class=&c1&&// One cycle of the main loop&/span&
&span class=&k&&virtual&/span& &span class=&kt&&void&/span& &span class=&nf&&Tick&/span&&span class=&p&&();&/span&
&span class=&k&&protected&/span&&span class=&o&&:&/span&
&span class=&n&&NSWindow&/span&&span class=&o&&*&/span& &span class=&n&&m_pWindow&/span&&span class=&p&&;&/span&
&span class=&p&&};&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=http%3A//cocoaapplication.mm/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&CocoaApplication.mm&/a&&/p&&div class=&highlight&&&pre&&code class=&language-objective-c++&&&span&&/span&&span class=&cp&&#include&/span& &span class=&cpf&&&string.h&&/span&&span class=&cp&&&/span&
&span class=&cp&&#include&/span& &span class=&cpf&&&CocoaApplication.hpp&&/span&&span class=&cp&&&/span&
&span class=&cp&&#include&/span& &span class=&cpf&&&MemoryManager.hpp&&/span&&span class=&cp&&&/span&
&span class=&cp&&#include&/span& &span class=&cpf&&&GraphicsManager.hpp&&/span&&span class=&cp&&&/span&
&span class=&cp&&#include&/span& &span class=&cpf&&&SceneManager.hpp&&/span&&span class=&cp&&&/span&
&span class=&cp&&#include&/span& &span class=&cpf&&&AssetLoader.hpp&&/span&&span class=&cp&&&/span&
&span class=&cp&&#import &AppDelegate.h&&/span&
&span class=&cp&&#import &WindowDelegate.h&&/span&
&span class=&k&&using&/span& &span class=&k&&namespace&/span& &span class=&n&&My&/span&&span class=&p&&;&/span&
&span class=&k&&namespace&/span& &span class=&n&&My&/span& &span class=&p&&{&/span&
&span class=&n&&GfxConfiguration&/span& &span class=&n&&config&/span&&span class=&p&&(&/span&&span class=&mi&&8&/span&&span class=&p&&,&/span& &span class=&mi&&8&/span&&span class=&p&&,&/span& &span class=&mi&&8&/span&&span class=&p&&,&/span& &span class=&mi&&8&/span&&span class=&p&&,&/span& &span class=&mi&&24&/span&&span class=&p&&,&/span& &span class=&mi&&8&/span&&span class=&p&&,&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&mi&&960&/span&&span class=&p&&,&/span& &span class=&mi&&540&/span&&span class=&p&&,&/span& &span class=&s&&&Game Engine From Scratch (MacOS Cocoa)&&/span&&span class=&p&&);&/span&
&span class=&n&&IApplication&/span&&span class=&o&&*&/span& &span class=&n&&g_pApp&/span&
&span class=&o&&=&/span& &span class=&k&&static_cast&/span&&span class=&o&&&&/span&&span class=&n&&IApplication&/span&&span class=&o&&*&&/span&&span class=&p&&(&/span&&span class=&k&&new&/span& &span class=&n&&CocoaApplication&/span&&span class=&p&&(&/span&&span class=&n&&config&/span&&span class=&p&&));&/span&
&span class=&n&&GraphicsManager&/span&&span class=&o&&*&/span& &span class=&n&&g_pGraphicsManager&/span& &span class=&o&&=&/span& &span class=&k&&static_cast&/span&&span class=&o&&&&/span&&span class=&n&&GraphicsManager&/span&&span class=&o&&*&&/span&&span class=&p&&(&/span&&span class=&k&&new&/span& &span class=&n&&GraphicsManager&/span&&span class=&p&&);&/span&
&span class=&n&&MemoryManager&/span&&span class=&o&&*&/span&
&span class=&n&&g_pMemoryManager&/span&
&span class=&o&&=&/span& &span class=&k&&static_cast&/span&&span class=&o&&&&/span&&span class=&n&&MemoryManager&/span&&span class=&o&&*&&/span&&span class=&p&&(&/span&&span class=&k&&new&/span& &span class=&n&&MemoryManager&/span&&span class=&p&&);&/span&
&span class=&n&&AssetLoader&/span&&span class=&o&&*&/span&
&span class=&n&&g_pAssetLoader&/span&
&span class=&o&&=&/span& &span class=&k&&static_cast&/span&&span class=&o&&&&/span&&span class=&n&&AssetLoader&/span&&span class=&o&&*&&/span&&span class=&p&&(&/span&&span class=&k&&new&/span& &span class=&n&&AssetLoader&/span&&span class=&p&&);&/span&
&span class=&n&&SceneManager&/span&&span class=&o&&*&/span&
&span class=&n&&g_pSceneManager&/span&
&span class=&o&&=&/span& &span class=&k&&static_cast&/span&&span class=&o&&&&/span&&span class=&n&&SceneManager&/span&&span class=&o&&*&&/span&&span class=&p&&(&/span&&span class=&k&&new&/span& &span class=&n&&SceneManager&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&kt&&int&/span& &span class=&n&&CocoaApplication&/span&&span class=&o&&::&/span&&span class=&n&&Initialize&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&result&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&p&&[&/span&&span class=&n&&NSApplication&/span&
&span class=&n&&sharedApplication&/span&&span class=&p&&];&/span&
&span class=&c1&&// Menu&/span&
&span class=&bp&&NSString&/span&&span class=&o&&*&/span& &span class=&n&&appName&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&bp&&NSString&/span& &span class=&nl&&stringWithFormat&/span&&span class=&p&&:&/span&&span class=&s&&@&%s&&/span&&span class=&p&&,&/span& &span class=&n&&m_Config&/span&&span class=&p&&.&/span&&span class=&n&&appName&/span&&span class=&p&&];&/span&
&span class=&kt&&id&/span& &span class=&n&&menubar&/span& &span class=&o&&=&/span& &span class=&p&&[[&/span&&span class=&n&&NSMenu&/span& &span class=&n&&alloc&/span&&span class=&p&&]&/span& &span class=&nl&&initWithTitle&/span&&span class=&p&&:&/span&&span class=&n&&appName&/span&&span class=&p&&];&/span&
&span class=&kt&&id&/span& &span class=&n&&appMenuItem&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&n&&NSMenuItem&/span& &span class=&k&&new&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&menubar&/span& &span class=&nl&&addItem&/span&&span class=&p&&:&/span& &span class=&n&&appMenuItem&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&NSApp&/span& &span class=&nl&&setMainMenu&/span&&span class=&p&&:&/span&&span class=&n&&menubar&/span&&span class=&p&&];&/span&
&span class=&kt&&id&/span& &span class=&n&&appMenu&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&n&&NSMenu&/span& &span class=&k&&new&/span&&span class=&p&&];&/span&
&span class=&kt&&id&/span& &span class=&n&&quitMenuItem&/span& &span class=&o&&=&/span& &span class=&p&&[[&/span&&span class=&n&&NSMenuItem&/span& &span class=&n&&alloc&/span&&span class=&p&&]&/span& &span class=&nl&&initWithTitle&/span&&span class=&p&&:&/span&&span class=&s&&@&Quit&&/span&
&span class=&nl&&action&/span&&span class=&p&&:&/span&&span class=&k&&@selector&/span&&span class=&p&&(&/span&&span class=&nl&&terminate&/span&&span class=&p&&:)&/span&
&span class=&nl&&keyEquivalent&/span&&span class=&p&&:&/span&&span class=&s&&@&q&&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&appMenu&/span& &span class=&nl&&addItem&/span&&span class=&p&&:&/span&&span class=&n&&quitMenuItem&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&appMenuItem&/span& &span class=&nl&&setSubmenu&/span&&span class=&p&&:&/span&&span class=&n&&appMenu&/span&&span class=&p&&];&/span&
&span class=&kt&&id&/span& &span class=&n&&appDelegate&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&n&&AppDelegate&/span& &span class=&k&&new&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&NSApp&/span& &span class=&nl&&setDelegate&/span&&span class=&p&&:&/span& &span class=&n&&appDelegate&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&NSApp&/span& &span class=&nl&&activateIgnoringOtherApps&/span&&span class=&p&&:&/span&&span class=&nb&&YES&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&NSApp&/span& &span class=&n&&finishLaunching&/span&&span class=&p&&];&/span&
&span class=&n&&NSInteger&/span& &span class=&n&&style&/span& &span class=&o&&=&/span& &span class=&n&&NSWindowStyleMaskTitled&/span& &span class=&o&&|&/span& &span class=&n&&NSWindowStyleMaskClosable&/span& &span class=&o&&|&/span&
&span class=&n&&NSWindowStyleMaskMiniaturizable&/span& &span class=&o&&|&/span& &span class=&n&&NSWindowStyleMaskResizable&/span&&span class=&p&&;&/span&
&span class=&n&&m_pWindow&/span& &span class=&o&&=&/span& &span class=&p&&[[&/span&&span class=&n&&NSWindow&/span& &span class=&n&&alloc&/span&&span class=&p&&]&/span& &span class=&nl&&initWithContentRect&/span&&span class=&p&&:&/span&&span class=&n&&CGRectMake&/span&&span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&n&&m_Config&/span&&span class=&p&&.&/span&&span class=&n&&screenWidth&/span&&span class=&p&&,&/span& &span class=&n&&m_Config&/span&&span class=&p&&.&/span&&span class=&n&&screenHeight&/span&&span class=&p&&)&/span& &span class=&nl&&styleMask&/span&&span class=&p&&:&/span&&span class=&n&&style&/span& &span class=&nl&&backing&/span&&span class=&p&&:&/span&&span class=&n&&NSBackingStoreBuffered&/span& &span class=&nl&&defer&/span&&span class=&p&&:&/span&&span class=&nb&&NO&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&m_pWindow&/span& &span class=&nl&&setTitle&/span&&span class=&p&&:&/span&&span class=&n&&appName&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&m_pWindow&/span& &span class=&nl&&makeKeyAndOrderFront&/span&&span class=&p&&:&/span&&span class=&nb&&nil&/span&&span class=&p&&];&/span&
&span class=&kt&&id&/span& &span class=&n&&winDelegate&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&n&&WindowDelegate&/span& &span class=&k&&new&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&m_pWindow&/span& &span class=&nl&&setDelegate&/span&&span class=&p&&:&/span&&span class=&n&&winDelegate&/span&&span class=&p&&];&/span&
&span class=&k&&return&/span& &span class=&n&&result&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kt&&void&/span& &span class=&n&&CocoaApplication&/span&&span class=&o&&::&/span&&span class=&n&&Finalize&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&p&&[&/span&&span class=&n&&m_pWindow&/span& &span class=&k&&release&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&span class=&kt&&void&/span& &span class=&n&&CocoaApplication&/span&&span class=&o&&::&/span&&span class=&n&&Tick&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&bp&&NSEvent&/span& &span class=&o&&*&/span&&span class=&n&&event&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&n&&NSApp&/span& &span class=&nl&&nextEventMatchingMask&/span&&span class=&p&&:&/span&&span class=&n&&NSEventMaskAny&/span&
&span class=&nl&&untilDate&/span&&span class=&p&&:&/span&&span class=&nb&&nil&/span&
&span class=&nl&&inMode&/span&&span class=&p&&:&/span&&span class=&n&&NSDefaultRunLoopMode&/span&
&span class=&nl&&dequeue&/span&&span class=&p&&:&/span&&span class=&nb&&YES&/span&&span class=&p&&];&/span&
&span class=&k&&switch&/span&&span class=&p&&([(&/span&&span class=&bp&&NSEvent&/span& &span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&n&&event&/span& &span class=&n&&type&/span&&span class=&p&&])&/span&
&span class=&p&&{&/span&
&span class=&k&&case&/span& &span class=&nl&&NSEventTypeKeyDown&/span&&span class=&p&&:&/span&
&span class=&n&&NSLog&/span&&span class=&p&&(&/span&&span class=&s&&@&Key Down Event Received!&&/span&&span class=&p&&);&/span&
&span class=&k&&break&/span&&span class=&p&&;&/span&
&span class=&k&&default&/span&&span class=&o&&:&/span&
&span class=&k&&break&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&[&/span&&span class=&n&&NSApp&/span& &span class=&nl&&sendEvent&/span&&span class=&p&&:&/span&&span class=&n&&event&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&NSApp&/span& &span class=&n&&updateWindows&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&event&/span& &span class=&k&&release&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&这里需要特别说明的就是我们去掉了[NSApp run],取而代之在Tick()当中使用了我们自己的EventLoop。这是为了保证我们的主循环在我们自己所写的main函数(最终会在驱动模块)当中。&/p&&p&然后在Platform/Darwin/CMakeLists.txt当中添加如下编译规则:&/p&&div class=&highlight&&&pre&&code class=&language-cmake&&&span&&/span&&span class=&c&&# MyGameEngineCocoa&/span&
&span class=&nb&&add_executable&/span&&span class=&p&&(&/span&&span class=&s&&MyGameEngineCocoa&/span& &span class=&s&&MACOSX_BUNDLE&/span&
&span class=&s&&CocoaApplication.mm&/span&
&span class=&s&&AppDelegate.m&/span&
&span class=&s&&WindowDelegate.m&/span&
&span class=&p&&)&/span&
&span class=&nb&&find_library&/span&&span class=&p&&(&/span&&span class=&s&&COCOA_LIBRARY&/span& &span class=&s&&Cocoa&/span& &span class=&s&&required&/span&&span class=&p&&)&/span&
&span class=&nb&&target_link_libraries&/span&&span class=&p&&(&/span&&span class=&s&&MyGameEngineCocoa&/span&
&span class=&s&&Common&/span&
&span class=&o&&${&/span&&span class=&nv&&OPENGEX_LIBRARY&/span&&span class=&o&&}&/span&
&span class=&o&&${&/span&&span class=&nv&&OPENDDL_LIBRARY&/span&&span class=&o&&}&/span&
&span class=&o&&${&/span&&span class=&nv&&XG_LIBRARY&/span&&span class=&o&&}&/span&
&span class=&o&&${&/span&&span class=&nv&&COCOA_LIBRARY&/span&&span class=&o&&}&/span&
&span class=&p&&)&/span&
&span class=&nb&&__add_xg_platform_dependencies&/span&&span class=&p&&(&/span&&span class=&s&&MyGameEngineCocoa&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&执行build.sh之后,就会在./build/Platform/Darwin/MyGameEngineCocoa.app/Contents/MacOS/MyGameEngineCocoa当中生成我们的可执行文件。执行它就可以看到我们的窗体了:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c512c39df1aacbedecb55be_b.jpg& data-caption=&& data-rawwidth=&2144& data-rawheight=&1348& class=&origin_image zh-lightbox-thumb& width=&2144& data-original=&https://pic4.zhimg.com/v2-c512c39df1aacbedecb55be_r.jpg&&&/figure&&p&看上去不错哦。然后让我们加入对于OpenGL的初始化代码。首先根据参考引用*4,创建两个新文件,GLView.{h,mm},从NSView派生出我们自己的View类:&/p&&p&GLView.h&/p&&div class=&highlight&&&pre&&code class=&language-objective-c&&&span&&/span&&span class=&cp&&#import &Cocoa/Cocoa.h&&/span&
&span class=&k&&@interface&/span& &span class=&nc&&GLView&/span& : &span class=&nc&&NSView&/span&
&span class=&p&&{&/span&
&span class=&k&&@private&/span&
&span class=&n&&NSOpenGLContext&/span&&span class=&o&&*&/span& &span class=&n&&_openGLContext&/span&&span class=&p&&;&/span&
&span class=&n&&NSOpenGLPixelFormat&/span&&span class=&o&&*&/span& &span class=&n&&_pixelFormat&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&@end&/span&
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=http%3A//glview.mm/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GLView.mm&/a&&/p&&div class=&highlight&&&pre&&code class=&language-objective-c&&&span&&/span&&span class=&cp&&#import &GLView.h&&/span&
&span class=&cp&&#import &OpenGL/gl.h&&/span&
&span class=&k&&@implementation&/span& &span class=&nc&&GLView&/span&
&span class=&p&&-&/span& &span class=&p&&(&/span&&span class=&kt&&id&/span&&span class=&p&&)&/span&&span class=&nf&&initWithFrame:&/span&&span class=&p&&(&/span&&span class=&n&&NSRect&/span&&span class=&p&&)&/span&&span class=&nv&&frameRect&/span& &span class=&nf&&pixelFormat:&/span&&span class=&p&&(&/span&&span class=&n&&NSOpenGLPixelFormat&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&nv&&format&/span&
&span class=&p&&{&/span&
&span class=&nb&&self&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&nb&&super&/span& &span class=&nl&&initWithFrame&/span&&span class=&p&&:&/span&&span class=&n&&frameRect&/span&&span class=&p&&];&/span&
&span class=&k&&return&/span& &span class=&nb&&self&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&-&/span& &span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&)&/span&&span class=&nf&&drawRect:&/span&&span class=&p&&(&/span&&span class=&n&&NSRect&/span&&span class=&p&&)&/span&&span class=&nv&&dirtyRect&/span& &span class=&p&&{&/span&
&span class=&p&&[&/span&&span class=&nb&&super&/span& &span class=&nl&&drawRect&/span&&span class=&p&&:&/span&&span class=&n&&dirtyRect&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&_openGLContext&/span& &span class=&n&&makeCurrentContext&/span&&span class=&p&&];&/span&
&span class=&n&&glClearColor&/span&&span class=&p&&(&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span&&span class=&mi&&0&/span&&span class=&p&&,&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span&&span class=&mi&&1&/span&&span class=&p&&);&/span&
&span class=&n&&glClear&/span&&span class=&p&&(&/span&&span class=&n&&GL_COLOR_BUFFER_BIT&/span&&span class=&p&&);&/span&
&span class=&p&&[&/span&&span class=&n&&_openGLContext&/span& &span class=&n&&flushBuffer&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&span class=&p&&-&/span& &span class=&p&&(&/span&&span class=&kt&&instancetype&/span&&span class=&p&&)&/span&&span class=&nf&&initWithFrame:&/span&&span class=&p&&(&/span&&span class=&n&&NSRect&/span&&span class=&p&&)&/span&&span class=&nv&&frameRect&/span&
&span class=&p&&{&/span&
&span class=&nb&&self&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&nb&&super&/span& &span class=&nl&&initWithFrame&/span&&span class=&p&&:&/span&&span class=&n&&frameRect&/span&&span class=&p&&];&/span&
&span class=&n&&NSOpenGLPixelFormatAttribute&/span& &span class=&n&&attrs&/span&&span class=&p&&[]&/span& &span class=&o&&=&/span& &span class=&p&&{&/span&
&span class=&n&&NSOpenGLPFAOpenGLProfile&/span&&span class=&p&&,&/span& &span class=&n&&NSOpenGLProfileVersion3_2Core&/span&&span class=&p&&,&/span&
&span class=&n&&NSOpenGLPFAColorSize&/span&&span class=&p&&,&/span&&span class=&mi&&32&/span&&span class=&p&&,&/span&
&span class=&n&&NSOpenGLPFADepthSize&/span&&span class=&p&&,&/span&&span class=&mi&&16&/span&&span class=&p&&,&/span&
&span class=&n&&NSOpenGLPFADoubleBuffer&/span&&span class=&p&&,&/span&
&span class=&n&&NSOpenGLPFAAccelerated&/span&&span class=&p&&,&/span&
&span class=&mi&&0&/span&
&span class=&p&&};&/span&
&span class=&n&&_pixelFormat&/span& &span class=&o&&=&/span& &span class=&p&&[[&/span&&span class=&n&&NSOpenGLPixelFormat&/span& &span class=&n&&alloc&/span&&span class=&p&&]&/span& &span class=&nl&&initWithAttributes&/span&&span class=&p&&:&/span&&span class=&n&&attrs&/span&&span class=&p&&];&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&n&&_pixelFormat&/span& &span class=&o&&==&/span& &span class=&nb&&nil&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&n&&NSLog&/span&&span class=&p&&(&/span&&span class=&s&&@&No valid matching OpenGL Pixel Format found&&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&nb&&self&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&n&&_openGLContext&/span& &span class=&o&&=&/span& &span class=&p&&[[&/span&&span class=&n&&NSOpenGLContext&/span& &span class=&n&&alloc&/span&&span class=&p&&]&/span& &span class=&nl&&initWithFormat&/span&&span class=&p&&:&/span&&span class=&n&&_pixelFormat&/span& &span class=&nl&&shareContext&/span&&span class=&p&&:&/span&&span class=&nb&&nil&/span&&span class=&p&&];&/span&
&span class=&p&&[[&/span&&span class=&bp&&NSNotificationCenter&/span& &span class=&n&&defaultCenter&/span&&span class=&p&&]&/span& &span class=&nl&&addObserver&/span&&span class=&p&&:&/span&&span class=&nb&&self&/span&
&span class=&nl&&selector&/span&&span class=&p&&:&/span&&span class=&k&&@selector&/span&&span class=&p&&(&/span&&span class=&nl&&_surfaceNeedsUpdate&/span&&span class=&p&&:)&/span&
&span class=&nl&&name&/span&&span class=&p&&:&/span&&span class=&n&&NSViewGlobalFrameDidChangeNotification&/span&
&span class=&nl&&object&/span&&span class=&p&&:&/span&&span class=&nb&&self&/span&&span class=&p&&];&/span&
&span class=&p&&[&/span&&span class=&n&&_openGLContext&/span& &span class=&n&&makeCurrentContext&/span&&span class=&p&&];&/span&
&span class=&k&&return&/span& &span class=&nb&&self&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&-&/span& &span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&)&/span&&span class=&nf&&lockFocus&/span&
&span class=&p&&{&/span&
&span class=&p&&[&/span&&span class=&nb&&super&/span& &span class=&n&&lockFocus&/span&&span class=&p&&];&/span&
&span class=&k&&if&/span&&span class=&p&&([&/span&&span class=&n&&_openGLContext&/span& &span class=&n&&view&/span&&span class=&p&&]&/span&&span class=&o&&!=&/span& &span class=&nb&&self&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&p&&[&/span&&span class=&n&&_openGLContext&/span& &span class=&nl&&setView&/span&&span class=&p&&:&/span&&span class=&nb&&self&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&span class=&p&&[&/span&&span class=&n&&_openGLContext&/span& &span class=&n&&makeCurrentContext&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&span class=&p&&-&/span& &span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&)&/span&&span class=&nf&&update&/span&
&span class=&p&&{&/span&
&span class=&p&&[&/span&&span class=&n&&_openGLContext&/span& &span class=&n&&update&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&span class=&p&&-&/span& &span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&)&/span& &span class=&nf&&_surfaceNeedsUpdate:&/span&&span class=&p&&(&/span&&span class=&bp&&NSNotification&/span&&span class=&o&&*&/span&&span class=&p&&)&/span& &span class=&nv&&notification&/span&
&span class=&p&&{&/span&
&span class=&p&&[&/span&&span class=&nb&&self&/span& &span class=&n&&update&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&span class=&k&&@end&/span&
&/code&&/pre&&/div&&p&然后再添加两个文件,CocoaOpenGLApplication.{h,mm},从CocoaApplication派生出带有OpenGL Context(GLView)的应用类型,在CocoaApplication的初始化之后,将GLView的实例替换到窗口客户区:&/p&&div class=&highlight&&&pre&&code class=&language-objective-c&&&span&&/span&&span class=&kt&&int&/span& &span class=&n&&CocoaOpenGLApplication&/span&&span class=&o&&::&/span&&span class=&n&&Initialize&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&result&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&n&&result&/span& &span class=&o&&=&/span& &span class=&n&&CocoaApplication&/span&&span class=&o&&::&/span&&span class=&n&&Initialize&/span&&span class=&p&&();&/span&
&span class=&n&&GLView&/span&&span class=&o&&*&/span& &span class=&n&&view&/span& &span class=&o&&=&/span& &span class=&p&&[[&/span&&span class=&n&&GLView&/span& &span class=&n&&alloc&/span&&span class=&p&&]&/span& &span class=&nl&&initWithFrame&/span&&span class=&p&&:&/span&&span class=&n&&CGRectMake&/span&&span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&n&&m_Config&/span&&span class=&p&&.&/span&&span class=&n&&screenWidth&/span&&span class=&p&&,&/span& &span class=&n&&m_Config&/span&&span class=&p&&.&/span&&span class=&n&&screenHeight&/span&&span class=&p&&)];&/span&
&span class=&p&&[&/span&&span class=&n&&m_pWindow&/span& &span class=&nl&&setContentView&/span&&span class=&p&&:&/span&&span class=&n&&view&/span&&span class=&p&&];&/span&
&span class=&k&&return&/span& &span class=&n&&result&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&最后是修改CMakeLists.txt。已经很长了,不赘述了。最后运行效果如题图。完成的代码在mac2分支当中。&/p&&p&&br&&/p&&p&&b&参考引用&/b&&/p&&ol&&li&&a href=&https://link.zhihu.com/?target=https%3A//www.khronos.org/opengl/wiki/Getting_Started%23macOS& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Getting Started - OpenGL Wiki&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//www.khronos.org/opengl/wiki/Platform_specifics%3A_macOS& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Programming OpenGL on macOS&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//stackoverflow.com/questions/2997333/creating-a-cocoa-application-without-nib-files& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Creating a Cocoa application without NIB files&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//developer.apple.com/opengl/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&OpenGL for macOS&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_drawing/opengl_drawing.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Drawing to a Window or View&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//cmake.org/cmake/help/v3.0/prop_tgt/MACOSX_BUNDLE.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MACOSX_BUNDLE - CMake 3.0.2 Documentation&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/appkit/nsapplicationdelegate& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AppKit | Apple Developer Documentation&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//cocoawithlove.com/2009/01/demystifying-nsapplication-by.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Demystifying NSApplication by recreating it&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Minimalist Cocoa programming&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Objective-C& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Objective-C - Wikipedia&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=http%3A//philjordan.eu/article/mixing-objective-c-c%2B%2B-and-objective-c%2B%2B& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Mixing Objective-C, C++ and Objective-C++: an Updated Summary&/a&&/li&&/ol&
上一篇我们实现了在macOS上面的编译。但是我们采用的并不是macOS的原生接口,因此获得的OpenGL Context所支持的API版本也被限制到2.1版本。这显然是不够的。因此,接下来我们来尝试使用macOS的原生图形接口 —— Cocoa,来完成OpenGL Context的创建工作。Co…
&figure&&img src=&https://pic3.zhimg.com/v2-5e25f3ded6_b.jpg& data-rawwidth=&1594& data-rawheight=&895& class=&origin_image zh-lightbox-thumb& width=&1594& data-original=&https://pic3.zhimg.com/v2-5e25f3ded6_r.jpg&&&/figure&&p&&b&~谨以此文,献给我玩不到的最终幻想14 4.0~&/b&&/p&&p&——感谢游研社约稿的分隔线——&/p&&p&&br&&/p&&p&我又欠下了FF14一篇文章。我曾经写过1.0、2.0,如今最终幻想14到了4.0,又该是给它写文章的时候了。&/p&&p&&br&&/p&&p&然而,我并没有实际玩到4.0。史克威尔埃尼克斯(Square Enix,以下简称SE)似乎完全不在乎老用户到底能不能玩到他的游戏。我从未见过“老用户回归”搞得像FF14这么复杂的游戏:似乎由于我的旧帐号是购买Crysta充值,他们通过非常复杂的算法断定我是个非法用户,不管我怎么折腾也无济于事,只能盯着2501错误发呆。通过贴吧和专业的“FF14续费专家”朋友们讲述,对很多不幸的中国区玩家来说,重新续费进国际服资料片需要一些有点复杂的操作……我说,我可是来送钱的啊!最后我做出了一个艰难的决定:放弃回归的努力(不过那个帐号的两三百万余款早就捐给公会朋友买了房子,进不去就算了)。&/p&&p&&br&&/p&&p&但看起来,SE确实有不在乎我这样的中国玩家回归的理由:FF14正走在一条缓慢却稳健的上升曲线上。&/p&&p&&br&&/p&&p&FF14是一款MMORPG。这个类型曾经是一个时代的象征,但如今这个时代已经过去。随着魔兽世界们慢慢老去,MMORPG看似已经不再是最热门的游戏类型,不再是整个行业的瞩目点。但在最近两三年中,最终幻想14却逆势而上。&/p&&p&&br&&/p&&p&日,最终幻想14的玩家帐号总数(不是角色),终于超过了一千万个。仅仅在两年又一个月前,也就是日,这个数字还只是五百万——那个数字,已经让这款游戏变成了游戏历史上第二成功的MMORPG,仅次于伟大的、不朽的魔兽世界。在这短短的25个月之中,这款游戏增加了五百万个订阅者账户,相当于每月都有20万新用户进入FF14并注册——就算他们中的很多人并没有真的付费订阅,这两年五百万新增对大多数非中国的网络游戏来说也是个相当惊人的数字。&/p&&p&&br&&/p&&p&作为另外一个对比数字,同样是在2015年的第三季度,动视暴雪(Activision Blizzard)停止了公布魔兽世界的订阅者人数。在这个他们决定不再公开订阅数的最后一个季度,此数字永远停留在550万的订阅数上;之后的两年中,暴雪没有再度公布这个数字的后续。最近一个大资料片军团再临可以算得上好评如潮,但暴雪也只是公布了300多万套的资料片数据,没有公布订阅数是否再次超过了550万。这对一个曾经达到过1200万付费订阅数的游戏——这个数字甚至不包含魔兽世界的中国服务器——来说,实在不能说是一个很鼓舞人心的数字。世界第一的MMORPG魔兽世界依然庞大,但它明显已经到了衰老的时候;最终幻想14虽然远没有魔兽世界订阅数多,但它的回升势头令人印象深刻。如果根据SE的1季度财报数字粗略估算,在头两周FF14的4.0版本资料片销售数量应该在120-130万套——是的,他们已经重新在这个衰退的MMORPG市场中站稳了脚跟,和魔兽世界站在了同一个数量级的战场上,并一点一点逼近仍旧拥有优势的对手。&/p&&p&&br&&/p&&p&但是,最终幻想14并不是一生下来就是世界第二大MMORPG的。在7年前,也就是FF14 1.0的时代,这个游戏的在线数字,甚至一度跌落到数千人;当时的SE,甚至不得不长期停止游戏的月卡收费以维持游戏内的基本人气。版本最惨的时候,FF14的1.0服务器里,是不是能凑出三千在线用户,都成了一件可疑的事情……&/p&&p&&br&&/p&&p&然而,正如那挽名联所说——有志者,事竟成,百二秦关终属楚;苦心人,天不负,三千越甲可吞吴。FF14不仅从这谷底爬上来了,还一点一点,收复了FF11甚至魔兽世界的部分失地,稳稳地站在了“世界第二”的位置上。&/p&&p&&br&&/p&&p&哦,甚至还有野生的记录片制作团队noclip,为此拍了一部纪录片《卧薪尝胆》……不对,是《最终幻想14:死亡与新生》,来记录这一伟业。而在Youtube上,也有不止一位主播制作过以此为主题的记录片。所以,我们现在可以结合这些新的消息来源,更为全面地探讨关于FF14从失败中重新复活的原因。&/p&&p&&br&&/p&&p&&b&1.0的失败中,埋藏了再次振兴的种子&/b&&/p&&p&&br&&/p&&p&关于FF14 1.0的资料,在中文互联网上可以说是相当稀有的:这是一款造成了公测大地震的游戏,导致它在整个互联网上都如流星般一闪即逝。当然,你可以在旗舰这种历史悠久的评论专栏的老文章中,找到关于FF 14 1.0的测试感想,来了解那到底是个怎样的游戏;你也可以找到另外一套极富盛名的主播视频《最终幻想14:毁灭与重生》来仔细了解1.0那些惊人糟糕的内容,比如那张整个游戏史上最惨烈的“黑森林”(Black Shroud)地图。我不会在这里重复那些我曾经写过的内容,各位可以看图激起对这些内容的兴趣,然后自行去搜索阅读背景知识。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c1cdbaadb813_b.jpg& data-rawwidth=&640& data-rawheight=&593& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic4.zhimg.com/v2-c1cdbaadb813_r.jpg&&&/figure&&p&&br&&/p&&p&之后,你可以搜索《最终幻想14:死亡与新生》,开始看noclip拍摄的这部记录片了。noclip是一个在Patreon上面长期众筹的民间纪录片拍摄团队,致力于拍摄游戏制作方面的纪录片和采访短片。这可能是历史上第一部关于游戏开发过程的失败的记录片。他们耗费大量人力物力,采访了包括制作人吉田直树在内的很多Square Enix的当事人,这让这部记录片变成了一部重要的历史文献。他们从另外一个角度,讲述了1.0真正的故事。当然,你可以想象,这些采访并不全面——毕竟,他们肯定采访不到河本信昭啊。但他们确实描述了1.0失败的成因,以及在其中埋藏着的未来重振的火花。&/p&&p&&br&&/p&&p&各位需要知道的是,在中国非常没名气的最终幻想11,曾经是个极为成功的游戏。FF11是家用机上最早的MMORPG,在很长时间里也是唯一的家用机MMORPG。它曾是公认的日本第一、全球第二网络游戏,设计上的贡献仅次于MMORPG开山祖师无尽的任务(Everquest),江湖地位也仅次于2005年的魔兽世界。这个游戏的设计里,也充满着传奇般的故事,例如花了整整七年才被打倒的一个世界BOSS——虽然鲜为人知,但这是我个人最喜欢的网络游戏设计案例之一。你甚至可以说,正是因为FF11的存在,魔兽世界才没有日本服务器。在整个日本,你提起“MMORPG”,人们的第一反应就是FF11;如果你读那些日产轻小说,里面的网游世界几乎全都以FF11为蓝本。有大量欧美用户为了玩FF11,用游戏内置的短语在日本服务器上和日本玩家一起打副本。FF11的魅力在当年就是这么惊人。如果具体到没有魔兽世界的家用机平台,FF11就是当时最成功的网络游戏,没有之一。&/p&&p&&br&&/p&&p&FF11当然也非常赚钱。游戏2002年开始PS2日本测试,2003年起正式收费,一收费就开始大捞特捞,每年最少收入也是100亿日元(超过1亿美元)以上,单款游戏给SE带来的总收入远超过1000亿日元(10亿美元),是整个FF系列里总收入最高的作品,也是中国以外MMORPG收入第二高的游戏。因FF11的这一成绩,2010年SE将MMO事业部(FF11)的报表合并进游戏事业部,负责制作的田中弘道和外号“河豚”的河本信昭则早在那之前就已经担负起了FF14的重大责任。&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-eb4a8a35639e6_b.jpg& data-rawwidth=&1154& data-rawheight=&632& class=&origin_image zh-lightbox-thumb& width=&1154& data-original=&https://pic3.zhimg.com/v2-eb4a8a35639e6_r.jpg&&&/figure&&p&&br&&/p&&p&失败的种子就在这里埋下了。由于FF11的巨大成功,日本人发展出了自己对MMO的独特理解:相比于现代网游,那更接近于EQ、UO时代的原始理念,对合作、社交和残酷世界的描述更加精准。在采访中,吉田率直地批判了当时的FF14团队,乃至整个SE公司:SE在整个PS、PS2时代都是世界第一的技术力,这导致整个公司非常自大,对自己做游戏的能力非常自信,自信到了没有人玩过其他人的游戏的程度。这是一家由世界上最好的刀匠组成的公司,可以一点点做出世界上最好的刀,却因为这种自大缺乏感受技术与时代变革的能力。整个公司,居然一个玩过魔兽世界的人都没有!&/p&&p&&br&&/p&&p&当然,就算不需要采访,我们每个见过FF14 1.0的人也都猜出了这一点。这个游戏不仅仅是完全无视魔兽世界,更完全无视了FF11。无视对手,忽略自己的积累,落后时代变化,对玩家的接受力一无所知,还妄想着像FF11一样一点点改好,正是整个FF14 1.0惨败的根源。&/p&&p&&br&&/p&&p&整个公司里唯一的西方游戏爱好者,制作人吉田拯救了这个项目。作为空降的救火队长,经过了紧急处理小组7周的研究之后,他面前有着两个方案:通过补丁更新,来试图拯救FF14,这样游戏有极大的可能性失败,会损失这个品牌,但经济损失会小;另外一个选择,就是重新开发一个新的FF14,告诉大家这就是2.0。从没有人这么做过,谁都知道再做一个项目究竟要花费多少资金和人力,而且随时都可能随着公司政治的变动而被砍掉。&/p&&p&&br&&/p&&p&但在记录片里,他说的这句话打动了我。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-1f33eabe76df639d8a2533_b.jpg& data-rawwidth=&645& data-rawheight=&477& class=&origin_image zh-lightbox-thumb& width=&645& data-original=&https://pic4.zhimg.com/v2-1f33eabe76df639d8a2533_r.jpg&&&/figure&&p&“但这也许会让粉丝们觉得,最终幻想果然还是能做一些疯狂的事情啊。这也许会挽回他们的心。”&/p&&p&&br&&/p&&p&&br&&/p&&p&虽然我们一般都认为,SE的前任和田洋一社长算不上什么成功的游戏行业管理人员,他甚至不怎么懂得游戏,但能够身体力行地执行这句话,他应该也没有大多数SE粉丝想得那么糟糕。&/p&&p&&br&&/p&&p&基于这一思路,B计划得到了执行。FF14的团队被分成两个,一个很小的团队坚持维护1.0,将它一点点从一个完全不可救药的游戏,变成了一个仍旧很糟糕,但好歹可以运转的主流网络游戏;而另外一组人马,则以在日本业界极为罕见的敏捷开发方法,以车轮战般的高效率开发着FF14 2.0。在这一过程中,他们还战胜了东日本大海啸,这一几乎令整个日本经济为之停摆的巨大灾难,并坚决地维持了游戏在此期间的运转。作为中国玩家,我们中的很多人都还记得5.20大地震时,那些头像永远不再亮起的魔兽玩家传说;而在日本,对应的就是在坚持运转的FF14里人们彼此保持联系的逸闻(毕竟魔兽世界没有日服啊)。&/p&&p&&br&&/p&&p&而在1.0最后的结束到来之前,1.0团队更是非常用心地设计了整个世界的结束事件。这成为了整个网络游戏史上最有魄力的设计之一,虽然绝大多数2.0玩家都没能亲眼见到这个事件,但它的录像迄今仍然能在nicovideo和youtube上找到。1.0的稳定用户数量并不多,少到了可以让SE在FF14 2.0(ARR)中把所有玩家都在制作人员表单中感谢一遍的程度,但这些人无疑会真地非常忠诚于这个游戏。还有另外一大批会受到这一行为影响的玩家:他们就是FF11的用户。直到2014甚至15年,FF11都还是一款在日本运转良好的网络游戏,他们往往鄙视FF14 1.0,却通过一些转过去打的朋友关心着这款游戏的进展。&/p&&p&&br&&/p&&p&所有这些努力都被这两群玩家们看在眼里。这将会给SE带来他们意想不到的收获:坚定的核心FF14 1.0玩家,以及那群不肯从FF11转换到FF14的日本核心MMO玩家,开始逐渐扭转对FF14的偏见。在我看来,这将会成为FF14最大的一笔财富,进而支撑着他们从1.0的泥潭中爬出来。&/p&&p&&br&&/p&&p&&b&从1.0到2.0:敏捷开发的败笔和魔兽模仿秀&/b&&/p&&p&&br&&/p&&p&在2年半的时间里同时维护1.0和重做2.0,的确是游戏开发史上罕见的冒险之举。吉田所引进的敏捷开发方法,实际上对游戏来说是一柄双刃剑:它的确可以极大提高开发效率,最快速度完成整个游戏,但它同时也意味着游戏设计师们将会失去打磨游戏的机会。这样的敏捷开发和对效率的极度追求,在最终完成的2.0中也造成了很大的副作用。整个团队几乎没有时间来思考游戏应该如何设计,每个模块的耦合、深度也不得不为了开发周期让位。如果说FF14 1.0各个小组各自为战造成了巨大的悲剧,那么2.0就是对魔兽世界的一次大型模仿秀。当然,旗舰也曾经写过关于FF 14 2.0的文章,各位可以自行搜索,那些琐碎的问题我在这里不再一一详述。&/p&&p&&br&&/p&&p&总而言之,最初的2.0看起来答案是如此明显:吉田的制作文档,就是完全按照魔兽世界的框架进行了复制。我几乎可以想象那些“去玩魔兽世界的对应系统,然后在3小时内写完文档”这样的指令——在国内的游戏开发中,这样的“高效率开发”并不鲜见,甚至可以说是我们生活的一部分。除了副本从5人减少到4人之外,你几乎难以找到2.0和魔兽世界的本质区别;由于制作团队和1.0并不一样,就连职业特性也完全重设,更接近魔兽世界而非1.0。而整个游戏的制作质量更是非常令人担忧:整个游戏中,“急就章”的感觉随处可见。你可以在很多个角落中看到“敏捷开发”留下的创伤:反复前往沙之家的可笑流程、很多任务和野外Fate那太过草率的设计、部分职业任务和支线的日文文本完成度甚至还不如英文本地化组的再创作,你都能在这些设计中看到那个被敏捷开发逼得两眼发红的人的影子。&/p&&p&&br&&/p&&p&不管是对于熟悉FF11的玩家来说,还是对于熟悉魔兽世界的玩家来说,这个新的FF14 ARR在2.0版本时都远远称不上令人满意。FF11的玩家,很多都觉得这个游戏的深度太差,虽然好过FF14的1.0,但和FF11当时的版本差得还太远,只是一个割草、刷副本式的快餐游戏。而魔兽世界的玩家,则对这个游戏的细节充满了挑剔,游戏战斗节奏比60年代的魔兽世界还要缓慢,部分副本淡而无味,部分副本对操作、走位、延迟的要求高得又像最难的那些英雄本一样。&/p&&p&&br&&/p&&p&所以,在当时我就对这个游戏的未来感到非常的担心。在2013年,很多人都已经能看到魔兽世界模式显露出的疲态。一个版本又一个版本飞快地被玩家啃光,然后陷入排随机本的汪洋大海之中;而这些随机本又在疯狂地破坏着整个游戏的社交结构和用户稳定性。FF14 2.0看起来就是要完全复制这一模式。玩家打随机本能积累怎样的社交关系?每周去肝那么难的大迷宫和8人高级本会对游戏内的环境造成怎样的冲击?我简直能想象到类似魔兽后期本中那些玩家互相谩骂的场面。和魔兽不同,FF14 2.0看起来还很缺乏周边玩法,玩家们甚至不能去刷声望、刷竞技场打发时间。这些高难度本,到底需要怎样的玩家才能支撑起来?连魔兽世界,都没有这样的玩家啊!&/p&&p&&br&&/p&&p&我的这个担心成真了一半。如果熟悉国服FF14初期情况的人,可能还记得这款游戏遭遇的水土不服。绝大多数用户都碰到了这样的问题:游戏难的副本太难,消耗时间的部分又太过单调,导致了游戏大规模流失用户和合服。虽然画面确实凌驾于市面上的大多数网游,但排副本的体验比起同期的魔兽世界甚至更要糟糕。如果你会打,你可能会排到大量对游戏和副本没有任何认识的“小白”玩家,然后在野队中一次次灭得找不到北;如果你是个“小白”玩家,情况可能会更糟糕,你会面对着那些老玩家出于愤怒本能的谩骂和退团。游戏后期的高难度本们问题就更大了:这些设计给固定队伍的顶级BOSS们拥有比魔兽世界多不少的动作性成分,要求参与者们对BOSS的基本行动规律有着预判,当然也要求更高的反应和网速。在这些本里,如果你排随机队伍,很可能会发现这样一个结果:当有“首次进入”或者装备等级“不够”(通常是一个比较高的数字)的玩家出现在队伍里时,你的队友纷纷退出队伍,剩下的人则面面相觑。这种现象直到大规模合服结束、剩下的基本都是这款游戏的忠诚玩家后才逐渐消失。但时至今日,热爱最终幻想14的国服玩家仍然能不时在贴吧看到来挑衅的野生玩家。&/p&&p&&br&&/p&&p&不过,和国服不同,国际服并没有走上这条大规模衰退,然后稳定的道路。虽然国际服也经过了初期的短暂下滑,也有部分服务器沦为鬼服的情况,但整体来说,整个国际服的人口随着新补丁的一步步增加是在不断回升的。到了3.0后期,国际服的大多数服务器都重新进入到了高人口状态,所有价格昂贵的玩家房屋、公会房屋都被销售一空,无家可归的玩家们甚至在呼吁SE给他们增加新的可购房屋空间。&/p&&p&&br&&/p&&p&那么,国际服到底是依靠着什么,才弥补了游戏体验上的问题呢?那个答案,就是“J语言玩家”——以1.0和FF11的核心忠诚用户为基础,逐渐扩大的核心用户群。国服经过大浪淘沙反复努力后才得到的用户们,国际服一开始就有了。&/p&&p&&br&&/p&&p&&b&核心玩家的力量:是什么支撑着最终幻想14国际服缓慢上升?&/b&&/p&&p&&br&&/p&&p&从最终幻想14的2.0到4.0,并不是一个像从1.0到2.0那么戏剧化的故事。相比于2.0 ARR“突然变成了魔兽世界”那样惊天动地的变革,后面这些版本缓慢地、但却坚定地探索着一套和魔兽世界乃至市场主流都不太一样的设计方向。在魔兽世界努力拓宽它的游戏内容,增加不同的游戏方式和掉落逻辑,试图扩展新用户、稳定现有用户、回流老用户的时候,最终幻想14坚定地走在MMORPG古老的道路上:制作更多、内容更有深度、逻辑更复杂、演出更出色的副本;给游戏世界增添更长、更多变的主线和支线任务;提供多样但不强制的内容填充玩家的多余时间。用制作者自己的话来总结,可能会显得这种设计思想更加鲜明。&/p&&p&&br&&/p&&p&“从设计角度来说,我从来不想让玩家每天登陆才能跟上游戏版本。我想避免强制性内容,只要AFK(离开游戏)一次就跟不上进度的事情是绝对要避免的。因为大家都很忙,不是每人每天都能登陆,所以我觉得每个大版本回来一口气再玩通也是可以接受的。然后大家就可以觉得无聊,可以慢下来,可以去玩别的游戏,或者去陪陪家人什么的。我觉得未来的MMO必须这样,否则这个类型就没有希望了。每次大资料片推出,里面会有相当于一个单机RPG那么多的实际内容,大家就再一起回来体验最新的最终幻想。这就是我们的设计理念。”&/p&&p&&br&&/p&&p&这段话来自《死亡与新生》中对FF14 2.0-4.0制作人吉田直树的采访。这次采访就发生在2017年,可以说象征着从FF14 2.0到4.0期间他们逐渐确认的新设计思路:这个思路和魔兽世界,乃至于市面上主流的MMORPG,又已经不一样了。这段话代表着,FF14的制作团队,已经从2.0时魔兽的影子里走了出来,并开始寻找自己游戏在市场上独特的定位。&/p&&p&&br&&/p&&p&这可能是我见过最直率的“反对留存率”的意见了。自社交游戏时代引进“留存率”这个概念以来,几乎整个网络游戏的设计思路都为其把持。不管你的游戏类型是怎样的,所有网络游戏设计师都在挖空心思延长玩家每天都回来上线和持续长时间在线的奖励。每日/每周/每月任务、每日首胜、每日连胜、在线时间奖、连续登陆奖、玩家的基地或要塞、可定时收取内容、公会每日产出、定时活动、跑环活动、定期排名、定期擂台战……这个列表上能找到上百种、或许数百种不同的设计,它们都是围绕着“怎样让玩家天天在线、持续在线”这个设计目标而展开的。甚至就连FF14 2.0所参考的魔兽世界,也逃离不了留存率的阴影:要塞系统我们人人都知道是干什么的,“上线了就出不了要塞”和“在魔兽世界里打页游”也不完全是戏言。而从大约2.0中期开始的FF14虽然也有给那些时间真的富裕的玩家反复游戏的内容,但绝大多数主线、副本内容确实如吉田所说,这些是“如同单机RPG”一般,虽然总时间很长,内容非常充足(你如果从2.0主线一直做到4.0主线,很可能要2-300个小时),但不需要玩家每天上来重复登陆打卡做任务才能追上进度。游戏也仍然保留着“几乎无视PVP内容”的传统,不会有排行榜或者竞技场之类的东西威逼着你追上服务器的装备等级。&/p&&p&&br&&/p&&p&这个设计思路好吗?嗯,理论上来说很好。它就是我这样的玩家,乃至家用机核心玩家们会喜欢的那种设计:内容密集丰富、没有太多压力、弃坑了也还有机会回来。可实际上呢,我就直说了吧——通常来说,一个网络游戏如果这么干,他们是会死的!&/p&&p&&br&&/p&&p&为什么其他人都要努力维持玩家的留存率和在线时间?因为如果一个多人游戏没人玩,它很快就会匹配不到其他玩家彻底死了啊!不管是在中国还是在欧美,绝大多数网络游戏的生命线都维系在那些轻度、非核心玩家的身上。制作内容本身,又是一个成本高昂的事业:绝大多数游戏公司都喜欢英雄联盟或者绝地求生这种不需要制作内容、又能盈利很多的游戏,制作内容这种事情实在显得吃力不讨好。除非你有一种办法,能够从网络游戏的海量轻度玩家之中,持续而低成本地将不多的核心网络玩家爱好者筛进你的游戏里面,否则最终幻想14这种设计思路就是自寻死路。&/p&&p&&br&&/p&&p&这就是最终幻想14这个特立独行的设计思路还能存活下来的真正原因了:他们还真的有这群核心玩家!&/p&&p&&br&&/p&&p&当我这样的FF14国际服轻度用户打不过一个副本,我的公会朋友没时间带我过团,在野队中灭得死去活来的时候,我会做出这样一件事情:在匹配语言中点掉自己平时用的“E语言”(英语),偷偷地钩上“J语言”(日语),并拿出一个记录着“50句常用聊天日语”的文本文档,假装自己懂得日语的样子,然后开始匹配。然后,我就会进入一个神奇的用户群体:这些人中的大多数很有礼貌、不会开口骂人、有耐心不会退队,更重要的是,技术远远高过平均水平。当我在“J语言”的世界里的时候,经常能碰到这样的情况:一个8人随机本有1个人甚至2个人不会打的时候,剩下6个人也能强行带着全队人破关,大家还能非常友善地在队伍频道里互致问候。我FF14国际服里的所有朋友(大多数是中国人)几乎都有这么一个伪装成J语言玩家的小本本,在他们的QQ群里会经常探讨“怎么更好地伪装成J语言用户”,连带着他们自己排本的时候都似乎变得更友善了一些。据我听说,不光是中国人,甚至连美国人、欧洲人中的核心用户,很多人也都偷偷藏着一套“J语言伪装指南”,试图在排不到自己语言的时候找到J语言用户一起匹配打本。因为这些理由,你甚至不能说这些人是“日语用户”:他们中的很多人甚至都不会说日语,只是因为喜爱“J语言匹配”所象征着的游戏方式,才聚集在这个标志之下。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-3bf394bb3dfd8c8debb89a_b.jpg& data-rawwidth=&527& data-rawheight=&845& class=&origin_image zh-lightbox-thumb& width=&527& data-original=&https://pic3.zhimg.com/v2-3bf394bb3dfd8c8debb89a_r.jpg&&&/figure&&p&&br&&/p&&p&而这些“J语言用户”也根本不怕FF14官方的副本做得有多难,你看看他们做的各种攻略就知道了。如果你上过日服FF14那些日语第三方攻略网站,能看到他们那些详细得令人发指的攻略和视频指南;不知道为什么,这些信息似乎是大多数J语言玩家的必修课,他们就是会去自己学习这些内容,对挑战高难度甘之若饴,不会像大多数MMORPG那样逼着策划成天琢磨怎么降低难度、“让数据上更大比例的玩家体验到我们制作的内容”。这些使用着“J语言匹配”的用户,构成了FF14国际服环境和FF14国服环境最大的区别:在国际服的时候,我总是有“去J语言匹配打本”的最后选择,但在国服我会时常感觉到无路可走。在FF14两个补丁的间歇期,也是这些J语言匹配玩家默默地带着副本和蛮神队伍,维护游戏的基本氛围,将这个游戏的乐趣传承下去。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-f1f6c51fabeab54eb671f4_b.jpg& data-rawwidth=&500& data-rawheight=&286& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic1.zhimg.com/v2-f1f6c51fabeab54eb671f4_r.jpg&&&/figure&&p&&br&&/p&&p&他们中有极小一部分,是从FF14 1.0时代遗留至今的死忠用户。他们之中的相当一部分,是随着FF14改善以后,从FF14 1.0的流失用户中回流的人。他们之中的很大一部分,是随着FF11进入游戏末期,从FF11转移阵地过来的家用机网络游戏玩家。这些加起来,几乎就是日本所有MMORPG的核心用户了。Square Enix过去这些年中在FF14上破釜沉舟的努力,赢得了这些人最终的认可。在日本,由于从来没有传奇、天堂、魔兽世界、英雄联盟这些大用户量游戏的冲击,他们的MMORPG用户仍旧保持着大约15年、甚至20年前,网络创世纪、无尽的任务那时的部分感觉:小众而核心、待人友善、擅长挑战难度、对待游戏世界犹如真实世界……就好像魔兽世界60香草年代的那种味道。以他们为核心,结合了全世界仍然喜欢这种氛围的极少数其他语言的MMORPG用户,就构成了FF14中的“J语言匹配”的核心群体。这群人,构成了FF14从2.0到4.0的脊梁,是他们支撑着吉田和整个制作团队,能将“内容制

我要回帖

更多关于 趣头条职业 的文章

 

随机推荐