精华内容
下载资源
问答
  • 这是一本游戏设计方面的好书 ... 感谢天之虹的无私奉献 ...玩家和游戏设计师之间都有着一些初始的共同语言,但我们从来没有为了讨论特定游戏而一起去定义游戏感。我们会用“飘”、“响应灵敏”或者“操作很差”来描述一个

    这是一本游戏设计方面的好书
    转自天:天之虹的博客:http://blog.sina.com.cn/jackiechueng
    感谢天之虹的无私奉献
    Word版可到本人的资源中下载

     

    第一章定义游戏感

        游戏感是没有一个标准的定义的。玩家和游戏设计师之间都有着一些初始的共同语言,但我们从来没有为了讨论特定游戏而一起去定义游戏感。我们会用“飘”、“响应灵敏”或者“操作很差”来描述一个游戏的感觉,这些描述的含义是能跨越多个游戏的,例如“我们需要让这个游戏感觉起来响应更灵敏,就像《陨石大战》那样”。但假如我像写这本书之前所做的准备工作那样,找10个从业的游戏设计师来问问什么是游戏感,那我能得到10个不同的答案。而问题也在于这里:这10个答案中的每一个都是正确的。每个答案都描述了游戏感的一面,一个不同的领域,对游戏感来说都是至关重要的。

        对不少设计师来说,游戏感是和操作直觉有关的。一个在游戏感上表现出色的游戏能让玩家想做什么就能做什么,无需过多思考。好的游戏感会让一个游戏易学难精。游戏的快乐在于学习的过程,在玩家技巧和游戏呈现的挑战间有着完美的平衡。在学习过程中,掌握游戏技巧的感觉能让玩家得到感动内心的报酬。

        还有一些设计师持有不同的观点,他们关注于玩家和虚拟对象的身体交互上。他们觉得游戏感是和时限有关的,它能让玩家真正感觉到各种行为带来的影响,感受到每次移动感受到的帧率的不同,感受到交互过程的优美程度。

        另外一些设计师觉得游戏感是让玩家真正觉得自己就在游戏里的感觉。游戏感的效用就是在玩家面前造就一种“仿现实感”,借以提升玩家的沉浸感(沉浸感这个词的定义也是很宽泛的)。

        最后,剰下的设计师觉得游戏感等同于游戏的吸引力。你需要一层层画面效果地叠加,精心粉饰每一个交互行为,无论有多琐碎多细致,最终让游戏的交互过程有着一种视觉上的愉悦感。

        接下来的问题就在于统一了,如何让这些经验都整合成一个一致的整体呢?它们都描述出游戏感的某一方面,但都不能帮助我们对其给出定义。这让我想起了St. Augustine在尝试去给“时间”一个定义时所说的话:“到底什么是时间呢?假如没有人问我这个问题,我很清楚这是什么。然而一旦我要解释给问我的人听,那我就完全没有头绪了。”

        游戏感也是一眼的。不多加思考的话,我们都清楚它代表什么。但假如要尝试定义和解释它,那很快就考验到我们实践能力和个人经验了。

        这本书谈的是如何做出有着出色感觉的游戏。但首先我们需要清楚知道什么是游戏感。我们需要从内容中抽取出方法。我们需要通过一个定义来帮助我们从游戏感的判断中抽取出游戏感的必要条件。

        在我们创作游戏过程中的个人经验和工艺学问背后到底潜藏著什么样的现象呢?其建筑构造又是怎么样呢?到底什么是游戏感呢?

    游戏感的三大基础构件

        游戏感在玩家体验过程中是由三部分构造而成的,它们分别是:实时控制、模拟空间和润色。

    实时控制

        实时控制是交互行为中的一种特有形式。正如所有的交互行为那样,交互过程中至少有着两个参与者(换在游戏的情况里是计算机和用户),它们正如下图1.1那样共同形成一个封闭的循环,其概念是再简单不过了。

        用户有着某些意图,这些意图通过用户的输入传达到计篇机上。计算机用自己的内部模型去核对其输入,然后把结构输出给用户。随后用户感知到变化,思考其结果和原本的意图间的差别,再构思出新的行动,该行动通过另一次的输入再传达给计算机。

        在游戏设计师Chris Crawford的《Chris Crawford on Game Design》一书里,他把这个过程类比作一次交谈,它是“两个活跃个体交替(且象征性)地聆听、思考和言谈的循环过程”。

        这个交谈过程如下图1.2所示,它是在一方参与者Bob开始言谈时发生的。另一个参与者Bill聆听他所说的内容,思考后构思出一句回应的话。然后现在轮到Bob去聆听了,而后思考再说出回应,如此反复。在Crawford的模型里,计算机替代了其中一方的参与者,通过输入设备“不断聆听”玩家的输入,通过处理输入信息和改变系统状态来思考,再通过屏幕和扬声器来“说出回应”(图1.3)。

        不过人机交谈这样的比喻也不能用到所有的情形下。实时控制并不像交谈那样,它更像你在驾驶一澈汽车。只要驾驶者希望向左转,那更多的是直接行动而不是花时间思考,他会把方向盘转向相应的方向,利用他看到、听到和感受到的一切来对转向的行为作出细微调整,直到转弯完成为止。这个过程几乎是瞬间发生的。这种“交谈”在极短时间内在人的潜意识层面上完成,过程中伴随着一系列不间断的指令。输入后的结果感觉上像是在传达的同一瞬间就得到了。这正是游戏感的基础:对一个不断运动的角色精确且不间断的控制。

        这也是我们对游戏感下定义的起点:

    对虚拟对象的实时控制。

        这个定义的问题在于它所身处的环境和背景。假如一个小球悬浮在一片空白的空间里,你如何能了解到它是否不断运动呢?空间里没有参照的背景是看不出任何运动的,更重要的是各个对象之间没有任何的物理交互。要在游戏世界中感到交互过程的感觉,这需要具备某种模拟空间。

    可玩的实例

        假如你旁边就有一台电脑,那可以打开CH01-1的游戏感实例去体验一下环境和背景的必要性。这个实例是一个第一人称射击游戏。你可以用WASD去四处移动,用鼠标来瞄准。此时你能感觉到运动吗?不能?那现在按下“1”键,此时有了一个模拟空间了,感觉来了吧?

    模拟空间

        模拟空间是指在虚拟空间中模拟物质交互,让玩家能主动地感知到。比方说在玩家实时控制角色时,角色和游戏世界中的对象具有碰撞检测和碰撞响应。又比方说关卡设计中,各种物件的构造和间隔都是相对于角色的移动速度决定的。模拟空间为玩家控制的角色提供了各种对象作为参考框架,让角色能在其中四处移动、能撞上去,能借以表现出角色的运动速度,从而让角色所有动作都有了意义。这让我们和虚拟环境交互过程中有了符合自然规律的触感,这种触感就像我们在日常生活中与周边的自然空间交互那样。利用操纵的角色来传达和感知,借此让我们能以可触碰可感知的方式去体验身处的游戏世界。

    可玩的实例

        打开CH01-2的实例能让你体验到个中的区别,你可以四处移动,感受一下操作感怎么样,然后再按下“1”键来开启场景中的碰撞属性。这时候你能感觉到有什么不同吗?

        模拟空间的另一个必备点是它必须是能主动感知的。感知分为被动和主动:我们看电视和看电影时,屏幕中各种对象的交互是我们被动感知的;而通过实时控制去探索一个模拟空间是主动感知的。游戏感是主动感知获得的。

        这里的关键问题在于“玩家如何能与空间交互?”。一些游戏尽管有着细微的碰撞响应系统和精巧的关卡设计,但玩家却无法直接体验到。《星际争霸》在我们刚开始玩一会的时候就是这样的情况了。在很多游戏里,空间只是一种抽象概念。那些利用方格、菱形格和六边格移动的游戏就是抽象地运用空间的。凭直觉来说这并不是对空间的模拟,我们所定义的游戏感是在如实的空间里可主动感知的。

        现在我们把这种背景和环境的概念加到前面的定义里:

    在模拟空间中对虚拟对象的实时控制。

        这个定义已经很接近了,但光是这样还漏掉了动画、声音、粒子特效和镜头摇动所带来的影响。没有这些“润色”效果,大半的游戏感会丧失殆尽。游戏环境中有很多对象在交互过程中只能模拟出一部分的反应,而无法从中得知它们的轻重、软硬、粘稠度、金属感等属性。事后的润色能通过提升这些对象的交互效果来提供这些线索。

    润色(Polish

        润色是指人为地提升对象的交互效果,而不改变其内在的模拟方式。例如说在玩家滑落时在其脚底加上灰尘的粒子效果,在两辆车相撞时加入猛烈撞击的声音,通过“镜头晃动”来强化一记重击的效果,又或者一段关键帧动画来让角色看起来变形蠕动。润色效果能强化交互行为的物理特性,增加交互行为的吸引力,让设计师展现在玩家面前的对象像真的那样。这种做法和前面碰撞一类的交互效果是不同的,它不会影响到交互行为的内在模拟方式。例如,假设你把《街头霸王2》里的动画拿走,那最终你会得到下图1.4那样的效果。

    1.4 没有动画的《街头霸王2》:只有奇奇怪怪的格斗盒子(略)

        一旦把所有润色的元素取掉,游戏的本质运作方式还是没有改变,只是玩家会觉得整个体验在感知上没那么有说服力了,从而整个游戏显得没那么吸引了。这是因为对玩家来说模拟方式和润色元素是很难区分的。我们的感觉很容易受到润色效果的影响,把润色后的元素就看作是一个碰撞系统。例如在运动角色身上套上一个可拉伸变形的动画外壳,这会从根本上改变一个游戏的感受——正是这种做法造就出一个由学生制作的很流行的游戏——《De Blob》。它的创作者Joost Van Dongen曾经在某篇文章里说道:“当整个球弹得很快或者移动很快时,它会发生轻微变形,而滚动的过程中也会有凹陷效果。在截图里这只是一个很细微的效果,但实际玩的过程中看起来是很有趣的。一个有趣的细节会完全改变了整个游戏的感觉。在没有拉伸变形效果前,整个游戏感觉上就像是玩一个石头做的滚球。随后增加的拉伸变形效果根本没改变其物理系统,却让游戏感觉更像一个油漆凝聚成的滚球。只是利用图像就能巧妙骗过玩家的感觉,这让我很高兴。”(如下图1.5

    1.5 De Blob》中的拉伸变形效果(略)

        把这三种元素(实时控制、模拟空间、润色)整合在一起,我们能得到游戏感的一个基本可用的定义:

    在一个模拟空间中对虚拟对象进行实时控制过程中通过润色来强化其交互效果。

        玩家控制着角色,角色和游戏环境交互,而润色效果强化了这些交互行为,为游戏提供了额外的吸引力。

    实例

        紧接下来一个很自然会想到的问题是“那XXX这个游戏有着游戏感吗?”。借助这个基本定义,我们能把大部分游戏都按这种方法来分类。例如《刺猬索尼克》有着游戏感,而《文明4》没有。《刺猬索尼克》是实时控制的,而《文明4》是回合制的,因此它在我们的定义之外。不过如果说玩《文明4》的过程没有感觉,这一定程度上又是错的。它有着润色效果(动画、声音和特效),并且这些改变了游戏交互过程的感觉,尤其是当点中对象或者军队冲突时。

        这说明了游戏感是有着不同类型的(见下图1.6)。

    1.6 三种因素的交叠展现出游戏感的不同层级

        1. 在上图图形的中间是三大基础构件交叠在一起的地方,这是真正的游戏感。例如《半条命》、《刺猬索尼克》、《超级马里奧64》这些游戏都定位在这里。这些游戏具备了我们所定义的游戏感的所有要素。这种游戏感也正是这本书讨论的主题。

        2. 这是一种天然未加工的游戏感。它没有润色效果,只通过碰撞的模拟来让不同对象间的交互有了物理体验。但其中大部分的交互效果和吸引力都因此丧失。游戏一般都不会没润色过就发行,不过你可以玩一下CH01-3的例子来了解这是怎样一个感觉(当你打开游戏后按下“2”键)。

        3. 这纯粹是一种操作上的美感。它把实时控制润色了,但在交互行为上没有实质上的增强。这种游戏会感觉很奇怪——有着声音和特效,却没有任何的模拟交互行为,看上去就像在幕后观看那样。这对玩家来说是一种不协调。粒子特效和声音传达出一定程度的物质真实性,但在对象动作和润色效果上却出现明显的不匹配。当没有模拟方式以后,游戏很难建立出物理交互感。现实中极少有游戏是只有实时控制和润色效果却没有空间模拟的。(假如你想体验一下,在CH01-3这个例子里按下“3”键。)

        4. 这种方式是用物理模拟来强化被动控制的感受同时驱动游戏过程。像《幻幻球》(Peggle)、《Globulos》和《犰狳空间》(Armadillo Run)这几个游戏就是采用这种方式。这些游戏有着一套精细的物理模拟方式来驱动对象间的交互,但交互所产生的感觉是被动感知的,因为玩家在这个过程中无法实时控制。类似地,像声音和特效那些润色效果也只用于强化对象间的交互,让交互过程显得更吸引,但这些感觉也是被动感知的,就像在电影或者动画里那样。(你可以在CH01-3这个例子里按下“4”键体验一下。)

        5. 这是只有实时控制,而没有润色和模拟空间。类似前面的,我想不出有哪个游戏是只用实时控制而不加入任何的润色和模拟效果的。(要体验这类游戏,你可以在CH01-3这个例子里按下“5”键。你可以傻傻地四处逛,这个过程是很有趣的,但在没有任何模拟和润色的情况下,这些动作都没有太大的意义。)

        6. 这是只有空间模拟,这类游戏我能想到的最好的例子是一个叫《Bridge Builder》的免费游戏。这是对各种物件的运动和力学的物理模拟,但整个过程是被动感知的。

        7. 最后,这是只有润色的游戏。像《文明4》和《宝石迷阵》这样的游戏就用润色来表现了,它们都没有实时控制和空间模拟。在这些游戏里,润色效果表现出所有交互行为的特征,让各种对象有了重量、外观、体积等属性,但对这些属性的感知都是间接的。

        接下来现在让我们尝试应用一下。例如说《星际争霸》在这个图表里处于哪个位置呢?

        第一眼看来,《星际争霸》是有着实时控制的。你能在任何时候通过点击来为各个单位发布新指令。当单位在移动时,你能在屏幕上四处点击来快速改变它们的目的地。不过玩家对这些单位的控制也不是一个不间断的指令流。每一次点击都是一次瞬间的控制脉冲,在开始的时候打断上一次脉冲。你可以设定目的地,但你不能导航整条路线。从这点来说,它并不完全能算作是我们定义的实时控制。

        《星际争霸》中看起来也有模拟空间。单位部队能走到悬崖边、建筑旁和岩石上。但无论是驾驶、瞄准还是开炮,这些真正能为玩家提供身体触感的行为都是由电脑控制的,游戏里的确有着模拟空间,可空间里塑造的碰撞和交互行为都只能间接地感知。

        唯一做得很丰富的是润色。每个单位部队都有着精细的动画、声音和特效,这充分展现出它们和游戏世界的交互以及它们之间相互的交互。《星际争霸》的感觉来源于这些润色的效果,这些效果都是很有效的。虫族的爬行、机枪兵的步行,所有建筑在摧毀时壮观的爆炸效果。这些都让《星际争霸》在上图中处于第4个区间:空间模拟和润色的交色处,

        但这并不是真正的游戏感。对单位部队的控制并不是实时的,玩家也无法直接地和模拟空间交互。由于《星际争霸》只有三个标准中的一个,所以它不在我们对游戏感的定义内。

        来,来,先深呼吸一口,冷静一点。在你生气得打算用你的虫族部队围歼我之前,先听我说。这个定义并不是一种价值评判。我们迄今为止都是在定义游戏感,而没有说什么是好的游戏感,什么是坏的游戏感,也没有评判一个游戏的好坏。《星际争霸》里的动画、声音和特效是很出色的,从游戏的角度来看,它在平衡性和系统设计上也是无可比拟的。

        但从这本书的目的来看,我们所说的“游戏感”意味着真正的游戏感,也就是上图中间的位置。换句话说是同时有着实时控制、空间模拟和润色的游戏。这本书谈的是如何制作出有着特定这类感受,且感受出色的游戏。其他类比的感受虽然也很重要,但或许不是我们这本书谈的话题了。

        可是像《暗黑破坏神》那样的游戏又该怎么算呢?这时我们的定义会有点难下手了。《暗黑破坏神》是实时控制的吗?它看起来像是实时的,但其界面是靠多次点击来操作的。那实时控制的关键在于哪里呢?这个游戏模拟空间又是怎么样的呢?《暗黑破坏神》里的角色是会绕过碰撞的,但玩家是主动感知到这点吗?它感觉像是在一个日常的物理空间中行走吗?我们会在第2章深入研究实时控制和模拟空间,到时候就能回答这些问题了。

        那靠着这个定义和游戏感的三个因素,我们到底能做些什么呢?要回答这个问题,我们需要先回归到内容上,看看游戏感本身的表现和体验。特别地,我们来看看游戏感的一些不同的体验是怎么样的,看看游戏设计师通过实时控制、模拟空间和润色该怎样去刻画这些体验。

    游戏感的体验

        游戏感是由很多不同的体验组成的。例如当玩家拿起游戏控制器时,操作的快感、熟练操作或者操作笨拙的感觉,以及和虚拟对象交互过程中产生的触感,所有这些感觉都会在短短数秒里发生。而我们所称的游戏感是所有这些体验融合在一起,并在不同时间浮现出来的总体感觉。因此,要理解游戏感,我们需要理解组成游戏感的不同体验:看看到底有哪些不同的体验,看看它们是如何打造出来的,以及它们是如何相互关联和相互影响的。

        游戏感中包含着五种最常见的体验:

      操作的美感

      学习、练习和掌握一项技能的愉悦感

      感觉的延伸

      个体的延伸

      与游戏里一个独特的物理实体的交互感

    操作的美感

        当我还很小时,我在我老爸的Commodore64上玩《Frogger》和《Rastan》,那时候的游戏感就像玩玩具那样。当我能在游戏里控制某件东西时,这种如同操纵木偶般的感觉是很快乐的。但这两个游戏对我来说也像是游戏在控制我。我在玩的时候不断在椅子上左倾或右倾着身体,尝试靠身体去让它跑得更快或更准确。我还试着伸长脖子去看看能不能看到屏幕以外的东西。而最让我感到快乐的是,屏幕会随着我按下按扭而移动,让我看到更多的东西。那时候我的手脚协调并不足以应付这两个游戏设定的挑战,但操作游戏有着一种纯粹的美感。我很喜欢这种感觉,也因此玩了好几个小时。这正是游戏感在操作美感上的体验。

    学习、练习和掌握一项技能的愉悦感

        几年以后,当我第一次玩《超级马里奧兄弟》时,当时我笨拙极了。那时我和住在楼下的朋友一起玩,他比我年纪要大,手脚更协调,也有着自己的任天堂机。我玩的一轮经历的时间很短,玩得很狂暴,满脸通红。然而在我把手柄移交给下一个人时,我感觉即使是小小的动作也能产生一连串有趣的事件,这让人感觉很值得玩下去。用头去撞砖块会让它摇一下,发出一声笨拙的响声。同样去头撞一个闪闪发光的问号砖块会冒出一个金币,同时伴随着一下清脆的声音和一小段动画。所有这些丰富的低层次的交互方式都掩盖了一个重要的事实——这个游戏对一个9岁的小孩来说是极具挑战的。由于在游戏里四处闲逛和头撞砖块是很有趣的,所以也会让我沉迷。

        换到足球游戏里,尽管看起来用的是不同的技能,但你还是同样能感受到练习带球、踢球和顶球后带来的快乐。例如我必须在游戏里学会越过对手,在合适的时机按下按键,然后再按着十字键来控制速度。通过在这些操作上极小地一点一滴地进步,我开始玩得越来越好了,在游戏里也玩到越来越后的关卡。三周以后,当Browser看到我这样的技术时瞪大眼睛吓呆了,那时我感觉太有成就感了,这就像在加时赛里射门得分那样!我在现实生活中玩足球已经两年了,但这个游戏在三周内给我同样的自豪感。就在这精美包装的一个游戏里,有着很多可以让我去掌握的技能,在每一关都有着奖励,并且有着一系列不断提升的挑战来让我测试这些技能。而更大的好处是我不必像真正踢球那样因为天黑了或者太疲劳而停止练习。这就是游戏感在技能上带来的体验。

    感觉的延伸

        后来我长大点了,学会了如何驾车。学习的过程和掌握一个新游戏的操作方式是很相似的,但看起来这个过程要花更长的时间、没那么有趣,并且缺乏一些里程碑来让我估量当前的学习进度,不久之后,我开始养成一种空间感了,我能感觉到整辆车在我身体外部的每个方向上延伸了多远。我能估量出我和其他车靠得有多近,能估量出我的车能不能挤到停车位里。我依靠着一种奇怪的直觉以此来让整辆车成为了我身体的延伸,感觉就像这辆车是我身上一个庞大笨重的依附物那样。这也像是以一种有趣的方式来玩游戏。当我开车时,这就像在玩《希魔复活》那样,我感觉自己在操控的一切都是我身体的延伸。这是游戏感在感觉延伸上的体验。

    个体的延伸

        当我经历了一场牵涉了我父母在内的记忆尤深的车祸后,我意识到这种感觉其实是双向流动的。那天我为了赶时间,跳进汽车里匆匆踩油门开走。谁知砰的一声!车子侧边撞到一根水泥电线杆上了。我还依然记得车子撞到停下来的感觉,就就像我的脚趾撞到一个庞大昂贵的金属物上那样。有趣的是我当时想的不是“Oh!我坐着的车撞到水泥杆上了。”,而是“Oh!我撞到杆上了!”在汽车撞到水泥杆的那一刻,汽车就像是我个体的一部分那样,无论是身体上还是概念上都感觉这样。而后不久我才想起我的父母,这时才快速想到我和汽车是分离的。我们之中唯一遇到大麻烦的是我坐着的那辆Volvo

        几乎也是那一阵子,我也在玩《超级马里奥64》。那时候我刚撞完水泥杆,我控制着的马里奥也发生同样的事。此时我的个体也包含了它了,就好像自己在游戏里那样,但当我被一个磨菇撞到死掉时,我才突然间恢复过来,发现那只是一个分离的实体。这正是游戏感在个体延伸方面的体验。

    与游戏里一个独特的物理实体的交互感

        正是这点让我意识到控制着马里奥四处跑有着多明显的物理感。当马里奥撞到世界里的物件时,他会突然急停下来,脚后跟着一列灰尘或者一束黄色的星星,这看起来是很自然的。这些加工效果让我感觉到马里奥世界中各种可交互物件的质量感。有些东西是他可以轻易检起来和丟出去的,例如小石块;有些东西是有着明显的重量的,例如Bowser。有时候游戏世界里的事物会比我想象中更重或者更轻。例如CoolCool Mountain那关的Snowman's Lost His Head的那一个任务里雪人的头。马里奧在路过看到这个雪人头时需要把它放到山腰的大雪球上。这在现实世界里也有类似的情况:有时候我也会把一些东西检开,例如垃圾袋,或者一个极少挪开的家具。这正是游戏感在独特的物理实体上的体验。

    游戏感的体验

        操作的美感是游戏感最初始的体验。这是控制着某样东西四处移动并感受输入后得到的响应的愉悦感。当玩家说一个游戏很飘或者很流畅,则他们描述的正是这样的体验。这换到日常生活中和驾车的感觉是类似的:开着一辆2009年新款的保时捷肯定比一辆1996年的福特稳达感觉要好。

        游戏感在技能方面的体验实际上也是学习的过程。这会经历三个步骤,首先是对不熟悉的操作的笨拙使用,而后在克服挑战过程中得到巨大的成就感,最后在掌握技能时充满喜悦。你可以把游戏感看成是一种技能,这也解释了当玩家的技能水平提升时,为什么玩家对游戏的操作有着不一样的感受,借此你能明白什么是“符合直觉的操作方式”,也能明白为什么一些操作方式会比别的操作方式更容易掌握。这换到日常生活中就像学习一项新技能那样,比方说学习驾车、练习杂耍,或者是学习切萝卜。

        熟练的操作往往会让你感觉置身在游戏中,很容易会失去了现实空间中自我的意识。假如你曾经在玩视频游戏时不知不觉地花了很多时间,那你一定体验过这样的感觉。你本来只打算在一个游戏里玩几分钟,却最终花了好几个小时在里面,最后精疲力竭却又兴高采烈。我们在日常生活中也时常会发生这样的情况。当你在高速公路上驾车、叠袜子,或者打篮球时都有类似的感觉。

        当玩家说到:“这就像我真在那里那样”、“我像在游戏里了”,或者“这个世界看起来就像真的那样”,此时他们真正体验着游戏感中的延伸感。游戏世界之所以显得真实是因为游戏中给予的反馈改良了他们的感觉。他们看到的不是屏幕、房间和手柄,而是魔兽世界中的艾泽拉斯大陆、二战里的诺曼底,或者马里奥世界中的甜甜圈平原。玩家的角色化身在游戏中演绎着各种行为,让玩家能借以感知整个游戏世界。这种体验在现实生活中并没有类似的例子,因为它是玩家在游戏这样一个虚拟现实中的感觉延伸。

        这种在游戏世界中感觉延伸的其中一个结果是个体身份的转移。玩家常常会在充满技巧地获胜后大叫“我太厉軎了!”,当失败的瞬间会说“他怎么会这样做?”。当实时控制一个对象时,玩家的个体身份是不稳定的。它会寄居在玩家控制的角色上。这在现实世界中相对于你把个体身份转移到你驾驶的车上。你不会说“他的车撞到我的车了”而是说“他撞到我了!”

        当玩家的感觉转移到游戏世界时,他们能透过交互行为,像日常生活中感知其他事物那样去感知这些虚拟的事物。通过这种方式在游戏中感知事物,各种物件看起来会有着更具体的物理特征。物件看起来会有重量、粘度、硬度、锋利程度等等。当玩家充分观察这些交互方式后,脑海里就会浮现出一个方方面面都具备的独特的物理世界,世界里出现的所有元素都会对其心智模型带来强化。这正是游戏感在独特的物理实体方面的体验——设计师创造出整个游戏世界的物理定律。下图1.7展现了以上所有要素是如何组合在一起的。

    1.7 游戏感的三个因素是如何转变成体验的

    创造游戏感

        在这章剰下的部分,让我们来详细探讨这些体验,看看游戏设计师能如何塑造和建立它们。

    操作的美感带来的游戏感

        在游戏里控制着某样东西时会有着一种无需思考的快感。人们在踩滑板、冲浪、滑冰和驾车时都能体验到这种感觉。这是在空间中移动、创造出运动的流动曲线,并感受到你的身体或者你所控制的事物随着你一瞬间的想法而不断响应着的运动喜悦感。即使此时脑海里没有一个特定的目标,但本能的快乐控制着一切的操作。这种控制感有着某些人所共知的美感上的特点,这就像我们前面说到的保时捷和福特稳达的例子那样。保时捷车身更流畅,操纵更好,并且过弯更稳。在视频游戏里,游戏的操作也有着类似的美感上的特点。假如玩家控制的角色在运动中能产生流动般的曲线感,那就能让玩家感受到操作那漫无目的的快乐。这种感觉正是玩家形容一个游戏流畅、飘、或者僵硬的意思所在,这是游戏设计师可以用来吸引和鼓励玩家的极好的工具(见下图1.8)。

        当设计师头脑里感觉到某种特别的感受,然后坐下来开发一个游戏时,首要的任务是把各种输入信号映射到行为动作上。其表达的潜力在于这两者的关系。当你按下一个按钮时,它的响应是慢慢表现的还是即时发生的呢?角色的向前移动是相对于屏幕还是相对于他自身呢?他是在旋转还是在移动呢?相对于旋转的速度,移动的速度有多快呢?一旦输入和响应间有了恰当的关系,那在游戏里控制某样东西的过程是有着韵律般的美感的。其反面是不和谐的、让人厌烦的,或者是玩家的输入带来的是毫无美感的行为表现的情况。

        这种映射关系是一种美感的表现。它界定出当玩家控制角色时会有着什么样的感觉。就现今在美术上看到的已作出的尝试来看,没有任何公式能打造出“最合适”的感觉。设计师需要做出成百上千个细微的决定来处理输入和响应间复杂的关系。我们会在第7章详细地探讨各种映射方式,以及这些映射方式是如何转化成游戏感的。现在我们只需要知道这些是美感上的判断,以及最终产生的感觉正是体现了设计师的识、别力就行了。

        让我们来想象一下,在一次映射里可能会出现多少种可能的行为。这些可能的行为的总数界定了玩家可以产生的各种可能性的空间。它界定的不是玩家会做什么事,而是能做什么事。玩家能通过控制的角色去达成什么事,这都是靠设计师设定的输入与响应间的映射关系决定的。

        每一个可能的行为都有着一种能被玩家体验到的美感特征,在玩家控制着角色演绎该行为时就能感受到了。这种美感上的愉悦感是一种能从内心感受到的奖励,它能鼓励玩家探索游戏中可能性的空间,控制着角色四处移动去看看如何能让自己得到美感上最大的快乐。但问题在于,如果没有一定程度地聚焦玩家的注意力,那即使是感觉再好的操作也会很快变得单薄乏味的。

        对游戏设计师来说,解决这个问题的方法在于增加一定程度的挑战。当玩家有了一个目标以后,操作的各种行为有了崭新的意义。如今玩家能把自己最初的意图和得到的结果作对比了。现在也会出现成功和失败的可能了。操作的美感顿时能变成一种技能。

    技能带来的游戏感

        正如我在前面定义的,技能是一种已经掌握的模式,用这种模式能协调肌肉运动来达成一个特定的结果。衡量技能的高低正是衡量这种协调的效率,看看通过行动能把意图在多有效的程度上转化成想要的结果。

        假如你在踢足球,你的意图可能是带球穿过球场上所有其他的对手,然后把决定胜负的球踢过那拼了老命的守门员。这在事实上是众多可能的结果里的其中一种。更大的可能是你的技能没达到这样的水平,球在你到达中场前已经被别人抢走了。但技能是可以提升的,你能攻克掉一个个不断提升的挑战。假如你的目标是带球越过对方的后卫,然后熟练地传给一个没有被严密防守的队友的话,那你达成这个目标的几率相对来说是更高的。虽然做到这点或许并不像自己得分那样快乐,但从每一次小小的不断提升的胜利里得到极好的感受。即使是在球场练习时一次熟练的传球或者带球也能让人感觉很棒,因为你知道下一次能把它用到真正的球场上了。足球有着一连串很吸引人的整套的挑战,所以即使不在比赛上,单独去练习各个技能一起来也是很值得的。

        这类似于你在玩《反恐精英》时的体验。我对游戏给予我的挑战是很着迷的,我会打开“cs_italy”的关卡,不加入任何的玩家来练习三项技能:当从一端移动到另一端的过程中射击墙上一个特定的点,把我的准星从一个点快速移动到另一个点,以及当我前后左右移动时保持鼠标指针在同一个点上。我会一个人待在关卡里连续23个小时地反复练习这三个技能,然后再上线玩这个游戏。这看起来是值得的,它能让我的技能水平推进到更高的程度。

        这说明了游戏里的技能和现实生活中的技能在本质上是一样的。它们都是协调肌肉运动的一种已掌握的模式。只不过这种肌肉运动更细微,其技能更专注,并且这些动作和行为都不受物理现实的约束,但学习和建立技能的过程是一样的。两者间主要的不同在于电子游戏设计师同时控制着挑战和物理特性而已。在现实世界中都有着一套固定的属性——人类身体的重力、摩擦力和生理机制都是固定的。足球的设计师必须围绕着这些固定的属性去做出有趣且有意义的挑战。他们能用的工具只是场地上的界线、球网的大小、足球的物理特性,以及诸如“你不能用手去碰球”的规则。但《反恐精英》的设计师MinhGoosemanLe就能控制所有的东西了:他不单单能建立起游戏里所有的规则和挑战,他还能界定玩家能跑得多快、跳得多高、武器射得多准,甚至还能定义游戏里一切对象的重力、摩擦力等属性。

        调整玩家的行动方式或者建立各种挑战都能改变游戏的游戏感。改变各种全局属性(例如重力、摩擦力和角色的移动速度)能界定出操作的基础感觉。增加各种规则和挑战,建立起一套供玩家练习和掌握的技能,这能进一步改变这种基础感觉。但问题在于怎样做?如何区分熟练的操作和普通的操作呢?这两者间有什么不同的体验呢?

        这里的答案在于游戏感和技能是有着三重关系的:

    l  挑战能改变操作的感觉。通过让玩家专注在不同的行为集里,并在玩家探索这个集合中的可能性的过程中作出奖励能达成这点。

    l  游戏感会根据玩家技能的改变而改变。

    l  当玩家能毫无疑问地把意图转变成结果时,他们会觉得操作是符合直觉的。

    挑战改变操作的感觉

        从游戏设计师的角度来看,即使是最棒的操作感也是有问题的。操纵行为是一件赏心悦目的事,但这种快乐是稍纵即逝的。即使一个游戏感觉再好,漫无目的地操纵也是很快会让人无聊的(见下图1.9)。

    1.9 没有焦点,操作的快乐会变得无聊(略)

        之所以产生这个问题,其中一部分原因在于操作在审美上的快乐只是一种激素,激励玩家去体验一小部分可能的行为集。假如我们把一种映射中所有可能的行为都看作是一个可能性空间,玩家能探索的区域是有限的,这就像下图1.10那样。

    1.10 玩家在这个可能性空间中体验到的游戏感只来源于他们倾向探索的区域(略)

        然而当玩家有了一个可追求的特定目标后,操作就有了崭新的意义了。原来漫无目的的快乐的行为被替代,如今是为了达成眼前的挑战而有焦点有明确目标的尝试。这为玩家提供了一种诱因去发现可能性空间中新的区域,让他们能体验到过去曾经错过了的操作感。挑战相当于在玩家的远处提供了可达的里程碑,鼓励玩家不断去探索游戏中尚未开发的区域。

        例如,一个新手玩家刚进入《超级马里奥世界》时,可能他还没体验过所有飞行机制的感觉。这需要他练习一阵子才能掌握按下按键的时机,让马里奥能来回飞行。此时这种操作方式正是整个游戏里其中一种最快乐的操作感。当掌握了这种操作感,或者甚至仅仅是意识到,这都能让游戏显得更吸引和更值得玩了(见下图1.11)。

    1.11 当有了挑战,玩家就有理由去探索单个映射的更多可能的感觉了(略)

        挑战不单鼓励玩家去探索所有可能的行为,还为这些行为赋予了新的意义。这能改变操作感。例如想象一下鼠标的指针。这是一种实时操作方式,它根深蒂固到我们每天在不断使用都极少会注意到。但当它换上一个不同的挑战的背景后,鼠标操作能给予我们不同的感觉,我们能在网页游戏《Cursor Attack》里体会到这点。《Cursor Attack》要求玩家在一条极为精确的路线里尽快移动鼠标来达到一个目标点。一般情况下,我们使用鼠标的目标是在网页里有效地导航,像一个明智的消费者那样去挑选和购买东西,或者在电脑里点击、拖移和操控程序。在《Cursor Attack》里有着一个明确的目标(通过触碰到目标点来到达迷宫的尽头)和一个隐含的目标(尽可能快地做到这点)。而约束是不能碰到迷宫的墙壁,这会马上导致Game Over。结果是玩家能感觉到自己完全聚焦在鼠标的细微移动上。这种感觉和在网页上导航是有很大不同的。游戏里把鼠标指针的移动变得惯性很大且没那么精确。此时鼠标指针的大小和它在空间里的位置突然间变得很重要了。这个游戏需要玩家极大的专注能力,就像把线头穿进绣花针或者在黑板上画一个完美的圆圈那样。游戏里仅仅改变了两个目标和一种约束,操控鼠标指针的感觉马上变成一种新鲜有趣的体验。对游戏设计师来说,幸运的是实时控制能有助于产生这类挑战(如下图1.12)。

        挑战往往由两部分组成:一个是目标,另一个是约束。目标通过给予玩家一种途径去衡量他们的表现,以此来影响玩家的感觉。当有了目标后,玩家就有可能失败或成功了。也有可能只是一部分失败了,在下一次尝试里或许能做得更好或者更糟。这让玩家能模糊感觉到自己的技能去到什么样的程度,也就是自己把意图转变成现实的能力有多大。根据这种可感知程度的不同,游戏感会显现出笨拙难用或者简单易懂。除此之外,目标还能帮助玩家保持注意力。正如我们在《Cursor Attack》里看到的,实时控制的感觉会根据玩家如何利用控制方式来完成任务而改变。游戏的目标到底是像《Cursor Attack》那样需要玩家做极其精确和特定的移动吗?还是像《小熊托尼大冒险》那样可以在更宽广的范围里移动呢?角色移动的速度有多快呢?玩家作为移动参考的物件之间的间隔有多远呢?玩家是要避开它们、收集它们,还是轻轻地碰这些物件呢?这是和游戏感密切相关的游戏设计的艺术:玩家会做的事是和他们能执行操作一样重要的。

        单个目标能为玩家创造出多个目的。例如像“到达山顶”这样高层次的目标可能需要分很多步来执行。但最终它都会细化到实时控制的层次。到达山的顶峰意味着要到另一根柱子上,一根接一根地走下去(如下图1.13)。

    1.13 —个目标分解以后会变成多个不同的目的(略)

        约束会通过明确限制玩家的行动来影响游戏感。约束做的并不是强调特定某个行为,而是有挑选性地从可能性空间中去除一些行为。例如足球场上的边线排除了不少可能的行为,鼓励了那些能从球场的一边快速转换方向到另一边的球员,也鼓励了那些擅长在对方队伍中寻找防守空缺的球员。假如足球场上没有边线,那球员就能无止境地朝着一个方向跑来避开防守方,足球根本上要求的技能就会改变了。这换到《小蜜蜂》里,当小蜜蜂被打中会导致你扣掉一条命,道理是一样的。通过限制行动,玩家能再次专注于特定的行为集里,从而改变了游戏的操作感。

        约束和目标这两种工具能让游戏设计师把实时控制塑造成特定的感觉。目标强调了可能的行为集中的特定部分,而约束把其他行为都去除了。结果游戏设计师就能塑造出想要的感觉了。

        不过游戏设计师想要的感觉应该是怎么样呢?当然,这完全是由设计师定下的,但我发现通过实验的方法往往能让这个问题不问自答。建立一个原型,让角色能实时控制地在一个可探索空间里四处移动,空间中放着各种不同形状、大小和类别的物件供角色交互,这时控制会有机地转化成技能和挑战。我能登上那座山的顶峰吗?我能在这些建筑间自由飞行吗?我能跳过这条沟吗?在原型里你所不断寻求的东西正是有着最佳感觉的行为和交互方式。通过这种方法,游戏设计师刻画游戏感正是在一张地图里探索可能性空间,然后用目标来强调好的可能,用约束来修剪坏的可能。

    游戏感的改变依赖于玩家的技能

        当初接触一个不热悉的游戏的操作控制时,玩家会觉得自己笨手笨脚且不知所措。但对骨灰级玩家来说,同样一个游戏会显得流畅、简单且反应灵敏。从客观的立场来看游戏的控制方式始终都是一样的——程序代码是一种完全精确的设定,它容不得其他情况的发生。然而感觉会随着玩家而变,这决于他们有多擅长于吧自己的意图转化成游戏现实。基于过去的经历和天生的资质,每个玩家都会从不同的技能水平开始,且以不同的速度去学习,最终根据他练习的强度来得到不同程度的技能。这意味着即使对同一个玩家来说,游戏的感觉也会随时间演变。正是这种变数使得即使是同一个游戏也会显得自相矛盾。这种矛盾可能是像如下这样的:

        心声1:“如果说什么才是一个游戏最完美的‘感觉’,那我一定会首推《超级马里奥64》。除去其摄像仉以外,它的操作是完美的。”

        心声2:“天啊,我讨厌《超级马里奥64》,它的操作太可怕了!”

        心声1:“你不喜欢它的操作是因为你很菜,菜鸟!”

        由于两个心声都是没错的,这个争论可能永远不会停下来。对心声2来说,它既不能也不愿意去掌握游戏的操作方式,感觉起来是笨拙和反应迟钝的。但心声1的观点也同样是对的。对它来说,控制马里奥的过程感觉就像让自己在游戏世界里得到延伸那样,每一个行为都准确表达出他的意图,就像在现实生活中转方向盘或者挥动网球拍那样。它所立足的论点是对的,假如一个玩家没有到一定的技能水平,那是完全无法欣赏甚至体会到这种游戏感的。这点既适用于突发式的技能里(例如《雷神之捶》中的火箭跳【在第一人称射击游戏中,用火箭发射器或其他类似的爆炸武器向地面或墙上射击的同时跳跃的一种技术】),也适合用在深层嵌套的操作里(例如《马里奥赛车DS》中的蓝火花【蓝火花时进入飘移状态】)。当你新玩一个游戏时,你不会用上所有的行为。在这种情况下,技能水平就相当于游戏感的入场券的票价了。

        但也有情况是玩家学会了在很高的水平下玩某个游戏却依然觉得它控制起来很措糕的。对我来说,街机上经典的《吃豆人》就是这样了。我喜欢这个游戏,但从审美的角度来看,让吃豆人绕着一个迷宫四处跑的感觉是僵硬、死板且亳不吸引的。我的另一个朋友却反过来,他从来都不喜欢《小蜜蜂》。飞船在操作过程中是充满美感的,但躲开敌人和射击敌船的技能显得太不吸引了,完全不值得去学。这说明有一重特殊的关系牵引着游戏感的两种不同的体验:一是操作带来的最基础的美感快乐,另一个是学习实践和掌握某个技能的感觉。这重关系是循环轮换的,在玩家玩游戏过程中一直存在,并且不断地改变着游戏感。这个循环看起来就像下图1.14那样。

    1.14 技能和游戏感的循环。随着玩家技能水平的改变,操作感也在改变

        当玩家最初拿起一个游戏时,感觉是很槽糕的。玩家清楚这一点也接受这点——技能就是入场券的票价,他们相信游戏设计师。玩家会觉得:“假如我花时间去学习,经受过一些挫折,那你就会让我在稍后得到很捧的体验了。”在这个阶段中,感觉是显得笨拙、无序和一团糟的。它需要玩家极努力地下意识去完成游戏中最基础的任务。此时游戏里操作所带来的最单纯的美感上的快乐会成为滋润剂,在玩家初次成功前一直抚慰着他的内心,但无可避免地,每个游戏对新玩家都要以这种方式开始。每个新玩家在最初的学习阶段都会感到笨拙、无序和充满挫败。

        随着时间流逝,玩家开始掌握很多技能了,他们也在意识加工的过程中开始接受游戏。玩家的水平越来越接近呈现在眼前的挑战了,感觉也变得越来越好。最终,玩家掌握的技能足以让他有所突破了,终于完成了当前的目标。玩家不再感觉到难以忍受的笨拙感,操作的美感走到台前了,同时在克服挑战后得到一份对应于达成这种技能水平的奖励所带来的满足感。随后又出现了下一个挑战,然后再开始了下一个循环。相对于挑战所表现出来的不熟练的笨拙感再次超越了操作在美感上的快乐。

        客观来说,技能总是会随时间提升的。但主观地说,玩家会感觉操作在复杂难用和简单易懂间轮换,而这种轮换是取决于他们相应于游戏当前呈现出的挑战的相对技能水平的。

        最出色的设计师能在不同的技能水平上打造不同的感觉。聪明的设计师会了解玩家的技能水平,且知道他在不同技能水平下在想什么以及关注什么,以此来分别调整每一层技能水平下的游戏感。对玩家技能的这种洞察要细致到了解玩家当前所处哪个关卡,了解玩家当前身上有着哪些道具,以及在多人游戏中要进行综合的游戏测试。例如,倘若一个玩家身处于第12关,且关卡的进展是线性的,那你可以假设他已经掌握了完成前11关所必须的技能了。你清楚他刚刚学会了哪些技能(这是玩家当前关注的),哪些技能是已经完全成为本能反应了(也就是那些早已掌握的技能),以及哪些技能是还没遇见过的。当了解了这点后,这才可能去塑造游戏在时间经历线上的感觉。通过用这种方式来引导玩家,设计师能在行为的可能性空间里洒满面包屑,强调出当前最好的操作感,同时维持着技能和挑战的平衡。当玩家达成了最高的水平后,游戏就能满足设计师要打造出最棒游戏感的目标了。

        不过,要做到这点必须让玩家永远不觉得无聊和烦躁,这样才不会让他们停下来不玩。假如能一直完美地维持着玩家技能和游戏挑战间那精巧的平衡,那玩家就会进入心流的状态了。

        心流理论指的是当你承担的挑战难度很接近你当前的能力水平时,你会进入心流状态。该状态的特征是失去自我意识,对时间的感知开始变得不正常,并且一直享受着快乐的感觉。心理学研究者Mihayli Csikszentmihalyi认为这种感觉和运动员、舞者和世界级象棋选手所处的忘我状态是有关系的,他们都是“正处于心流的状态"。这里的关键在于能力和当前面对的特定挑战很好地匹配上,这样你就能进入心流状态了。如果你的技能比你当前的挑战高得多,那你会感到无聊。如果你的技能比当前挑战低得多,那你会觉得充满挫败(见下图1.15)。又或者当身处于一些危险的情形下(例如在四处落石的山坡上攀爬),你会感到焦虑。Csikszentmihalyi说过:“游戏这种媒体在提供心流体验上是最棒的”,这样说是有道理的。电子游戏在建立和维持心流上有着众多优势,例如能为玩家提供清晰的目标,能塑造一个受限的模拟空间,能直接或间接地给予玩家反馈。

    1.15 心流状态:当挑战和技能相互平衡时,玩家的快乐会得到最大化。(略)

        从游戏感的角度来看,心流是一种最理想的体验。但说到玩家正沉浸在游戏时,实际上他们正是在体验着心流的状态。就像最初研究心流的研究者发现的,进入心流状态并停留在其中是最让人满足的体验,它是每个人都有可能拥有的。无论是外科医生、画家,还是攀山者,每个体验过心流状态的人都显得更快乐、更健康、更放松和更有精力。人们也清楚这种感觉,喜欢它并因此去寻找能产生心流的活动。在电子游戏或者现实生活中,要不断满足这种瘾头是需要承担越来越大的挑战的,这样才能匹配上越来越高的技能水平。当技能水平变得更高,达到了原定的挑战以后,操作感也会随之改变。专业的《反恐精英》玩家就像专业球员那样,他们和一般人对游戏的感受是完全不同的。

    符合直觉的操作

        游戏和现实生活不同,有时候玩家会感觉操作无法准确地把自己的意图转化到游戏里。这是游戏里的技能和现实世界的技能的另一个不同点。在现实世界中,如果你尝试挥棒击球却被三振出局,那你无法怪任何人,只能怪自己。但在游戏里,玩家往往会迁怒于游戏设计师。

        这里重要的在于玩家的感知情况:玩家无法把意图转变成想要的现实是因为他缺乏技能还是因为游戏造成的问题呢?当玩家无法得到自己预期的结果时,他们往往会报怨游戏里的操作,而这种报怨有时候是公平的。游戏设计师不大可能会把一次输入映射成随机的结果,但很多情况下一次无心的操作会干扰了操作感,让玩家感觉游戏好像无法精确地把他们的意图转化成输入。

        当发生这种情况时,玩家就会觉得游戏没有精确地把他的意图融入到游戏世界里了,这是一种很槽糕的感觉,是游戏感中极可怕的情况。它和玩家所赞赏的“符合直觉的操作”是相反的。

        符合直觉的操作意味着游戏几乎能完美地把意图转化成游戏中的现实。玩家能本着自己已有的技能去在不同程度的效率下把意图转化成现实。如果你正在控制的东西能做出你想要和期望的事情,能把各种突如其来的念头准确地转化到游戏里,那这种操作就是符合直觉的。这时控制着游戏角色就像你自己的身体在游戏里延伸那样。

        挑战(它在技能的维度上让游戏变得更难)和干扰(它随意地让玩家的意图混淆不清)是有区别的,换句话说,只要一次行动的结果是可预测的,其目标是清晰的,并且反馈是即时的,那它就能归入挑战的范畴里。否则这就是干扰了,它会在玩家的预期和游戏的现实间造成障碍。

        当设计特定的游戏机制时,设计师一直会去寻求一些难以捉摸的有价值的技能。这些技能是符合直觉且易学的,但深入在游戏底层里。它们有着柔美的富有表现力的特质,你只要把游戏里一些值得去做的挑战它们关联起来,那这些技能就永远不会让人感到厌倦了。

    感觉延伸带来的游戏感

        玩挑战游戏往往是把注意力有意地集中到屏幕上,然后排除除此之外的所有事物。虽然这种做法会让家长、老师和政客们惶恐惊慌,但这并不是一种走神的状态,而是一种感觉的转移。此时屏幕变成了玩家视觉感觉上的替身。玩家不会再去看着电视的模样、沙发的状态,或者手里的手柄,而是透过屏幕去进入到游戏世界里。当玩家坐到电视机前盯着看时,他们并不是精神分裂了,而是把自己的视觉感觉置入到游戏世界里,把它扩展到一个新的场所里。他们会四处观察,敏锐地下意识去获知游戏里周边的事物。这样做是因为电子游戏中的角色成为了玩家的工具,它为玩家的各种行为提供了可能性,为玩家在游戏里的感知提供了一个通道。

        想象你手里拿着一个棰子。你用锤子去敲打铁钉,你能看到铁钉越敲越深,在每一次敲打时你会听到声音的调子随着铁钉的深入而不断改变。这些都是直接的感知。但你也能透过捶子来感受到铁钉。你每敲一次都会感觉到铁钉插得更深,或者是直着插进去,或者是铁钉开始被敲弯了。这种触感上的反溃会透过铁捶返回到你手上。铁锤成为了触感上的延伸。

        接下来让我们再回想一下《块魂》里控制的角色。我们控制的这位宇宙王子为我们延伸了三种感觉:分别是视觉、听觉和触觉。玩家都有一个目标:让主角变大到特定的尺寸。要达成这个目标,第一步是把周围能看到的一些图钉都检起来,放到我当前位置的左边。当这步达成后,我们需要往前压到图钉上往我们要走的方向移动。为了了解当前转的是否正确的方向,了解什么时候该不再转弯,什么时候该向前直冲,我们需要利用屏幕上得来的视觉反馈。我们会估量角色与图钉间的距离。每当主角转到稍微不对的方向时,我们就会设置图钉来不断进行微调,以此维持想要的结果。我们会不断重复这些步骤,直到成功转向并听到满意的“成功收集”的音效。当控制的角色撞上太大的物件时,我们会看到角色停下来,身上的东西掉了不少,还听到一声很响的撞击声,伴随着屏幕晃动和手柄震动。

        在这种情况下,每一种设备都覆盖到我的其中一种感觉。屏幕会变成我的视觉,音箱会覆盖听觉,手柄的震动会引起触觉。从这些设备上传来的反馈能让我体验到游戏里的一切,这就像这些物件是在我当前身处的物理现实空间里那样。我能感觉到在物理空间中的移动,能感受到与空间中的物件的触感和交互感。屏幕、音箱和手柄成为了我在游戏世界中的感觉延伸。由于这些感觉被游戏中的反馈直接地覆盖到,因此游戏世界显得很真实。通过与感觉的关联,屏幕、音箱和手柄能让虚拟世界显得更真实。

        当游戏设计师设定好摄像机的行为、实现了各种音效,并且安里好各种手柄震动的触发条件时,他们不是界定了玩家将要看到、听到和感觉到什么,而是界定了玩家如何才能在游戏中看到、听到和感觉到各种事物。设计的任务在于用虚拟的感觉来覆盖玩家现实中的感觉。在定义游戏感时,我们必须清楚这点并接受这点。体验游戏感的过程实际上也就是透过不同的眼睛去看,借助不同的耳朵去听,以及利用不同的身体去触碰的过程。

        从游戏设计师的角度来看,这三部分中最重要的在于界定摄像机的行为。摄像机是玩家的视点,是游戏世界呈现在玩家面前的视点,它决定了游戏世界的哪些景象会显示在屏幕上。假如游戏设计师要创造出特定的游戏感,那第一项任务是把输入信号映射到行为上,第二项任务是建立一个空间和各种对象来让行为有一个参考框架,第三项任务正是界定出摄像机的行为。我还没见过哪个游戏是用声音或者手柄去作为实时控制的主要反馈源的。虽然说做出一个游戏,其实时控制的反馈主要来自听觉和触觉上的感受是很有趣的,但大部分的游戏都是建立在视觉反馈的基础上的,声音和手柄震动只会作为润色效果上的增补。这正解释了为什么摄像机和摄像机的行为是游戏感原型制作中第三项必需的步骤。没有这三步(映射、基础层级的布局,以及摄像机行为),那游戏感是无法确实地感受到的。对设计师来说,这些正是游戏感的三大基础。

        在设定摄像机时有两大决策,其一是摄像机的位置,其二是摄像机相对于玩家控制的角色是怎样移动的。这两者会共同界定出玩家在速度上的感觉。

        由于摄像机不单单是需要控制的对象,而且还是玩家感知游戏世界所借助的“器官”,因此它的行为必须经过一定的特殊处理。通常其中出现的问题都会引发出其处理方式。比方说,假如摄像机的移动太过拖沓或者方向混乱,或者假如玩家看不到自己解决挑战过程中需要看到的东西时,设计师就需要不断重复去解决这些问题,直到这些问题减少或者缓和了。其中最常见的方案是:在不必移动摄像机的时候别去移动,在移动时平滑地移动,并且在程序无法很好地处理时给予玩家自主的控制权。否则摄像机就会在玩家意图和结果间引起干扰,让操作对玩家来说显得没那么直觉易懂了。更糟糕的是摄像机的运动会引起身体上的不适。这个有趣的现象更进一步确定了屏幕上的反馈确实会覆盖了玩家的视觉感知这一事实。当眼睛接收到的信号和内耳听到的信号不一致时,人会产生运动上的不适感。对一个静止坐在房间里的玩家来说,他会在一个不动的屏幕前玩游戏,体验着屏幕里的运动,此时他的视觉感知会透过屏幕延伸到游戏里。于是毫不奇怪地,当帧率突然下降时,一切会显得很拖沓,玩家也会感觉很糟糕。这就像你走在路上,视线突然开始断断续续,不断被打断那样。所以说当一个玩家静止不动地坐在房间里对着一台静止的显示器时,很大可能他会产生各种各样的运动不适感。因此只要玩家在游戏里的“眼睛”是摄像机,那摄像机带来的视觉上的反馈流就应该是流畅和不受干扰的。

    游戏感和本体感受

        在游戏过程中,有一种感觉可能我们不会把它当作是游戏感的一部分——这就是肌肉运动知觉。肌肉运动知觉是用来检测身体位置、重量、以及肌肉、筋腱和关节的运动的。用更酷的词来说,我们可以把它叫做“本体感觉”,这个词通常是和肌肉运动知觉交替使用的。本体感觉在这里显得更适用,它更准确地描述了人的潜意识对自己的身体身处于一个空间里的意识程度。要进一步理解什么是本体感觉,你可以闭上你的眼睹,把手臂平举在你身体前面,让左右手的手指相碰。这能让你无需得到视觉或听觉上的反馈都能确定手指在空间中的位置,这种感觉正是本体感受。当警察怀疑你醉酒驾驶上你走直线时,他也是想测试你现在的本体感受是怎么样的。

        那游戏感和本体感受有什么关联呢?本体感受来源于生理学,这是一门很复杂且不是特别容易理解的学科,了解它要和血管中液体的流动以及引力拉扯筋腱肌肉的感觉打交道。这些最终都让你感觉到身体在空间中的方位。这也是为什么大部分的宇肮员在初次面对零重力的前几天以及往后陆陆续续的时间里都会体验到“宇宙病”的原因。即使所有宇肮员都必须具备在极端的引力下极高的适应力,但由于缺少了本体感受上的反馈,身体还是会变得混乱无序。当引力消失后,身体失去了“身体上方”的感觉,只能在无法预期的空间中作出各种反应,于是往往会不舒服得大量呕吐。这在太空里是一件很可怕的事。

        当在电子游戏中操控对象时是没有“真正的”本体感受的,事实上也不可能有。虽然你会感觉到游戏里控制的角色是身体的延伸,但你永远不会得到跟现实世界中一样的本体感受——毕竟敲按键的肌肉反馈和拿起网球拍挥动的反馈是完全不同的。

        那它对我们带来了什么帮助呢?本体感受貌似是一个很重要的线索,因为操作游戏过程中感受到的感觉明显多于光靠视觉和听觉能带来的感觉。如果我们在玩赛车游戏时无法感受到弯道的引力,那为什么我们会觉得这个感觉很像现实中的转弯呢?为什么我们会坐在椅子上倾斜身体呢?让我们来看一个有趣的例子——Ian Waterman。他在19岁时因为病毒感染破坏了皮肤和肌肉上的神经。虽然还能感觉到温度、深层的压力和肌肉疲劳,但他的本体感受上的感觉都全部丧失了。他只能通过视觉上的观察或者其他细微的线索来辨别身体在空间中的方位。假如他站在厨房里,房间突然没电了,那他只能蹲在地板上,无助地等到房间重新亮起来时才能继续。但让人吃惊的是,如今他的行为看起来基本上和正常人无异了。经过了心智上的极大努力后,他利用能感觉到的所有线索(他还能利用声音和温度上的反馈去了解自己的位置)去估量出身体在空间里的方位。

        从表面上来看,在虚拟空间中操纵一个虚拟对象的体验在很多方面上和这个例子是相似的。我们基于有限的反馈会体验到一定程度的本体感受。我们能借此感觉到这个虚拟对象在虚拟空间中的方位、大小和重量。虽然用Waterman先生的例子来作为我们这里的辅助说明是不尊重的。但我想说明的是,即使是在一个纯电子构造的空间里操纵某样事物,我们还有着一项明显的优势:我们能用身体对方位的感觉来自我引导。这多棒啊!

        当你操作鼠标、手柄或者是Wii控制器时,你的本体感受也在发挥效用。你的拇指通过小小的动作依然能给你空间方位上的信息,它也能接收到按键、手柄或者控制器上传来的反馈。你能感觉到身体是处于空间里的,即使你得到的主要反馈都来源于虚拟空间中的虚拟对象。透过这种方式,控制游戏里某样事物的过程就相当于把你的感觉在空间中扩张那样,因为你从现实世界中很小的行为里得到极大量的反馈信息。这就像在你的拇指上装了扩音器那样,如今你关注着现实世界中的动作是如何影响这些虚拟对象的,动作和反馈的处理是互相转换的。当我们在游戏中操纵某样事物时,我们使用的不是全部的本体感受,而是一种经过扩张的本体感受。

        于是,这些由视觉、听觉和触觉上的反馈产生的本体感受会被扩张成游戏感的体验。这种经过扩张的感受是透过错觉产生的方式创造出来的,但在感觉后体验像是真的那样。这种游戏感的感受多于其各种组成部分(图形、声音、动作和特效)的总和,我们把它叫做“虚拟的本体感受”。

    玩家个体延伸带来的游戏感

        当感知延伸到游戏世界时,玩家的个体身份也会延伸进去。在你驾车时也会发生这种情况。当你开着车时,你能感觉到车子在空间中的方位,能感受到它在你身体周围有多远。这让你在停车时、在狭道中超车时,以及在把车子开进车库时不会撞到。你的感觉会向外延伸,包容了整辆汽车,并接受着周围的反馈。当进入这种情形时,你的车子会变成你的一部分,成为你身体和你自己的一个延伸。这也是为什么人们在撞车时往往会说“你撞到我了!”,而不是“他的车撞到我了!”,或者“他的车撞到我的车了!”

        当游戏里控制的角色感觉像是自己身体和感觉的延伸时个体身份的识别也会以同样的方式向外扩展。游戏设计师Jonathan Blow把这种延伸叫做“代理替身”,也就是个体身份延伸到某种代理替身里,寄居在其中,并让它成为身体的一部分。“我的那个家伙”变成“我自己”了。有趣的是这种个体身份的转换是变化莫测的。它会向外延伸,包容了我们正在操控的事物,但一瞬间以后或许又会撤除掉了。可能当我们在《半条命》里把满屋子的水手都轻松地清理干净时,我们会大喊“哈!我太疯狂了!”,但当突然不小心掉下悬崖经历了虚拟世界中可怕的死亡时,我们又会喊“不!不不!Gordon Freeman你这笨到家的家伙!绝对是糟透了!”对游戏设计师来说,这种个体身份的流动是很好的。它减轻了挑战给玩家的挫败感。对角色产生一点点的抱怨和沮咒总比玩家变得无聊或挫败从而放下游戏不玩要好。这为玩家提供了一个很好的释放方式,避免了自责,也保持了快乐,让玩家重新快速地投入回操作的快感里。

        这种个体身份的延伸还能给玩家一种直接进行物质接触的感觉。这是一种缓和的接触——正如在《雷神之锤》中被火箭炮击中那样,感觉起来不会像现实生活中被击中那样,但感受却很贴近。当我被撞倒、被推开一边、被猛地丟出去,或者被钉在尖柱上时,感觉都很糟糕,因为看起来就好像真实发生在我身上那样。这种感觉和我开的Volvo撞到水泥杆上是一样的——虽然不会真的让我感到痛苦,但感觉起来就像是自己受伤那样。类似地,当我在抓、扔、砍或者打某样东西时,感觉都很棒,因为延伸的虚拟身体成为了我自己的一部分了,依靠它我进入到游戏里直接地影响着这些对象。这时候你能感受到这种物质交互真的很强大。设计师能进一步通过润色和空间模拟的组合,让玩家感觉到自己击中了某样东西或者被某样东西击中了,以此塑造出很准确的交互体验。

        个体身份的延伸并不能直接设计出来,而是从实时控制中自然地成长起来的,假如在意图和结果间掺入了太多的挫败感、无聊感和模糊不清,那这种个体身份的延伸就会被打断了。它会基于操控的敏感度而在程度上有所改变。例如,在玩《俄罗斯方块》时,我不太会把自己的个体身份转移到每一个不断掉落的方块里。游戏里的时间是不断流逝的,方块移动没有为我带来太高的操控敏感度。方块本身并没有赋予人的特征,这件事对于玩法和操控来说也是不怎么重要的。在《小密蜂》里,玩家控制的也是一个极简单的角色,但此时个体身份的转移变得明显得多了,因为在操控上有了更高的敏感度了。敌人在上方盘旋,沿着曲线地向下攻击,你控制的飞船需要在狭窄的导弹区域里生存。此时你真正能感受到飞船的延伸,不断关注着你操控的飞船的大小和所处的方位。即使对于《Pong》这个游戏,它虽然只用了线条和方块来表现,但在个体身份转移上也有着很高的潜力。游戏里挡板的操控敏感度足够让你感受到个体身份和感觉的延伸。到了《雷神之锤》这类游戏时,这种情况被带到极致的程度:个体身份与控制的角色间不再有任何屏障了。《俄罗斯方块》在操控上敏感度很低,只允许旋转和左右移动;《雷神之锤》把鼠标这种高敏感度的输入设备直接映射到角色的旋转里。只要操控过程中不出现让玩家感到挫败的现象,也不显得操作含糊,那越高的操控敏感度的确能带来越大可能的个体身份转移。

    独特的物理实体带来的游戏感

        接下来我需要你帮我完成一个小实验。首先在你脑海里想象这样一个情景,假如你在房间里把这本书扔到墙上,会发生什么样的情况呢?能想象到吗?现在请张开眼睛,真真正正地把这本书扔到墙上。来吧,现在没有人在看你,尽管扔吧。

        我想此时你可能扔出去了,又或者基于你自己对书籍的个人道德没有扔出去,继续往下阅读。假如扔出去了,那你期望和实际的结果的体验有什么不同呢?现在把书放到你手上,感受它的重量和体积,用拇指快速掠过书页,听听那悦耳的声音。你注意到什么了吗?像这样的一本纸质书是很重、很松软的,它能扔到任何你想扔的地方,丟的过程中书页会在空中散开,然后落在地上时像个小山堆那样。基于你以前对纸质书的体验,这可能就是在你扔出这本书时认为会发生的倩况。但你是如何知道会发生这样的情况呢?当你看到一本没见过的书放在你的桌子上时,你是如何确认这本书有着厚厚的书页,压制的木边书脊,以及薄薄的封面呢?答案是行动,你会把它扔一下来找出答案。

        基于你以前经历过的纸质书,你能很可靠地猜测出之后会发生什么事,但唯一真正体验一个物件的物理特性的方法是观察它在运动中的情况。当一个物件和其他物件交互时,用上你的手,你会很快能解析出它的种种物理特性。在游戏里这个物理感知的过程也是一样的。从某种意义上来说,游戏感的体验正是来源于一个伪的牛顿物理体系。

        人们都很擅长于分辨虚拟空间中的物理体系,因为他们在潜意识里都是很熟悉现实世界中各种事物的运作方式的。只要遇到一个虚拟空间,我们会把物理法则中得来的全部线索都拼凑到一起,让它成为我们的心智模型。我们无法抑制自己不这样做。这个过程发生得很快也很有效,它会基于有限的模拟环境中可收集到的一切去进行,包括图像、声音、触感上的反馈和动作。当所有这些元素都协调在一起时,仿照出来的物质世界是完全符合逻辑的——无论是模拟碰撞、动画、声音、屏幕震动,还是粒子特效,每一个细小的线索都会支撑起同一种物质感。然而很多时候一点点的反馈都会和其他元素冲突起来,这会导致玩家对虚拟空间的心智模型产生内部矛盾。即使是像《战争机器》这种做了大量工作去渲染游戏世界里真实的物质感的游戏,也常常能找到一些内部矛盾的地方(例如角色的脚部经常会穿过梯子)。

        在电子游戏里,你并不是替代了正在操控的对象。你也不能这样做——你所操控的对象是没有实物形态的。电子游戏中的对象在虚拟空间中是一种电子的构造。不管它们能多成功地模仿出现实世界,它们也只能传达出一种物质感而已。创造出一个感受良好的游戏,从某种意义上也相当于建立这种感觉。通过声音和动作,我们能给玩家一整个有着物理法则的体系,让玩家在头脑里重构出虚拟空间的心智模型。这个过程和我们日常生活中每天把事物映射到物理空间里是一样的。把书扔出去的时候会在空中拋动,以某条痕迹划过,以某种方式重重地落到地上,发出噪耳的重击声。这种感觉和这些归纳都来源于我们对声音、触感和动作组合起来的理解。

        让我们来看看下图1.16两个保龄球相撞的例子。你能想象到假如它们撞在一起后,彼此会发出一声悦耳的撞击声,然后往反方向慢慢滚开。但假如撞击后有一个球变形了,发出的声响就像皮球被踢到那样,那你会推测这个球是什么样的呢?此时你一定觉得这个球只是视觉上塑造得像保龄球而已,只不过是披上保龄球外壳的皮球。即使它看起来像保龄球,但从声音和动作这两种别的反馈所提供的证据来看,彻底指明了它不是一个真正的保龄球。

    1.16 保龄球撞在一起并表现出正常的行为。(略)

        现在让我们来看看图1.17的两个球。你觉得这两个球带上速度地撞在一起会发生什么情况呢?想象乒乓球在撞击时发出低沉而不详的噪声,而后靠其冲击力把保龄球撞成裂开两半会怎么样?你觉得如果发生这种情况,它们的物理特性会是怎么样的?这在你所感知过的现实世界中是没有可类比的例子的。你需要靠想象去找出背后的物理现实。显然,即使这看起来像重量很轻的乒乓球,只要它能破坏一个保龄球,那它肯定是由某种很硬很重的物质组成的。我们努力地摒弃了视觉上的线索才解决了这种不和谐,因为运动和声音表现出来的状况超越了视觉,留下了明显的证据上的迹象。类似地,对保龄球来说,即使我们不能听到它的声音,但在它移动且与其他物件交互的过程中能传递出重量感的信息。虽然视觉和声音上的情况不完全贴合,但运动往往成为了最后一张王牌,决定性地建立出最终的感觉。

    1.17 保龄球和乒乓球:谁能撞赢呢?(略)

        这也是为什么互相穿插的物件和怪诞难测的运动会对玩家造成干扰的原因。例如id Software所做的《毀灭战士3》里的图像表现就极为明显了。游戏里每个生物都以法线贴图级的细节来表现,它也是把真实光照的模型用到玩法里的第一个商业游戏。游戏建筑中的角落里的确是很黑的,玩家必须用手电筒才能看清角落里的人。但不幸的是这些让人印象深刻的图像表现配上的是单薄无力的声音(尤其是散弹枪和机枪的效果)和不合情理、忽停忽动的运动,而且这些表现所组合出来的都是日常生活中每天都能看到的物件,散布在整个游戏里。一些物件会像直升飞机那样旋转和飞行,有着自己的生命,而其他物件都是完全没有交互反应甚至是完全不动的。这看起来根本不符合运动上的逻辑,结果游戏在图像和动作间建立起极大的矛盾。物质感的印象被撕扯得支离破碎。游戏设计师Brian Moriarty这样评价道:“……假如玩家不得不在游戏里参考你所创造的虚拟世界以外的对象时,那这种情况足以破坏了整个世界。”

        拿这点和最近有着超棒感受的《战争机器》来对比一下。《战争机器》极大量地使用粒子特效(尤其是角色猛地撞到墙上造成的大量灰尘)和摄像机技巧(例如镜头扭曲、屏幕震动),并且做出了极为精良的声音效果。这些都进一步加强并塑造出一种强大和富有吸引力的物质感印象。正如独立游戏设计师Derek Yu所说的:“……在《战争机器》里,你就像一个拿着机枪的巨型破坏球那样,感觉是很棒的。”

    总结

        要说清游戏感是什么,我们最初对游戏感给出了一个基础定义:

    在一个模拟空间中对虚拟对象进行实时控制,过程中通过润色来强化其交互效果。

        利用在这个定义中包含的三大基础构件(实时控制、模拟空间和润色),我们能创造出感觉很好的游戏。

        在本章后半段我们进一步深入定义了游戏感出色的游戏,这些游戏能为玩家传达出五类不同的体验:

    l  操作的美感

    l  学习、练习和掌握一项技能的愉悦感

    l  感觉的延伸

    l  个体的延伸

    l  与游戏里一个独特的物理实体的交互感

        这五种体验中没有任何一种是单独造就出游戏感的。游戏感是所有这些体验同时生效的结果。在玩游戏的过程中,可能其中一种体验会特别显著。玩家可能有时会觉得极其挫败,有时会被操作的美感吸引好一阵子,有时会因为巧妙地击中了一个对手而感受到荣誉的满足感。这些体验在任何时候都不是互斥的,每一种体验都会以某种程度展现出来。

        游戏感的这五种体验展现出玩家体验游戏感的方式,也指明了游戏设计师利用游戏感的方式。但它们没有指示出如何在生理上和心理上塑造出这些体验的过程。要在这种层级下进一步理解游戏感,接下来让我们稍微绕开人类体验,先看看人类感知。

     

    展开全文
  • MMO游戏技能攻击区域的计算

    千次阅读 2016-03-11 18:45:38
    游戏技能攻击区域的计算,关乎服务端的效率。需要确保正确,简洁地计算攻击区域,才能快速寻找攻击对象。 今天只讨论地图上距离的问题。 一般情况下攻击区域分为以下几种: 1.点对点,对个人进行攻击 2.射线攻击,...

    本文来自肥宝传说之路,引用必须注明出处!

    游戏技能攻击区域的计算,关乎服务端的效率。需要确保正确,简洁地计算攻击区域,才能快速寻找攻击对象。

    今天只讨论地图上距离的问题。

    一般情况下攻击区域分为以下几种:

    1.点对点,对个人进行攻击

    2.射线攻击,其实就是矩形区域

    3.扇形攻击

    4.圆形攻击

    当然,还有其他情况,例如多区域和其他奇奇怪怪的形状。不过考虑的实际观赏价值,和精度的问题,多区域,只考虑圆形和扇形,其他形状也不考虑了。

    释放技能需要几个事物,攻击者,主要被攻击者(也可能是攻击地点),其他围观的群众

    class CPoint//点的定义
    {
        double x;
        double y;
    }
    typedef std::vector<CPoint> SeqCPoint;
    double skillDistance = 123;//技能释放距离
    CPoint attackerPoint;//攻击者位置
    CPoint defenserPoint;//被攻击者位置或技能释放点
    SeqCPoint otherRoles;//其他需要检测的角色

    下面再细细讲解:

    ====================================================================================================================================

    1.点对点的攻击

    这个是最简单的,只要达到技能释放的距离,就可以释放。只要攻击者和被攻击者的位置小于配置的skillDistance即可。

    bool isFarThanDistance(CPoint a, CPoint b, double distance)
    {
    	double x = a.x - b.x;
    	double y = a.y - b.y;
    	if(x*x + y * y > distance *distance) return true;//超过距离
    	return false;//未超过
    }
    
    if (!isFarThanDistance( attackerPoint, defenserPoint, skillDistance) )
    {
    //在技能范围内,攻击处理
    }
    ====================================================================================================================================

    2.射线攻击,矩形区域

    怪物向目标喷出一条长长的火线,在火线上的玩家受到攻击,如下图。A向B喷火。同时也要检测周围的玩家是否中招


    判断一个点是否在矩形内是很简单的,如下:

    //判断点是否在矩阵内
    bool inRect( double minx, double miny, double maxx, double maxy, CPoint p)
    {
        if(p.x >= minx && p.x <= maxx && p.y >= miny && p.y <= maxy) return true;
        return false;
    }
    但这个是在矩形的边跟坐标轴平行的情况下的。如果攻击者的攻击方向跟坐标轴不平行,如上图,就无法计算了
    怎么办呢,如果能转换成相对坐标就简单很多了。相对坐标的知识

    要从绝对坐标转换成相对坐标,需要确定相对坐标的原点和x轴方向。

    原点是A点,也就是attackerPoint,X轴方向从A点指向B点。现在是求图中C点的相对坐标。

    ABC三点确定位置。根据余弦定理可以求出角CAB的余弦,从而可以求出相对坐标。然后在判断是否在矩阵内。


    //计算两点之间的距离
    double computeDistance(CPoint& from, CPoint& to)
    {
    	return sqrt(pow(to.x - from.x, 2) + pow(to.y - from.y, 2));
    }
    /**
    * 直角坐标--绝对坐标转相对坐标
    * originPoint 相对坐标系的原点
    * directionPoint 指向x轴方向的点
    * changePoint 需要转换的坐标
    */ 
    CPoint changeAbsolute2Relative(CPoint originPoint, CPoint directionPoint, CPoint changePoint)
    {
           //originPoint为图中A点,directionPoint为图中B点,changePoint为图中C点
    	CPoint rePoint;
    	if (originPoint == directionPoint)//方向点跟原点重合,就用平行于原坐标的x轴来算就行了
    	{//AB点重合,方向指向哪里都没所谓,肯定按原来的做方便
    		rePoint.x = changePoint.x - originPoint.x;
    		rePoint.y = changePoint.y - originPoint.y;
    	}
    	else
    	{ 
    		//计算三条边
    		//计算三条边
    		double a = computeDistance(directionPoint, changePoint);
    		double b = computeDistance(changePoint, originPoint);
    		double c = computeDistance(directionPoint, originPoint);
    		
    		double cosA = (b*b + c*c - a*a) / 2*b*c;//余弦
    		rePoint.x = a * cosA ;//相对坐标x
    		rePoint.y = sqrt(a*a - rePoint.x*rePoint.x);//相对坐标y
    	}
    	return rePoint;
    }	
    for(SeqCPoint::iterator iter = otherRoles.begin();
    	iter != otherRoles.end();
    	iter ++)
    {
    	//检测每一个角色是否在矩形内。	
    	CPoint rePoint = changeAbsolute2Relative(attackerPoint, defenserPoint, *iter);//相对坐标
    	//skillWidth为图中宽度,skillLong为图中长度
    	//宽度是被AB平分的,从A点开始延伸长度
    	bool beAttack = inRect(0, - skillWidth/2, skillLong, skillWidth/2, rePoint);//相对坐标下攻击范围不用算了,跟目标的相对坐标算一下
    	if (beAttack)
    	{
    		//受到攻击,攻击处理
    	}
    }		
    ====================================================================================================================================

    3.扇形区域

    攻击者对前方角度α,长度为L的区域进行攻击。如下图,攻击目标为B,要计算旁边的C是否也受到攻击

    扇形,当然是用极坐标最方便,判断一下距离,在半径范围内,然后判断一下角度是否适合。

    策划配置:扇形半径R和扇形总角度β

    所以构建以A为原点的极坐标。根据中心线的角度α,求出扇形的角度范围为[α-β/2,α+β]。再求出C点的极坐标进行比较

    void changeXYToPolarCoordinate(Common::CPoint p, double& r, double& angle)
    { 	
    	r = sqrt(p.x*p.x + p.y*p.y);//半径
    	angle = atan2(p.y , p.x) * 180/PI;//计算出来的是弧度,转成角度,atan2的范围是-π到π之间
    	angle = (angle + 360)%360;
    }
    CPoint changeAbsolute2Relative(CPoint originPoint, CPoint changePoint)
    {
    	CPoint rePoint;
    	rePoint.x = changePoint.x - originPoint.x;
    	rePoint.y = changePoint.y - originPoint.y;
    	return rePoint;
    }
    double baseR, baseAngle;
    CPoint rePoint = changeAbsolute2Relative(attackerPoint, defenserPoint);//图中B点的相对坐标
    changeXYToPolarCoordinate(rePoint, baseR, baseAngle);//转变成极坐标,baseAngle是角度
    for(SeqCPoint::iterator iter = otherRoles.begin();
    	iter != otherRoles.end();
    	iter ++)
    {
    	CPoint rePointC = changeAbsolute2Relative(attackerPoint, iter2);//图中C点相对坐标 
    	double cr = 0;//极坐标半径
    	double cangle = 0;//极坐标角度
    	changeXYToPolarCoordinate(rePointC, cr, cangle);
    	if (cr > R)//超过技能半径就无法攻击到了
    	{
    		continue;
    	}
    	if ( abs(cangle - baseAngle) < β/2 )//相差的角度小于配置的角度,所以受到攻击。要注意,这里的角度都是在0°到360°之间
    	{
    		//受到攻击
    	} 
    }

    今天发现还有一种方法,就是利用向量的点积,可以百度一下。

    CPoint rePoint = changeAbsolute2Relative(attackerPoint, defenserPoint);//图中B点的相对坐标
    double longB = sqrt(rePoint.x * rePoint.x + rePoint.y * rePoint.y);//长度
    rePoint.x /= longB;
    rePoint.y /= longB;//求单位向量
    for(SeqCPoint::iterator iter = otherRoles.begin();
    	iter != otherRoles.end();
    	iter ++)
    {
    	CPoint rePointC = changeAbsolute2Relative(attackerPoint, iter2);//图中C点相对坐标 
    	double longC = sqrt(rePointC.x * rePointC.x + rePointC.y * rePointC.y);//长度
    	rePointC.x /= longC;
    	rePointC.y /= longC;//求单位向量
    	double jiaodu = acos(rePoint.x * rePointC.x + rePoint.y * rePointC.y) * 180 /PI;//角CAB的大小
    	if(jiaodu < β/2)
    	//相差的角度小于配置的角度,所以受到攻击。要注意,这里的角度都是在0°到360°之间
    	{
    		//受到攻击
    	} 
    }
    对于扇形计算面积的优化,可以参考这里
    再说一下多个扇形的情况


    分开算就行了,用个for循环,每个扇形分别计算。

    ====================================================================================================================================

    4.圆形区域,圆形其实是最简单的了。


    判断是否在攻击者半径范围内就行了。

    for(SeqCPoint::iterator iter = otherRoles.begin();
    	iter != otherRoles.end();
    	iter ++)
    {
    	CPoint rePointC = changeAbsolute2Relative(attackerPoint, iter2);//图中C点相对坐标 
    	double cr = sqrt(rePointC.x*rePoint.x + rePointC.y*rePointC.y); //点到圆心的距离
    	if (cr <= R)//超过技能半径就无法攻击到了
    	{
    		//受到攻击
    	} 
    }	

    对于多个圆形区域的计算


    本质上还是一样,用一个for循环,计算出圆心的位置,然后计算点到圆心的距离就完成了。




    展开全文
  • 之前已经写过关于攻击区域的算法。但后来,发现别人的游戏(《凡人修真2》其实也不算别人的游戏了)不是这么写的。我居然找不到算矩形之类的代码。找来找去,发现实现思路跟我的差别太大。我的思路在这里 先说一种...

    本文来自肥宝游戏,引用必须注明出处!

    之前已经写过关于攻击区域的算法。但后来,发现别人的游戏(《凡人修真2》其实也不算别人的游戏了)不是这么写的。我居然找不到算矩形之类的代码。找来找去,发现实现思路跟我的差别太大。我的思路在这里

    先说一种情况,就是一个玩家的角色在地图上走动的时候,是需要不断的跟服务端同步的。


    例如图中坐标系,从位置1走到位置2.这个过程中是要不断的跟服务端同步的。服务端也会不断地跟其他客户端同步

    在玩家的角度来看,角色A是从位置1到位置2匀速运动。而在同一个屏幕下的其他玩家也是在匀速运动。这是正常情况下,也是理想的情况。

    但客户端不可能没移动一个像素就跟告诉服务端一下,我走了一个点。

    这个是不现实的,首先没有必要,移动一个像素,客户端可能根本没什么变化。

    第二,假如屏幕上有10个人,一个角色移动了,服务端就要广播给10个人,在一瞬间就是发送了10个消息。假如这10个人都在动,就是同时发送10*10个消息

    假如屏幕上有100个人同时在动,就是100*100,假如是1000,就是1000*1000了。我相信这个游戏基本就只能在地图上跑,其他什么都做不了了,甚至跑都跑不动。

    所以可以得出结论:客户端跟服务端必须不是同步的,以减少通信。这样的话就只能走一段距离才同步一下位置。其他客户端收到这个角色在动,就自己做一个匀速运动过去。

    对于客户端来说,角色移动依然是上图的样子,对服务端来说是怎样的呢?


    对服务端来说,没收到客户端的一个请求,就改变一下位置,所以从位置1到位置2,是按着图中换色路线闪过去的。

    其他客户端收到同步的消息,就做匀速运动,然角色看起来好像在一直走着。

    那么现在问题来了,客户端跟服务端同步的标准是什么呢?什么情况下需要同步一下?

    1.按时间同步

    每隔一段时间同步一下,例如100毫秒。我们的服务端的怪是这样同步的,因为每隔一段时间需要执行一下AI处理,是否遇到玩家,是否需要追击,是否需要释放技能等等。这些都要做,所以移动的话,也就放在这个模块里面了。我不知道会不会有的游戏客户端会这样处理。

    2.按空间同步

    每移动一段距离,就同步一下。但是这个距离怎么算呢?是从上一个点开始算起么?我们没这么算过,可能等我客户端的时候可以这么试试。实际上我们是把地图画了一个个格子。就像上图那样,假如地图是1000*1000的。我每100个像素划分一个格子。就可以得出X轴和Y轴都分成10份了。角色只有从一个格子移动到另外一个格子的情况下,才通知服务端。

    这样做的好处是大大缩减了通信的数量。当然也不是没有代价的,如果卡的时候,就会看到角色从位置1走到位置1.1,然后定住了,过几秒钟,它可能出现在位置2了。如果是在多人同屏夺旗之类(独步天下的帮派战),真是日了狗了,杀过去后发现玩家居然闪过去了。

    上面是地图的处理,总结一下:

    无论服务端是把地图分成一个个格子,在同一个格子内,是算同一点的,如果你把格子画得很大很大,例如整个地图就是一格子,那么在服务端看来,所有角色都是堆在同一个位置上面了。

    那么格子需要多大呢?

    问了一下策划,他们回答:肯定是越小越好啦,这样就能越精确。

    问了一下客户端,说:他们没有格子的概念的,服务端分的格子,他们基本上只是判断一下消息发送的时机而已。

    对服务端来说,虽然不可能整个地图就一个格子,但是也是想越大越好啊。

    顺便一说,地图主要是客户端的天下,地图格子是服务端用来做技能处理的。

    对于一般的2d游戏来说,格子大小最好还是先设定好一个值,然后看实际效果,再慢慢调。因为每个游戏的场景、建筑、任务的大小都不同,尤其是像大战神这种可以放大缩小的游戏,更加需要直接看游戏来作调整。一切以实际感官为标准,然后尽量画大一点。


    =================================================================================

    我们先把格子分好!!!无论用哪个计算方式,都需要先把格子分好。按每个格子CellBase大小为20像素,当然实际应用中可以长宽不同。如下面就是一个220*220的地图,分成11*11个格子。格子中的数字就是其坐标,格子的坐标。



    明确好格子的概念,就可以开始思考新的技能攻击区域了。

    先定义好一些变量,

    DataManage.h 存放一些测试用的数据,数据的值不是很重要,不能太小,否则全部在一个点上面就没意思了。也不能太大,一个技能把整个地图包含进去也没啥意思的。

        
        //数据管理,数据来源应该是来自于配置文件以及游戏运行过程的实际数据,这里是为了方便管理
        class CDataManager
        {
        private:
            CDataManager();
            ~CDataManager();
            void init();
        public:
            static CDataManager* instance();//单例
            
            //======技能数据,这个应该来自配置文件,由策划配出技能实际要求==============
            int skillDistance;//技能释放距离
            
            int rectWidth;//矩形攻击区域的宽度
            int rectHeight;//矩形攻击区域的高度
            
            int angleBeta;//扇形攻击区域的角度
            int rBeta;//扇形攻击区域的半径
            
            //======角色站位数据,必须来自实际的运算,玩家是不断走动的=================
            SPoint attackerPoint;//攻击者位置
            SPoint defenserPoint;//被攻击者位置或技能释放点
            SeqSPoint otherRoles;//其他需要检测的角色
        }; 
    


    一般情况下攻击区域分为以下几种:

    1.点对点,对个人进行攻击

    点对点,其实就是判断两个角色所在的格子之间的长度是不是在所配置的长度之内了。具体函数如下

    //判断两点间是否超过一定距离
    bool CMapManager::isFarThanDistance(SPoint a, SPoint b, int distance)
    {
        //求出相对距离xy
        int x = (a.x - b.x) * CellBase;//坐标点都是格子的坐标点,所以要乘以格子的长度
        int y = (a.y - b.y) * CellBase;
        
        if(x * x + y * y > distance *distance) return true;//超过距离(勾股定理)
        return false;//未超过
    }

    2.射线攻击,其实就是矩形区域


    //判断一个点是否在矩形内,这个要求与坐标轴平行
    bool CMapManager::inRect( double minx, double miny, double maxx, double maxy, SPoint p)
    {
        //判断点p的xy是否在矩形上下左右之间
        if(p.x >= minx && p.x <= maxx && p.y >= miny && p.y <= maxy) return true;
        return false;
    }
    
    //计算两点之间的距离
    double CMapManager::computeDistance(SPoint& from, SPoint& to)
    {
        return (sqrt(pow(to.x - from.x, 2) + pow(to.y - from.y, 2)))* CellBase;
    }
    
    //直角坐标--绝对坐标转相对坐标
    SPoint CMapManager::changeAbsolute2Relative(
                                                SPoint originPoint,//相对坐标系的原点
                                                SPoint directionPoint,//指向x轴方向的点
                                                SPoint changePoint)//需要转换的坐标
    {
        //originPoint为图中A点,directionPoint为图中B点,changePoint为图中C点
        SPoint rePoint;
        if (originPoint.x == directionPoint.x && originPoint.y == directionPoint.y)//方向点跟原点重合,就用平行于原坐标的x轴来算就行了
        {//AB点重合,方向指向哪里都没所谓,肯定按原来的做方便
            rePoint.x = changePoint.x - originPoint.x;
            rePoint.y = changePoint.y - originPoint.y;
        }
        else
        {
            //计算三条边
            double a = computeDistance(directionPoint, changePoint);
            double b = computeDistance(changePoint, originPoint);
            double c = computeDistance(directionPoint, originPoint);
            
            double cosA = (b*b + c*c - a*a) / 2*b*c;//余弦
            rePoint.x = a * cosA / CellBase;//相对坐标x
            rePoint.y = sqrt(a*a - a * cosA * a * cosA) / CellBase;//相对坐标y
        }
        return rePoint;
    }
    
    bool CMapManager::inRectRelat( SPoint originPoint, SPoint directionPoint, SPoint checkPoint)
    {
        //检测每一个角色是否在矩形内。
        SPoint rePoint = changeAbsolute2Relative(originPoint, directionPoint, checkPoint);//相对坐标
        //skillWidth为图中宽度,skillLong为图中长度
        int skillWidth = CDataManager::instance()->rectWidth/CellBase;//矩形攻击区域的宽度
        int skillLong = CDataManager::instance()->rectHeight/CellBase;//矩形攻击区域的高度
    
        //宽度是被AB平分的,从A点开始延伸长度
        return inRect(0, - skillWidth/2, skillLong, skillWidth/2, rePoint);//相对坐标下攻击范围
    }
    
    SPoint changeAbsolute2Relative(SPoint originPoint, SPoint changePoint)
    {
        SPoint rePoint;
        rePoint.x = changePoint.x - originPoint.x;
        rePoint.y = changePoint.y - originPoint.y;
        return rePoint;
    }
    

    3.扇形攻击

    bool checkInFan( SPoint originPoint, SPoint directionPoint, SPoint checkPoint )
    {
        //先求主目标的单位向量
        SPoint rePoint = changeAbsolute2Relative(originPoint, directionPoint);//攻击者与主目标的向量
        double longB = sqrt(rePoint.x * rePoint.x + rePoint.y * rePoint.y) ;//长度
        rePoint.x /= longB;
        rePoint.y /= longB;//求单位向量
        
        //然后求出检测点的向量
        SPoint rePointC = changeAbsolute2Relative(originPoint, checkPoint);//图中C点相对坐标
        double longC = sqrt(rePointC.x * rePointC.x + rePointC.y * rePointC.y);//长度
        rePointC.x /= longC;
        rePointC.y /= longC;//求单位向量
        
        //根据向量的点击来求角度
        double jiaodu = acos(rePoint.x * rePointC.x + rePoint.y * rePointC.y) * 180 /PI;//实际的角度大小
        double angleBeta = CDataManager::instance()->angleBeta;
        
        if ( jiaodu < angleBeta)
        {//相差的角度小于配置的角度,所以受到攻击。要注意,这里的角度都是在0°到360°之间
            return true;//在角度范围内
        }
        return false;
    }

    4.圆形攻击

    圆形攻击其实就是跟点对点的攻击计算一样的。

    //判断两点间是否超过一定距离
    bool CMapManager::isFarThanDistance(SPoint a, SPoint b, int distance)
    {
        //求出相对距离xy
        int x = (a.x - b.x) * CellBase;//坐标点都是格子的坐标点,所以要乘以格子的长度
        int y = (a.y - b.y) * CellBase;
        
        if(x * x + y * y > distance *distance) return true;//超过距离(勾股定理)
        return false;//未超过
    }


    ======================================================

    上面的计算其实跟上一篇文章技能攻击区域的计算差不多。只是加了个格子。但是基本算法思想完全没有变化。

    但是加了格子后,就多了一种计算方法。

    我们看一下不同技能在区域在格子上面是怎么表示的。

    1.点对点,没变化,就不说了。

    2.扇形,如果是小的扇形,其实就是自己所在的那个格子,就判断玩家是否在同一个点就行了。如果扇形很大。


    图中使用的是相对位置,以攻击者为圆心,这个只考虑了一个朝向。

    3.矩形,其实就是一条射线


    4,再看看圆形:


    那么怎么算呢?

    这些点都是通过配置来实现的。

    SPoint p;
        p.x = 0; p.y = 0;
        cellsFan.push_back(p);
        p.x = -1; p.y = 1;
        cellsFan.push_back(p);
        p.x = 0; p.y = 1;
        cellsFan.push_back(p);
        p.x = 1; p.y = 1;
        cellsFan.push_back(p);
        p.x = -2; p.y = 2;
        cellsFan.push_back(p);
        p.x = -1; p.y = 2;
        cellsFan.push_back(p);
        p.x = 0; p.y = 2;
        cellsFan.push_back(p);
        p.x = 1; p.y = 2;
        cellsFan.push_back(p);
        p.x = 2; p.y = 2;
        cellsFan.push_back(p);
    这是扇形的配置。可以想象到,这种配置是跟图形无关的,几乎所有工作了都在策划身上,就看策划怎么配了。


    ==========================================================================

    上面的代码很碎,下面黏贴所有代码。由于文章太长了,下一章再进行效率对比了。

    ========DataManager.h===========

    //
    //  ConfigManager.h
    //  HelloWorld
    //  关注微信公众号:传说之路,大家共同学习
    //  Created by feiyin001 on 16/4/3.
    //  Copyright (c) 2016年 FableGame. All rights reserved.
    //
    
    #ifndef __HelloWorld__ConfigManager__
    #define __HelloWorld__ConfigManager__
    
    #include <stdio.h>
    #include "MapManager.h"
    
    
    namespace FableGame {
        
        
        //数据管理,数据来源应该是来自于配置文件以及游戏运行过程的实际数据,这里是为了方便管理
        class CDataManager
        {
        private:
            CDataManager();
            ~CDataManager();
            void init();
        public:
            static CDataManager* instance();//单例
            
            //======技能数据,这个应该来自配置文件,由策划配出技能实际要求==============
            int skillDistance;//技能释放距离
            
            int rectWidth;//矩形攻击区域的宽度
            int rectHeight;//矩形攻击区域的高度
            
            int angleBeta;//扇形攻击区域的角度
            int rBeta;//扇形攻击区域的半径
            
            //======格子区域的配置===========
            SeqSPoint cellsFan;//扇形区域
            SeqSPoint cellsRect;//矩形
            SeqSPoint cellsCyc;//圆形
    
            
            
            //======角色站位数据,必须来自实际的运算,玩家是不断走动的=================
            SPoint attackerPoint;//攻击者位置
            SPoint defenserPoint;//被攻击者位置或技能释放点
            SeqSPoint otherRoles;//其他需要检测的角色
        };
    }
    #endif /* defined(__HelloWorld__ConfigManager__) */
    
    =========DataManager.cpp=============

    //
    //  ConfigManager.cpp
    //  HelloWorld
    //  关注微信公众号:传说之路,大家共同学习
    //  Created by feiyin001 on 16/4/3.
    //  Copyright (c) 2016年 FableGame. All rights reserved.
    //
    
    #include "DataManager.h"
    using namespace FableGame;
    
    
    CDataManager::CDataManager()
    {
        init();
    }
    CDataManager::~CDataManager()
    {
        
    }
    
    CDataManager* CDataManager::instance()
    {
        static CDataManager _instance;
        return &_instance;
    }
    
    void CDataManager::init()
    {
        //数据的具体数值不是很重要,重要的是不要太大或者太小,导致技能效果全在一个点上面或者包含整个地图。
        //算法是一样的,关键是看计算时间
        //=================技能相关配置==============
        skillDistance = 50;//技能释放距离,像素
        
        rectWidth = 50;//矩形区域的宽度,像素
        rectHeight = 20;//矩形区域的高度,像素
        
        angleBeta = 60;//扇形攻击区域的角度
        rBeta = 30;//扇形攻击区域的半径,像素
        
        //=================通过配置的格子的方式来做技能===
        SPoint p;
        //扇形
        p.x = 0; p.y = 0; cellsFan.push_back(p);
        p.x = -1; p.y = 1; cellsFan.push_back(p);
        p.x = 0; p.y = 1; cellsFan.push_back(p);
        p.x = 1; p.y = 1; cellsFan.push_back(p);
        p.x = -2; p.y = 2; cellsFan.push_back(p);
        p.x = -1; p.y = 2; cellsFan.push_back(p);
        p.x = 0; p.y = 2; cellsFan.push_back(p);
        p.x = 1; p.y = 2; cellsFan.push_back(p);
        p.x = 2; p.y = 2; cellsFan.push_back(p);
        //矩形
        p.x = 0; p.y = 0; cellsRect.push_back(p);
        p.x = 0; p.y = 1; cellsRect.push_back(p);
        p.x = 1; p.y = 1; cellsRect.push_back(p);
        p.x = 1; p.y = 2; cellsRect.push_back(p);
        p.x = 2; p.y = 2; cellsRect.push_back(p);
        p.x = 2; p.y = 3; cellsRect.push_back(p);
        p.x = 3; p.y = 3; cellsRect.push_back(p);
        p.x = 3; p.y = 4; cellsRect.push_back(p);
        
        //圆形
        p.x = -1; p.y = 2; cellsCyc.push_back(p);
        p.x = 0; p.y = 2; cellsCyc.push_back(p);
        p.x = 1; p.y = 2; cellsCyc.push_back(p);
        p.x = -2; p.y = 1; cellsCyc.push_back(p);
        p.x = -1; p.y = 1; cellsCyc.push_back(p);
        p.x = 0; p.y = 1; cellsCyc.push_back(p);
        p.x = 1; p.y = 1; cellsCyc.push_back(p);
        p.x = 2; p.y = 1; cellsCyc.push_back(p);
        p.x = -2; p.y = 0; cellsCyc.push_back(p);
        p.x = -1; p.y = 0; cellsCyc.push_back(p);
        p.x = 0; p.y = 0; cellsCyc.push_back(p);
        p.x = 1; p.y = 0; cellsCyc.push_back(p);
        p.x = 2; p.y = 0; cellsCyc.push_back(p);
        p.x = -2; p.y = -1; cellsCyc.push_back(p);
        p.x = -1; p.y = -1; cellsCyc.push_back(p);
        p.x = 0; p.y = -1; cellsCyc.push_back(p);
        p.x = 1; p.y = -1; cellsCyc.push_back(p);
        p.x = 2; p.y = -1; cellsCyc.push_back(p);
        p.x = -1; p.y = -2; cellsCyc.push_back(p);
        p.x = 0; p.y = -2; cellsCyc.push_back(p);
        p.x = 1; p.y = -2; cellsCyc.push_back(p);
        
        //=================角色实际站位==============
        attackerPoint.x = 0;//攻击者位置
        attackerPoint.y = 1;//攻击者位置
        
        defenserPoint.x = 8;//被攻击者位置或技能释放点
        defenserPoint.y = 8;//被攻击者位置或技能释放点
        
        //其他角色位置,为了方便测试,在每个格子都放一个人吧。
        //otherRoles其他需要检测的角色
        for (int i = 0; i <= 100; i++) {
            for (int j = 0; j <= 100; j++) {
                SPoint p;
                p.x = i;
                p.y = j;
                otherRoles.push_back(p);
            }
        }  
    }
    ==========MapManager.h===============

    //
    //  Header.h
    //  HelloWorld
    //  关注微信公众号:传说之路,大家共同学习
    //  Created by feiyin001 on 16/4/3.
    //  Copyright (c) 2016年 FableGame. All rights reserved.
    //
    
    #ifndef HelloWorld_Header_h
    #define HelloWorld_Header_h
    
    #include <vector>
    #define PI 3.1412;//圆周率
    #define CellBase 20;//格子的大小
    namespace FableGame {
        
        struct SPoint
        {
            int x;
            int y;
        };
        typedef std::vector<SPoint> SeqSPoint;
        
        //地图上各种处理都放在这里
        class CMapManager
        {
            
            //判断两点间是否超过一定距离
            bool isFarThanDistance(SPoint a, SPoint b, int distance);
            
            //判断一个点是否在矩形内,这个要求与坐标轴平行
            bool inRect( double minx, double miny, double maxx, double maxy, SPoint p);
            //判断一个点是否在矩形内,
            bool inRectRelat( SPoint originPoint, SPoint directionPoint, SPoint checkPoint);
            //判断是否在扇形内
            bool checkInFan( SPoint originPoint, SPoint directionPoint, SPoint checkPoint );
            
            
            //计算两点之间的距离
            double computeDistance(SPoint& from, SPoint& to);
            
            /**
             * 直角坐标--绝对坐标转相对坐标
             * originPoint 相对坐标系的原点
             * directionPoint 指向x轴方向的点
             * changePoint 需要转换的坐标
             */
            SPoint changeAbsolute2Relative(SPoint originPoint, SPoint directionPoint, SPoint changePoint);
            //这个转换的坐标轴是跟原来的平行的
            SPoint changeAbsolute2Relative(SPoint originPoint, SPoint changePoint);
            
            
            //======检测是否在格子配置的图形里面======
            bool checkInCellFan(SPoint originPoint, SPoint checkPoint);
            bool checkInCellRect(SPoint originPoint, SPoint checkPoint);
            bool checkInCellCyc(SPoint originPoint, SPoint checkPoint);
            
        }; 
    }
    #endif
    
    ===========MapManager.cpp================

    //
    //  MapManager.cpp
    //  HelloWorld
    //  关注微信公众号:传说之路,大家共同学习
    //  Created by feiyin001 on 16/4/3.
    //  Copyright (c) 2016年 FableGame. All rights reserved.
    //
    
    #include "MapManager.h"
    #include <math.h>
    #include "DataManager.h"
    
    
    using namespace FableGame;
    
    //判断两点间是否超过一定距离
    bool CMapManager::isFarThanDistance(SPoint a, SPoint b, int distance)
    {
        //求出相对距离xy
        int x = (a.x - b.x) * CellBase;//坐标点都是格子的坐标点,所以要乘以格子的长度
        int y = (a.y - b.y) * CellBase;
        
        if(x * x + y * y > distance *distance) return true;//超过距离(勾股定理)
        return false;//未超过
    }
    
    //判断一个点是否在矩形内,这个要求与坐标轴平行
    bool CMapManager::inRect( double minx, double miny, double maxx, double maxy, SPoint p)
    {
        //判断点p的xy是否在矩形上下左右之间
        if(p.x >= minx && p.x <= maxx && p.y >= miny && p.y <= maxy) return true;
        return false;
    }
    
    //计算两点之间的距离
    double CMapManager::computeDistance(SPoint& from, SPoint& to)
    {
        return (sqrt(pow(to.x - from.x, 2) + pow(to.y - from.y, 2)))* CellBase;
    }
    
    //直角坐标--绝对坐标转相对坐标
    SPoint CMapManager::changeAbsolute2Relative(
                                                SPoint originPoint,//相对坐标系的原点
                                                SPoint directionPoint,//指向x轴方向的点
                                                SPoint changePoint)//需要转换的坐标
    {
        //originPoint为图中A点,directionPoint为图中B点,changePoint为图中C点
        SPoint rePoint;
        if (originPoint.x == directionPoint.x && originPoint.y == directionPoint.y)//方向点跟原点重合,就用平行于原坐标的x轴来算就行了
        {//AB点重合,方向指向哪里都没所谓,肯定按原来的做方便
            rePoint.x = changePoint.x - originPoint.x;
            rePoint.y = changePoint.y - originPoint.y;
        }
        else
        {
            //计算三条边
            double a = computeDistance(directionPoint, changePoint);
            double b = computeDistance(changePoint, originPoint);
            double c = computeDistance(directionPoint, originPoint);
            
            double cosA = (b*b + c*c - a*a) / 2*b*c;//余弦
            rePoint.x = a * cosA / CellBase;//相对坐标x
            rePoint.y = sqrt(a*a - a * cosA * a * cosA) / CellBase;//相对坐标y
        }
        return rePoint;
    }
    
    bool CMapManager::inRectRelat( SPoint originPoint, SPoint directionPoint, SPoint checkPoint)
    {
        //检测每一个角色是否在矩形内。
        SPoint rePoint = changeAbsolute2Relative(originPoint, directionPoint, checkPoint);//相对坐标
        //skillWidth为图中宽度,skillLong为图中长度
        int skillWidth = CDataManager::instance()->rectWidth/CellBase;//矩形攻击区域的宽度
        int skillLong = CDataManager::instance()->rectHeight/CellBase;//矩形攻击区域的高度
    
        //宽度是被AB平分的,从A点开始延伸长度
        return inRect(0, - skillWidth/2, skillLong, skillWidth/2, rePoint);//相对坐标下攻击范围
    }
    
    SPoint changeAbsolute2Relative(SPoint originPoint, SPoint changePoint)
    {
        SPoint rePoint;
        rePoint.x = changePoint.x - originPoint.x;
        rePoint.y = changePoint.y - originPoint.y;
        return rePoint;
    }
    
    bool checkInFan( SPoint originPoint, SPoint directionPoint, SPoint checkPoint )
    {
        //先求主目标的单位向量
        SPoint rePoint = changeAbsolute2Relative(originPoint, directionPoint);//攻击者与主目标的向量
        double longB = sqrt(rePoint.x * rePoint.x + rePoint.y * rePoint.y) ;//长度
        rePoint.x /= longB;
        rePoint.y /= longB;//求单位向量
        
        //然后求出检测点的向量
        SPoint rePointC = changeAbsolute2Relative(originPoint, checkPoint);//图中C点相对坐标
        double longC = sqrt(rePointC.x * rePointC.x + rePointC.y * rePointC.y);//长度
        rePointC.x /= longC;
        rePointC.y /= longC;//求单位向量
        
        //根据向量的点击来求角度
        double jiaodu = acos(rePoint.x * rePointC.x + rePoint.y * rePointC.y) * 180 /PI;//实际的角度大小
        double angleBeta = CDataManager::instance()->angleBeta;
        
        if ( jiaodu < angleBeta)
        {//相差的角度小于配置的角度,所以受到攻击。要注意,这里的角度都是在0°到360°之间
            return true;//在角度范围内
        }
        return false;
    }
    
    
    bool checkInCellFan(SPoint originPoint, SPoint checkPoint)
    {
        SPoint rePoint = changeAbsolute2Relative(originPoint, checkPoint);//计算出相对位置
        //判断是否跟配置某一点相同
        for (SeqSPoint::iterator iter = CDataManager::instance()->cellsFan.begin();
             iter != CDataManager::instance()->cellsFan.end() ;
             iter++)
        {
            if (iter->x == rePoint.x && iter->y == rePoint.y) {
                return true;
            }
        }
        return false;
    }
    bool checkInCellRect(SPoint originPoint, SPoint checkPoint)
    {
        SPoint rePoint = changeAbsolute2Relative(originPoint, checkPoint);
        for (SeqSPoint::iterator iter = CDataManager::instance()->cellsRect.begin();
             iter != CDataManager::instance()->cellsRect.end() ;
             iter++)
        {
            if (iter->x == rePoint.x && iter->y == rePoint.y) {
                return true;
            }
        }
        return false;
    }
    bool checkInCellCyc(SPoint originPoint, SPoint checkPoint)
    {
        SPoint rePoint = changeAbsolute2Relative(originPoint, checkPoint);
        for (SeqSPoint::iterator iter = CDataManager::instance()->cellsCyc.begin();
             iter != CDataManager::instance()->cellsCyc.end() ;
             iter++)
        {
            if (iter->x == rePoint.x && iter->y == rePoint.y) {
                return true;
            }
        }
        return false;
    }











    展开全文
  • Android 游戏检测碰撞方法:多种检测碰撞的定义及说明

           一、地图格子划分检测
      最简单的一种检测,就是把地图(或者称为场景,总之是指碰撞发生的范围)划成一个个格子,类似仙剑奇侠传这样。假设地图有800*600px,20*20个像素为一格。那么可以划为40*30个格子。地图中参与检测的对象都存储着自身所在的格子坐标,判断碰撞就显而易见了,例如可以认为两个物体在相邻格判为碰撞,或者两个物体在同一格。采用这种方式有个要求,就是地图中所有可能参与碰撞的物体都要是20*20像素左右大小或者是其整数倍,例如房子占了3*3个格子,诸如此类。如果不遵守这个规则,有的物体只占了格子的一半,那么在玩家眼里这种检测就显得非常的粗糙。这种检测就像是把地图的像素点放大几十倍一样,与逐像素检测相比,效率提高了几十倍甚至上百倍。这种方式可运用于对检测要求不严格的游戏,例如踩地雷的RPG、推箱子之类的智力游戏。
      二、矩形检测
      当地图中的物体不能严格按照某个块大小的整数倍来绘制时,那么就需要另想其他的方法。这种方法适用于地图中的物体近似为矩形或者虽然不是矩形,但是碰撞精度要求不高的情况下。每个物体记录一个能够将自己框住的最小矩形的左上角坐标和矩形长宽。碰撞退化为判断矩形与矩形之间是否重叠,而这仅需要4次比较即可得出,速度很快。但为了判断整个场景中的物体,必须取第一个物体,迭代其他所有物体进行判断,再取第二个物体,迭代除第一第二个物体外的所有物体进行判断,以此类推。总计要进行(n-1)!次矩形判断才能准确得出场景中所有的碰撞可能。

      三、圆形检测
      与上一种方法类似,区别在于用一个能够包含物体的最小圆代替了矩形。主要是考虑到游戏中的物体外形以平滑为主,例如人物角色。而判断两个圆是否碰撞的计算也很简单,就是判断两个圆心之间的距离是否小于两个圆的半径之和。虽然球形检测在某些情况下提高了精度,但却损失了速度,因为点距离的计算需要用到平方和开方。具体相比慢多少我就不太清楚了。另外,为了计算整个地图的所有碰撞可能,也要进行(n-1)!次比较。

      四、像素检测
      精确到像素级,已经不能比这更精确了,相对的,效率也是最低的。怎样判断两个物体是否碰撞呢?在过去png格式图片还不盛行的时候,游戏中用到的图片中的透明部分是指定用某种颜色来表示的,例如洋红色。就像电影中的绿幕蓝幕,通过处理把这些颜色的像素点当做透明点处理,而为了判断检测,需要准备一张原图像的黑白图,黑色区域表示透明,这张图片中的每个像素值为0或者1,判断检测的时候取两张图片的黑白图,进行与运算,结果为1(有白点重叠),则判为碰撞。但是现在有了PNG和XNA,逐像素检测就相对简单一些。首先仍然需要有一个矩形框包围物体,通过矩形检测得到重叠的矩形区域可以大大减少检测的像素点数量。然后在这个区域内,取两个图片的点逐行逐列迭代,如果遇到某个点两张图片均有颜色存在,即判为碰撞。同理,进行(n-1)!次比较后得到全地图的碰撞可能。

      五、四叉树检测
      准确的说这事在第三四五种方法的基础上的优化策略,或者说是第一种方法同后三种方法的组合应用。主要是针对那最后的(n-1)!次比较。方法是,像第一种方法一样将地图分为格子,格子的大小应该能够容纳10个左右的地图中最大物体,例如一个800*600的地图可能就划为9个区。同样的,每个物体要记录自己所在的区坐标以及矩形包围盒。如果该物体完全位于该区内,则只要将其与该区内的其他物体判断碰撞。如果该物体虽然位于某个区,但是小部分位于隔壁区,则额外的需要迭代隔壁区的物体,这点效率损失是可以容忍的,相比于迭代全地图的物体。

      有个问题,我怎么知道哪些物体是跟该物体位于同一个区呢?那不是还是要迭代一遍所有物体?这时候就是题目发挥的地方的,之所以称为四叉树检测(当然,这名字是我自己取的),就是因为那些区块是以四叉树的方式链接的,即得到一个区块的对象,就可以直接得到其上下左右相邻的区块的对象,而物体可以是存储在所在区的一个列表中。这样就不用遍历所有物体也可以直接取出隔壁区的物体了。当地图很大的时候,四叉树的优势体现得很好。

      六、3D中的碰撞检测
      以上是我所掌握碰撞方法,可能还有更多吧。那么3D中的检测其实是2D的延伸,例如矩形检测变为立方体检测,圆形检测引申为球形检测,四叉树检测进化为八叉树检测。

      当然了,凡是有例外。逐像素检测方法在3D中没有相对应的方法,因为3D中的物体的表示最小单元是三角型而非点。其实也可以说逐三角片检测是逐像素检测的3D版,但毕竟是平面碰撞的检测,需要一定的计算公式,而不是与或一下就OK的。这里就不赘述了

    展开全文
  • 我花了一夜用数据结构给女朋友写个H5走迷宫游戏

    万次阅读 多人点赞 2019-09-10 23:27:18
    起因 又到深夜了,我按照以往在csdn和公众号写着数据结构!...而我答道:能干事情多了,比如写个迷宫小游戏啥的! 当我码完字准备睡觉时:写不好别睡觉! 分析 如果用数据结构与算法造出东西来呢? ...
  • 目前地图编辑器已经能够对地图图片进行切片...本节我们最终要达到的目的是利用这些切片自适应的填充游戏窗口区域,即用最少的地图片实现窗口的无缝填充,从而提升游戏的整体性能。教程示例游戏窗口模式时的窗体尺寸为80
  • 为未来企业而定义

    千次阅读 2014-03-04 13:14:58
    为未来企业而定义一、未来企业到底应该是什么样?我把企业简单粗暴分为:农业、制造业、零售与服务业。而世界也是经历了:农业从牛犁耕种到现代大农业、制造业从手工作坊到现代大工业制造、零售业从小卖店到商超MALL...
  • 在这篇文章里,我们一起学习了在OpenCV中如何定义感兴趣区域ROI,如何使用addWeighted函数进行图像混合操作,以及将ROI和addWeighted函数结合起来使用,对指定区域进行图像混合操作。 PS:文章末尾提供了博文配套程序...
  • 外挂,辅助,游戏,QQ连连看,各种入门外挂,最近女朋友都在玩游戏,晚上还不理人,刚好近期对游戏辅助制作很感兴趣,打算搞个辅助让她失去玩游戏的信心,所以在接下来的几天里逛了一些论坛、教程。
  • //先按照目前的移动方向来进行贴图坐标修正,并加入人物往上移动的量(每次按下一次按键移动10个单位),来决定人物贴图坐标的X与Y值,接着判断坐标是否超出窗口区域,若有则再次修正 switch...
  • 我们开发一个App的时候, 通常希望它在 iPhone, iPad, Mac上同时能运行, 尤其是游戏。这样就需要我们考虑不同设备不同的分辨率,但处理起来比较麻烦,比如说,按照官方的做法,我们需要提供诸如 ifiero@1x,ifiero...
  • 转自浅墨毛星云:http://blog.csdn.net/poem_qianmo/article/details/53240330...我们知道,游戏行业其实一直很缺一本系统介绍游戏编程进阶技巧的书籍,而《游戏编程模式》的出现,正好弥补了这一点。之前已经有提到过
  • 3D游戏引擎剖析

    千次阅读 2015-02-21 10:11:09
    这种模块化,可伸缩和扩展的设计观念可以让游戏玩家和程序设计者深入到游戏核心,用新的模型,场景和声音创造新的游戏, 或向已有的游戏素材中添加新的东西。大量的新游戏根据已经存在的游戏引擎开发出来,而大多数...
  • 提要 游戏需要分享才能获得快乐,想想你以前玩过的那些游戏,那些会是真正地存在你婶婶的脑海里?是独自一人躲在被窝里酣战PSP,还是和哥们在网吧一起开黑?是一个人单刷迅龙三连,还是和朋友联机怒刷黄黑龙? ...
  • [Unity3D]Unity3D游戏开发之塔防游戏项目讲解(上)

    万次阅读 多人点赞 2015-01-07 00:41:40
    作为2015年的第一篇博客,博主首先想要感谢各位朋友的鼓励和支持,在新的一年里,博主将努力为大家分享更多、更好的游戏开发方面的原创技术文章,希望大家能继续关注和支持博主的博客。那么,今天博主想和大家分享...
  • C语言字符串定义

    千次阅读 2013-07-02 13:24:21
    #include #include int main() ... //错误,s指向的字符串是存放在不可以更改的内存区域,更改s指向单元的任何数据都是不允许的。 s++; // s1++;//错误,表达式必须是可修改的左值 // s1 = s1 +2;
  • 本系列文章由zhmxy555编写,转载请注明出处。 http://blog.csdn.net/zhmxy555/article/details/7390624作者:毛星云 邮箱: ...在那个物质并不充裕的时代,一台配置并不高的电脑,一款名叫《仙剑奇侠传》的游戏
  • 游戏框架搭建

    千次阅读 2018-03-14 14:18:03
    1创建TetrisGame类 首先贴出游戏界面图: 上图是俄罗斯方块游戏的主界面,从图中我们可以分解出以下部分: 游戏主界面边框 游戏方块显示区域 游戏方块显示区域网格线 ... 游戏分数显示区域...
  • Unity 3D 网络游戏架构设计

    千次阅读 2018-04-12 10:41:41
    本课程是 Unity 3D 系列教程,目标是带领读者搭建一个商业游戏的网络架构设计,该架构设计是游戏的核心技术,将采用 Unity 2017.2 最新版本作为开发工具。内容分为 UI 架构、技能架构、服务器和网络同步四大部分,共...
  • canvas游戏学习笔记(三)--打砖块

    千次阅读 2017-03-16 20:13:14
    写在前面用scratch和processing都写过打砖块,所以在逻辑上比较熟悉,区别在于编写代码的方式做了些改变。只要能正确理解JavaScript的语法,打砖块还是很快就可以写好...定义游戏区域//定义游戏区域 var myGameArea={
  • 游戏开发中的程序生成技术

    千次阅读 多人点赞 2016-10-09 11:26:51
    简介PCG是程序生成游戏内容的简称,它使用了随机或者伪随机数的技术,给游戏带来了无限的可能。相比于传统的由设计师将游戏世界中的一草一木都精心配制,PCG的方法是去配置一些生成的规则,然后由生成算法自动去生成...
  • 游戏开发入门(三)图形渲染

    万次阅读 多人点赞 2018-01-17 22:15:34
    视频链接:游戏开发入门(三)图形渲染(3节课 时常:约2小时20分钟) 视频链接:一堂课学会shader——渲染拓展课程(3节课 时常:约1小时20分钟) 笔记与总结(请先学习视频内容): 游戏开发入门(三)图形...
  •  http://blog.csdn.net/zhmxy555/article/details/7496200作者:毛星云 邮箱: happylifemxy@qq.com 欢迎邮件交流编程心得我们可以毫不夸张的说,在当今的任意一款成功的3D游戏引擎中,物理建模都是非常核心的...
  • 游戏开发 | 基于 EasyX 库开发经典90坦克大战游戏

    千次阅读 多人点赞 2019-06-14 04:32:30
    EasyX图形库介绍、 、游戏初始化与地图设计 、坦克初始化与控制(热键控制实现) 、子弹定义和初始化 、敌方坦克初始化与坦克选路算法 、子弹运动与碰撞检测 、游戏结束控制
  • C语言—实现扫雷游戏(注释详解)

    千次阅读 多人点赞 2021-06-04 22:56:51
    扫雷是一个十分经典的游戏,一张棋盘中有很多个不确定是雷还是安全区域的格子,当点击之后若没有雷则会在该区域显示周围八个格子雷的数目,若有雷则游戏结束。今天我就跟大家分享一下如何用c语言实现初阶版扫雷。 二
  • OpenSim :介绍和定义

    千次阅读 2009-10-02 11:07:00
    OpenSim :介绍和定义28分钟前OpenSim :介绍和定义简介和定义OpenSimulator OpenSim项目是一个可扩展的平台,可以模拟虚拟的三维空间。这些虚拟空间可以允许用户动态创建,修改,删除,和执行脚本的原始物体。其中...
  • C++游戏引擎开发

    万次阅读 2009-02-06 10:18:00
    游戏引擎开发用C++实现跨平台游戏引擎开发 你是否梦想写一部格斗游戏但却无从着手呢?是否你只因游戏开发好玩而对之感兴趣?本文我们将分析一个通用的跨平台游戏引擎,每个游戏开发新手都可以自由地使用它。 1. 3D...
  • Python开发接水果小游戏

    万次阅读 多人点赞 2016-03-13 19:32:22
    我研发的Python游戏引擎Pylash已经更新到1.4了。现在我们就来使用它完成一个极其简单的小游戏:接水果。游戏操作说明:点击屏幕左右两边或者使用键盘方向键控制人物移动,使人物与水果接触得分,碰到非水果的物品,...
  • Unity游戏制作,本篇文章介绍了复刻皇室战争玩法的一个自制小游戏,共有该游戏的两万字教程加制作过程,欢迎品尝! 世界上没有绝对的公平,如果我们起点就比别人第一步,那就更需要比别人努力了。
  • pyQT实现自动找茬游戏

    千次阅读 2015-06-19 21:18:53
    找茬游戏地址[美女大家来找茬] 游戏窗口探查 下载PyWin32库(对windows接口的Python封装)http://sourceforge.net/projects/pywin32/,但不能直接点Download图标,不然下下来是一个Readme.txt,点“Browse All ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 51,762
精华内容 20,704
关键字:

区域游戏定义