H5游戏服务端(六)
对接完客户端协议之后, 基本上暂时可以没什么需要与客户端对接的;
接下来则是对接策划工具了, 更进一步说是 excel 表导出工具, 用来映射成类表格式, 表格的格式:
- 第一行: 转化的字段名, 该字段只允许英文字母等特殊符号, 用来给静态类设定名称
- 第二行: 转化的类型, 用来给强类型数据转化声明, 这里可以采用 JSON 类型风格处理
设定下表格风格如下, 这里以设定场景地图 场景配置#SceneTable.csv 方式:
| id | resource | name | award |
|---|---|---|---|
| 标识 | 场景 | 名称 | 解锁奖励 |
| 0 | res://Scenes/Login/Main.tscn | 登录页面 | [] |
| 1 | res://Scenes/Home/Main.tscn | 玩家首页 | [“100_1”,“200_2”] |
| 2 | res://Scenes/CityLight/Main.tscn | 游戏城镇A | [“100_1”,“200_2”] |
| 3 | res://Scenes/CityMoon/Main.tscn | 游戏城镇B | [“100_1”,“200_2”] |
这里就是比较简单的配置表工具, 预留前三列作为数据配置转化风格, 注意 id 是 int 类型且唯一存在, 最后转化成 Java 结果类:
/**
* 场景配置
*/
public class SceneTable {
/**
* 场景配置 - 数据行
*/
public static class Row {
public int id;
public String resource;
public String[] award;
}
/**
* 静态数据数据行
*/
public static final Map<Integer, Row> Rows = new HashMap<>() {{
put(1, new Row() {{
id = 1;
resource = "res://Scenes/Login/Main.tscn";
award = new String[]{
"100_1",
"200_2"
};
}});
}};
}
注意: 上面仅仅是渲染 Java 静态样例并不是最终样例, 如果要做到可用至少要实现 C#|Lua|Python 这几种静态实现.
策划对接
策划数据基本上是游戏用到最频繁的数据, 所以值得放在进程堆内存数据之中;
上面的 Java 样例其实是有问题的, 最核心的就是没办法动态获取数据, 要知道策划需求变动是极其频繁的,
所以考验服务不停机能力!
注意: 策划相关功能核心点除了效率之外, 更核心的要求就是策划配置需要
不停机热更新, 热更新策划配置是绝对需要的.
在公司中后期的时候, 策划对于游戏内部要做大量数值版本测试, 甚至会花费几周测试几十个版本确定最后上架版本.
这里可以思考下, 怎么将策划数据表转化成程序?
- 首先策划配置表是要客户端和服务端共享的, 有跨平台需求(PC|Android|Web|Linux等)
- 其次不可能让策划手写代码将数据表转化成常量表
- 最后不可能让策划登录 Linux 复制配置之后重启服务
按照上面需求那么基本上能够满足的就是改成 JSON|XML 之类通用格式加载;
虽然类似 Lua 有热更新可以把静态元表( table )更新, 但是客户端可能采用 C#|Python 之类方案可能没办法直接使用.
这里推荐策划表数据采用 JSON 转化, 相对来数据量少且类型完备.
处理策划配置有好几种方案, 这里说下常见几种:
- 本地目录加载: 检索目录内部所有转化过JSON策划配置启动时候加载, 运维后台推送信号服务端去本地加载文件
- 数据库加载: 追加配置运维后台, 提供给策划自己去提交同步配置, 之后推送给游戏服务端进程信号让其同步配置
按照游戏规模和人员配置来选择, 最简单就是本地目录直接加载配置; 直接维护 SVN 版本库并设置 hook 更新的时候直接脚本做提交信号更新.
项目初期一般采用本地目录模式, 没有精力去维护专门策划后台管理, 所以只需要简单 svn+hook 处理下就行了.
这里编写 Python 脚本将 csv 处理成 json 数据( csv2json.py ):
# !/usr/bin/python
# -*- coding: UTF-8 -*-
import pathlib
import sys
import getopt
import json
import csv
# 查询目录所有csv文件
def search_csv_file(path):
return [f for f in pathlib.Path(path).iterdir() if f.is_file() and f.glob(".csv")]
# 入口方法
def main(argv):
input_dir = ''
output_dir = ''
encoding = 'utf-8'
delimiter = ','
try:
opts, args = getopt.getopt(argv, "hi:o:e:d:", ["input_dir=", "output_dir=", "encode=", "delimiter="])
except getopt.GetoptError:
print('csv2json.py -i <input_dir> -o <output_dir> -e <encode:(default utf-8)> -d <delimiter:(default ,)>')
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print("csv2json.py -i <input_dir> -o <output_dir> -e <encode:(default utf-8)> -d <delimiter:(default ,)>")
sys.exit()
elif opt in ("-i", "--input_dir"):
input_dir = arg
elif opt in ("-o", "--output_dir"):
output_dir = arg
elif opt in ("-e", "--encode"):
encoding = arg
elif opt in ("-d", "--delimiter"):
delimiter = arg
if len(input_dir) <= 0 or len(output_dir) <= 0:
print("找不到输入|输出目录信息")
sys.exit(1)
print("输入目录: ", input_dir)
print("输出目录: ", output_dir)
files = search_csv_file(input_dir)
if len(files) <= 0:
print("找不到策划文件")
sys.exit()
# 遍历数据
csv_list = []
for file in files:
pos = file.name.find("#")
if pos != -1:
name = file.name[pos + 1:]
name = name.replace(".csv", "")
if len(name) > 0:
name = name + ".json"
print("加载策划文件: ", name)
csv_list.append({
"file": file,
"name": name,
})
# 开始写入策划文件
for table in csv_list:
with open(table["file"], mode="r", encoding=encoding) as file:
reader = csv.reader(file, delimiter=delimiter)
# 获取首行类型数据
headers = next(reader)
if len(headers) <= 0:
break
# 遍历出类型数据
names = []
for header in headers:
names.append(header)
if len(names) <= 0:
print("表格错误", table["name"])
break
# 检索出注释, 用于给策划看的内容, 可以跳过
_descriptions = next(reader)
# 检索出数据
data = []
for row in reader:
# 没有数据就跳过
if len(row) <= 0:
break
# 筛选数据
lines = {}
for i, name in enumerate(names):
try:
value = row[i]
line = json.loads(value)
except IndexError:
value = None
except json.decoder.JSONDecodeError:
if isinstance(value, str):
line = str(value)
else:
line = None
lines[name] = line
data.append(lines)
# 写入文件
if len(data) > 0:
f = pathlib.Path(output_dir)
f = f.joinpath(table["name"])
fd = f.open(mode="w+", encoding="utf-8")
json.dump(data, fd, ensure_ascii=False)
# 入口调用
if __name__ == "__main__":
main(sys.argv[1:])
注意: csv 默认保存成 utf-8 格式, 不过也可以指定格式进行读取, 最后直接执行目录导出 Excel 数据:
py.exe .\tools\csv2json.py -i .\tables\ -o .\target\
最后会导出成 JSON 文件(SceneTable.json)来提供给客户端和服务端使用:
[
{
"id": 0,
"resource": "res://Scenes/Login/Main.tscn",
"name": "登录页面",
"award": []
},
{
"id": 1,
"resource": "res://Scenes/Home/Main.tscn",
"name": "玩家首页",
"award": [
"100_1",
"200_2"
]
},
{
"id": 2,
"resource": "res://Scenes/CityLight/Main.tscn",
"name": "游戏城镇A",
"award": [
"100_1",
"200_2"
]
},
{
"id": 3,
"resource": "res://Scenes/CityMoon/Main.tscn",
"name": "游戏城镇B",
"award": [
"100_1",
"200_2"
]
}
]
现在就拿到策划生成的源数据, 这里客户端和服务端拿到之后就不需要和策划, 而是直接在游戏需求里应用这些数据.