找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 6050|回复: 0

如何设计游戏中道具功能(一)

[复制链接]
发表于 2014-2-14 13:12:30 | 显示全部楼层 |阅读模式
acejoy重新换机房花了一些时间,这一段时间无法登陆。现在终于搞好了。把春节前抽空写的一点东西补上来。

以前写过一些游戏的道具设计,沉淀了几年,一直没有时间回头整理,春节前几日,好多人都回家了,工作也不这么紧张了。于是抽时间写了一个道具功能。放在这里,供大家有兴趣玩赏。
这里是抛砖引玉,代码未必是最优的,但是提供大家一个思路。
本以为几日就能完成,毕竟还是小看它了,因为综合了自己以前的一些代码,发现自己当初的代码通用性以及扩展性都有欠缺。
道具系统有它自己的独立性,可以完全独立于其他系统。比如player,比如AI,比如寻路。(以后有时间我会一一写出相关文章,如果大家有兴趣的话)
想趁机写一个可以支持弹性扩展的。
于是拿了一张白纸,写下如下文字:
(1)实现道具字典的增删改查。
(2)实现道具的组合,分解。
(3)实现道具的任意属性扩展,也就是说,设计者可以随时添加自己想添加的道具属性,而不会影响已有道具的数据流。
(4)实现道具的交易
(5)实现道具的管理,生成监控。
(6)顺带实现一个可循环的邮件系统
(7)实现背包相关功能,存放,删除。以及道具相关执行。
(8)代码必须支持Linux和windows两种操作系统。

代码是在2014年春节前两周开始编写的,一直到春节前3天才真正写成。期间考虑了不少情况,导致一些之前的接口和结构不适应而返工。所以花费时间较长。
我会一点点的把这些设计点滴记录下来,给自己,也给想看它的朋友们,整个程序是可以运行的。我会在这个系列文章的最后,把完成可运行的代码程序贴进来供大家玩。

道具系统在整个游戏系统中是很重要的系统。无论MMO,还是页游手游,都需要它。
看上去很简单,但是当实际实现的时候,你会发现其中并不是那么好控制的,要想做到现实意义上的完美,那么需要大家踏下心来一点点的精雕细琢。

先看看,最终实现的界面吧。



有意思吧。
确实很有意思,呵呵(有点小成就感)

在开始了解它之前,我们必须知道,它需要注意什么。
因为前几年做过一些手游和MMO,所以,在这里,我第一考虑的是,道具的唯一性,这很重要,如果是单机游戏,完全可以不用考虑,但是网游中,如果你给别人留下复制的漏洞,你会死的很惨的。
首先,在我看来,先必须对道具总量进行控制。
总量怎么理解?
总量就是,如果把你的游戏想象成一个世界。我在这个世界构造的时候,我就会给这个游戏世界一个现实意义上的上限。(说白了就是道具对象池)
也就是说,无论这个世界上的道具是否被真正创造出来,我会给你一个上限。
比如,这个世界最多容纳1000件道具,无论这些道具是否被创造出来,我都会在程序启动的时候,把这1000件道具对象全部new出来。
你可能会问,你怎么可能知道未出现的道具是什么?你这样new出来是否有意义?
是的,我不知道,你也不知道,但是我能确定的是,这1000件道具的内存大小,这个很重要。
因为在整个道具系统运行过程中,我将不再new和delete任何东西,除了指针。
这样做,一是我可以完全控制我要创造的这个世界中的道具总数量,如果再多,那么道具创造的时候会就会返回失败。
二是我可以在BUG出现的时候,比如某些BUG大量道具生成的时候,有一个上限,让系统损失最小。
三是在游戏过程中,程序不会再有多余的new和delete操作。减少了内存碎片的可能性。
光控制道具总是往往在网游内是不够的,还有一项重要的工作要做。
那就是关键道具的追踪,有些稀有关键道具,要记录生成时间,销毁时间,甚至是转移时间,便于后期的运营追踪。
比如某个玩家被盗号了,物品要必须提供被追踪的途经。
如果对所有的物品都进行这样的追踪,在高并发的游戏服务器中,服务器是受不了的,你也不能为了一个血瓶的出现而煞费苦心。
但是所有的道具追踪,都现定于不可叠加的单一道具,因为,如果道具叠加了,这个道具的GUID就会消失。给追踪带来困难。
所以,在稀有道具中,对可叠加的物品,策划的时候就要专门考虑一下。
道具的限制,追踪是网游的基本功。这些事情一般的游戏服务器都是要干的。
好了,废话少说。先看看如果是我,我是怎么做的。

在动手前,我先罗列出了我需要对道具是用的一些基本材料,就像厨师一样,你要准备一些材料才能动手。
  1. #ifndef _OBJECT_DEFINE_H
  2. #define _OBJECT_DEFINE_H
  3. //所有物品所需要的类型都在这里定义
  4. //add by freeeyes
  5. #include <stdio.h>
  6. #include "stdint.h"
  7. #include <WinSock2.h>
  8. typedef uint8_t uint8;
  9. typedef uint16_t uint16;
  10. typedef uint32_t uint32;
  11. typedef uint64_t uint64;
  12. #define MAX_ITEM_NAME      200     //对象名称最大长度
  13. #define MAX_ITEM_DESC      1024    //对象描述最大长度
  14. #define MAX_ITEM_ATTRIBUTE 51      //对象属性个数最大大小
  15. #ifdef WIN32
  16. #define MEMCOPY_SAFE(x, y, len) memcpy_s(x, len, y, len);
  17. #else
  18. #define MEMCOPY_SAFE(x, y, len) memcpy(x, y, len);
  19. #endif
  20. inline uint64_t htonll(uint64 v) {  
  21.         union { uint32 lv[2]; uint64 llv; } u;  
  22.         u.lv[0] = htonl(v >> 32);  
  23.         u.lv[1] = htonl(v & 0xFFFFFFFFULL);  
  24.         return u.llv;  
  25. }
  26. inline uint64_t ntohll(uint64 v) {  
  27.         union { uint32 lv[2]; uint64 llv; } u;  
  28.         u.llv = v;  
  29.         return ((uint64_t)ntohl(u.lv[0]) << 32) | (uint64_t)ntohl(u.lv[1]);  
  30. }  
  31. #define HTONS(x, y)  y = htons(x);
  32. #define HTONL(x, y)  y = htonl(x);
  33. #define HTON64(x, y) y = htonll(x);
  34. #define NTOHS(x)  x = ntohs(x);
  35. #define NTOHL(x)  x = ntohl(x);
  36. #define NTOH64(x) x = ntohll(x);
  37. //定义一个函数,可以支持内存越界检查
  38. inline void sprintf_safe(char* szText, int nLen, const char* fmt ...)
  39. {
  40.         if(szText == NULL)
  41.         {
  42.                 return;
  43.         }
  44.         va_list ap;
  45.         va_start(ap, fmt);
  46. #ifdef WIN32
  47.         vsnprintf_s(szText, nLen, nLen, fmt, ap);
  48. #else
  49.         vsnprintf(szText, nLen, fmt, ap);
  50. #endif
  51.         va_end(ap);
  52. };
  53. enum ENUM_OBJECT_TYPE:uint16   //对象类型
  54. {
  55.         OBJECT_UNKNOW       = 0x00,          //未知
  56.         OBJECT_ITEM         = 0x01,          //道具
  57.         OBJECT_MAIL         = 0x02,          //邮件  
  58. };
  59. enum ENUM_OBJECT_STATE:uint8  //对象状态
  60. {
  61.         OBJECT_UNKNOW_STATE  = 0x00,            //未知
  62.         OBJECT_DISPLAY_STATE = 0x01,            //可视
  63.         OBJECT_INVALID_STATE = 0x02,            //无效
  64. };
  65. enum ENUM_ITEM_ATTRIBUTE:uint8
  66. {
  67.         ITEM_ATTRIBUTE_MIN_ATTACK      = 0x00,    //最小攻击
  68.         ITEM_ATTRIBUTE_MAX_ATTACK      = 0x01,    //最大攻击
  69.         ITEM_ATTRIBUTE_PHYSICS_DEFENCE = 0x02,    //物理防御
  70.         ITEM_ATTRIBUTE_MAGIC_DEFENCE   = 0x03,    //魔法防御
  71.         ITEM_ATTRIBUTE_LIFE            = 0x04,    //生命
  72.         ITEM_ATTRIBUTE_MAGIC           = 0x05,    //魔法
  73.         ITEM_ATTRIBUTE_POWER           = 0x06,    //精力
  74.         ITEM_ATTRIBUTE_CRIT            = 0x07,    //暴击
  75.         ITEM_ATTRIBUTE_ATTACKSPEED     = 0x08,    //攻速
  76.         ITEM_ATTRIBUTE_HIT             = 0x09,    //命中
  77.         ITEM_ATTRIBUTE_DODGE           = 0x0A,    //躲闪
  78.         ITEM_ATTRIBUTE_RECOVER_LIFE    = 0x0B,    //生命回复
  79.         ITEM_ATTRIBUTE_RECOVER_MAGIC   = 0x0C,    //魔法回复
  80.         ITEM_ATTRIBUTE_SKILL_ATTACK    = 0x0D,    //攻击技能
  81.         ITEM_ATTRIBUTE_SKILL_P_DEFENCE = 0x0E,    //物防技能
  82.         ITEM_ATTRIBUTE_SKILL_M_DEFENCE = 0x0F,    //魔防技能
  83.         ITEM_ATTRIBUTE_SKILL_CRIT      = 0x10,    //暴击技能
  84.         ITEM_ATTRIBUTE_SKILL_HIT       = 0x11,    //命中技能  
  85.         ITEM_ATTRIBUTE_SKILL_A_SPEED   = 0x12,    //攻速技能
  86.         ITEM_ATTRIBUTE_SKILL_DODGE     = 0x13,    //躲闪技能
  87.         ITEM_ATTRIBUTE_POSITION        = 0x14,    //位置
  88.         ITEM_ATTRIBUTE_CLASS           = 0x15,    //物品类型
  89.         ITEM_ATTRIBUTE_PHOTOID         = 0x16,    //图片ID
  90.         ITEM_ATTRIBUTE_BASEID          = 0x17,    //物品基础ID
  91.         ITEM_ATTRIBUTE_JEWEL_COUNT     = 0x18,    //孔槽个数
  92.         ITEM_ATTRIBUTE_JEWEL_1         = 0x19,    //孔槽1
  93.         ITEM_ATTRIBUTE_JEWEL_2         = 0x1A,    //孔槽2
  94.         ITEM_ATTRIBUTE_JEWEL_3         = 0x1B,    //孔槽3
  95.         ITEM_ATTRIBUTE_JEWEL_4         = 0x1C,    //孔槽4
  96.         ITEM_ATTRIBUTE_JEWEL_5         = 0x1D,    //孔槽5
  97.         ITEM_ATTRIBUTE_VERSION         = 0x1E,    //道具版本号
  98.         ITEM_ATTRIBUTE_USE_LEVEL       = 0x1F,    //使用等级
  99.         ITEM_ATTRIBUTE_LEVEL           = 0x20,    //道具等级
  100.         ITEM_ATTRIBUTE_SYNTHETIC_COUNT = 0x21,    //合成所需道具数量(图纸专属)
  101.         ITEM_ATTRIBUTE_SYN_BASEID_1    = 0x22,    //合成道具1的BaseID(图纸专属)
  102.         ITEM_ATTRIBUTE_SYN_BASEID_2    = 0x23,    //合成道具2的BaseID(图纸专属)
  103.         ITEM_ATTRIBUTE_SYN_BASEID_3    = 0x24,    //合成道具3的BaseID(图纸专属)
  104.         ITEM_ATTRIBUTE_SYN_BASEID_4    = 0x25,    //合成道具4的BaseID(图纸专属)
  105.         ITEM_ATTRIBUTE_SYN_BASEID_5    = 0x26,    //合成道具5的BaseID(图纸专属)
  106.         ITEM_ATTRIBUTE_SYN_BID_1_COUNT = 0x27,    //合成道具1的BID数量(图纸专属)
  107.         ITEM_ATTRIBUTE_SYN_BID_2_COUNT = 0x28,    //合成道具2的BID数量(图纸专属)
  108.         ITEM_ATTRIBUTE_SYN_BID_3_COUNT = 0x29,    //合成道具3的BID数量(图纸专属)
  109.         ITEM_ATTRIBUTE_SYN_BID_4_COUNT = 0x2A,    //合成道具4的BID数量(图纸专属)
  110.         ITEM_ATTRIBUTE_SYN_BID_5_COUNT = 0x2B,    //合成道具5的BID数量(图纸专属)
  111.         ITEM_ATTRIBUTE_SYN_NEWID_COUNT = 0x2C,    //合成新道具BaseID的数量
  112.         ITEM_ATTRIBUTE_SYN_NEWID_1     = 0x2D,    //新BaseID1
  113.         ITEM_ATTRIBUTE_SYN_NEWID_2     = 0x2E,    //新BaseID2
  114.         ITEM_ATTRIBUTE_SYN_NEWID_3     = 0x2F,    //新BaseID3
  115.         ITEM_ATTRIBUTE_SYN_NEWID_C_1   = 0x30,    //新BaseID1数量
  116.         ITEM_ATTRIBUTE_SYN_NEWID_C_2   = 0x31,    //新BaseID2数量
  117.         ITEM_ATTRIBUTE_SYN_NEWID_C_3   = 0x32,    //新BaseID3数量
  118. };
  119. enum ENUM_ITEM_CLASS:uint8
  120. {
  121.         ITEM_CLASS_SKILL_BOOK = 0x00,    //技能书
  122.         ITEM_CLASS_BOX        = 0x01,    //盒子(容器)
  123.         ITEM_CLASS_EQUIPMENT  = 0x02,    //装备
  124.         ITEM_CLASS_RESOURCE   = 0x03,    //材料
  125.         ITEM_CLASS_JEWEL      = 0x04,    //宝石
  126.         ITEM_CLASS_RUNE       = 0x05,    //符文
  127.         ITEM_CLASS_STONE      = 0x06,    //石头
  128.         ITEM_CLASS_TASK       = 0x07,    //任务道具
  129.         ITEM_CLASS_DRAW       = 0x08,    //图纸
  130. };
  131. #endif
复制代码
以上是我对我需要的道具的一般性兴义。
可能有些朋友问,你不是支持跨平台么,怎么还会有#include <WinSock2.h>
这里我要解释一下,我用它并非是用socket部分,而是用里面的字符大小端转换。
为了兼容系统,我所有道具数值序列化以后,都会以网络字序存放,当解开的时候,都会以主机字序展示。
在多一句嘴,我为了保证数据的一致性,对enum做了一些限制。
enum ENUM_ITEM_ATTRIBUTE:uint8
这类代码必须在C++0x下才能编译。

如果你不喜欢,直接把:后面的数据类型去掉就行了。

这里我定义了,我可能要用到的道具类型,当然,你可以扩展和删除,只要符合想要的条件就行。
另外,ENUM_ITEM_ATTRIBUTE是数组,你所有道具需要的属性都可以在这里定义。
这里要注意,这里的定义必须是连续的,因为,我需要用这个做数组的索引。
当你要扩展和删除你的属性的时候,必须修改MAX_ITEM_ATTRIBUTE的属性数组总个数
#define MAX_ITEM_ATTRIBUTE 51      //对象属性个数最大大小
这个宏会被程序调用,当程序启动的时候,会生成于此相等的一个数组。
MAX_ITEM_ATTRIBUTE 的值必须和ENUM_ITEM_ATTRIBUTE里面的数值一一对应才可以。
另外,由于我个人需要对64位长整形的字序转换,我自己实现了一个函数htonll和ntohll
因为我的道具唯一ID是uint64位的。
好了,这样材料就差不多了。
下一讲,我将会讲怎么设计道具对象。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?用户注册

×
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-12-23 04:28 , Processed in 0.035289 second(s), 8 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表