51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2249|回复: 1
打印 上一主题 下一主题

[转贴] Unity结合Flask实现排行榜功能

[复制链接]
  • TA的每日心情
    无聊
    3 天前
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2016-5-31 11:19:10 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

    业余做的小游戏,排行榜本来是用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'}
    转自:http://www.uml.org.cn/Test/201503235.asp
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-24 04:29 , Processed in 0.074631 second(s), 27 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表