关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » UI前端 »Unity结合Flask实现排行榜功能

    Unity结合Flask实现排行榜功能

    2014-12-24 00:00:00 出处:ITJS
    分享

    业余做的小游戏,排行榜本来是用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,大功告成!

    本地的rank_server.py文件下载

    部署后的rank_server.py文件下载

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    商业智能成CIO优先关注点 技术落地方显成效(1)

    用linux安装MySQL时产生问题破解

    JAVA中关于Map的九大问题

    windows 7旗舰版无法使用远程登录如何开启telnet服务

    Android View 事件分发机制详解

    MySQL用户变量的用法

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。