关闭 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文件下载

    上一篇返回首页 下一篇

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

    别人在看

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

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

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

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    IT头条

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    更容易爆炸?罗马仕充电宝被北京多所高校禁用,公司紧急回应

    17:19

    天衍”量子计算云平台,“超算+量算” 告别“算力孤岛时代”

    18:18

    华为Pura80系列新机预热,余承东力赞其复杂光线下的视频拍摄实力

    01:28

    技术热点

    MySQL基本调度策略浅析

    MySQL使用INSERT插入多条记录

    SQL Server高可用的常见问题

    3D立体图片展示幻灯片JS特效

    windows 7上网看视频出现绿屏的原因及解决方法

    windows 7 64位系统的HOSTS文件在哪里?想用它加快域名解析

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

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