Unity结合Flask实现排行榜功能
业余做的小游戏,排行榜本来是用PlayerPrefs
存储在本地,现在想将数据放在服务器上。因为功能很简单,就选择了小巧玲珑的Flask来实现。
闲话少叙。首先考虑URL的设计。排行榜无非是一堆分数score
的集合,按照REST的思想,不妨将URL设为/scores
。用GET
获得排行榜数据,用POST
添加一条新纪录到排行榜。此外,按照惯例,排行榜的数据不需要更新和删除。
Flask自身不支持REST,但我们可以通过route
和method
自己实现。下面创建一个原型版本的rank_server.py
。命名沿袭了Rails的习惯:
from flask import Flask
app = Flask(__name__)
@app.route('/scores', methods=['GET'])
def index():
return 'index'
@app.route('/scores', methods=['POST'])
def create():
return 'create'
if __name__ == '__main__':
app.run(debug=True)
执行python rank_server.py
来启动自带的服务器。下面我们安装cURL
来测试应用。
brew install curl
测试GET
:
`curl -i -X GET 127.0.0.1:5000/scores`
测试POST
:
`curl -i -X POST 127.0.0.1:5000/scores`
-i
参数可以展示响应的头部信息,便于debug。-X
参数指定请求的方法method。
可以看到测试成功。
下面我们建立存储数据的表。本地测试我们使用sqlite,之后部署使用mysql。
建表文件create_rank.sql
内容如下:
DROP TABLE IF EXISTS rank;
CREATE TABLE rank(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL,
score INTEGER NOT NULL
);
Mac自带sqlite。执行下面语句导入sql文件:
sqlite3 rank.db < create_rank.sql
然后随便插入几条测试数据。如:
INSERT INTO rank (name, score) VALUES ('A', 100);
INSERT INTO rank (name, score) VALUES ('B', 200);
INSERT INTO rank (name, score) VALUES ('C', 300);
针对数据库,我们在rank_server.py
中加入下面一段代码,用于在请求前后处理数据库连接。
import sqlite3
DATABASE = 'rank.db'
@app.before_request
def before_request():
g.db = sqlite3.connect(DATABASE)
@app.teardown_request
def teardown_request(exception):
if hasattr(g, 'db'):
g.db.close()
我们规定服务器和客户端使用JSON
传输数据。
GET
请求返回的JSON
格式如下:
{
"data":
[
{
"id": 0,
"name": "A",
"score": 100
},
{
"id": 1,
"name": "B",
"score": 200
}
]
}
这里的id
其实是自增主键,可以不必保留,但为了后面处理方便就一起保留了。
POST
提交的JSON
格式如下:
{
"id": 0,
"name": "C",
"score": 300
}
现在我们可以着手实现index
方法了:
def index():
cur = g.db.execute('select id, name, score from rank order by score desc;')
result = cur.fetchmany(100)
data = []
for row in result:
data.append({'id': row[0], 'name': row[1], 'score': row[2]})
return jsonify({'data': data})
(其中jsonify
和g
在flask
模块内。后面不再对导入进行说明,默认都是从flask
导入。)
在查询时对数据做了排序,并且只返回了前100条记录。可以用curl
再测试一下。测试无误再实现create
方法:
def create():
status = {'status': 'OK'}
if not request.json or not 'name' in request.json or not 'score' in request.json:
status['status'] = 'bad request'
try:
g.db.execute('insert into rank (name, score) values (?, ?)', [request.json['name'], request.json['score']])
g.db.commit()
except:
status['status'] = 'database error'
return jsonify(status)
我们的POST
请求都是JSON
类型的,所以要从request.json
获得,而不是args
或者form
。此外,返回了一个status
变量,便于查看出错原因。
再用curl
测试一下POST
。这次,我们要向POST
请求中加入数据:
curl -i -X POST -H "Content-Type: application/json" -d '{"id": 0, "name": "xyz", "score": "800"}' 127.0.0.1:5000/scores
-H
参数用于指定头部信息,-d
参数可以携带数据,这里就是一条符合我们提交格式的JSON
数据。
现在服务器端就(暂时)实现完了。下面该写C#代码啦。
我们需要设计一个和服务器交互、并返回数据给UI层的类。
首先,这个类应该是单例的,要继承MonoBehaviour
(因为和服务器交互要利用Coroutine
);而且最好独立于场景之外。关于Unity中实现单例类的集中方式,请看我的另一篇文章。单例的代码如下:
private static SaveLoad _instance = null;
public static SaveLoad Instance {
get
{
if (_instance == null)
{
GameObject go = new GameObject("SaveLoadGameObject");
DontDestroyOnLoad(go);
_instance = go.AddComponent<SaveLoad>();
}
return _instance;
}
}
还需要定义一些常量:
const int recordsPerPage = 5;
const string URL = "127.0.0.1:5000/scores";
定义一个数据结构:
public struct Data {
public int id;
public string name;
public int score;
}
在动手之前,还要了解两个东西:WWW
类和LitJson
库。WWW
类是Unity自带的处理HTTP请求的类;LitJson
是一个C#处理JSON
的开源库。要使用LitJson
,先从官网下载dll文件,然后导入Asset。
SaveLoad
类的功能就像名字一样,包括保存Save
和载入Load
。
public void Save(Data data)
{
var jsonString = JsonMapper.ToJson(data);
var headers = new Dictionary<string, string> ();
headers.Add ("Content-Type", "application/json");
var scores = new WWW (URL, new System.Text.UTF8Encoding ().GetBytes (jsonString), headers);
StartCoroutine (WaitForPost (scores));
}
IEnumerator WaitForPost(WWW www){
yield return www;
Debug.Log (www.text);
}
这里创建WWW
实例,指定了URL、header和提交数据。第一行的JsonMapper
可以在对象和JSON
之间进行转换,前提是对象中的属性和JSON
中的键要保持一致。
public void Load()
{
var scores = new WWW (URL);
StartCoroutine(WaitForGet(scores));
}
IEnumerator WaitForGet(WWW www){
yield return www;
if (www.error == null && www.isDone) {
var dataList = JsonMapper.ToObject<DataList>(www.text);
data = dataList.data;
}else{
Debug.Log ("Failed to connect to server!");
Debug.Log (www.error);
}
}
Load
方法中是将前面index
方法返回的JSON
文本转换成对象,这里为了实现转换,新建一个DataList
类,其中的属性是List<Data>
。
到这里,客户端的读取和保存数据就实现了。其余的逻辑,比如和UI的交互,在这里就不写了。感兴趣的可以看我的小游戏的完整代码。GitHub传送门
最后谈谈部署的事情。如果要部署到SAE有几点要注意:
代码要进行一定的修改以适应
MySQLdb
。要注意中文的编码。如用
unicode
方法转换名字属性,以及文件头部的:# -*- coding:utf8 -*-
#encoding = utf-8
最后说说比较坑的Unity跨域访问的限制。在我成功部署后,curl
测试没有问题了。结果Unity报了错:
SecurityException: No valid crossdomain policy available to allow access
经过一番搜索,原来要在服务器的根目录增加一个crossdomain.xml
文件。文件内容大致如下:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM
"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only"/>
<allow-access-from domain="*"/>
<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>
但是SAE好像不支持上传文件到根目录。只能用Flask仿冒一下了:
@app.route('/crossdomain.xml')
def fake():
xml = """上面的那堆内容"""
return xml, 200, {'Content-Type': 'text/xml; charset=ascii'}
OK,大功告成!
Unity结合Flask实现排行榜功能的更多相关文章
- 使用 Redis 实现排行榜功能
排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择. 一般排行榜都是有实效性的,比如“用户积分榜”.如果没有实效性一直按照总榜来排,可能榜首总是几个老用户,对 ...
- 使用 Redis 实现排行榜功能 (转载 https://segmentfault.com/a/1190000002694239)
排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择. 一般排行榜都是有实效性的,比如"用户积分榜".如果没有实效性一直按照总榜来排,可能榜 ...
- (NO.00001)iOS游戏SpeedBoy Lite成形记(二十八):增加排行榜功能
游戏大体上基本也就完成了,还差一个排行榜.否则如何激励各位选手创造新纪录呢? 排行榜功能也没什么难的,不过需要一点点排序的算法上的考虑. 这里我们把排行榜记录数据和排序都放在GameState类中,在 ...
- Redis实现排行榜功能(实战)
需求前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+1分,错误+0分,一人一场只能猜一次.1.展示前一百名列表.2.展示个人排名(如:张三,您当前的排名106579). ...
- Redis实现世界杯排行榜功能(实战)
转载请注明出处:https://www.cnblogs.com/wenjunwei/p/9754346.html 需求 前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+ ...
- Redis 有序聚合实现排行榜功能
排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择.Redis有序集合非常适用于有序不重复数据的存储 一般排行榜都是有实效性的,比如“用户积分榜”.如果没有实 ...
- Egret白鹭开发微信小游戏排行榜功能
推荐阅读: 我的CSDN 我的博客园 QQ群:704621321 我的个人博客 最近事情特别多,今天终于实现了排行榜功能,记录下来大家一起学习学习. 一.调用默认排行榜 首先我们需要了解: 1.白鹭开 ...
- Redis的Sorted-Sets排行榜功能实现
Redis的ZSet排行榜功能实现 1. 功能需求 类似给用户n张图片, 用户左滑不喜欢右滑喜欢.所以每个用户就会有一些喜欢的图片集合和不喜欢的图片集合.现在我们要做一个将按照一个算法将喜欢的排到前面 ...
- redis实现排行榜功能
目录 加入排行榜 操作排行榜 redis的zset可以很方便地用来实现排行榜功能,下面简单介绍python如何使用redis实现排行榜功能 加入排行榜 获取redis实例 import redis m ...
随机推荐
- nginx访问日志中添加接口返回值
因为nginx作为web服务器时,会代理后端的一些接口,这时访问日志中只能记录访问接口的status码,也就是说,只能获得200.404 这些的值 那么如何获得接口返回的response值呢? 下面开 ...
- haml
创建: 2019/05/23 文档: http://haml.info/docs/yardoc/file.REFERENCE.html 安装 安装 gem "haml" ...
- bzoj3876: [Ahoi2014&Jsoi2014]支线剧情(上下界费用流)
传送门 一道题让我又要学可行流又要学zkw费用流…… 考虑一下,原题可以转化为一个有向图,每次走一条路径,把每一条边都至少覆盖一次,求最小代价 因为一条边每走过一次,就要付出一次代价 那不就是费用流了 ...
- 关于写PPT
如果你要给别人讲东西,要记得你的受众的不同,你的讲法也应该有不同,侧重点应该有所区别. 如果作为一个老师,你的PPT应该是让人看懂,把人讲懂,这是你的最终目的所在.如果你是一个毕业生,你要围绕你要解决 ...
- AT2166 Rotate 3x3
传送门 这个题网上有两种做法,一种是树状数组的,还有一种是暴力模拟的,暴力模拟显然不够优美,所以我用的树状数组 显然可以从初状态推到目标状态,我们也可以考虑倒推回去 首先可以容易发现每列的数字是不变的 ...
- 20165224 陆艺杰 Exp 8 Web基础
.基础问题回答 (1)什么是表单 html的一个控件 表单在网页中主要负责数据采集功能 (2)浏览器可以解析运行什么语言 html xml jsp php python 等 (3)WebServer支 ...
- python 3.x 安装问题及连接oracle数据库
最近有用到python去处理一些问题,发现现在3已出来,遂用直接下3.7使用 发现问题还是有一点的 1. pip 会出现ssl问题 Could not install packages due to ...
- js 为对象添加和删除属性
对于一个普通的js对象: var obj = { name:"mary", age:21 } 如果我们要对它添加新属性的话可以使用下列方式: obj.address = " ...
- Sharepoint 根据文件相对路径获取、操作SPFolder
public AjaxResult LoadDocInfo(HttpContext httpContext) { var result = new ArrayList(); try { var org ...
- vue——做了一个幼稚的小页面
我的小花花没有转起来,不开心  ̄へ ̄