您的位置:首页 > 其它

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

2014-11-04 09:19 267 查看
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[2]; uint64 llv; } u;

u.lv[0] = htonl(v >> 32);

u.lv[1] = htonl(v & 0xFFFFFFFFULL);

return u.llv;

}

inline uint64_t ntohll(uint64 v) {

union { uint32 lv[2]; uint64 llv; } u;

u.llv = v;

return ((uint64_t)ntohl(u.lv[0]) << 32) | (uint64_t)ntohl(u.lv[1]);

}

#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位的。

好了,这样材料就差不多了。

下一讲,我将会讲怎么设计道具对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: