分类 "游戏宝鉴" 的存档.

如何成为一名游戏数值策划

在这之前你首先要知道自己到底想成为哪一种策划。所谓术业有专攻,如今随着游戏本质的不断提升,游戏策划岗位的划分也越来越细。在上第一课前,先问自己一个问题:“我知道游戏策划一共能分几种吗?”

下面看看你的想法是不是也和我一样吧。游戏策划根据工作内容一般分为创意策划、系统策划、文案与剧情、数值设计,但是有些更大型的项目还会细分到关卡设计、道具设计以及角色设计等等。根据不同的游戏类型策划的种类也可能有所不同,更让你吃惊的是一些女性向的育成游戏里还会有专门的服装设计策划!

“创意策划”,这 个名字听起来酷酷的很有趣,相信很有玩家都对这个工作很感兴趣,然而创意策划工作起来一点也不轻松.创意策划的主要工作在游戏的前期,游戏的原型就是靠创 意策划来设计的.如果游戏是一个蛋,创意策划就是下蛋的母鸡了,而其他策划是负责喂食照顾的人.一般来说,鸭子生不出鸡蛋(^-^),所以创意策划的设计 方向会完全决定游戏以后发展成什么样。创意策划有些时候也会兼任主策划或者制作人这个职责,负责统筹全局.

系统、文案策划在创意策划提出了最基本的概念之后就开始运作了。一个游戏该怎么操作?角色的背景?有多少类道具?有没有攻城战?这个时候这些问题就会慢慢 开始浮现出来,并且由这两种策划去解决。当然这些工作不是无序的,也需要经过一个成长的过程,开始阶段甚至要和创意策划互相交流争论以修改一些无法实现的 概念。

很 多朋友对数值策划都很感兴趣,可能是因为名字听起来就比较高端.其实数值策划在游戏策划里也是一个必不可少的位置.尤其在RPG游戏中.但是我要说的是, 数值策划其实一点也不好当,因为数值策划需要考虑的东西太多太多,而且影响当整个游戏的平衡度和可玩度.那么,一个数值策划应该具备的基本知识有哪些呢?

1.数学是基础,尤其关于概率学;

2.逻辑推理能力,有些时候你需要创造一些公式;

3.统筹掌握全局的能力,尤其是大型游戏;

4.细致缜密不烦躁的良好品质;

5.对数据库的掌握和熟悉。

举个简单的例子,下面这个这是最简单的人物伤害模型:
HP – 伤害 = 剩余HP(当剩余HP<0的时候,人物死亡)
但一般的游戏都会引入护甲的概念,比如:
HP – (伤害 – 护甲) = 剩余HP(伤害 – 护甲最小值>=0)
引入护甲的意义是:人要是穿着一件钢板甲,被砍一刀受到的伤害总比裸体被砍要少。
如果再复杂点,人要是除了护甲外,还拿着一块坚硬的盾牌呢?那公式可能就变成:
HP – [伤害 – (护甲+盾牌护甲)]= 剩余HP
…等等

如果再推演下去,还要增加偏斜率,躲闪率,武器与护甲克制,相性克制等等,甚至引入魔法攻击。关于伤害目标的所有复杂的公式最终目的是什么?只有一个:目标什么时候死?
HP / DPS = 死亡时间(DPS = damage per second,每秒伤害。如果是回合制游戏则是DPR,每回合伤害。)

这就应验了前面所说的那句话,很多时候这种公式都是由数值策划创造出来的.这样一个简单公式就可以解决问题,但为什么数值策划们要千方百计的想出许多古怪复杂的公式呢?

“游戏里的公式并非为了解决问题而存在”

所有存在于游戏里的复杂或者简单的公式,都是被创造的。模拟现实,增加策略性,提高或者降低难度,这些都是创造公式的原因。如果我们要创造一个公式,那我们必须产生以下的想法和讨论,具体请看下图.只有被这一系列过程所论证并证实确实可行,才算是成功的例子.

许多规则,公式,使游戏变得复杂而具有策略性,但往往这些变化的根本原因都是满足玩家的需求。作为数值策划并不是一个数学家,必须要明确玩家需求才是数值设计的根本。

========—– —–========

现在放出一个WEB2.0概念的模拟网游的策划概要,首先介绍下游戏的特点:游戏主题是虚拟人生,玩家可以在游戏中工作、赚钱、买房子、家具;可以开公司 当老板,经营自己的事业,最终的成为富翁,成家立室。游戏的特点是玩家在游戏当中可以充当多个角色,就好象在现实世界中,我们是可以工作,是雇员,下了班 之后,我也又可以消费,是消费者,当玩家发展到一定的程度,积累有足够的金钱之后,还可以开公司,成为雇主。
下面是游戏构架图:

========—– —–========

上面WEB2.0概念的策划案先放一放,先给大家看看目前MMORPG类型的一些游戏的策划解决方法,先来讲一下现在基本所有游戏中通用的经验值的设定,然后讲下游戏中的核心,经济系统的设定.

由于目前大多的的游戏采取的方案仍然是以长时间在线来获得更高的级别,因此在经验值方面仍应采取递进的方式让玩家获得经验值。而这个递进的数值既不能太大 也不能太小,太大会造成级别差距太大,使得玩家升下一级越来越过于困难。造成玩家的厌恶心理。而太小则会使总经验缩短,造成游戏寿命缩短。

因此我们可以抛开当前级别的经验进行累加,以级别的增长进行。设计公式如下:

经验值计算公式:

N=当前级别 EXP=30(N*N*N+5N)-80

该 公式完全与当前经验值无关,是以级别为主要值进行的累加。而数字30则是关键值。如果日后测试发现级别增长速度过快或过慢,只需要调整该数字就可以对经验 值进行调整。并且幅度呈三角型。级别越高则经验幅度差别越小。而在低级别时由于升级速度很快变化基本察觉不出来。对于我们进行调整也是非常方便的。

我们可以通过经验值划分表来查看这种明显差距。

以等级90级为例:
========—– —–========

通 过上表我们可以看出,当关键值为30时,总经验已达到近5亿。在初期的级别里,由于经验值获得速度快,所以即便3级是2级经验值差距的3倍多,对于刚进入 的游戏者,以及简单得怪物来说,获得的速度依然很快,一天升数级也没问题,因此游戏者根本感觉不出来。因为游戏者在对比时通常以上一级别进行对比的。

而在最后的90级经验值却只比89级经验值多了30分之1,差距非常小。这样的增长使得游戏者也很难察觉。因此采取该公式即可以增加游戏的寿命(经验值的总值),也不会让游戏者感觉到一级比一级的难升(各级别之间的差距)。

下面将关键值减少到20,在看一下1~90级经验的划分。这样可以更清晰的与之前的经验划分表进行对比,看看关键值缩小后的改变。

N=当前级别 EXP=20(N*N*N+5N)-80

========—– —–========

通 过上表可以看出,总经验降为3亿,升3级的所需经验达到了升2级所需经验的7倍,而90级所需经验依然比89级多30分之1.因此证明该公式对于经验值的 平衡性调整是非常有效的.无论游戏中设计总级别是60级还是200级,无论需要的总经验是1万还是10亿,均可以通过该公式调整游戏者的升级速度.可以说 该公式适合任何需要升级的游戏.

关于该公式最后的减80.这是一个微调数值.为什么要设置此数值呢,我们依然通过公式来看.

将公式的关键值改为10

N=当前级别 EXP=10(N*N*N+5N)-80

玩 家为1级,需要升到2级时 公式为 10(1*1*1+5*1)-80=-20 我们可以看到如果关键值过低,那么会导致经验值变为负数,这时就需要调整最后的微调数值,将它调低。同时,通过修改微调数值也会对各个级别的经验产生影 响。例如将初期各级别之间经验值的数据缩小等等。

关键值进行宏观上的调节,微调值调节微观部分,这就是该公式在等级与经验提升上的作用。

有人说我的策划还是摆脱不了泡菜,我只想说2点,1、目前我刚刚写了一点点数值,数值与是否泡菜无关,传统的MMO升级还是需要的,即便EQ WOW 也都还是需要升级,只是升级的方式与难度降低罢了。怎么升,如何升才是关键。2、有人拿WOW来说,在我看来WOW的副本RAID和传奇的枯燥打怪没什么 区别。和数值就更没什么关系了,数值应比较的还是数值、更加合理的数值。而不是拿游戏性来比较。

杀怪获得的经验值:

公式:获得的经验值=怪物经验*怪物等级/玩家等级

这个公式非常的简单,目前大多游戏也都使用这个公式,好处就是根据玩家的等级来判定杀怪获得的经验,级别越低,杀死高级别的怪物获得的经验越高,级别越高,杀死低级别怪物获得的经验就越低。

举个例子:60级的怪物,假设怪物经验值为3000 分别被40级和80级的玩家杀死。

3000*60/40=4500经验 3000*60/80=2250经验

注: 如果服务器条件允许,可以在最后获得经验中随机乘0.80~1.20之间的任意一个数字,这样就可以设置经验不是固定不变的,而是每次都不相同。甚至可以 与技能相关联,如果最后一击为暴击杀死,则经验翻倍。 或者设置成带有刺激和挑战的模式吸引玩家,比如一小时内连续杀怪时第10、50、100……个怪的时候这个怪的经验也是10、50、100倍。总之这是一 个简单的设置,想怎么设随心所欲。

关于怪物的经验。通常来说怪物经验就是游戏者与怪物同级别时杀死怪物所获得的经验值。一般都是预先设置 好的,可以简单的设置成1,2,3级怪物经验为5,10,15。也可以像前一章一样设置一个怪物的等级经验值提升。或者完全没有关联的给每个等级的怪物一 个经验值。只是这样进行后续的开发时略微麻烦一些。

BOSS级怪物设置特殊的、更高的经验值,这个就看各个游戏的不同设置了,无须在讲。

组队经验值分配数值

不 考虑自己个人做任务的经验,只考虑组队杀怪时的经验值分配,关于这点我们需要考虑很多,现在很多游戏的组队经验值分配都是平均分配或者根据伤害不同来分 配。所以很多游戏中玩家都喜欢单练。级别越高越是如此,这样造成的结果就是游戏中的交流减少。新人没人带。换来的后果就是新人都喜欢去新区,老区没有新鲜 血液的加入。最后不断的合区,在开新服。

我们可以通过人数的增加来增加经验获得的百分比解决这个问题。

例如游戏中最高可6人组队。

人数对应的经验百分比:

2人打怪获得的经验值为%120

3人打怪获得的经验值为%140

4人打怪获得的经验值为%160

5人打怪获得的经验值为%180

6人打怪获得的经验值为%200

个人获得的经验值=(杀死怪物获得的经验值/小队等级总和)*个人等级

例:5人组队,级别分别为:30、33、45、50、60。杀死等级为60级怪物(假设60级怪物经验为3000)。

结果为:{3000*60/[(30+33+45+50+60)/5]*1.8}/(30+33+45+50+60)*个人等级=

30级玩家获得经验:1023

33级玩家获得经验:1125

45级玩家获得经验:1534

50级玩家获得经验:1704

60级玩家获得经验:2045

这样根据等级获得的经验值非常公平。组队就变的更有意义,而玩家之间也乐于快速提高等级而去组队。高级的玩家则更乐于带新手玩家练级,因为带越多级别越低的玩家,则自己的经验值就会越高。

依然通过2个直观的例子来看看。

例1:60级的角色与一个10级的角色组队。在60级左右的区域杀怪。(假设60级怪物经验为3000)

{3000*60/[(10+60)/2]*1.2}/(10+60)*个人等级=
10级玩家获得经验:882

60级玩家获得经验:5290

例2:60级的角色与4个10级的角色组队。在60级左右的区域杀怪。(假设60级怪物经验为3000)

{3000*60/[(10+10+10+10+60)/5]*1.8}/(10+10+10+10+60)*个人等级=

10级玩家每个获得经验:1620

60级玩家获得经验:9720

通 过以上2个例子我们可以看出,60级角色杀死60级怪物只有3000的经验,而与一个10级的角色组队就达到5290的经验,与4个10级的角色组队就达 到9720的经验。对于这样优厚的经验补偿,我想是每一个高级的玩家都乐于去组队的。并且该公式所达到的目的是让经验呈曲线上升,根据每个角色的等级来判 定应该获得的经验。使得无论哪个级别的角色都能获得最大的利益。

同时,公式中的百分比也可以根据游戏需要而修改,可以是%200也可以是%300.但注意一定要根据组队的人数按比例修改就可以了.

FAQ:

1、如果玩家通过自己建数个小号与自己大号组队给自己升级,怎么办?

答:可以通过限制组队等级来解决,比如等级差距30级以上不能组队。这样使得玩家之间的组队划分更加明晰。中级玩家会去带低级玩家(25~30级带1~5级),高级玩家会去带中级玩家(55~60级带25~30级)。

2、那玩家把自己的小号都练到和自己相差30级左右的等级,怎么办?

答:不可能,因为根据经验值计算公式和组队经验分配公式的设计,组队练级不可能达到同步的速度,级别越低的玩家组队升级的速度越快,而级别越高的玩家则越慢。

3、玩家如果将小号通过死亡的方式保留在和自己相差30级左右的等级,怎么办?

答:可通过限制死亡方式来进行,例如设计成死亡只掉当前经验,不会掉级。另外如果游戏采取正常的月卡、点卡模式收费,这种方式反而会使玩家得不偿失。玩家如果采取这种方式必然是自愿的,而运营商的利润则会增加。

4、假设6人组队,也就是在1个高级别的玩家带5个所能带的最低级别玩家时经验的获得最高。一个服务器中很难同时找到这么多适合的级别。玩家是否会因此不习惯于组队?

答:不会,根据组队经验分配公式的设计,只要带比自己级别低的玩家,就是有经验补偿的,只是多少不同。例子中的60级带5个10级的玩家的组队情况只是展示在获得经验最大化的状态下。随便什么状态下组队都是有利的。即使同级别的组队,也会因为人多加快了打怪速度而组队。

5、升级速度这么快会减少游戏寿命,玩家很快都达到当前版本的最高等级怎么办?

答:不会,因为经验值计算公式的关键值和微调值对经验&等级部分的处理,使得总经验完全可以配合怪物经验值和组队经验值公式来进行最适合的调整。

6、玩家不愿意组队有着很多的原因,比如打钱,打装备,打材料,不仅仅是因为升级的问题。

答:的确是这样,因此我们需要把每个数值都做好。至少按以上数值制作时,玩家不会因为升级问题而不肯组队。

多人&多队攻击同一怪物经验分配.

多人多队攻击同一怪物是指没有组队的玩家打同一个怪物的情况。部分游戏采取了按最先攻击的人分得经验、按最后一击的人分得经验,或者干脆就是被攻击后的怪物 若干时间内他人无法攻击等等模式。这些都是不可取的。按最先攻击的人或最后一击的人分得经验,容易造成恶意抢怪的发生。而被攻击后怪物若干时间内他人无法 攻击的模式虽然可以避免抢怪,但对于掉宝的BOSS等怪,却又形成了抢怪,只是抢夺的对象从经验变成了物品。
所以针对抢怪似乎没有最好的解决办法。采取分配才是最公平的办法。

公式:单人&单队获得经验=怪物经验/(怪物总血量/单人&单队造成伤害)

这个公式只能说对于能者多得的方式来说最公平,但假设高级玩家去低级区域恶意抢怪,那依然是不公平的。

如果服务器条件允许,可设置最后一击奖励,最后一击杀死怪物者额外获得怪物总经验的%5。

死亡造成经验损失:

这 一点每个游戏都有不同设置,有的游戏借此限制玩家升级速度,也有的游戏完全不设置死亡惩罚。这里就不多说了。我的观点是在服务器稳定的情况下,被玩家杀死 不造成经验损失,被怪杀死损失经验当前级别至下一级别所需经验的X%。经验值减少的极限是到当前等级所需经验值。即永不低于当前等级所需要的经验值。(不 会掉级)。

这样的好处是不会被恶意PK杀死后掉经验,而在服务器稳定(不会卡)的情况下一般不会被怪杀死,如果被怪杀死说明玩家的技巧不 够或者挑战和自己级别装备差距太大的怪了。而设置了不会掉级的处理,则是为了玩家去击杀BOSS级怪或挑战自身极限的设置,一般这种情况死亡频率较高,玩 家会在刚升级时去进行。即便不在刚升级时去进行,也不会因死亡次数太多导致的级别降低而丧失对游戏的信心。

Flash社交游戏开发攻略(转载)

引言:
如今SocailGame火爆的如日中天,AS也是热得滚烫,农场、餐厅以及花园类游戏的火爆使得很多人都打起了开发Flash游戏的算盘。作为公司项目主管,我就负责了两个项目的开发,一个是餐厅,另一个是花园。
这里是游戏的链接(非广告,大家可以只看截图不点连接)
接下来我会以这两个游戏开发的过程为范例,给大家分享一些在游戏开发方面的经验以及一些比较不错的技术解决方案
花园游戏:
餐厅游戏:
基础控制流1:
本来有很多想说的,可一旦真的写了,突然发现无从下手,不过这个感觉就和我第一次开发游戏一样,因此,我就从新手最容易碰到的问题上来说吧。
很多游戏开发新手在这个问题上都会很迷茫,究竟如何去控制整个FLASH游戏?
比如,我们要做一个横版飞行游戏,那么我们会设想敌机在空中飞,那么这个飞行肯定是有速度的,新手一般会想到使用Timer类来实现每隔几秒就让某个元件按照一个数学函数来移动一下位置。这的确是最直观的方法。
不过这么做有两个坏处:
1 每个Timer如果都是独立的,那么则无法从全局上控制流程,比如让所有飞机都停止。如果是使用一个Timer来处理,那么对很多速度不相同的飞机就无法很好的控制。
2 我们不能保证每次Timer过后,显示出来的结果都是我们预期的,因为FLASH有相对固定的帧数,比如帧数是每秒1帧,如果你Timer设定10毫秒就移动一个像素,那么你看到的仍然是1秒移动了100像素,但是系统却运算了100次。
因此我们使用在Flash中更为实用的控制流方式,ENTER_FRAME事件作为控制流的入口。这样做的好处如下:
1 只有在进入下一帧的时候才会做出相应运算,也就是说,所有内容的运算次数是最为优化的,因为你的眼睛只能看到每一帧的内容,因此我们只要在每帧的时候进行相应的改变就可以了
2 使用恰当的方式,我们可以由上而下的让所有的类都相对独立,但又可以受到上一级的控制,可以随时暂停任何一个你想暂停的内容。
那么接下来我们就举一个例子看一下实际的代码:
在文档类的构造函数中书写如下代码:
stage.addEventListener(Event.ENTER_FRAME, tick);
lastTickTime = getTimer();
lastTickTime 是个私有属性。
新增加一个私有函数tick
private function tick(e:Event) : void
{
var timer:int = getTimer();
var timespan:int = timer – lastTickTime;
lastTickTime = timer;
tickBase(lastTickTime);
}
这个tickBase函数的作用是,对需要被控制的对象执行相应的控制代码,具体内容大家可以根据需要自己写。
我们需要知道的就是,tickBase可以传2帧的时间间隔给你所需要控制的对象。因为虽然FLASH中我们设定了固定的帧数,但实际帧数会根据运算情况而有所改变,所以我们要使用这样的方式计算两帧的间隔时间。
好了,大家可以尝试一下,或者休息一会,之后我会写如何去更好的实现这个tickBase中的内容,以达到对你想要控制的内容进行控制

基础控制流2:

上一次讲了如何使用Enterframe做基础控制流的入口,我们使用了Timer配合,使得我们可以在每次进入下一帧的时候获得两帧之间的时间。
现在我们讲讲如何通过tickBase方法进而去控制程序中需要控制的每一个类。
这次我们用《梦想花园》中的花的成长控制作为例子。
如果每一朵花都需要自动的生长,我们应该怎么做呢?没错,我们要使用tick,在每次进入下一帧的时候调用tick函数,传入时间间隔timeSpan,累加这些时间,并且判断花朵是否应该长大,从而更换花朵的状态。
为此,我们写了一个新的类BaseObject类,该类继承Sprite类,该类有个方法addObject代替addChild方法(但是我们不重写addChild,因为也有用到的时候),addObject只能用于添加另一个BaseObject类,该方法的作用是,将另一个BaseObject类的实例化对象加入该类中的一个数组里,作为他的“子”,并且在tick方法中调用它所有“子”的tick,而他所有“子”还将调用“子”中“子”数组的tick方法(如果有这么多“子”)的话。
可以看一下这个类的构造函数和addObject方法以及tick方法
public function BaseObject(mc:MoviClip) : void
{
baseObjects = new Array();   //  baseObjects 是私有属性
content = mc; //  content 是私有属性
}
public function addObject(baseObj:BaseObject)
{
objectsToAdd.push(obj); //objectsToAdd 是私有属性    用于存储需要添加的对象(这是一个临时的数组,当执行了addAndRemoveObjects()方法以后,objectsToAdd才会真正被添加到baseObjects数组中)
}
public function tickBase(timeSpan:uint) : void
{
for each(var i in baseObjects)
{
i.tickBase(timeSpan);
}
tick(timeSpan);
addAndRemoveObjects();//这个方法下次会讲到,很重要,功能是在tick的时候处理把对象添加到显示列表中
return;
}
public function tick(timeSpan:uint)
{
//该方法没有实现是个抽象方法,子类继承该抽象方法以完成tick中的内容
}
虽然这个类没有完整的书写(还不能用),但是现在已经可以对其功能进行了解。任何继承这个类的子类都会有tick方法,一旦使用了addObject方法添加其他继承该类的对象,只要在上一讲中的ENTERFRAME中,调用一个“父”级的BaseObject中的tickBase,就可以自动的传入所有该对象中的所有“子”级对象中的tick。
一旦这么做,我们可以先建立一个BaseObject对象land,然后使用land.addObject(flowe)方法,不过flowe对象也得是BaseObject的实例,然后在EnterFrame事件中执行land.tickBase()方法,在每次进入下一帧的时候,flower就会执行自己的tick方法,于是在tick中我们可以执行计算时间并选择播放花朵的那段动画的过程函数了。
这样的好处是,即便对象之间的关系很复杂(比如land中有grass(草地),grass里有pot(花盆),pot中有5个flower),我们仍然能自由的去写每一个类的tick方法,他们可以有自己的处理方式,然后只要通过addObject方法来添加他的子级就可以达到预期的效果了。
好了,下次我们再说BaseObject中的具体实现方式,现在只要了解我们是如何控制我们的对象的就可以了。

上一次讲了如何使用Enterframe做基础控制流的入口,我们使用了Timer配合,使得我们可以在每次进入下一帧的时候获得两帧之间的时间。现在我们讲讲如何通过tickBase方法进而去控制程序中需要控制的每一个类。这次我们用《梦想花园》中的花的成长控制作为例子。如果每一朵花都需要自动的生长,我们应该怎么做呢?没错,我们要使用tick,在每次进入下一帧的时候调用tick函数,传入时间间隔timeSpan,累加这些时间,并且判断花朵是否应该长大,从而更换花朵的状态。为此,我们写了一个新的类BaseObject类,该类继承Sprite类,该类有个方法addObject代替addChild方法(但是我们不重写addChild,因为也有用到的时候),addObject只能用于添加另一个BaseObject类,该方法的作用是,将另一个BaseObject类的实例化对象加入该类中的一个数组里,作为他的“子”,并且在tick方法中调用它所有“子”的tick,而他所有“子”还将调用“子”中“子”数组的tick方法(如果有这么多“子”)的话。可以看一下这个类的构造函数和addObject方法以及tick方法 public function BaseObject(mc:MoviClip) : void {        baseObjects = new Array();   //  baseObjects 是私有属性   content = mc; //  content 是私有属性   }
public function addObject(baseObj:BaseObject){        objectsToAdd.push(obj); //objectsToAdd 是私有属性    用于存储需要添加的对象(这是一个临时的数组,当执行了addAndRemoveObjects()方法以后,objectsToAdd才会真正被添加到baseObjects数组中)        }
public function tickBase(timeSpan:uint) : void {         for each(var i in baseObjects) {                    i.tickBase(timeSpan); } tick(timeSpan); addAndRemoveObjects();//这个方法下次会讲到,很重要,功能是在tick的时候处理把对象添加到显示列表中        return; }
public function tick(timeSpan:uint){ //该方法没有实现是个抽象方法,子类继承该抽象方法以完成tick中的内容}
虽然这个类没有完整的书写(还不能用),但是现在已经可以对其功能进行了解。任何继承这个类的子类都会有tick方法,一旦使用了addObject方法添加其他继承该类的对象,只要在上一讲中的ENTERFRAME中,调用一个“父”级的BaseObject中的tickBase,就可以自动的传入所有该对象中的所有“子”级对象中的tick。
一旦这么做,我们可以先建立一个BaseObject对象land,然后使用land.addObject(flowe)方法,不过flowe对象也得是BaseObject的实例,然后在EnterFrame事件中执行land.tickBase()方法,在每次进入下一帧的时候,flower就会执行自己的tick方法,于是在tick中我们可以执行计算时间并选择播放花朵的那段动画的过程函数了。
这样的好处是,即便对象之间的关系很复杂(比如land中有grass(草地),grass里有pot(花盆),pot中有5个flower),我们仍然能自由的去写每一个类的tick方法,他们可以有自己的处理方式,然后只要通过addObject方法来添加他的子级就可以达到预期的效果了。
好了,下次我们再说BaseObject中的具体实现方式,现在只要了解我们是如何控制我们的对象的就可以了。

基础控制流3:

前段时间比较忙,所以一直没有更新,不好意思。废话不多说,继续我们的控制流第三讲

这次主要讲两个内容:1、完善插入对象以及移出对象功能

2、在合适的时候更新显示列表,并更新层深

1插入以及移出对象

我们设想,因为程序需要,我们要在场景中插入一个对象,但又因为其他因素,这个对象需要立刻被移出,而这之间的间隔非常小,小到在两帧之间,这个时候我们就没有必要将该对象真正插入到显示列表中(这种情况虽然不多,但是也有可能出现)。

对应public var objectsToAdd:Array(待添加对象数组) ,我们在建一个数组  public var objectsToRemove:Array(待移出对象数组)

然后我们在addObject方法中判断该对象是否也在objectsToRemove出现,如果有,则从objectsToRemove数组中移出该对象,否则,添加到objectsToAdd数组中,removeObject方法也如法炮制。代码如下:

public function addObject(obj:BaseObject) : void

{

var removeIndex:int;

if (obj)

{

removeIndex = objectsToRemove.indexOf(obj);

if (removeIndex != -1)

{

objectsToRemove.splice(removeIndex, 1);

}

else if (objectsToAdd.indexOf(obj) == -1 && baseObjects.indexOf(obj) == -1)

{

objectsToAdd.push(obj);

if (obj.parent != null && obj.parent is BaseObject)

{

BaseObject(obj.parent).removeObject(obj);

}

}

}

return;

}

移出代码就不写了,也是一样的。这么一来,最终我们得到的肯定是一组真正要被添加到显示列表中和要从显示列表中移除的对象数组。注意,无论怎么添加和移出,尚未进入下一帧之前,显示列表中的内容都是没有变的,不会对显示性能造成影响,同时baseObjects中的内容也不会发生变化,通常baseObjects中的内容总是与现实列表中的内容保持一致。

以上两个方法为我们将要在tickBase中的方法addAndRemoveObjects()做了铺垫,那么接下来我就讨论一下addAndRemoveObjects()方法的作用于特点。

一旦大家开始做AS3游戏了,很快就会发现一个大问题,那就是层深的控制。as2中层深控制看起来可能比较简单,设置层深的深度就可以了,但这在AS3中被ADOBE取消了,可能是因为性能的低下。取而代之的是使用addChildAt方法来替代,这样一来,我们可能不能很直观的去控制我们想要控制的层深。因此我们使用addAndRemoveObjects()方法结合我们自己新建的属性drawPriority(绘画优先级)来控制层次之间的关系。这样的好处就是:无论一个场景内有多少东西的层次要发生变化,我们只会在进入下一帧的时候对这些对象排序一次。

public function addAndRemoveObjects() : void

{

var obj:BaseObject;

var index:int;

var isPrio:Boolean;

if (objectsToRemove.length > 0)

{

for each(var i in objectsToRemove)

{

obj = i;

index = baseObjects.indexOf(obj);

if (index != -1)

{

baseObjects.splice(index, 1);

}

if (obj.parent != null && obj.parent == this)

{

removeChild(obj);

}

}

objectsToRemove.splice(0, objectsToRemove.length);

}

if (objectsToAdd.length > 0)

{

for each(var k in objectsToAdd)

{

isPrio = false;

for (var j = 0; j < baseObjects.length; j++ )

{

if (k.drawPriority < baseObjects[j].drawPriority)

{

addChildAt(k, getChildIndex(baseObjects[j]));

baseObjects.splice(j, 0, k);

isPrio = true;

break;

}

}

if (!isPrio)

{

baseObjects.push(k);

addChild(k);

}

}

objectsToAdd.splice(0, objectsToAdd.length);

}

return;

}

简单的解释一下代码,一共做了两件事:

1 把放在objectsToRemove数组中的对象从baseObjects中移出,同时从显示列表中移出,清空objectsToRemove

2 根据objectsToAdd中的每个对象的drawPriority属性,将对象按顺序插入显示列表以及baseObjects数组中,清空objectsToAdd。

这样,我们就完成对该对象内的所有“子”的排序与显示层深的重置。并且每帧最多只会排序一次。

好了,现在我们的BaseObject类已经完成了一大半。拥有了添加删除对象的两个方法,并且会将帧间隔时间传入该对象的子对象以及该对象的子对象的子对象以及……(太多了不写了)中,并且拥有属性drawPriority,只要改变该属性,在进入下一帧的时候就会改变显示列表中的层次关系。

接下来,我们只要继承这个类,就可以轻松的控制每一个对象的行为(只要在类中重写tick的方法即可),以及轻松的控制他们层次关系(由于只在帧间隔的时候做,即不会在肉眼上有延迟,且执行效率也会比较高)。

大家可以动手写一个这样的类,然后去用用看,记得在文档类中调用最外层的BaseObject对象的tickBase函数,然后尝试着做添加,删除,修改层深以及覆盖tick方法的操作,这样一来,我们的基本控制流的结构就差不多了。

接下来我们会讲到如何用反射(Reflection)来简化以及模块化开发。

反射的应用:

反射(Reflection),是众多面向对象语言都有的功能,不过我在论坛上常常看到有人不理解反射的用途,甚至有人说反射没有任何用处。今天我不讲反射究竟有多少可挖掘的地方,我们就着我们的游戏内容,来讲一下反射的现实应用,很多东西,不用你是不知道的。

我们来到花园游戏,花园游戏中有许多的花,很多很多,估计有108种,并且一直在不断的增加。大家都知道,花肯定是flash做好的元件,因此我们要去库里面取,从库里面取也有很多种方法。最简单的就是在库里面元件中的属性点导出,然后在程序里就可以去实例化这个类了。比如你导出类的名字是Flower1, 那么在程序里你就可以这么写:var flower=new Flower1();然后就可以将这个类的实例添加到屏幕了。那么,如果真的这么去做游戏,你就会发现很多问题:1、每次新增加一朵花,我就不得不修改程序,重新新增加一个类的实例化过程。2、如果你想要把主程序和这些资源文件分离的话,你甚至都无法正确编译你的程序,因为在主程序中,并没有Flower1这个类。

所以我们使用了反射,在这种情况下,反射的作用相当于用字符串来取代类名,正因为他是字符串,你可以不顾及这个类当前存不存在。

好了我们开始做了,我很简单的讲一下我们的做法,我们把花的元件全部放到一个单独的fla中,每个花都有单独的导出,从Flower1到Flower108,然后生成flower_asset.swf,我们在主程序中加载这个swf文件,与此同时我们加载一个XML配置文件(具体如何加载我之后会详细说明),总之我就是加载,并且读取到了每个id的花所对应到的导出类名(比如id为20001的红玫瑰花对应的元件的类是Flower1),现在我要显示出某个用户的所有的花,比如这个用户有个花朵的数组,数组中有如下数据[20001,20002,20001,20003],那么我们就做一个循环,实例化所有的FlowerXXX的类,然后添加到舞台上。

我们先在文档类中添加一个静态方法:

public static function getMovieClip(mcname:String) : MovieClip

{

try

{

var mcClass:* = Class(getDefinitionByName(mcname));

if (mcClass == null)

{

return null;

}

return new mcClass;

}

catch (e)

{

return null;

}

return null;

}

注意我使用了try,这是为了系统的稳定性,即便因为一些特殊原因,我们取到了一些错误或者过期的数据(比如有一朵花现在已经在我们的库中删除了,但是因为策划疏忽而没有删除),我们也可以良性地处理这个问题而不至于flash报错。

然后我们在程序中就可以调用这个方法,根据从配置文件中取到的类名的字符串,调用getMovieClip方法去库中取到相应的元件。几乎在我们所有涉及到显示的类中,最后都有调用到该方法。

反射的好处在于,动态的决定了主程序将要调用的类,比如一个玩家的场景中只有5种花,我们就没必要让程序导入108个花的类(从性能上来说,反射其实是有待考究的,因为动态的东西通常比静态的慢,但是这其中的性能的得失很复杂,大家可以再讨论。)就从开发以及维护的角度来说,尽量减少编译以及版本发布的次数,让程序更容易被“定制”总是比将代码写死在程序中要好得多。

RPG风格的文本系统——Make an RPG-Style Text System for Your Next Game

在这个教程里面我们将要创建一个文本系统,它跟你见过的大多数角色扮演游戏的文本系统是一样的。我们的类要动态显示说话角
色的图标。并逐个字母的显示每块对话的内容。

最终结果的展示

下面这个例子就是我们将要制作的文本系统
RPG Style Text System (46)

第一步:建立你的flash文件
创建一个新的flash文件(actionscript 3).根据你的游戏具体情况设置影片参数,在这个演示中我设置影片大小为500*300,黑色

的背景,30帧/秒

第二步:添加背景图片
你通常会把游戏中的文本模块放到图像或动画的上面,在这个演示里我只用了一张图片把站在雪景里的所有需要的角色放到了一起。把背景图片放到单独的一层里面起名叫做“background”

第三步:创建RPGText影片剪辑
在舞台上创建一个新的影片剪辑(插入〉影片剪辑)命名为“RPGText”.在这个元件属性里面购选“Export for Actionscript”设

置类的名字为“RPGText”. 我们将使用这个类名链接代码和影片剪辑。设置完成单击ok。如果你看到这样的警告“定义的类可能没

有找到”。那是正确的,它的意思是还没有任何代码链接到这个元件上。
给你的影片剪辑实例命名为“rpgText”记住,当我们谈到大写的RPGText时,它表示的就是类或者是影片剪辑,如果是小写的

rpgText,那么它就是类的一个实例。

第四步:添加文字面板背景
在新建的RPGText影片剪辑里面画一个矩形。用它作为人物图标和说话框的背景。你也可以根据自己的嗜好来设计它,但必须保证它

的宽度能横跨整个影片并且高度大小不能覆盖太多的游戏空间。
我设置的大小是500像素宽(匹配我的影片)100像素高。我用从#666666到#999999(从暗灰到亮灰)的过渡色填充

快速提示:画一个特殊尺寸的矩形,选择矩形工具按住Alt键单击舞台你会得到一个对话框,可以输入矩形的尺寸。

第五步:角色图标的影片剪辑
在RPGText影片剪辑新添加一个层,命名为“icon”。创建一个新的影片剪辑命名为“characterIcon”并在icon层添加这个影片实例“characterIcon”。

并在新建的characterIcon影片剪辑里面创建两个新层“icons”和“labels” 。icon层包含所有的角色图标(一个图标对应一个关键桢)而labes层设置帧标签,设置帧标签的目的是为了准确定位角色。

导入(或者是在flash里面绘制)每个将要说话角色的图标,在这个演示里我做了75*75像素的jpg图片,把它们添加到图标层。为每一个新的角色做一个关键桢。他们的显示顺序并不重要。但必须保证每个图标的位置在x:0,y:0。因为只有这样做,当角色切换的时候才不会有跳动的感觉。

第六步:添加帧标签
现在为labels层创建关键帧。快速的方法是选择这些帧按F6键。
为每个关键帧添加显示在该帧上对应角色的名字。如果你添加一个空桢(F5),它可以很容易让你读取你的关键帧的内容,但是必

须保证你的标签在对应图标关键帧的上方。

而且必须保证每个标签的命名不一样。如果你有两个角色的名字是一样的你需要区别它们(例如:(‘John_L’ 和 ‘John_K’ )

第七步:画一个说话框
回到RPGText影片剪辑,创建一个新的层叫做“textBackground”。
画一个说话框。我用一个圆角矩形简单画了一个,你可以根据自己的需要做。让它充满大部分的背景。在角色图标的旁边。
选中你的说话框转换成为影片剪辑(修改->转换元件),现在他变成了影片剪辑,我们可以给它添加投影滤镜。我设置我的投影滤镜黑色,50%强度,5像素的模糊(blur),1像素的距离。

第八步添加动态文字区域
在RPGText中创建一个新的层叫做“text”,使用文本(text)工具在该层上画一个文本区域。让它的大小刚刚符合说话框的大小。
设置这个文本区域为多行,定义实例名为“txt”。记得嵌入字体。我使用的是13pt courier

第九步添加“下一个”按钮
我们需要一种方式让玩家在阅读完当前文字后能看到下一段文字,让我们在拐角处添加一个“下一个”按钮。
在RPGText上创建一个新的层叫做“button”添加一个新的按钮元件叫做“b_next”.给你的按钮设计四种状态。我使用的是一个下标的小箭头,因为我在很多的游戏中看到它,把它放在对话框右侧的角落里。不要担心给它一个实例名。我一会在解释

第十步创建一个文档类
创建一个新的actionscript文件叫做“Main.as”添加代码
package {
import flash.display.MovieClip;
public class Main extends MovieClip {

// CONSTRUCTOR
public function Main() {
}
}
}
在flash里面设置Main为文档类,如果你想快速重温文档类的使用方法,我认为威廉斯迈克尔的一片快速提示的文章是最好的选择。

第十一步添加文本区
如果你在开发游戏的时候,你会把文本区的处理放到别处,但是现在我要把它放到文档类里面。在Main类的构造方法里面添加这些代码
var textBlocks:Array = new Array(
["Kid",     "Look, a robot!"],
["Abe",     "BLEEP-BLOOP. I am an Autonomous Botanical Engineer. You can call me ABE."],
["Kid",     "Hi Abe. Meet Frosty the Snowman."],
["Frosty",  "Happy Birthday!"],
["Abe",     "BEEP-BIP. Hello, Frosty."],
["Abe",     "Does this frog belong to you?"],
["Frog",    "Ribbit..."],
["Kid",     "No I've never seen him before. Aren't you cold frog?"],
["Frog",    "Ribbit..."]
);

rpgText.textBlocks = textBlocks;
这里我们创建了一个二维数组(一个数组嵌套另一个数组)保存我们场景中用到的语言脚本。在你改变任何东西的时候,注意一下

它的结构,每个数组是一个分开的文本内容
它包含两个元素。第一个是角色的名字,第二个是角色要说什么。这个文本区列出的是在场景中出现的次序。
最后一行仅仅是把textBlocks数组赋值给rpgText影片实例。(记住,rpgText是影片剪辑RPGText的实例名)。更多的稍后再说。
来吧,编辑这段代码让它符合你自己的需要。但要格外注意的是角色的命名和你characterIcon影片剪辑里面的帧标签是否对应

第十二步创建RPGText类
我们终于开始写RPGText类的代码了
创建一个新的actionscript文件命名为“RPGText.as”,添加代码如下
package {
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.MovieClip;
import flash.media.Sound;
public class RPGText extends MovieClip {
private const SPEAKER:int = 0;
private const TEXT:int = 1;
private var _currentTextBlockIndex:int=0;
private var _currentTextBlock:String;
private var _textBlocks:Array;
// CONSTRUCTOR
public function RPGText() {
}

}
}
这仅仅是一个基本的类结构。它不能做任何事情。但是我们先来看看他都包括了什么
前几行,导入我们需要的类库
定以这个类扩展自MovieClip类。我们需要这么做的原因是这个类要链接到库里面的RPGText影片剪辑。
我定义了两个常量,SPEAKER和TEXT,用他保存textBlocks里说话者的名字和内容
_currentTextBlockIndex变量追踪当前的显示对象
_currentTextBlock获得实际的文本内容
_textBlocks持有整个文本的数组
最后是一个空的构造器

备注:我是用带下划线的变量名表示私有变量

第十三步textBlocks设置方法
由于我们的_textBlocks是私有的所以我们需要一个方法把它传递出去,好让我们在Main类里面设置这个文本区域。我们创建一个

setter方法。添加在RPGText类构造方法的下面
public function set textBlocks(txt:Array):void {
_textBlocks = txt;
}
setters最酷的是可以通过方法把一个私有属性变成一个公共属性。我们在Main类的第11行是这样做的
rpgText.textBlocks = textBlocks;

第十四步添加updataText方法
在RPGText类里面添加这个方法
private function updateText(e:Event):void {
if(txt.text.length < _currentTextBlock.length){
txt.text = _currentTextBlock.substr(0, txt.text.length+1);
} else {
removeEventListener(Event.ENTER_FRAME, updateText);
fillText();
}
}
这个方法是这个类的核心功能,让我们自己看一下这都发生了什么。
第27行这个方法接收一个事件作为参数我们通过ENTER_FRAME事件调用它
第28行我们比较txt和_currentTextBlock里面的字母的数量
第29行如果是txt里面的字母数量少,则我们用substr方法获得从0到txt字母数量这么多个,把截得的字符串添加到文本区域里面实

现字符逐个添加到文本区域里面的效果
第31行如果txt和_currentTextBlock的字符数量相同则移除ENTER_FRAME监听事件。
第32行调用fillText方法。我们下面将要实现这个方法。

第十五步添加fillText方法
在RPGText类里面添加则个方法
private function fillText(e:MouseEvent = null):void {
txt.text = _currentTextBlock;
if(_currentTextBlockIndex < _textBlocks.length-1){
addEventListener(MouseEvent.CLICK, nextTextBlock);
}
}
这个方法的主要目的是通过变量_currentTextBlock(37行)填充文本区域。我们让updateText方法来控制动画的播放,这样做可以确保不会出现问题。我们也可以把方法挂靠到“next”按钮上,让玩家掠过文本动画直接显示文本内容注意这个方法接受一个鼠标事件作为参数,我们把它默认设置为null。我们可以让这个方法作为鼠标事件监听器,因为它可以接收事件,因为我们可以给事件一个默认值,当然我们也可以不需要发送任何事件直接在updateText方法的结尾处调用当我们填充完文本区域后,我们检查是否到达文本块所在数组的末尾。(看_currentBlockIndex是否小于_textBlock数组元素的个数 )如果没有到达末尾,我们就添加一个单击事件监听器调用nextTextBlock方法继续显示文本。

第十六步关于单击监听器
还记得开始在我们创建“下一步”按钮的时候,我曾经说个不要着急给这个按钮起实例名吗?你注意到最后一步我们如何链接单击监听器给RPGText 影片剪辑代替按钮?这样做可以让游戏者单击影片的任何地方继续。我们其实根本就不需要这个按钮,但是我觉得有这样一个按钮可以给用户一些提示内容。当然这也仅是个人意见。如果你想给这个按钮一个名字并且给它关联一个单击事件也没有问题,但我觉得让它有一个大一点的单击区域使用会很方便。

第十七步添加nextTextBlock方法

返回来,在RPGText类里面添加方法
private function nextTextBlock(e:MouseEvent):void {
removeEventListener(MouseEvent.CLICK, nextTextBlock);
txt.text = “”; // clear the text
_currentTextBlockIndex++;
_currentTextBlock = _textBlocks[_currentTextBlockIndex][TEXT]; // set the text
characterIcon.gotoAndStop(_textBlocks[_currentTextBlockIndex][SPEAKER]); // set the character icon
addEventListener(Event.ENTER_FRAME, updateText); // start updating the text
addEventListener(MouseEvent.CLICK, fillText);
}
起始的三行很简单。移除鼠标移动事件监听,清除文本区域,增加_currentTextBlockIndex变量值并指向下一个文本块
第四十七行用TEXT常数从_textBlocks数组中取出当前的文本字符串给_currentTextBlock赋值。下一步我们用SPEAKER常量获得当前角色的名字。因为在characterIcon影片剪辑里面角色的名字和帧的标签是匹配的。我们可以用gotoAndStop设置角色图标显示的位置。最后我们添加一个事件监听器开始键入新的文本字符串并且增加一个单击事件执行fillText。

第18步添加startText方法
我们已经做完所有的一切,那么剩下要做的事情是添加一个方法让所有的一切开始。我们定义一个公共方法“startText”,因为他是一个公共方法,让我们把它放到RPGText头部,仅在的textBlocks设置器的下方
public function startText():void {
_currentTextBlock = _textBlocks[_currentTextBlockIndex][TEXT];
characterIcon.gotoAndStop(_textBlocks[_currentTextBlockIndex][SPEAKER]);
addEventListener(Event.ENTER_FRAME, updateText);
addEventListener(MouseEvent.CLICK, fillText);
}
是不是似曾相识?这个代码和nextTextBlock方法做的事情几乎相同。它设置当前文本和角色图标,为updateText和fillText添加监听事件。因为这个方法仅仅在文本第一次显示的时候使用,跟nextTextBlock方法不同的是我们不用清除这个文本区域或者是增加_currentTextBlockIndex。

第十九步调用startText方法
现在我们有一个公共方法可以让对话开始了。那么接下来我们把它放到程序里面。
在Main类构造方法的最底一行添加rpgText.startText();
这里仅仅是调用RPGText类的startText方法。这样所有的一切就开始了。

第二十步添加声音
你可能要测试一下你们的影片,看看它们是不是都已经正常工作了。那么现在就差一件事情了没有做了那就是声音。为文本显示的时候配一个声音,我们可以找一个或者是单独做一个。我们必须保证选择的声音比较短小,因为这个声音随着文本显示循环播放。一个小的“boop”或者是按钮单击的声音是最好的。
导入声音文件到flash文件,勾选 “Export for Actionscript” 给它一个类名字叫做“TypingSound”

让声音播放我们只需要在RPGText类里面添加两行代码。首先我们需要声音的实例。在类的开始其它三个私有变量的下面添加
private var _typingSound:Sound = new TypingSound();
现在我们向下找updateText方法添加一行让每次文本更新的时候都播放声音。
private function updateText(e:Event):void {
if(txt.text.length < _currentTextBlock.length){
txt.text = _currentTextBlock.substr(0, txt.text.length+1);
_typingSound.play();
} else {
removeEventListener(Event.ENTER_FRAME, updateText);
fillText();
}
}

第二十一步谈一下它的未来
上面做的所有工作,的仅仅是为这个例子服务的。如果你想把它整合到游戏里面,那么你好有好多工作要做。
首先,依据你自己游戏建立对话方式,你肯定会把文本块脱离文档类单独拿出来。你可能有一个场景类,用它来管理人物语言或者是一个字符串类保存所有对话。
其次,你可能会思考,文本模块在什么时候,怎么样出现在你的游戏中。你可能会添加一个过渡动画来实现如在对话开始和结束的时候让它在底部滑入滑出。你也可能想监听当对话结束后隐藏文本模块或者开始下一段对话。
因为我们已经校验最后的文本块是否进入fillText方法。所以你可以很容易添加对话结束时的处理方式。在这个教程里面并没有包含这些话题,因为你要根据自己的游戏去做这些事情。这些已经足够帮你开始做了。
我希望你能喜欢这篇教程!留下你的评语让我知道你们的想法。

网游审批流程图

繁杂的网游审批流程图:

敏捷开发应用于游戏原型创作

本文是于 2005 年时发表的文章,虽然距今已有三年以上的歷史,但绝对无损这篇文章的价值。同时,本文也与极具创意的优秀独立游戏作品《World of Goo》,有非常深的渊源以及关连性存在。
Kyle Gabler、Kyle Gray、Matt Kucic 与 Shalin Shodhan 是四位就读于卡内基美隆大学 (Carnegie Mellon University) 研究所的学生。在 1 个学期的时间内,他们仅仅凭藉着 4 个人的力量,完成了超过 50 个游戏的原型 (Prototype)!同时他们也设置了一个名为 Experimental Gameplay Project 的网站发表他们所制作的各款游戏原型;其中最受欢迎的游戏之一,就是由 Kyle Gabler 所制作的《Tower of Goo》,而这个游戏原型也正是《World of Goo》的前身作品! Gamelook|www.gamelook.com
为了达到 1 个学期之内完成 50 款游戏原型作品这项近似于不可理喻且不可思议的目标,他们将自己锁在房间内,遵循着以下三项规则进行开发工作:

  1. 每个游戏必须在 7 天以内完成。
  2. 每个游戏必须从头到尾以 1 人之力完成。
  3. 每个游戏必须立基于一般常见的主题,例如「重力」、「植物」或「群聚生物」等等。

7 天,1 人,以及 1 个主题,制作成为一个游戏原型作品。许多人好奇他们是如何在这么短的时间内完成一款游戏原型作品?又是如何能够维持上述规则与纪律,长达一整个学期之久?在此,四位作者共同将这段过程中所学习到的各种宝贵知识,包括正确以及不可行的尝试经验沈淀整理之后,分为准备、设计、开发以及游戏性四个项目,一一阐述如下:

准备:敏捷,是一种意向的状态
敏捷式原型开发 (Rapid Prototyping),不只是前置开发时期的有用工具,同时也可以是一种生活方式。从思维层面出发,首先要使自己的心理状态合乎「敏捷」的原则,才能够真正成为一位敏捷式的原型开发者。首先,第一步就是要乐于拥抱失败的可能性。
优秀的原型开发者能够瞭解,失败是一件可以接受的事情!而那也正是建立原型的目标,所以请大胆地放手去做吧!万一最终真的失败了,也能够从其中的过程学习到某些具有价值的经验,并且唯有藉由拥抱失败的可能性,才有可能得到有所回报的实验结果。在 Gray 所制作的《Mime After Mime》与《A Mime to Kill》中,很大胆地只使用位置性音源 (Positional Audio) 做为游戏性 (Gameplay) 的来源;虽然这两个游戏是全然失败的作品,但也充分证明了仅有音源游戏性的游戏设计概念,是一项不可行的作法。
坚持实行极短的开发週期,是另外一项快速开发的诀窍。开发者们似乎会很自然地说:「嘿,既然我们在 1 週内完成了一个很棒的游戏,那么如果我们花费 2 週的时间进行开发,我们将会得到一个 2 倍优秀的游戏作品!」事实上,他们发现一般来说,任何游戏性都能够有效地在一週内完成原型建置;额外的投入时间,总是会倾向于产生功效递减的结果。举例来说,有些原型仅仅花费一个晚上的时间组装完成,另外有些原型则使用了额外的一两週时间,而令人意外的是,每个原型所花费的时间,与其最终获得的成功程度,并没有相互关连性存在。
在他们所制作出的成功游戏原型中,多数都是出自于特定的主题,他们曾经探索过的主题包括了「重力」、「弹簧」、「演化」、「声音」、「植物」与「平衡」等等。不知为何,当有限制存在的时候,反而会更易于产生创意。另外,与团队成员同时进行某个特定主题的原型开发,某种程度上也能够避免着力于太过显而易见的游戏性,迫使所有人都必须接受挑战,探索在这个主题下的所有游戏性的可能性。如果缺少了稳固明确的主题限制,游戏将会花费更多的时间创造,同时也会减损团队的方向目标,以及那种使他们能够挤压出最后一滴创造能力的友善竞争感。
每一位团队成员都必须能够独立面对并且负责游戏开发的所有面向,包含程式、美术、声音以及其他所有将成为最终成品的必需品。但是个人的才能并不代表一切,团队成员必须体认到对于这种形式的开发方式来说,「设计」才是至高无上的准则,而包括程式、美术与所有其他的东西在内,都只是为了服务最终的设计结果而存在。一位不具备这种思维的优秀工程师,将不会比完全瞭解这项关键要点的平庸工程师来得更为成功。
当团队建立完成后,他们就开始停止与其他成员一起工作。「啥?这样还算是团队吗?」听起来或许有些奇怪,但是四个人分头并进,同时开发四个游戏原型的「不协同合作」方式有许多益处,像是减少风险(四个成品中至少会有一、二个成功的结果)、友善竞争感、更广阔的主题探索(避免设计出与别人相同的游戏性),以及相互分享彼此所习得的知识。在每个原型开发週期的起始和最终阶段,团队合作是最具有价值的期间,而当进入开发期之后,与其他成员一起工作只会造成分心,而无法产生良好的正面效应。
设计:创意以及脑力激盪之谜

A great idea takes a split second to happen, but waiting for that lightning to strike can be excruciating.8only.cn||创见未来

Tower of Goo
一个伟大的点子或许能够在须臾间诞生,然而等待那个被闪电击中的时刻,却会使人备受痛苦与折磨。各位应该都听过「脑力激盪」(Brainstorming) 这项引导创意思考的团体活动吧!他们曾排定脑力激盪的会议,尝试过各种不同的创意发想方法,最后在他们开发出来的全部游戏里,没有任何一个作品是出自于团体脑力激盪议程所得出的成果,这实在是一项相当令人挫败且震惊的结论。经过许多次的研究后,他们终于发现了一个事实:「你就是无法为创意安排时程。」(You just cannot schedule creativity.) 你不能够说:「嘿,我们的计画是在 4:15 时开始脑力激盪,然后到了 5:00 我们就能得到 4 个超杀的绝妙点子,然后就可以开始动手了!」不过,脑力激盪至少能够使团队开始进行思考。
做为脑力激盪方式的替代方案,他们发现蒐集美术与音乐材料,特别有助于创造出具有情感的目标。以《Tower of Goo》为例,这个游戏背后的想法,源自于当 Gabler 听完了《Tango Apasionado》(电影《春光乍现》的主题曲)曲目后,在回家的路上,想像在某一个小镇里,当太阳下山时,镇上的每个人都扛起他们的桌子椅子或其他东西走出房子,为了某个神秘的理由,试图堆叠建立起一座高耸入云的巨塔,毫不停歇地向上攀升;然而这些居民并不是很称职的工程师,所以玩家必须协助他们建造这座高塔。
为了制作出令人喜爱的游戏,你必须先在脑海中想像玩家们会如何发出「哇!」的赞嘆声,然后依循此目标由后向前进行各项设计与开发。对于他们多数充满乐趣且获得成功的游戏作品,其实并不是令人感到意外的结果。在最佳的状况下,甚至在动手开始写任何一行程式码之前,就已经能够明确知道游戏点子是否稳固,因为他们已经事先在自己的脑袋中运行了模拟与实验的程序。没有任何游戏是偶然或者意外成功的;在见到成品之前,最终的成果早已昭然若揭。
开发:没有人知道你如何达成,也没有人会在乎
当你想出了绝妙的游戏点子后,接下来就是要在很短的时间内开发出游戏原型。首先,必须从核心机制 (Core Mechanic) 开始着手动工,建造出一个「玩具」;所谓的玩具,应该是核心机制减去任何游戏层面的实质目标或决定。不论该原型的核心机制是弹簧系统、生物群聚行为、重力机制或者其他系统,最多只会花费几个小时的时间就能够建构完毕。玩具并不存在赢或输的状态,只是一个有趣且可供玩耍的小玩意儿。先打造出玩具,能够让开发者及早确认核心游戏性的稳固性,并且找出设计层面的潜在问题。
在专案的进行过程中,他们所学到最重要的一课就是:「正确」的解决方案,通常不是最佳的解决方案。策略性地使用伪装的方法,能够节省你的时间与金钱,使你能够更快速地制作游戏。当你能够使用可预先制作的贴图,就不要设置复杂的光影系统;当你能够使用同样的效果蒙混过去时,就不要设置样式辨识系统以分析图画;当你能够延展点阵图达到相同且快速的效果时,就不要画出 Spline 曲线或者制作向量图形函式库。请大方而且经常性地进行伪装吧!
刚开始进行游戏原型开发时,每个人都会有种想要拯救所有东西的渴望:「只要再多花一点点时间与努力,一定能够把一个本来很糟糕的游戏,转变成一个很棒的游戏成品!」然而事实并非如此。以「弹簧」主题的游戏原型为例,起先是以一个非常精巧的弹簧系统做为出发点,结果到最后反而无法使这个核心机制转化成为一个优秀的游戏。对于原型开发者来说,必须要能够迅速辨认出已走入死路的游戏点子,然后断然地终止花费于其中的损失,继续朝着下个目标前进。比起花费时间试图拯救既存的程式码,保持开发时程的纪律与自发性更为珍贵。
如果原型的游戏性差劲透顶,就不会有復原的方法,即使放入了许多精美而丰富的内容物,也无法使游戏获得救赎。所有的美术、音乐以及衍生物内容,都无法使糟糕的游戏性最终转变成一个优秀的游戏,玩家很快就会发现在精美的图像与动人的旋律中,其实游戏本身并没有深厚的内涵,而不过是虚有其表而已。虽然如此,但游戏的整体美学仍然很重要;一个经过仔细琢磨的游戏,的确能够使一个好游戏变得更具可玩性,提供给玩家更好的游戏体验。
需要再次提醒的是,一位优秀的工程师未必能够成为一位优秀的敏捷原型开发者。「正确性」或「復用性」的解决方案通常不是敏捷原型开发者所追求的目标。面对每个问题与挑战,敏捷原型开发者必须要能够想出一堆解决方案,并且从中选择最快的方法。如果陷入了过度工程 (Over-Engineering) 的迷思当中,最终成果很容易产出一般性的工具或是技术展示程式,而非一个真正可玩的游戏。请记得:对于游戏原型的最终使用者们来说,他们从不会看见你的伟大工程技术,而且他们也不会在乎。
游戏性:官能领域中的「多汁」乐趣
复杂的东西未必具有乐趣。如果人类能够在几千年来,以各种不同的「球与平面」发展成各种球类运动来娱乐我们自己,那么游戏开发者或许在游戏乐趣的尝试上太过于努力了。想想《Tetris》、《Pac Man》以及其他的经典机台游戏,我们完全有可能使用基本的元素制造出丰富的游戏乐趣。透镜炫光、凹凸贴图以及其他新技术很不错,但它们并不能使游戏变得更加有趣。请先向你自己证明,以一个简单的游戏原型来说,你的核心机制的确具有价值存在;当你确信之后,就可以更进一步地装扮它,使它变得更加闪亮动人。 8only.cn||创见未来
On A Rainy Day
在不断发想、制作以及发表原型作品的过程中,他们发现具有最高可重复游玩价值的游戏,是那些拥有某种创造或客制化层面的游戏,例如像是「用手和雨伞制造出一棵怪树」、「画出你的房子」、「建造你的高塔」或是「演化你的变异种族生物」等等游戏目标。只要提供给玩家独一无二的「所有权」感觉,就能够使他们满足而且想要获得更多的乐趣。实验并不意味着复杂。在这项计画的早期,他们所制作出来的游戏,往往远超过这些游戏应有的复杂度;不只是使用者介面令人困惑,而且按键对应至行动的方法也不够自然直觉。
对于以撰写程式为乐的人来说,请记得如果没有游戏性的目标,游戏原型就只是个「玩具」,而不是个「游戏」。所谓的游戏性目标,可以是任何东西,例如:在 X 时间内蒐集 Y 个元件,或是保持系统的稳定度,或是通过这个空间并且不触碰任何阻碍物体等等。而他们发现最佳的游戏目标,是如同《Tower of Goo》中与生俱来的游戏性,也就是不断地向上建造高塔而已。
最后,请记得让游戏看起来很「多汁」(Juicy)!「多汁」所代表的意思,就是游戏中接连不断而且丰富的「使用者回应」(User Feedback) 设计。当你碰触它的时候,多汁的游戏会跳动、摇动、喷射并且发出一点声响,让玩家感觉它像是活生生的,而且会对于你所做的每件事情做出回应。使用者回应是游戏中比较微小却非常关键的要素,能够让玩家感觉自己是真正掌控着游戏世界中的一举一动,并且藉由每次的互动行为,训练玩家逐渐熟悉游戏所制订的种种规则。
进行敏捷式的游戏原型开发,就像是孕育自己的小孩一样,或许未必每次都会有美好的结果,但你总是能够从每次的经验中学到些什么新的东西。无论如何,这些过程与结果通常都非常好玩!整理以上四项内容,可以归纳出下列纲要:

准备:敏捷,是一种意向的状态

  • 拥抱失败的可能性——它会鼓励开创性的风险承担
  • 坚持实行极短的开发週期(更多的时间不等于更好的品质)
  • 限制创意能够使你渴求更多
  • 召集优秀的团队成员以及一位客观的顾问——思维与才能同样重要
  • 平行开发以获得最大化的成果

设计:创意以及脑力激盪之谜

  • 正式的脑力激盪程序只有 0% 的成功率
  • 聚集概念美术与音乐以创造情感化的目标
  • 在你的脑袋中模拟——前置开发你的原型

开发:没有人知道你如何达成,也没有人会在乎

  • 首先建立玩具
  • 在可接受的情况下使用伪装
  • 终止你的损失并且学习如何断然捨弃
  • 着重于游戏内容物无法救赎差劲的设计
  • 整体美学仍然重要——运用有益的美术、声音与音乐
  • 没有人会在乎你的伟大的工程技术

游戏性:官能领域中的「多汁」乐趣

  • 复杂未必代表乐趣
  • 创造出所有权的感觉使玩家想要获得更多
  • 实验并不意味着复杂
  • 朝向具有良好定义的目标建置开发
  • 让它多汁!

在游戏开发领域中,游戏原型究竟具有什么样的重要性?在投入庞大的资源与人力之前,如果可以预先进行游戏概念或者核心机制的原型开发,不仅能够早期验证游戏设计的良窳与否,更有机会大幅降低专案开发时期的风险,避免到了专案中后期时才发现「这种玩法根本不有趣」的残酷事实,而只能够将已完成的游戏机制整个砍掉,然后重新来过。由知名游戏制作人 Will Wright 所主导的游戏《Spore》,在开发时期中甚至制作了上百款游戏原型,用以验证游戏中的各项设计机制与表现细节。他们也很大方地在游戏的官方网站上公开其中数款游戏原型,让感兴趣的玩家自行下载。
Super Tummy Bubble
之前偶然从 Gamasutra 的档案库里翻出这篇数年前的好文章,我觉得自己实在是非常非常地幸运!由这四位作者共同发起的 Experimental Gameplay Project,竟然能够在短短的一个学期内,完成超过 50 款游戏原型。不仅要在极为紧迫的週期内,完成游戏原型的开发工作,同时又要兼顾忙碌的研究所课业,一般人可能在制作出几个作品后就会产生放弃的念头,他们却能够保持纪律持之以恆地进行这项专案,绝对是一项相当难能可贵的成就。如果我是教授游戏开发课程的教师,我一定也会很乐意依循着这样的模式与原则,指导学生们进行如此具有乐趣与学习效果的游戏原型开发专案!
对于身处程式设计领域的人来说,即使你不知道如何制作精美的图片、不懂得如何谱出美妙的音乐,但只要你的脑袋里装满了「做出来应该会很有趣」的游戏点子,不妨大胆地放手一试吧!「左手只是辅助,工具也只是辅助。」不论你熟悉的开发工具是 OGRE、SDL、Torque 或者自己打造的引擎,条条大路通罗马,这些工具全都能够协助我们到达游戏开发疆土中的美丽境界。而对于美术、设计或具有其他专业的人来说,即使你所擅长的技能并不是程式设计,但只要能够学习使用 Flash 以及简单的 ActionScript 语法,同样能够以简单的工具创造出令人赞嘆的游戏原型。
以前的我,总认为如果想要制作一款游戏作品,必定要经过非常缜密的规划与周全的设计,才能够真正开始着手动工:「一定要有一个非常棒的游戏引擎,一定要有一个前无古人后无来者的游戏点子,一定要有一份超级详尽的游戏设计文件,一定要……」有许许多多的先决条件存在,一定要满足了这些条件以后,才能真正开始动手撰写游戏。但是过度的顾虑与计画,最后反而成为理想实践道路上的最大阻碍。所以与其戒慎恐惧而迟迟不敢跨出一步,不如豪迈地向前跨出步伐,大方拥抱包括失败在内的一切未知性与可能性吧!
阅读完这篇文章以后,给了我很大的鼓舞与激励,我也已经下定决心,准备要开始动手尝试游戏原型的实验开发计画。你呢?Happy Prototyping! :)

游戏策划谈:麻将与《魔兽世界》

即使没有什么所谓的游戏专业知识,以大众的眼光来看,我们也可以分辨出,麻将、斗地主、星际争霸这样的游戏与传奇、偷菜,魔兽世界,存在一些本质的区别。

有人说,原因是策略性。

麻将的策略性体现在哪里?麻将是这么一个游戏,基本张数108张,一般四个人打,随机的得到前13张,然后不断的组成规定的组合,胡牌为赢。曾经有个作家说,中国人的劣根性就体现在麻将,卡下家,宁可我不胡,也不让你胡。这话本身意有所指,但麻将本身确实存在多种不同的策略,比如摊大牌,比如稳健的不点炮,比如能胡就胡,分别可能是稳健的,激进的,随意的各种打法。

如上文所述,麻将这个游戏形式,存在几个特点:
1、封闭性。最多就那么100多张牌,不存在突然掉出第5个九筒的情况。
2、同时在线。打麻将,必须四个人都在,一个人离开上厕所什么的,如果没有替补,大家就得等着。
3、同一起点。麻将不可能中间突然加入一个人,也不可能一个人前面没打,后面打。
4、以盘为单位。一局终了,总是有人输,有人赢,几家欢乐几家愁的。
5、不可积累。上把你赢了,这把你不会多点什么特别的优势,当然,如果赌博的话,钱是可以积累的。
6、单一目标。麻将,就是胡牌。

反过来看魔兽世界呢,定义我就不做多的说明,魔兽也有这么几个特点:
1、开放性。新的装备,新的技能,新的地图……转眼间,T1到T10。
2、非实时。我可以给你写邮件,你可以在拍卖所寄卖。
3、非同一起点。开服第一天你来玩,开服第1000天你也可以来玩,你还可以中间AFK了半年再突然回来玩。
4、无终点。曾经有个游戏叫无尽的任务,魔兽,真是无法定义个明确的终点。
5、可积累。所有的现在,都是无数个过去的累计,经验值如此,装备亦如此。
6、多目标。魔兽的目标,任务?成就?生活技能?竞技场?FD?……

这种种的特点,如果要列,还可以列很多,但这并不是我们今天讨论的重点。重点是,所谓的麻将和星际这类对抗性的游戏,其特点无疑是“策略”,那么策略究竟是什么?魔兽有没有策略呢?

先说小策略,麻将是一个四人游戏,模型更复杂,简单的以围棋这个二人游戏为蓝本分析下。围棋的根本,或许是老祖中说的,知己知彼,百战不殆。围棋中,一个最小的循环是什么?是预测对方的行为-我方采取行动-对方采取行为-检查己方预测与对方行为的差异,从而总结对方的模式。然后进入了下一个循环。按照游戏的说法,这是一个典型的回合制游戏。

注意,这里的策略,就体现得很明显,反过来说,所谓的对抗策略,需要满足几个特征:
1、对方的行为是可预测的。
2、己方的行动是可调整的。

而最没有策略的游戏是什么?以最熟悉的翻牌比大小,你摸一张牌,我摸一张牌,同时翻开来,谁大谁赢。这样的游戏,既不可预测对方的行为,也不能调整自己的行动,于是就显得没有策略性,很不好玩。所以,对抗和竞技类的游戏,满足一些外围定义,比如封闭性,实时性,同一起点,明确终点,同时,更强调策略的2个基本特征-可预测和可调整。

接着说魔兽世界,在一场PVP的竞技场比赛中,你的职业,我的职业,不一样;你的天赋,我的天赋,不一样;我们的装备不一样;我们的技能和天赋不一样;我们因为在游戏中的时间不同,所以整体实力不一样。职业之间本来有相生相克,装备有好坏之分,实力有强弱之分,更遑论野外打仗,打着打着,来了个人帮忙,2打1,就更不一样了。所以,在局部,魔兽世界的竞技性和策略性是有限的。

但是,魔兽世界虽然缺乏局部的小策略,却又比麻将类的游戏多了个大战略:麻将要打得好,完全就是靠智慧与时间,一句话,熟能生巧。而魔兽,同样是练级,方法有很多:任务,杀怪,下副本,这些还是明显的,你还可以做生意,做生活技能赚钱,让别人带你血色。更何况,魔兽有无穷的目标,所以,衍生出无数的大策略,多花时间,少花时间,这样玩,那样玩,各个策略之间,差异十分明显。

行文至此,虽然意未尽,但且留到下文。总之到最后,佩服上帝是很公平的,配平很合理,短期策略越强的游戏,越缺乏长期策略;短期策略越弱的游戏,反而具备更丰富的长期策略选择。而你所开发的游戏,属于哪一种呢?你的游戏,好玩好玩在哪里呢?