一款网络游戏的设计,至少需要两种数据库。策划数据库是表示游戏玩法规则的数据库;用户数据库是表示玩家个人信息的数据库。除了这两类基本的数据库以外,还有其他数据库。例如有些跨服玩法需要配置数据库来寻找其他服务节点的链路地址;有些架构把日志放到独立的日志数据库进行统一管理,等等。

本文主要介绍玩法配置数据库与玩家用户数据库。

策划数据库的概念

策划数据库,顾名思义,是策划童鞋用于描述他心目中理想游戏世界的手段,是游戏的规则。例如,玩家当前级别可拥有的最大体力值是多少,长到下一级别需要获得多少经验,各种游戏规则的设定都是通过该数据库里的各种数据表进行控制。也就是说,策划配置表是游戏的玩法,因此,除了策划童鞋之外,绝不允许开发人员乱修改表内容。我曾呆过的一家游戏项目,经常看到开发新手不小心在代码里修改了策划数值,导致游戏规则被修改了。这可是要扣绩效的啊!!

用户数据库的概念

以前玩街机游戏的时候,玩家的数据是无法保存的,一旦断电了,那么就GameOver了。在网络游戏时代,游戏数据三是长时间保存的,那么就需要数据库来保存玩家的个人信息。打个比方,今天运气非常好,打野怪刷到了一把极品装备,如果没有持久化机制,那么玩家下线后再来玩,装备就不见了。玩家数据是玩家的私有财产,如果代码不小心把玩家的数据弄脏了,那么就一定要想方设法来帮助玩家恢复数据或进行游戏道具补偿。玩家数据库除了保存个人数据之外,还会保存一些公共数据,比如帮派数据是整个帮派成员共有的。

数据库ORM方案

不管是什么数据库,都会涉及到数据的增删查改操作。ORM(对象关系映射)是解决这些繁琐重复工作的利器。需要注意的是,策划配置表属于游戏规则,开发人员一般只有读取的权限,而没有修改的权限。

本文所采用的ORM框架在之前的文章自定义orm框架解决玩家数据持久化问题 已有详细介绍,这里不作详细介绍。

需要说明的是,orm工具这里采用的数据库连接池改为Proxool库;为了统一处理策划库与用户库,DbUtils工具类的多个方法增加一个参数,表示对应的数据库别名。例如:

/**

* 查询返回一个bean实体

* @param alias 数据库别名

* @param sql

* @param entity

* @return

*/

@SuppressWarnings("unchecked")

public static  T queryOne(String alias, String sql, Class> entity){

}

配置数据库的设计

从策划童鞋的角度上看,配置数据就是一张一张的excel表格。开发人员根据策划的表设计,转化成对应的数据库表格式。程序启动的时候,就会将所有的数据库表读取到缓存里,这样程序的逻辑就会按给定的数值进行运行。当然,策划表格不一样只能从数据库读取,有些项目连数据库都取消了。策划配置的表格,通过一种导表程序,转换为xml文件或者csv文件,程序一样可以读取到内存。但个人感觉,还是采用数据库处理配置比较方便,毕竟数据库对开发人员来说比较友好。

下边说明一下建立一张配置表的步骤:

1. 建立数据表结构(这个结果及即可以有程序制定,也可以由策划制定,看项目),并加入若干测试数据

DROP TABLE IF EXISTS `configplayerlevel`;

CREATE TABLE `configplayerlevel` (

`level` int(11) DEFAULT NULL,

`needExp` bigint(20) DEFAULT NULL,

`vitality` int(11) DEFAULT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of configplayerlevel

-- ----------------------------

INSERT INTO `configplayerlevel` VALUES ('1', '2345', '100');

INSERT INTO `configplayerlevel` VALUES ('2', '23450', '105');

2. 定义数据实体

/**

* 玩家等级配置表

* @author kingston

*/

@Entity(readOnly = true)

public class ConfigPlayerLevel {

/**

* 等级

*/

@Column

private int level;

/**

* 升到下一级别需要的经验

*/

@Column

private long needExp;

/**

* 最大体力

*/

@Column

private int vitality;

public int getLevel() {

return level;

}

public void setLevel(int level) {

this.level = level;

}

public long getNeedExp() {

return needExp;

}

public void setNeedExp(long needExp) {

this.needExp = needExp;

}

public int getVitality() {

return vitality;

}

public void setVitality(int vitality) {

this.vitality = vitality;

}

}

3. 为了方便管理表数据,对于每一张表都定义一个容器

/**

* 玩家等级配置表

* @author kingston

*/

public class ConfigPlayerLevelContainer implements Reloadable{

private Map levels = new HashMap<>();

@Override

public void reload() {

String sql = "SELECT * FROM ConfigPlayerLevel";

List datas = DbUtils.queryMany(DbUtils.DB_DATA, sql, ConfigPlayerLevel.class);

//使用jdk8,将list转为map

levels = datas.stream().collect(

Collectors.toMap(ConfigPlayerLevel::getLevel, e -> e));

}

public ConfigPlayerLevel getConfigBy(int level) {

return levels.get(level);

}

}

4. 容器表都实现Reloadable接口,该接口只有一个抽象方法,这样方便服务启动的时候能统一管理

public interface Reloadable {

/**

* 重载数据

*/

void reload();

}

5. 为了方便管理所有表数据,我们再定义一个配置数据池,每一个配置容器都在这里进行申明。这样做可以很方便在生产环境进行热更新配置,关于热更新配置的做法,后面文章再详细介绍。该数据池还需要提供一个公有方法用于读取全部配置数据。

/**

* 所有策划配置的数据池

* @author kingston

*/

public class ConfigDatasPool {

private static ConfigDatasPool instance = new ConfigDatasPool();

private ConfigDatasPool() {}

public static ConfigDatasPool getInstance() {

return instance;

}

public ConfigPlayerLevelContainer configPlayerLevelContainer = new ConfigPlayerLevelContainer();

/**

* 起服读取所有的配置数据

*/

public void loadAllConfigs() {

Field[] fields = ConfigDatasPool.class.getDeclaredFields();

ConfigDatasPool instance = getInstance();

for (Field f:fields) {

try {

if (Reloadable.class.isAssignableFrom(f.getType())) {

Reloadable container = (Reloadable) f.getType().newInstance();

System.err.println(f.getType());

container.reload();

f.set(instance, container);

}

}catch (Exception e) {

LoggerUtils.error("策划配置数据有误,请检查", e);

System.exit(0);

}

}

}

}

用户数据库设计

1. 用户数据表的设计是由开发人员在实现业务需求时自行设计的。以前的一篇文章游戏服务器关于玩家数据的解决方案 详细说明了两种用户数据设计策略。由于当前涉及的用户信息非常少,作为演示,我们只用到一张数据表。(针对不同业务所需要的用户信息保存方式,以后再作详细展开)。用户表的设计如下

DROP TABLE IF EXISTS `player`;

CREATE TABLE `player` (

`id` bigint(20) NOT NULL,

`name` varchar(255) DEFAULT NULL  COMMENT '昵称',

`job` tinyint(4) DEFAULT NULL  COMMENT '职业',

`level` int(11) DEFAULT '1' COMMENT '等级',

`exp` bigint(20) DEFAULT 0  COMMENT '经验' ,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. 用户数据是需要持久化的,所以我们需要借助orm框架的 AbstractCacheable类。同时为了能够将用户数据放入哈希容器,我们有必要重写object类的equals()和hashCode()方法。于是,有了下面的抽象类

/**

* db实体基类

* @author kingston

*/

public abstract class BaseEntity> extends AbstractCacheable

implements Serializable {

private static final long serialVersionUID = 5416347850924361417L;

public abstract Id getId() ;

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((getId()==null)?0:getId().hashCode());

return result;

}

@SuppressWarnings("rawtypes")

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

BaseEntity other = (BaseEntity) obj;

if (getId() != other.getId())

return false;

return true;

}

}

3. 定义用户模型Player类,该类只需要继承上面的BaseEntity抽象类即可。是不是很方便 ^_^

@Entity

public class Player extends BaseEntity{

private static final long serialVersionUID = 8913056963732639062L;

@Id

@Column

private long id;

@Column

private String name;

/**

* 职业

*/

@Column

private int job;

@Column

private int level;

@Column

private long exp;

public Player() {

this.id = IdGenerator.getUid();

}

@Override

public Long getId() {

return id;

}

public void setId(long id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getJob() {

return job;

}

public void setJob(int job) {

this.job = job;

}

public int getLevel() {

return level;

}

public void setLevel(int level) {

this.level = level;

}

public long getExp() {

return exp;

}

public void setExp(long exp) {

this.exp = exp;

}

@Override

public String toString() {

return "Player [id=" + id + ", name=" + name + ", job=" + job

+ ", level=" + level + ", exp=" + exp + "]";

}

}

用户数据异步持久化

当玩家的数据发生变动时,我们需要将最新的数据保存到数据库。这里有一个问题,当玩家数据有部分变动的时候,我们不可能即使保存到数据库的,这样对数据库的压力太大。所以,我们需要有独立线程来完成数据的异步保存。这里又要搬出我们可爱的生产者消费者模型啦。

/**

* 用户数据异步持久化的服务

* @author kingston

*/

public class DbService {

private static volatile DbService instance;

public static DbService getInstance() {

if (instance ==  null) {

synchronized (DbService.class) {

if (instance ==  null) {

instance = new DbService();

}

}

}

return instance;

}

/**

* 启动消费者线程

*/

public void init() {

new Thread(new Worker()).start();

}

@SuppressWarnings("rawtypes")

private BlockingQueue queue = new BlockingUniqueQueue<>();

private final AtomicBoolean run = new AtomicBoolean(true);

public void add2Queue(BaseEntity> entity) {

this.queue.add(entity);

}

private class Worker implements Runnable {

@Override

public void run() {

while(run.get()) {

try {

BaseEntity> entity = queue.take();

saveToDb(entity);

} catch (InterruptedException e) {

LoggerUtils.error("", e);

}

}

}

}

/**

* 数据真正持久化

* @param entity

*/

private void saveToDb(BaseEntity> entity) {

entity.save();

}

}

到这里,关于配置数据库和用户数据库的概念及实现就介绍完毕了。

热血江湖数据库MYSQL修改_手游服务端框架之配置与玩家数据库设计相关推荐

  1. 修改手游服务器端数据库,手游服务端框架之配置与玩家数据库设计

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 用户数据异步持久化 当玩家的数据发生变动时,我们需要将最新的数据保存到数据库.这里有一个问题,当玩家数据有部分变动的时候,我们不可能即使保存到数据库的,这 ...

  2. 手游服务端框架之自定义orm持久化工具

    前面一篇文章手游服务端框架之关于玩家数据的解决方案,介绍了当今游戏服务端对玩家数据进行持久化的两种方案.一种是将玩家数据通过json等格式统一打包成字符串或二进制流:另一种是根据模块功能拆分,一个模块 ...

  3. 手游服务端框架之后台管理工具

    后台管理工具在游戏运营中的作用 手游版本的更新迭代是非常频繁的,有些项目甚至每个星期都会进行停服更新.也就是说,对于生产环境的游戏进程,我们必须有工具能够对游戏服务进行维护.例如更新维护,或者对游戏内 ...

  4. 手游服务端框架之合区工程

    如今的游戏服务器运营策略一般为"滚服模式".简单来说,就是运营商不停的开新区,不活跃的旧区就进行合区.这样一来,运营商既可以降低服务器硬件配置,缓解大服人数压力,也可以利用新区拉活 ...

  5. mysql在手游中的作用_数据库虚拟化技术_手游业务MySQL数据库虚拟化漫谈 | By 肖力-云栖社区...

    作者简介 肖力, 资深运维专家,拥有15年运维经验,就职于金山西山居,担任系统运维经理,曾就职于盛大游戏,在运维圈有极大的影响力.国内最顶尖的KVM专家之一,从2009年开始研究KVM技术,是国内较早 ...

  6. 热血江湖数据库MYSQL修改_热血江湖SQL数据库架设方法

    SQL数据库架设方法 第一步:首先是下载SQLSERVER啦. 第二步:安装程序选择SQL2000组件就是第一个--安装数据库服务器还是第一个一直下一步本地计算机一直默认下一步到选择本地系统用户--到 ...

  7. 热血江湖数据库MYSQL修改_热血江湖门派端数据库详细解释(mysql)

    数据库:          表:                列: rxjhaccount┬tbl_account┬fld_id  帐号 │                  ├fld_passwo ...

  8. 热血江湖数据库MYSQL修改_求更多热血江湖私服 修改数据库语句

    --将所有防具的防御力增加F99*4updatetbl_dropsetfld_magic1=20000099,fld_magic2=20000099,fld_magic3=20000099,fld_m ...

  9. mysql在手游中的作用_手游业务MySQL数据库虚拟化漫谈 | By 肖力

    作者简介 肖力, 资深运维专家,拥有15年运维经验,就职于金山西山居,担任系统运维经理,曾就职于盛大游戏,在运维圈有极大的影响力.国内最顶尖的KVM专家之一,从2009年开始研究KVM技术,是国内较早 ...

  10. 传奇手游服务器搭建_复古传奇手游服务端架设教程

    浙江复古传奇手游服务端架设教程地处长三角中心位置,复古传奇手游服务端架设教程具有独特的地理和爆光柱的传奇手游商业优势,交通便利,复古传奇魔龙怎么去运输快捷. 在传奇私服游戏中,是进级兵器能力晋升游戏兴 ...

最新文章

  1. php读取西门子plc_基于Socket访问西门子PLC系列教程(二)
  2. Centos7 防火墙 firewalld 实用操作
  3. 010-你觉得单元测试可行吗
  4. Linux内核分析 - 网络[六]:网桥
  5. Java 中序列化与反序列化
  6. sicily 1762. 排座椅
  7. [数据库] Navicat for Oracle基本用法图文介绍
  8. TableView下拉图片放大
  9. java stub_Java Stub 研究学习(2)
  10. $.getjson异常信息提示_8种信息类型,中后台产品功能自查清单
  11. 数据结构—二叉树的遍历
  12. html div重叠上方,html – 将父div上方的子div对齐到右边,不要重叠
  13. CSS Reset的相关概念及实例
  14. python自动写工作日志_python自动化执行重复工作
  15. Android全局设置APP为黑白模式的两种方案
  16. 最优化理论与算法(袁亚湘)学习笔记---最优性条件和最优化算法的基本结构
  17. qq邮箱对方服务器退回,为什么我用QQ邮箱发邮件被退回来了?他说地 – 手机爱问...
  18. Android 检索相册视频文件
  19. oracle中时间大小比较函数,Oracle比较时间大小
  20. 九龙证券|AI重塑半导体基础设施,人工智能发展持续加速

热门文章

  1. idea无法下载源代码
  2. 半次元收藏夹爬虫(残疾版,不喜勿喷)
  3. C语言练习-还原算术表达式
  4. selenium自动化从0开始学习
  5. SSD、eMMC、UFS的区别
  6. 【https】利用keytool进行证书配置
  7. 计算机语言排行榜2015,2015年3月编程语言排行榜:F#排名达到11
  8. 英语学习第5篇_并列句
  9. Microsoft Excel 教程:如何在 Excel 中创建图表?
  10. HTMLCSS仿京东注册页面制作静态页面总结