Once upon a time
Once upon a time是前几天项目小组成员发过来,类似杀人游戏但比杀人游戏更好玩的多人游戏。这两天有空,用vs2005将游戏写成一个web游戏练练手,不过小组里相应平平,估计是在web上面玩的时候速度太慢(俺们的测试都在不停的催促“打字快点,打字快点”)。因此在网上玩了几次就搁浅了,准备周末到茶室大战几轮。现在在这里把游戏的实现过程以及源代码发布以下,有兴趣的网友可以找几个朋友周末聚聚玩玩这个游戏。
1.游戏的玩法:
游戏的名字叫做:Once upon a time,简称OUAT
OUAT首先需要的是至少一百张要素卡和五十张结局卡。要素卡上写的故事所必需或者不必需的一些要素:比如“国王”、“公主”、“魔法解除”、“意外的转折”等等。而结局卡上则写着各中各样的结局:比如“从此他们幸福地生活在一起”、“他回到了家里,和父母团聚”、“他们就一直那样跳舞,一直到现在都没结束”等等。
OUAT理论上需要2-无穷大的玩家数量,不过按照标准配置,是5名玩家。
1 首先所有玩家各自抽取一张结局卡和五张要素卡,抓在自己手里。
2 决定中断优先权顺位,通常是以讲叙者的右手边为最优先,然后逆时针排序。
3 上一轮的胜利者(如果是第一轮则通过骰子来决定)开始讲故事。
4 讲叙者需要以“很久很久以前”为第一句,任意发挥,讲一段故事。故事的长度不限,不过通常需要起码三段话。这段故事中,必须包含讲叙者手里一张牌上的要素。当讲完故事以后,讲叙者把用到的要素牌亮出来,放出牌堆,表明这张牌已经消耗掉了。
5 讲叙者讲完以后,其他玩家则观察自己手里的牌。如果讲叙者刚才的故事里出现了自己牌中的要素,那么就可以申请中断。然后所有中断者按照优先顺位依次亮牌。如果大家公认这张要素牌可以中断,那么讲叙者自己抓一张牌,中断者把中断用的要素牌丢入牌堆,继续(必须)接着讲叙者的故事讲;如果大家工人这张要素牌不能终端,那么中断者把这张牌丢入牌堆,另外抓两张牌;如果没有任何人表示中断或者中断全部失败,讲叙者可以继续讲故事。
6 每一段故事,只允许消耗一张要素牌。相应的,每一段故事,其他玩家也只有一次机会进行中断。
范例:
比如玩家A手里抓了“牧羊女”、“剑”、“受伤”、“失而复得”与“变形”
玩家B手里抓了“狼”、“谷仓”、“愤怒”、“朋友加入”和“死亡”。
A首先讲:“在很久很久以前,有一位【牧羊女】生活在一个幽静的小村庄,她很漂亮,大家都叫她弥塞娅。她的父母双亡,从小就是个孤儿,可是她一直很快乐地生活着,大家都喜欢她。”
然后A宣布这一段故事结束,并亮出自己的【牧羊女】牌,表明这张要素牌消耗掉了。
这时候,B宣布“中断”,然后亮出了【死亡】,宣称A的故事里出现了死亡的要素。
中断通过,因为A叙述里出现了“她父母双亡”。
A抓一张牌【决斗】,B则接着A的故事往下说(注:中断卡也被视做消耗掉了,不纳入故事情节)
B接着讲:她每天的工作就是放牧,可是,一直有一个困难困扰着她,因为森林里住着一只【狼】,经常潜入村子里来偷吃羊羔。
然后B宣布这一段故事结束,并亮出自己的【狼】,表明这张要素牌被消耗掉了。
A宣布中断,并亮出了【受伤】,并解释说狼偷吃羊,这应该有受伤的情况发生。
大部分玩家表示不同意,因为【受伤】并没有在B的故事里得到体现,有些牵强。于是驳回。A把【受伤】丢入牌堆,另外抓了两张。
(注:中断的合理性界定非常重要。一般来说,只能中断故事里明确提及到的要素,如未提及,即使可能有隐含的逻辑联系,也不能进行中断。比如A讲“他们结婚以后,六十年里他们一直幸福地生活在一起”B试图用【交配】来中断。尽管这六十年里他们可能H过,但是也可能没H过,A文没有提及,所以B驳回。而C用【夫妻】来断,【结婚】和【夫妻】有必然联系,因此中断成功)
7 当其中一位玩家把自己所有的要素卡都消耗光以后,就开始进入结局阶段。他必须承接先前的故事讲三段情节,每一段都停下来看别人是否要中断。如果这三段都无人中断,那么他亮出自己的结局卡,念出来作为结局。
(注:结局三段情节不需要出示卡片,但是讲叙者必须要把结局和前面的故事联系起来,构成一个完整的故事,符合逻辑。如果太过生硬,将会遭到全体玩家的反对,要罚两张卡)
8 游戏的目的就是尽快消耗掉自己的要素卡,尽量不被别人中断,首先讲完结局的人获得胜利。
以下是游戏中的经典事例:
A 这一次的故事,一开头就被转到了李世民和少林武僧,而大家手抓到的全是童话牌。轮到玩家甲讲,甲百般无奈,只好硬着头皮讲“李世民正打的兴起,却惊动了地下沉睡的上古巨人夸父,夸父一脚下去,把李世民踩成了肉酱。”然后亮出了自己的要素卡“巨人”。玩家乙随即宣布中断,并亮出了自己的要素卡【变形】。
“哪里有什么变形啊?”大家问。
“李世民变成了肉酱。”
“………………”于是全体通过。
B 这一次的故事讲到主角被巫师追杀,获得了一把神兵。玩家甲顺利地进入了结局阶段。他的第一段故事是:“主角不停地打不停地打不停地打,轰下了邪恶的巫师”。无人中断,狡猾的他一看这段话里的要素没人能中断,就采取邪恶的规避手段,故伎重演:“主角主角不停地打不停地打不停地打,轰下了其他所有邪恶的巫师”。这时候,玩家乙宣布中断。
所有的人都在猜想:“第一段和第二段的要素完全一样,什么卡能够中断第一却不能中断第二呢?”
这时候玩家乙从容亮出了自己的要素卡:【兄弟姐妹】
C 玩家丁抽到了一张【堂会】,这是我增补进去的。不幸的是,第一个讲故事的玩家选择了星战背景,于是在整个故事里,玩家丁就不停地在哀求别人。
“于是温度大师获得了自己的紫色光剑。”
“杰迪们得开个堂会庆祝一下吧……”
“别傻了!”(玩家丁灰溜溜地缩会角落里哭泣)
“西斯大帝轰下了所有的杰迪。”
“西斯们总得开个堂会庆祝一下吧……”
“别傻了!”(玩家丁灰溜溜地缩会角落里哭泣)
“you kill my father! No, I am your father!"
“父子相认,得开个堂会庆祝一下吧……”
“别傻了!”(玩家丁灰溜溜地缩会角落里哭泣)
一直持续到游戏结束,堂会还是没开成.。
2.开发思路
游戏采用B\S架构。后台使用asp.net 2.0 、c# ,前台用 asp.net ajax。
游戏基本流程:
游戏是多位玩家轮流说故事片段组成。第一位玩家先开始说一段故事,下一位玩家可以以手中的要素牌中断故事(要素牌与玩家说的故事剧情有关),或者放弃中断。
1. 如果所有玩家都放弃中断,则一开始说故事的那位玩家继续说故事。
2. 如果有玩家中断,则其他玩家需要判断中断的要素牌是否合适,
a) 如果合适则中断成功,中断玩家接着上面继续说故事,
b) 如果不合适则中断失败,中断玩家要受到惩罚,剩下的玩家接上面的故事判断是否中断还是放弃。
通过以上过程,一个故事片段就算完成了。在进行多次说故事片段后,哪位玩家手中的要素牌先用完,该玩家就获得游戏胜利。
游戏的流程流程图:
对象(类)设计:
整个游戏运行的逻辑都放在“游戏”类里,当玩家说故事、中断、故事判断、中断判断等操作的逻辑处理都放在该类里。
“玩家”类则存放玩家的信息,包括名字,要素牌等信息
“玩家队列”类继承自“循环队列”。循环队列的作用是把一个线性的队列首尾相连,形成一个环形队列。
“游戏状态”、“说故事状态”、“中断状态”则是根据游戏规则抽象出来的类。
a) 玩家列表里的玩家与“游戏”类里玩家队列里的玩家顺序一样,但是队列里的第一个元素则是发起该状态的玩家。
b) 玩家状态列表里保存当前状态里玩家的状态
I) 空:表示还没有轮到
II) 等待输入故事:这时玩家可以进行输入故事
III) 等待输入中断\故事是否合理:前面的玩家输入故事后,玩家可以进行中断,或者放弃中断
IV) 等待输入中断是否合理:前面的玩家进行中断后,玩家可以判断中断是否合理
V) 同意:表示故事里没有出现自己的要素牌无法中断\中断理由合适
VI) 反对:表示第一位玩家说的故事不合理,需要重新再说过\中断理由不合适
VII) 完成:该状态里第一位玩家(说故事的人\中断者)已经完成操作
前台设计:
界面设计:
因为游戏只是一个简单的Demo,所以,只是用Table简单的划分了一个田字区域:
游戏状态区域 |
游戏信息区域 |
玩家状态区域 |
玩家操作区域 |
1) 游戏状态区域:游戏里有多少玩家,每位玩家手上有多少张要素牌,以及当前轮到那位玩家进行游戏
2) 游戏信息区域:玩家说的故事、中断时候的理由、同意\反对故事(中断)的信息、玩家之间的聊天信息
3) 玩家状态区域:玩家当前手里拥有的要素牌
4) 玩家操作区域:轮到玩家进行时输入故事、中断、判断等操作界面
Ajax的使用:
整个游戏采用B\S架构,不像C\S架构那样客户端能够与服务器端一直保持连接,所以在取得游戏最新状态的时候就需要用到Ajax技术。因为是第一次开发Ajax的网站,所以用了Asp.net ajax的两种技术。
1) 使用客户端访问Web Service方法获得游戏状态以及游戏运行信息
客户端范围Web Service 的方法很简单,就是在<ScriptManager>里把Web Service的地址加进去,然后就想使用一个类一样直接调用Web Service 的方法就可以了。不过使用该方法的时候,在后台代码里无法获得Session 信息,因此如果需要从Web Service里获得针对某个玩家的信息,则还需要再传递一个自定义的SessionID 来帮助Web Service 判断当前访问是那个玩家发出的。当然因为这是第一次用Ajax,所以不确定是否还有其它能够知道当前范围Web Service的是那位用户,如果那位知道请告知一二。
2) 使用UpdatePanel + Timer获得玩家状态以及控制玩家的操作状态
在 UpdatePanel 里使用Timer控件,和在客户端调用Web Service方法很类似,都是定时的出发服务器端里某个时间。不同的是,客户端调用Web Service的时候,返回的是一个Xml(字符串),而Timer控件里就强大得多了,它不但能够获得用户的Session信息,还能够操作UpdatePanel里的控件。在游戏里玩家状态区域里会根据玩家当前的状态显示不同的操作Panel的功能,就是在Timer触发的事件里进行。3个不同的Panel的处理过程则和一般的Web页面事件处理过程一样