如何设计游戏中道具功能(一)
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就会消失。给追踪带来困难。
所以,在稀有道具中,对可叠加的物品,策划的时候就要专门考虑一下。
道具的限制,追踪是网游的基本功。这些事情一般的游戏服务器都是要干的。
好了,废话少说。先看看如果是我,我是怎么做的。
在动手前,我先罗列出了我需要对道具是用的一些基本材料,就像厨师一样,你要准备一些材料才能动手。
#ifndef _OBJECT_DEFINE_H
#define _OBJECT_DEFINE_H
//所有物品所需要的类型都在这里定义
//add by freeeyes
#include <stdio.h>
#include "stdint.h"
#include <WinSock2.h>
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;
#define MAX_ITEM_NAME 200 //对象名称最大长度
#define MAX_ITEM_DESC 1024 //对象描述最大长度
#define MAX_ITEM_ATTRIBUTE 51 //对象属性个数最大大小
#ifdef WIN32
#define MEMCOPY_SAFE(x, y, len) memcpy_s(x, len, y, len);
#else
#define MEMCOPY_SAFE(x, y, len) memcpy(x, y, len);
#endif
inline uint64_t htonll(uint64 v) {
union { uint32 lv; uint64 llv; } u;
u.lv = htonl(v >> 32);
u.lv = htonl(v & 0xFFFFFFFFULL);
return u.llv;
}
inline uint64_t ntohll(uint64 v) {
union { uint32 lv; uint64 llv; } u;
u.llv = v;
return ((uint64_t)ntohl(u.lv) << 32) | (uint64_t)ntohl(u.lv);
}
#define HTONS(x, y)y = htons(x);
#define HTONL(x, y)y = htonl(x);
#define HTON64(x, y) y = htonll(x);
#define NTOHS(x)x = ntohs(x);
#define NTOHL(x)x = ntohl(x);
#define NTOH64(x) x = ntohll(x);
//定义一个函数,可以支持内存越界检查
inline void sprintf_safe(char* szText, int nLen, const char* fmt ...)
{
if(szText == NULL)
{
return;
}
va_list ap;
va_start(ap, fmt);
#ifdef WIN32
vsnprintf_s(szText, nLen, nLen, fmt, ap);
#else
vsnprintf(szText, nLen, fmt, ap);
#endif
va_end(ap);
};
enum ENUM_OBJECT_TYPE:uint16 //对象类型
{
OBJECT_UNKNOW = 0x00, //未知
OBJECT_ITEM = 0x01, //道具
OBJECT_MAIL = 0x02, //邮件
};
enum ENUM_OBJECT_STATE:uint8//对象状态
{
OBJECT_UNKNOW_STATE= 0x00, //未知
OBJECT_DISPLAY_STATE = 0x01, //可视
OBJECT_INVALID_STATE = 0x02, //无效
};
enum ENUM_ITEM_ATTRIBUTE:uint8
{
ITEM_ATTRIBUTE_MIN_ATTACK = 0x00, //最小攻击
ITEM_ATTRIBUTE_MAX_ATTACK = 0x01, //最大攻击
ITEM_ATTRIBUTE_PHYSICS_DEFENCE = 0x02, //物理防御
ITEM_ATTRIBUTE_MAGIC_DEFENCE = 0x03, //魔法防御
ITEM_ATTRIBUTE_LIFE = 0x04, //生命
ITEM_ATTRIBUTE_MAGIC = 0x05, //魔法
ITEM_ATTRIBUTE_POWER = 0x06, //精力
ITEM_ATTRIBUTE_CRIT = 0x07, //暴击
ITEM_ATTRIBUTE_ATTACKSPEED = 0x08, //攻速
ITEM_ATTRIBUTE_HIT = 0x09, //命中
ITEM_ATTRIBUTE_DODGE = 0x0A, //躲闪
ITEM_ATTRIBUTE_RECOVER_LIFE = 0x0B, //生命回复
ITEM_ATTRIBUTE_RECOVER_MAGIC = 0x0C, //魔法回复
ITEM_ATTRIBUTE_SKILL_ATTACK = 0x0D, //攻击技能
ITEM_ATTRIBUTE_SKILL_P_DEFENCE = 0x0E, //物防技能
ITEM_ATTRIBUTE_SKILL_M_DEFENCE = 0x0F, //魔防技能
ITEM_ATTRIBUTE_SKILL_CRIT = 0x10, //暴击技能
ITEM_ATTRIBUTE_SKILL_HIT = 0x11, //命中技能
ITEM_ATTRIBUTE_SKILL_A_SPEED = 0x12, //攻速技能
ITEM_ATTRIBUTE_SKILL_DODGE = 0x13, //躲闪技能
ITEM_ATTRIBUTE_POSITION = 0x14, //位置
ITEM_ATTRIBUTE_CLASS = 0x15, //物品类型
ITEM_ATTRIBUTE_PHOTOID = 0x16, //图片ID
ITEM_ATTRIBUTE_BASEID = 0x17, //物品基础ID
ITEM_ATTRIBUTE_JEWEL_COUNT = 0x18, //孔槽个数
ITEM_ATTRIBUTE_JEWEL_1 = 0x19, //孔槽1
ITEM_ATTRIBUTE_JEWEL_2 = 0x1A, //孔槽2
ITEM_ATTRIBUTE_JEWEL_3 = 0x1B, //孔槽3
ITEM_ATTRIBUTE_JEWEL_4 = 0x1C, //孔槽4
ITEM_ATTRIBUTE_JEWEL_5 = 0x1D, //孔槽5
ITEM_ATTRIBUTE_VERSION = 0x1E, //道具版本号
ITEM_ATTRIBUTE_USE_LEVEL = 0x1F, //使用等级
ITEM_ATTRIBUTE_LEVEL = 0x20, //道具等级
ITEM_ATTRIBUTE_SYNTHETIC_COUNT = 0x21, //合成所需道具数量(图纸专属)
ITEM_ATTRIBUTE_SYN_BASEID_1 = 0x22, //合成道具1的BaseID(图纸专属)
ITEM_ATTRIBUTE_SYN_BASEID_2 = 0x23, //合成道具2的BaseID(图纸专属)
ITEM_ATTRIBUTE_SYN_BASEID_3 = 0x24, //合成道具3的BaseID(图纸专属)
ITEM_ATTRIBUTE_SYN_BASEID_4 = 0x25, //合成道具4的BaseID(图纸专属)
ITEM_ATTRIBUTE_SYN_BASEID_5 = 0x26, //合成道具5的BaseID(图纸专属)
ITEM_ATTRIBUTE_SYN_BID_1_COUNT = 0x27, //合成道具1的BID数量(图纸专属)
ITEM_ATTRIBUTE_SYN_BID_2_COUNT = 0x28, //合成道具2的BID数量(图纸专属)
ITEM_ATTRIBUTE_SYN_BID_3_COUNT = 0x29, //合成道具3的BID数量(图纸专属)
ITEM_ATTRIBUTE_SYN_BID_4_COUNT = 0x2A, //合成道具4的BID数量(图纸专属)
ITEM_ATTRIBUTE_SYN_BID_5_COUNT = 0x2B, //合成道具5的BID数量(图纸专属)
ITEM_ATTRIBUTE_SYN_NEWID_COUNT = 0x2C, //合成新道具BaseID的数量
ITEM_ATTRIBUTE_SYN_NEWID_1 = 0x2D, //新BaseID1
ITEM_ATTRIBUTE_SYN_NEWID_2 = 0x2E, //新BaseID2
ITEM_ATTRIBUTE_SYN_NEWID_3 = 0x2F, //新BaseID3
ITEM_ATTRIBUTE_SYN_NEWID_C_1 = 0x30, //新BaseID1数量
ITEM_ATTRIBUTE_SYN_NEWID_C_2 = 0x31, //新BaseID2数量
ITEM_ATTRIBUTE_SYN_NEWID_C_3 = 0x32, //新BaseID3数量
};
enum ENUM_ITEM_CLASS:uint8
{
ITEM_CLASS_SKILL_BOOK = 0x00, //技能书
ITEM_CLASS_BOX = 0x01, //盒子(容器)
ITEM_CLASS_EQUIPMENT= 0x02, //装备
ITEM_CLASS_RESOURCE = 0x03, //材料
ITEM_CLASS_JEWEL = 0x04, //宝石
ITEM_CLASS_RUNE = 0x05, //符文
ITEM_CLASS_STONE = 0x06, //石头
ITEM_CLASS_TASK = 0x07, //任务道具
ITEM_CLASS_DRAW = 0x08, //图纸
};
#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位的。
好了,这样材料就差不多了。
下一讲,我将会讲怎么设计道具对象。
页:
[1]