为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

网页游戏设计

2018-03-31 50页 doc 120KB 19阅读

用户头像

is_083599

暂无简介

举报
网页游戏设计网页游戏设计 webgame的设计思路(一)-- 服务器数量预估 ................................... 2 webgame的设计思路(二)--数据库表结构划分 ................................. 3 webgame的设计思路(三)--地图模块设计思路 ................................. 4 webgame的设计思路(四)-- 玩家数据的数据库设计 ....................... 7 webgame的设计思...
网页游戏设计
网页游戏 webgame的设计思路(一)-- 服务器数量预估 ................................... 2 webgame的设计思路(二)--数据库表结构划分 ................................. 3 webgame的设计思路(三)--地图模块设计思路 ................................. 4 webgame的设计思路(四)-- 玩家数据的数据库设计 ....................... 7 webgame的设计思路(五)-- 整体架构和总结 ................................... 8 web策略类游戏开发(六)缓存概述 .......................................................... 9 Once upon a time(一个web游戏的实现方法+源代码) ........................ 11 1 体系结构 ............................................................................................... 14 2(开发思路............................................................................................. 15 (四)一个可以承载万人在线的架构 ........................................................ 17 (二) WebGame事件 ................................................................................. 18 (三) 多线程下数据库并发更新的处理 .................................................. 28 (五)数据库表设计 .................................................................................... 32 webgame的设计思路(一)-- 服务器数量预估 服务器数量预估 在线人数预估: 在项目设计之前,需要先对运营后的服务器人数做一下预估,预计激活人数300w,活跃人数40w,同时在线10w。而服务器的设计极限则在激活人数500w,活跃人数60w,最高同时在线15w。 数据参考: 这里之所以预计这么低的激活人数,是从整个服务器考虑的。《热血三国》是将不同的用户放在不同的服务器里,所以单一服务器的激活人数不会对服务器压力产生太大影响。而如果将所有玩家统一到一组服务器里,则会导致用户表访问压力过大。偏低的激活人数靠定期清理不活跃账户来实现。 数据库服务器数量估计: 服务器在搭配上,一般分为db服务器和web服务器。在这之前的运营中,通常按照1:1的方式来配置数据库和web服务器,而实际情况可以使1:2的配置比例。不过在单一世界的设 计里,单台db服务器肯定无法满足需求。之前设计过一款策略类webgame,在运营时,每秒sql数为在线人数的1~1.5倍。不过这个测试数据,是在没有钱全面应用缓存的情况的数据,在新系统里,如果全面应用缓存,并采用类似于Memcache的软件提供数据缓存,这样数据库的访问压力将可以得到极大的缓解,因此我们暂定吧每秒sql数暂定为在线人数的1倍。正常情况下数据库的访问压力应该为 10w sql/秒 极限数据应该为15w sql/秒。 数据库使用ms sqlserver 2008,在这之前的一个策略类webgame项目,对一台CPU为 E5520核的服务器上做压力测试,得到的数据如下: Sql数 5k CPU 50% 硬盘IO 0.5M(突发3M) 网卡流量 30Mbit/s(100M网卡) 按照上面的分析,在正常情况下,我们需要为整个系统提供20台db。Web服务端按照1:1和db做搭配,也将安排20台,预计3个机柜的服务器。 webgame的设计思路(二)--数据库表结构划分 数据库表设计: 因为要把这么多访问量分担到不同的服务器里,原先的数据库表设计肯定不会合适。初步的想法是根据游戏的逻辑模块,将不同模块的数据库表拆分到各个服务器里,如果按照上面的服务器预估得到的结论是4~6组服务器,实际上这个还是可行的。但如果是20组服务器的话,除非是一台服务器一张数据库表,但这的设计会造成数据表太分散,在处理事务的时候,会跨多个数据库 策略类webgame一般的主要模块为:建筑物和资源、军事、英雄、物品、帮会、交易、地图。根据这些模块的应用场景,可以将数据库表分为2种类型,一种是属于玩家的数据,另外一种是公共数据。 1. 属于玩家的数据是指玩家个人说拥有的基地、资源、军事单位、物品等数据,它们都是围绕着玩家而产生的。 2(公共数据则是指由多位玩家共同组合而产生的数据,例如:账户信息、帮会、地图等。 这里划分两种数据的目的是在于他们的数据库表的划分。对于公共数据,则采用单一服务器,单一数据库表处理的方式来处理。例如帮会模块和地图模块就准备分别用3台服务器来存储各自对应的数据库表。而对于玩家的数据,则根据用户ID采用一定的划分方式,将玩家数据打散到各个服务器里 ()。 (数据表的结构划分) 用户表和其单表的设计思路: 这里所说的单表是指在逻辑上部队数据库表做拆分,程序在访问时只访问一个数据库。当然这只是逻辑上的单一,根据实际上的访问压力,可以将数据库文件作水平切割分布在不同的文件分区和服务器里。这部分的数据库表设计继续沿用之前的就可以了。 对于用户信息,帮会信息等数据,实际上插入和更新的频率不会太高,更多的是在查询上,因此这部分的设计重点应该是在缓存上。从以前的资料里得知Memcache服务器每秒可以响应4w次的读请求,用一台Memcache就能处理好用户和帮会信息的缓存处理。 webgame的设计思路(三)--地图模块设计思路 地图模块: 地图在传统策略类webgame里都是以平面的方式展示和存储的。地图的移动都是在这个平面上实现。但一般来说,平面地图的设计容量都会有一个上限,一般来地图多为400*400,他的人数上限就是16w,实际上服务器容纳3~5w人后,整张地图就会显得很拥挤了。如果要想容纳几百万人在线,平面地图的尺寸就需要扩容得相当大了,这样玩家从地图中间移动到边缘的时间会相当恐怖,因此平面地图在这里不是很合适。因此,地图不能用平面来构造,必须是立体的方法构造。在这里我设计了两组方案: 立体平面空间: 如上图所描述的,立体平面空间,就是把多块地图一层层叠加在一起,形成一个立体的空间。这样如果用户不够,再增加一个新的平面就行。游戏的背景可以根据需要做调整(例如整个世界是被大海隔开的5片大陆组成,在这5片大陆之外,还有其它的超位面空间,这些空间自身是互不相连的,但是可以通过传送阵进行位面传送)。这样做的好处是,用户容易理解,以往用户的操作习惯不用改变,毕竟都是在平面地图上战斗。只不过要做跨位面的战斗的移动计算上会存在问题(逻辑上的问题:是否允许跨大陆的远征军) 用户坐标的表示方法:地图层次、x坐标、y坐标 数据库设计方案: 采用了层次结构,只需要增加一个地图层次的字段,这个地图表就能沿用。(参考字段:ID、地图层次、X坐标、y坐标、地图类型、玩家ID、城池ID) 虽然说,加入了一个地图层次的字段能解决地图的表示问题,不过,因为整个游戏世界是单一世界的服务器,当所用地图信息存储到一张表的时候,这数据量就不容小视。在这之前做webgame项目的时候,整张地图是预先生成好数据库记录的,当有玩家加入游戏的时候,就去修改表里的玩家ID和城池ID。同时因为地图大小只有400*400,整张表也就16w条记录。但如果是要做一个承载500w人的服务器,那地图的尺寸最好是要800*800,并且地图的层次为15~20层,就算最小的15层,按照原先的设计思路,至少需要预先插入960w条记录。 数据量看上去比较夸张,不过对于SqlServer来说也不是处理不了,并且我们还将把地图表单独用一台服务器来处理,其压力远小很多。不过也不能不考虑当发生性能瓶颈时的优化处理。优化的方法有两个: 1( 拆分:按照地图层次,把这张表拆分成15~20张表,或者拆分到15~20个数据库里 2( 用疏矩阵存储:地图不预先生成用户的地图信息,而是有玩家加入时才插入数据。这个 方案在服务器早期人数比较少时会得到良好的性能效果,但当用户人数达到一定量时,还是避免不了因为记录函数过多而导致而外的开销。 全立体空间: 全立体空间就是取消了平面的坐标显示,用户都是在一个三维的立体地图里战斗。好处是地图不用那么分散,在移动计算让很好处理,存在的问题就是游戏在显示的时候,如何表现地图的三维效果会比较困难。 用户坐标的表示方式:x坐标 y坐标 z坐标 数据库存储方案: 三维空间的数据库表设计结构可以和上面的表一样,而且也只能采用疏矩阵的方式存储,因为做成三维空间后,可表示的位置的记录数更多了。 可移动基地在全立体空间的设想: 早在两年前,看过《超时空要塞F》的时候,就产生了一个想法,就是玩家的基地是可以移动的。玩家的母舰在游戏的过程中,已一定的速度在整个世界里移动。 可以移动体系的设计要点: 1. 用户的基地可移动 2. 用户基地只能拥有一个(武林三国、travian都能建立多个) 3. 空间坐标由x坐标 y坐标 z坐标 组成,并且坐标的值应为小数 4. 同一个坐标里运行多个玩家存在,玩家的航线交叉并不会造成影响(只是为了方便计算减少判断过程) 5. 移动的数据通过后台定时刷新 a) 每个短周期(1~60s)在内存里更新坐标 b) 每个长周期(10~100个短周期时间)将坐标的数据更新的数据库 6. 攻击舰队移动的时间是按照2个阶段来进行的 a) 第一个阶段是从母舰移动到目标坐标的时间 b) 第二个阶段,在快到达时(前60分钟),做一个判断,判断攻击舰队的雷达能否搜索到目标的母舰坐标,能则做攻击坐标的新修正,如果不能则继续按照原先的坐标点移动。以上判断将每隔1分钟做一次,直到到达目标坐标点。如果到达目标坐标点仍然无法视为攻击失败,舰队返回 7. 舰队的移动距离和舰队所携带的能量有关,超过移动范围的坐标,舰队是无法出发的。 8. 部队和母舰应该是可以进行空间跳跃实现长距离的移动,不过空间跳跃需要在制定地点消耗大量的能量才能实现。 9. 默认情况下,母舰移动速度为1格(x、y、z坐标)/天。 10. 默认舰队的雷达查询范围为1格 11. 默认母舰的雷达查询范围为3格 webgame的设计思路(四)-- 玩家数据的数据库设计 数据库的划分 在游戏里数据交互最频繁的还是玩家的数据,他的访问量是一台服务器所不能解决的,因此我们考虑将这部分数据分担到多台服务器里。分担的方法还是做水平切割,但这次不使用数据库自身的切割功能,而是在应用逻辑层上对数据库进行切割。根据用户的ID取模后写入对应的服务器里。 服务器1 用户ID % 服务器数量 = 0 服务器2 用户ID % 服务器数量 = 1 „„ 预计每台服务器能提供6k~8k的在线用户访问,预计一共需要16台服务器。考虑到服务器的进一步扩容问题,在初期规划时,建议规划为32个数据库,每台服务器可以先放3~5个数据库,等服务器用户人数上来后,再将数据库拆分到不同的服务器里。 用户数据库各个模块的设计 玩家基地里的建筑物,资源,物品,英雄等相关表,基本上都是玩家独立拥有的,不存在和其他玩家交互的情况,因此这些表的设计继续沿用之前的设计就可以了。 军事模块 军事模块分为部队表,部队创建事件表和战斗事件表。部队表和部队创建事件都是玩家自己内部的事情,把相关的数据和玩家其他数据放在一个数据库里就行了,但是战斗事件表则会设计到两位或者多为玩家则会比较复杂一些。 战斗事件表通常记录的是A玩家(城池)对B玩家(城池)的攻击,里面有攻击部队,到达时间等信息。这个条记录和A放在一起,那么B在查询自己被攻击的记录时,就需要访问32个数据库,反之,和B放在一起,这A查询自己部队的攻击情况时,就需要遍历32个数据库。如果和用户表一样单独把这张表拿出来,用单独的一个服务器来处理,则会导致表过大,查询会变慢以及战斗服务器的压力过大。 在之前的项目,战斗服务器处理每场战斗大约是100ms,也就是每秒能处理10场战斗。当然你也许说可以用多线程来进行,但是使用多线程后,战斗事件的顺序可能会点到,影响用户的战术安排。 在这里,我设想,将一个表设计改为2个表:攻击事件表和被攻击事件表。这两个表的结构一样。加入A玩家发起对B玩家的攻击,那么将攻击事件加入A玩家所在服务器里的攻击事件表,在B玩家服务器里,将数据插入被攻击事件表。然后每个数据库对应一个战斗服务器程序,这个程序在已被攻击事件表为依据,进行攻击计算。在计算完成后,在同时删除2个表里的数据。 好友模块 好友表本身就可以分为2个表,已某位玩家ID为主键和对应玩家放在同一个数据库里。但是好友申请则需要另外考虑了。如果申请的申请方不可见自己发出的申请,则只需把申请记录和被申请玩家放在一个数据里。但如果需要可见,则会麻烦一些,一种方法是参考战斗表的设计思路,分为申请表和被申请表。还有一种方法就是把申请表独立出来,所用用户的申请都放在这张表里。作为我个人,我倾向于后面的一种方法。 用户邮件表设计 用户邮件虽然是属于2位用户之间的交互数据,但从整个系统的角度上来说,用单一的一张表放在单独的服务器里会更简单一些。因为邮件表的内容基本为只读内容,只存在插入和读取功能,并且用户访问的频率不是很高,可以很方便的在逻辑层和web层作缓存。 webgame的设计思路(五)-- 整体架构和总结 总结: 虽然对于单一世界的webgame思考了很多,但到最后细化写成文字,也就只有这4篇短文。不是说不想深入细节去讨论,而是发现如果不做一些具体开发就没法深入写下去,因此本系列文章页就在这里点到即止,希望能给大家一些启发。 web策略类游戏开发(六)缓存概述 既然是概述,就没有太多详细的东西,本文主要针对asp.net开发环境。 webgame需要缓存的内容包括 1.游戏的配置信息 2.玩家的信息 对于游戏配置信息,通常是指游戏里一些固定不变的信息,例如建筑物每次升级时需要多少资源,需要多少时间等数据,这些数据当然可以写死在代码里,但通常这些数据应该放在代码外,要么是以文件形式存放(xml或者txt),要么就是放在数据库里。这部分数据的缓存很好做,只需要在应用开始时,统一做一次加载就可以了。一般来说,做过1年开发的同学都知道这种数据应该用单例模式来加载和使用,这点对java同样适用。当然做成静态属性也是可以的,只要把握好加载的时机就可以了。这里还顺便说一下,如果游戏的配置信息存在交叉访问(索引),则要注意两者之间的加载顺序。或者对交叉访问的部分不做索引,每次都动态的访问(查询)。 对于玩家的信息,则有一些说头了。基本上,现在的.net项目都做成3成结构+ORM访问。缓存的对象应该是游戏的实体对象。同上实体对象和数据库表之间都是一一对应的,这点没什么好多说的。以玩家或者村庄对象为了,它们的索引通常是ID,只需要创建对应的Dictionary 字典对象用来存取数据就可以了。 Dictionary playerMap = new Dictionary(); public Player Getplayer(int ID) { Player ret; if (playerMap.TryGetValue(ID, out ret)) return ret; ret = 从数据库里获取玩家对象(ID); playerMap.Add(ID, ret); return ret; } 上面是一个基本的用于从缓存里访问玩家对象的方法。这点对大家来说,都不算很难,有点经验的同学都能写得出来。 下一步如何更新这个缓存就是这个缓存系统才是webgame的麻烦地方。 我们缓存的对象和web应用的对象不一样,它存在着随时变化的可能,并且当他发生变化时,需要能及时反馈给用户。 web应用我们以blog为例,当某位用户添加了新文章到cnblogs的首页,可能不会立即被其他用户看到,因为cnblogs首页的缓存信息还没有被修改。通常根据需要这些缓存信息可能会是1分钟,也有可能是10分钟,只有当缓存过期了以后,系统才会生成新的首页内容。其目的是减少首页的数据库查询访问量。 webgame游戏则不太一样,我花资源升级,就希望在页面上能立即看到变化,因此,当我们完成某项业务逻辑操作后,需要人工的更新资源对象的缓存。 try { // 游戏逻辑处理 // 数据库数据提交 缓存更新(); } catch (Exception ex) { // 日志处理 } 按照通常的做法,每次逻辑操作都包含在一个事务里面,如果逻辑操作失败时,则可以对事务做撤销处理,尽量避免数据异常。 当然,这个也不是绝对的,前两天在QQ上谈论到缓存更新的问题时,某位同学帖出了他的代码,代码里,缓存的更是是在数据库事务提交之前。如果提交发生失败,则整个游戏系统已缓存的数据为主。这个问题咋一看来,和我们的思路不一样,我们就认为这样做有问题,后来回家的路上仔细的想了想,其实这样做也不无道理,因为它是在数据库提交之前更新缓存,也就是说,如果发生错误,唯一可能错误的地方就是写数据库时写失败了。但如果整个游戏系统是以缓存数据为准,只要游戏逻辑在计算时没发生错误,将错误的数据写入缓存,那么就算当前的数据修改提交到数据库失败了,数据还有可能在下一次修改时,提交一份正确的数据到数据库。整个系统不会因为数据库瘫痪了而无法运行。这点感觉和网游的服务器设计思路近似,毕竟对于网游来说,不可能每次玩家的操作都将数据写回数据库,玩家的数据都以在服务器内存里的数据为准,以定时的方式将内存数据会写到数据库。 其实这两种设计思路的差别就在于,数据是以数据库为中心还是以内存数据为中心。对与web系统来说,自然是以数据库为中心。从网游的角度来说,自然以内存数据为中心。而webgame是这两大系统的结合,其数据访问思路自然综合了这两种观点,具体到某个游戏,则需要根据游戏的需要而加以取舍了。 除了以字典为主的缓存设计外,还有一个重要的缓存对象的设计需要说一下,那就是地图。目前常见的Webgame(Travian,武林三国)都是以一张400*400的世界地图为玩家的交战地图。通常是一次性全部加载到内存里。存放的,自然是以x,y轴坐标为依据的二维表里。虽然首次加载是数据会比较慢一些,内存占用的空间会多一些,但当玩家查看地图页时,你会发现页面生成的数据比从数据库里获取相应数据要快很多。再加上现在服务器内存动则4G,8G的。则几十兆的地图数据还是毛毛雨了。 Once upon a time(一个web游戏的实现方法+源代码) 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!" “父子相认,得开个堂会庆祝一下吧„„” “别傻了~”(玩家丁灰溜溜地缩会角落里哭泣) 一直持续到游戏结束,堂会还是没开成.。 1 体系结构 1.1 传统的网站的架构 传统的网站一般都是以N层结构一般N为3,就是我们常说的三层架构。 3层架构分为数据层、业务逻辑层、页面显示层。 1.2 WebGame的架构 WebGame可以看作是网站和游戏的结合体,因此它具备了这两类系统的特性。我们不但可以把WebGame看作是一个网站,也可以把它看作是一个网络游戏。 的网站是B/S结构,网络游戏则是C/S结构,WebGame则是这两者的结合我们暂且称之为B/C/S结构。既在用户眼里,它是一个通过浏览器范围的网站。在服务器系统里,它又是一个传统的C/S结构的网络游戏。 从上图分析,用户通过浏览器访问服务器的时候,首先是访问网页服务器,如windows平台下的IIS,linux下的Apache。在通过网页服务器,以某种特殊的方式(分布式访问,如.net下的remoting)去访问游戏服务器,通知游戏逻辑服务器执行玩家操作,并从游戏逻辑服务器里获得游戏相关的信息,或者直接通过访问数据库而获得游戏数据。 1.2.1 为什么要将服务器分为游戏服务器和网页服务器 网页服务器的特点是触发执行,及当有用户访问网页的时候,才会执行该网页的程序代码。而我们常见的WebGame(Ogame,Travian)这些游戏实际上是需要24小时不间断执行的,因此网页服务器的执行方式并不适合与游戏。因此我们另外需要一个应用程序来执行这些24小时不间断要做的事情。这也就是我们需要增加一个游戏服务器的原因。 1.3 Web三国的架构 因为目前Web三国是非商业开发,因此如果照搬上面的WebGame架构来设计,会导致开发周期过长,开发效率低下的问题。实际上在开发初期,Web三国是按照上面的架构去开发,碰上了上述问题,才换了另外一种结构。 实际性现在Web三国的结构和WebGame架构差不多,只不过将游戏服务器集成到网页服务器里,项目里按照传统的网站架构,将游戏分为:数据层、游戏逻辑层、页面层。 至于如何实现24小时不间断处理,者是通过在游戏启动时,创建一个线程去处理。这个是asp.net里的一个功能,我不清楚php里是否也有这样的功能。 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 的方法很简单,就是在里把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页面事件处理过程一样 (四)一个可以承载万人在线的架构 web策略类游戏开发(四)一个可以承载万人在线的架构 Webgame现在已经开始需要进入大统一服务器时代,每个游戏区域容纳的玩家数量将从现在的几万人发展到几十万人,因此在新的背景下,webgame如何处理大量用户的请求将成为问题。目前一台asp.net做的weggame服务器每秒能处理500~1000个页面请求,按照每个玩家每隔3~5秒做一次页面操作(页面请求),一台服务器能承受2k~4k的玩家在线,对于一个只有几万人的策略游戏来说,已经是足够了。但对于一个未来将承载几十万人的游戏来说远远不够。 通过分析,玩家在游戏过程中,有80%以上的访问仅仅只是查看玩家在游戏里的状态,实际上真正会对游戏运行状态及数据修改的的页面请求不足20%。因此,我们可以将呈现页面和处理游戏逻辑的功能拆分为2组服务器:页面服务器和逻辑服务器。两者之间可以通过remoting的方式进行数据通讯。将服务器分离后,随着页面服务器的增加,页面访问能力能应该能提升4~6倍。在往上逻辑服务器就会出现访问瓶颈。解决方法可以让页面服务器在读取玩家数据时直接访问数据库或者增加一个对象缓存服务器。页面服务器只有在必要的时候(需要进行逻辑运算时)才访问逻辑服务器,而逻辑服务器在玩家数据发生改变后更新对象缓存服务器和数据库。这样就可以大大降低逻辑服务器的访问次数,使页面访问能力进一步提升,轻松突破万人在线。如果访问量还需要继续扩大,可以用httpd做前台负责相应图片以及css等静态文件。 (二) WebGame事件 作者:Yahle 曾用网名:Dogvane 原载: 版权所有。转载时必须以链接形式注明作者和原始出处。 1 事件系统 事件系统是整个WebGame系统里一个核心的组成部分,我们用它来控制的进程,让游戏世界里能够24小时运转。 1.1 事件的概念 事件是指游戏里玩家的某个(系列)活动,它可以分为瞬时活动和非瞬时活动。 瞬时活动顾名思义就是在玩家发出指令的瞬间就能完成的活动。像RPG游戏里,玩家从NPC里购买一瓶药水,在玩家发出这个指令后,玩家的金钱减少,并获得药水,这一切都在玩家发出指令后瞬间完成(当然实际逻辑上处理还需要几个ms处理时间)。 而非瞬时活动则是在玩家发出某个指令后一段事件才会被执行。例如RPG里玩家鼠标点击地图上某个地方,游戏角色则会自动行走到刚才点击处。这个移动过程就是一个非瞬时过程,它有了一个移动的过程,这个过程需要消耗一定的时间(玩家能感知的事件) 非瞬时系列活动是指一位或者多位玩家通过一系列的瞬时\非瞬时活动完成一个动作(功能)。例如wow里面的拍卖场,有1位玩家提供道具,同服务器里的其他玩家对该道具进行竞拍。 在WebGame里,玩家的很多操作其实是非瞬时部分事件是村庄资源减少(前提投资),非瞬时事件是建筑物建设,这个动作(同能)的结果是建筑物等级上升。 又比如《Travian》里的攻击,瞬时事件是当前村庄的士兵减少(派出部队),非瞬时事件是减少的士兵移动到需要攻击的村庄(行军过程),动作结束是,两个村庄的部队开打了(战斗)。 1.2 触发器(事件队列) 前面说了瞬时事件和非瞬时事件的概念,当WebGame在24小时运转的时候,系统会产生大量的非瞬时事件,这些非瞬时事件不会在玩家点击页面时执行,而是需要等一段时间后才会执行,因此在游戏里把这些非瞬时事件拿出来,按事件的执行时间进行排序,组成一个事件队列。再通过一个触发器,在事件设定的执行时间到达的那时执行相对应的事件。 这里面就涉及到两个内容: • 非瞬时的事件队列 • 事件触发器 1.2.1 事件队列 数据库除了用于存储外,其查询功能也非常强大,直接拿来做事件队列很合适。事件队列里通常保存事件涉及的对象(村庄),结束时间、事件类型以及事件相关参数等。 下表为我们系统里使用的事件表: ID int VillageCode int TargetVillageCode int Type int EndTime DateTime EventObject ntext 保存时将这些对象做xml序列化保存到EventObject字段里。当然如果为了效率还可以存储二进制序列化后的对象,这样在序列化以及反序列化时能节省大部分时间。 1.2.2 触发器 Asp.net Asp.net的处理在触发器上的处理就比较简单了。在服务器程序启动的时候,就执行一个线程,定时(1s)从数据库里取结束时间<当前事件的事件进行处理。 1 /// 2 /// 事件处理线程 3 /// 每间隔1s处理一次到时间了的事件 4 /// 5 static void EventThread() 6 { 7 DateTime lastTime = DateTime.Now; 8 while (Run) 9 { 10 try 11 { 12 Event.SystemEvent.DoEvent(lastTime); 13 } 14 catch (Exception ex) 15 { 16 Common.Logging.Error(ex.ToString()); 17 } 18 19 long def = DateTime.Now.Ticks - lastTime.Ticks; 20 lastTime = DateTime.Now; 21 22 if (def < 10000000) 23 { 24 // 线程休息,并等待下一次时间间隔 25 int ms = (int)(10000000 - def) / 10000; 26 if (ms > 0) 27 System.Threading.Thread.Sleep(ms); 28 } 29 } 30 } 31 1 /// 2 /// 系统事件 3 /// 主要作用是执行处理事件的过程 4 /// 5 public class SystemEvent 6 { 7 public static void DoEvent(DateTime time) 8 { 9 List evs = Event.GetEvents(time); 10 foreach (Event e in evs) 11 { 12 try 13 { 14 IEvent ie = e.Object; 15 if (ie != null) 16 { 17 ie.Parent = e; 18 ie.DoEvent(); 19 } 20 } 21 catch (Exception ex) 22 { 23 Common.Logging.Error(ex.ToString(), e.EventObject); 24 } 25 try 26 { 27 e.Delete(); // 从数据库里删除信息 28 } 29 catch (Exception ex) 30 { 31 Common.Logging.Error(ex.ToString()); 32 } 33 } 34 } 35 } 36 php PHP没用过,不过好像不能在PHP页面里无法创建线程,用纯PHP服务端来实现触发器估计有点难度。但是可以做成一个PHP的应用程序,由应用程序来实现触发器。 1.3 游戏资源的24小时自动增长 游戏资源的24小时自动增长,这是一个有趣的话题,很多刚开始设计WebGame的朋友都会在这个问题上卡一下。每个人对于如何实现这个功能都有自己的独到见解,我在这里就不能给出一个唯一的答案,这里给的解决方案只是自己正在做的WebGame用到的方案。 首先,我们否定了每个时间间隔(10分钟)就执行更新村庄资源的设计,这即不准确,同时也很消耗服务器资源。所以,我们的系统就只有在用户执行事件(瞬时的和非瞬时)的时候才将新的资源信息写入数据库。平时显示资源的时候用 (当前时间 - 上一次更新事件)*资源每小时产量+上一次更新产量 公式计算出当前资源并显示在页面上。也就是说,每次页面更新时重新计算资源,但只只要用户没有做任何修改资源的动作(事件),就不会把重新计算后的资源写回数据库。 《Travian》在前台界面上看到资源不断的上涨其实是利用JavaScript实现的小效果。 (三) 多线程下数据库并发更新的处理 作者:Yahle 曾用网名:Dogvane 原载: 版权所有。转载时必须以链接形式注明作者和原始出处。 1 多线程下数据库并发更新的处理 1.1 背景 不知道大家在玩《Travian》时有没有做过这样的事情: 同时打开多个集结点,并设定好要出发的士兵及数量,在快到压秒的时候,快速切换页面,不断的点确定,以确保游戏不会通讯问题导致压秒失败。 再看一个教科书里经常提到的数据库脏数据的案例: A操作从表里获得数据D=10,在计算的时候,线程刚好进行切换,切换到B,B也需要操作D,并从数据库里取道值为10,在进行简单操作(D=D- 2)后将D=8的值写回数据库。B操作处理结束后,线程再切换回A操作,这时A在做自己的操作时,仍然采用先前取到D(10)的值,在进行一个简单操作(D=D-1)后,仍然写回数据库。这时,数据库里的值变为9,而实际上D的值应该是7(D=10-2-1).。造成这个问题主要是因为CPU在执行A、B操作时没有按照顺序来执行,而是让B抢先在A执行完之前执行,导致它们在计算D的时候,因为数据没有同步而发生写入脏数(A的数据覆盖了B的数据)据的问题。 A操作伪代码: { D=GetDB() D=D-1 SetDB(D) } B操作伪代码: { D=GetDB() D=D-2 SetDB(D) } 一个简单的图表表示它们的操作: CPU D 1 A操作 数 =0 据线 G库程 e当切 t前换 D值 B ( ) 仿照第一个例子,当一位玩家同时打开多个页面点出兵进攻后,这些这些请求会同时到达服务器,服务器会根据这些Http请求创建相应的线程来处理进攻动作: 进攻的伪代码: { 村庄士兵数量=GetDB() if (村庄士兵数量 > 这次进攻士兵数量) { 村庄士兵数量=村庄士兵数量-这次进攻士兵数量 SetDB(村庄士兵数量) } } 按照第二个脏数据的例子,应该很容易想到,我们在这次进攻的时候,很有可能派出了2只部队,但是只减少了1只部队的士兵。 多线程未同步是造成游戏bug的原因之一。 仿照第一个例子,当一位玩家同时打开多个页面点出兵进攻后,这些这些请求会同时到达服务器,服务器会根据这些Http请求创建相应的线程来处理进攻动作: 进攻的伪代码: { 村庄士兵数量=GetDB() if (村庄士兵数量 > 这次进攻士兵数量) { 村庄士兵数量=村庄士兵数量-这次进攻士兵数量 SetDB(村庄士兵数量) } } 按照第二个脏数据的例子,应该很容易想到,我们在这次进攻的时候,很有可能派出了2只部队,但是只减少了1只部队的士兵。 多线程未同步是造成游戏bug的原因之一。 1.2 多线程并发与互斥 关于什么是多线程,以及多线程下面的同步及互斥的方法我这里就不过多的介绍了,相关内容可以到网上搜索,本文主要是讨论同步的时机以及避免死锁 cnblogs.com相关主题 1.3 WebGame里多线程数据同步的方法 1.3.1 在asp.net下用lock进行加锁操作 在我们的WebGame里,采用asp.net的lock方法。具体就是在方法体里,用lock里锁住一 个对象,使其它方法在访问这个对象的时候被阻塞。当第一个访问对象的线程退出并释放锁以后,其它的线程才能取消阻塞状态继续操作该对象。 我们通过修改上面进攻的伪代码,增加lock操作: { lock(锁定的对象) { 村庄士兵数量=GetDB() if (村庄士兵数量 > 这次进攻士兵数量) { 村庄士兵数量=村庄士兵数量-这次进攻士兵数量 SetDB(村庄士兵数量) } }//unlock(锁对象) } CPU执行流程表: CPU l 线程A 线村 o 程庄 B c士 k兵 (数 锁量 =对 G象 ) e t D B ( ) 1.3.2 锁的粒度 上面的伪代码是对游戏里的逻辑代码进行了加锁处理,但只是简单的描述该方法体里需要进行加锁以及加锁的范围。但在实际的代码里,我们需要明确lock里锁定的对象。如果这个对象选择不合适,很有可能会造成性能损失或者死锁。 1.3.2 锁的粒度 上面的伪代码是对游戏里的逻辑代码进行了加锁处理,但只是简单的描述该方法体里需要进 行加锁以及加锁的范围。但在实际的代码里,我们需要明确lock里锁定的对象。如果这个对象选择不合适,很有可能会造成性能损失或者死锁。 1.3.2.1 数据库锁 数据库锁是最一种简单的方法就,凡是在存在发生数据库写操作的代码里,都需要进行加锁处理,并同意用一个数据库锁对象。 这种方法使用简单,不容易造成死锁。当存在的问题也是明显,就是在发生写数据库操作时不能并发操作,这点特别是当用户访问量增大可能会造成一定的性能瓶颈。 1.3.2.2 村庄锁 为了提升写数据库的效率,我们必须解决锁粒度过大的问题,因此在我们的游戏系统里,对锁的粒度进行的细化,细化到村庄级别的对象。 在游戏一张村庄表对应是整个游戏里所有的村庄对象,而一个村庄对象在村庄表里只是一条记录。在使用数据库锁时,其实是告诉其它方法,现在我要写数据库,大家都等一下,等我写好后再写。当我们将锁的对象细化到村庄(一条数据库表记录)的时候,实际是告诉数据库,我现在要修改XXX村庄,大家都别动它,但你要修改YYY村庄我不管。 死锁 在前面虽然降低了锁的粒度,提高了数据库并发性能,但随之而来就很容易发生一个问题--死锁。 例如当两个村庄需要发生交易,这时我们需要同时修改A、B两个村庄对象,需要对其进行顺序锁定(先锁定A,再锁定B),这时候,又发生另外一个操作,也需要同时对A、B两个村庄进行锁定,恰巧这个锁定的顺序是B、A。这样就造成两个现在互相等待形成死锁。 中间变量解决死锁 对于死锁,在关于多线程介绍方面有很多解决方案,这里就不过多阐述。在WebGame里预防死锁,可以采用结合游戏的操作流程,对游戏处理流程及数据进行拆分,来预防死锁问题。简单的来说,就是将涉及2个村庄修改的流程拆分为2个流程,并用中间变量予以表示,两个处理流程涉及变化的值都在中间变量里予以保存。 以前面提到的交易为例,在游戏设计里,两个村庄在交易的时候,并不是瞬时交易,而是通过商人进行运输并交易。这样我们就以商人作为中间变量。 A<-->C<-->B 在上图里,交易开始,是由玩家触发交易事件,这时以村庄A为锁对象,进行锁定。C作为要交易资源从村庄A里被扣除。并将A修改后的数据回写到数据库。等过了一段事件后,商人C到达了目的地,这时由系统触发后续的交易事件,这时以村庄B作为锁对象进行锁定。村庄B在获得资源后写回数据库,整个交易事件就算完成了。 当然实际游戏的交易比上面的例子稍微复杂一点点,因为交易双方都有资源的减少与获得。完整的流程应该是需要锁定4次,并产生2个中间变量。 村庄A-->商人1-->村庄B 村庄A<--商人2<--村庄B 游戏里其它地方的锁定 基本上,凡是某个事件涉及到两个村庄修改的地方,都可以用上面的锁定的方法对处理流程进行修改。例如村庄A攻打村庄A。当然其它事件在数据修改方面只涉及1个村庄,那么就不需要怎么麻烦,直接对村庄加锁锁定即可。 好在在策略类的WebGame里涉及两个村庄的情况不多,涉及到的基本可以用中间变量对操作流程进行拆分,因此这种锁定方式在策略类WebGame还是比较合适的。当然实在不行也只能按照以往的预防死锁的方法进行处理 1.4 非Asp.net里同步的方案 1.4.1 Java java在线程同步机制上与asp.net基本一致,因此上面所述的asp.net的方法也适合与java 1.4.2 php 了解不多,不过好像php没有线程锁与同步这个概念,如果直接通过语言环境进行同步可能比较困难。 不过在MySQL里,存在一个表锁定的方法,可以通过lock table的方法锁定表,不允许其它MySQL用户去进行操作。基本上和前面提到的数据库锁类一样,只不多执行方法的时候是在MySQL端执行。 (五)数据库表设计 有人希望看数据库表,在这里发一下表设计,基本上没有什么特别的地方需要解释的,数据库的字段名都写得很清楚了。当然,目前的字段只是游戏的基本字段,如果游戏功能多起来后,表设计会比现在复杂。 表名:Village 1 ID int ?列数长小标主允默说 2 Name varchar 50 3 code int 据数许认名 度 识 键 明 1 类位 空 值 型 2 Namv5 ae 0 r c h a r 3 codi0 ne t 4 Plai0 yern ID t 5 vili1 lagn eTyt pe 6 Citi0 yCon de t 7 Buiv2ldia5 rng 5 c h a r 8 Buiv2ldia5ngLr5 evec hl a r 9 X i0 n t 1Y i0 n0 t 1Popi0 ulan1 tiot n 1Resv5oura2 0 rce c h a r 1Maxv5Resa3 0 ourr cce h a r 1Outv5 a4 Put 0 r c h a r 1LasdNtUpao5 dattweRee(sout) irce m e 表名:Troops 1 ID int ?列数长小标主允默说 2 PlayerID int 0 3 VillageCode int 据数许认名 度 识 键 明 1 类位 空 值 型 2 Plai0 yern ID t 3 Vili0 lagn eCot de 4 Num v5 a0 r c h a r 5 Trov5 opTa0 rype c h a r 6 Atti0 acknVilt lag eCo de 7 EnddTima te e t i m e 8 Stai0 nte t 9 Atti0 ackn Buit ldi ngI D 1Codv5 a0 e 0 r c h a r 1Heri0 n1 oID t 表名:Trade 1 ID int ?列数长小标主允默说 2 PlayerID int 0 3 VillageCode int 据数许认名 度 识 键 明 1 类位 空 值 型 2 Pli0 ayn ert ID 3 Vii0 lln agt eC od e 4 Tyi0 pen 1 t 5 Nui0 nm1 t 6 Tyi0 pen 2 t 7 Nui0 nm2 t 8 X i0 n t 9 Y i0 n t 表名:SystemMessage 列名 数长小标主允许空 默说 1 据数认度 识 键 明 类位 值 型 2 Pi0 ln at y e r I D 3 Ti0 yn pt e 4 Tv5 ia0 tr lc he a r 5 Ov0 ba jr ec ch at r 6 Ii0 sn Rt e a d 7 CdN rao etw ae( tt) ei Tm ie m e 表名:PlayerMessage 1 ID int ?列数长小标主允默说 2 PlayerID int 0 3 SendPlayerID int 据数许认名 度 识 键 明 类位 空 值 1 型 2 Pli0 ayn ert ID 3 Sei0 ndn Plt ay er ID 4 Sev5nda0 Plr ayc erh Naa me r 5 Tiv5tla0 re c h a r 6 Mev0 ssa agr ce h a r 7 Isi0 Ren ad t 8 CrdNeaaotetwTie( tme ) i m e 9 Isi0 Den let te 表名:Player 1 ID int ?列数长小标主允默说 2 Name varchar 50 3 Password varchar 据数许认名 度 识 键 明 类位 空 值 1 型 2 Nav5 ame 0 r c h a r 3 Pav5ssa0 wor crd h a r 4 Ali0 lin ant ce 5 Nai0 tin ont al it y 6 Gev5nda0 rer c h a r 7 Lov5 caa0 tir con h a r 8 Inv0 afo r c h a r 9 Inv0 foa r2 c h a r 1Poi0 pun0 lat ti on 表名:Log 列名 数长小标主允许空 默说 1 据数认度 识 键 明 类位 值 型 2 Pi0 ln at y e r I D 3 Ti0 yn pt e I D 4 Mv0 ea mr co h a r 5 LdNoaogtw Te(it) mi me e 表名:Hero 列名 数长小标主允许空 默说 1 据数认度 识 键 明 类位 值 型 2 Nv5 aa0 mr ce h a r 3 Pi0 ln at y e r I D 4 Vi0 in lt l a g e C o d e 5 Li0 en vt e l 6 ei0 xn p t 7 Bv5aa0 sr ec Ph ra or p e r t y 表名:GoodFriend 列名 数长小标主允许空 默说 1 据数认度 识 键 明 类位 值 型 2 Plai0 yern ID t 3 Gooi0 dFrn ient dID 4 Typi0 ne t 表名:Event 1 ID int ?列数长小标主允默说 2 VillageCode int 0 3 TargetVillage 据数许认名 度 识 键 明 Code 类位 空 值 1 型 2 Vili0 lagneCot de 3 Tari0 getnVilt lag eCo de 4 Typi0 ne t 5 BegdinTa time e t i m e 6 EnddTima te e t i m e 7 Shov5wTea0 rxt c h a r 8 Evev0 ntOa bjer cct h a r
/
本文档为【网页游戏设计】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索