您的位置:首页 > Web前端 > HTML5

手机做游戏手柄:python+websocket+html5

2016-11-18 00:00 381 查看
摘要: 手机通过浏览器访问由python在电脑上创建的服务器网页,使手机和电脑建立websocket连接。手机会把重力加速度的一些处理信息发给电脑,然后这些信息通过电脑端的python程序来模拟电脑按键,从而实现操控电脑游戏。

现在写博客取标题真头疼,写技术类喜欢取基于什么技术的开发,我又感觉太土了就没加基于两字,然后把技术词放冒号后面,换汤不换药,唉。希望标题没有打消你往下读的欲望。

一看标题,你肯定会想,现在手机做游戏手柄的软件不是很多嘛,你的是不是比别人家要好啊?如果你问我这个问题,那我可以明确告诉你,本文不是做一个出色的游戏手柄而是通过做它来了解python+websocket+html5三者怎么整合使用。



看了上面图,你会想怎么来实现把手机当成电脑游戏手柄的吗?你可以停下几分钟思考一下。

你可能会想到使用Java,C++之类的,如果我现在限定你使用python+websocket+html5,你打算怎么实现呢?可以想一下,是不是和我等下要介绍的的方法一样。

有人就在说了,python、websocket、html5是什么我都不了解怎么想啊。好了,那我就来简单的介绍下它们吧:

python:强大的粘合性语言,它可以和很多语言嵌套使用,它有强大的API包,你只需导入包就可以使用包里面的api接口了。主要运行在pc上。

websocket:一种全双工通信协议,在TCP协议上形成。它和语言、平台无关,只要遵循它的协议就能通信。它和html5有着千丝万缕的联系,在html家族中它和ajax有着同等的地位。

html5:新一代html标准,主要运行在浏览器上。在手机浏览器上发挥重要作用,它能读取手机的重力传感器、螺旋仪传感器数值,也能读取手机touch坐标。

上面的介绍才叫简单介绍嘛,不像有些明明说好了简介还大串大串的介绍。

现在来说下本文实现的思路吧。本文想在电脑端用python模拟电脑按键,达到操控游戏目的。在手机端用html5获取手机重力传感信息和按键信息,然后通过websocket通道把信息传给电脑端的python。python通过获得的信息来模拟对应的按键。

思路已经有了,接下来就是怎么实现了。现在把自己想成是《手机做游戏手柄》项目的负责人,你手下有python、websocket、html5三名同事。你已经了解了它们各自的特点和功能,现在你要召集它们分配给它们任务了。

你(项目负责人,人称老大):

上头给我们这个组安排了个项目《手机做游戏手柄》要求我们三天完成。项目需求书我已经看了,也发到你们邮箱了。我的思路是balabala。。。。如果大家没意见那我们就这样执行。python负责电脑端,模拟按键的工作就交个你了。html5负责手机端,读取用户手机操作数据。websocket你就协助python和html5的工作,他们两语言不通。

websocket:

老大又安排我做跑腿的工作,唉。

我先把我的方法发给他们吧,等待他们的呼唤。

先跟python大哥发过去,python版

01 from bottle import request, Bottle, abort
02 app = Bottle()
03 @app.route('/websocket')
04 def handle_websocket():
05     wsock = request.environ.get('wsgi.websocket')
06     if not wsock:
07         abort(400, 'Expected WebSocket request.')
08     while True:
09         try:
10             message = wsock.receive()
11             wsock.send("Your message was: %r" % message)
12         except WebSocketError:
13             break
14
15 from gevent.pywsgi import WSGIServer
16 from geventwebsocket import WebSocketError
17 from geventwebsocket.handler import WebSocketHandler
18 server = WSGIServer(("0.0.0.0", 8080), app, handler_class=WebSocketHandler)
19 server.serve_forever()

python大哥应该安装了bottle(pip install bottle)和gevent-websocket(pip install gevent-websocket)。python大哥可以通过第11行的wsock.send()发信息给html5老兄,也可以通过第10行的wsock.receive()接收html5老兄的信息。对了得告诉python大哥第18行的ip地址和端口号要和html5老兄一样,否则他们联系不上了。

再给html5老兄发个javascript版本的

1 var ws = new WebSocket("ws://0.0.0.0:8080/websocket");
2 ws.onopen = function() {
3 	ws.send("Hello, websocket");
4 };
5 ws.onmessage = function (evt) {
6 	alert(evt.data);
7 };

这里也得告诉html5老兄第1行的ip地址和端口号要和python大哥保持一致。

html5应该知道向python发数据应该在websocket被打开后吧。通过ws.send()发数据,ws.onmessage可以监听到python发给他的数据。

下面就看他们两个了,没事我刷会儿微信先~~~

python:

websocket老弟真积极,老大刚放话他就把方法发过来了,我也的抓紧点。

我得先在电脑端创建服务器,这样手机端才能访问html5。反正我包多,创建服务器分分钟的事。

01 from bottle import run, route, static_file
02
03 @route('/')
04 def index():
05     return static_file('index.html', './')
06
07 @route('/resource/<filename>')
08 def staticFile(filename):
09     return static_file(filename, './resource')
10
11 run(host='192.168.21.101', port='8080')

用bottle包创建(详细信息可以参考我上篇博客《通过WEB控制树莓派RGB灯光》)。

接下来就是通过websocket接收html5的信息然后根据键码模拟电脑按键了。

01 import win32api
02 import win32con
03 import time
04 from bottle import request, Bottle, abort
05 from gevent.pywsgi import WSGIServer
06 from geventwebsocket import WebSocketError
07 from geventwebsocket.handler import WebSocketHandler
08
09 app = Bottle()
10 @app.route('/websocket')
11 def handle_websocket():
12     wsock = request.environ.get('wsgi.websocket')
13     if not wsock:
14         abort(400, 'Expected WebSocket request.')
15     while True:
16         try:
17             message = wsock.receive()
18             print(message)
19             if message == 'reload':
20                 win32api.keybd_event(82, 0, 0, 0)
21                 time.sleep(0.1)
22                 win32api.keybd_event(82, 0, win32con.KEYEVENTF_KEYUP, 0)
23             elif message == 'shoot_start':
24                 win32api.keybd_event(32, 0, win32con.KEYEVENTF_KEYUP, 0)
25                 win32api.keybd_event(32, 0, 0, 0)
26             elif message == 'shoot_end':
27                 time.sleep(0.1)
28                 win32api.keybd_event(32, 0, win32con.KEYEVENTF_KEYUP, 0)
29             elif message == 'leftward':
30                 win32api.keybd_event(37, 0, 0, 0)
31                 time.sleep(0.1)
32                 win32api.keybd_event(37, 0, win32con.KEYEVENTF_KEYUP, 0)
33             elif message == 'rightward':
34                 win32api.keybd_event(39, 0, 0, 0)
35                 time.sleep(0.1)
36                 win32api.keybd_event(39, 0, win32con.KEYEVENTF_KEYUP, 0)
37             elif message == 'forward':
38                 win32api.keybd_event(38, 0, 0, 0)
39                 time.sleep(0.1)
40                 win32api.keybd_event(38, 0, win32con.KEYEVENTF_KEYUP, 0)
41             elif message == 'backward':
42                 win32api.keybd_event(40, 0, 0, 0)
43                 time.sleep(0.1)
44                 win32api.keybd_event(40, 0, win32con.KEYEVENTF_KEYUP, 0)
45             else:
46                 wsock.send(message)
47             wsock.send('reset')
48         except WebSocketError:
49             break
50
51 server = WSGIServer(("192.168.21.101", 8081), app, handler_class=WebSocketHandler)
52 server.serve_forever()

为了要模拟电脑按键,我得根据电脑系统下载pywin32这样才可导入win32api、win32con。按照websocket老弟说的方法安装了gevent-websocket(pip install gevent-websocket)。从第19行到第44行是按键模拟实现过程,模拟按键时一定记得要释放按键(KEYEVENTF_KEYUP)。为什么每次都要用time.sleep(0.1)呢?我发现如果去除不用,就不能模拟按键了,为什么时间是0.1s,我是根据人的反应时间0.1s设置的,可以换成其他值,不同的值操作游戏的体验不一样。太小游戏移动慢,大了移动就快了有点飘飘的感觉。为什么在第47行给html5发wsock.send('reset'),担心html5发数据太快了,快的我都没处理完上个数据。所以发“reset”的目的就是告诉html5上个数据已处理完可以发下个数据了。第51行的ip地址和端口和html5要保持一致。

我的工作已经完成接下来就看html5老弟怎么弄了~~~

html5:

websocket老兄、python大哥都弄好了,我也得抓紧了,看样子这个周末又是加班的节奏~~~

还好他们两个都弄好了,我现在就参考他们的把自己的那份实现了就OK了。

只能用javascript代码来实现了。创建websocket通信直接用websocket老兄发过来的代码就行了,获取手机的重力传感信息和按键信息也不难。创建个canvas把手柄画面绘制在canvas上,然后获取重力传感用window.ondevicemotion,获取按键坐标用myCanvas.ontouchstart。接下来就是要弄清楚用户什么时候是向左、向右、向上、向下等。

01	var websocketBusyFlag = 0;
02	var WS = null;
03	if(window.WebSocket)
04	{
05		WS = new WebSocket('ws://192.168.21.101:8081/websocket');	//注意:更改ip地址和端口号,要和python代码一致
06	}
07	else
08	{
09		alert("你的浏览器不支持websocket");
10	}
11	WS.onopen = function() {
12		WS.send('hello websocket!');
13	}
14	WS.onmessage = function(e) {
15		var message = e.data;
16		if(message == 'reset') websocketBusyFlag = 0;
17		else alert(message);
18	}
19
20	/**********************操作事件<开始>***************************/
21	//装子弹按键中心坐标:(x=65,y=115),有效半径:r=55
22	//射击按键中心坐标:(x=65,y=385),有效半径:r=55
23	var ReX = 65, ReY = 115, ReR = 55;
24	var SeX = 65, SeY = 385, SeR = 55;
25	myCanvas.ontouchstart = function(e) {
26		var x = e.touches[0].clientX;
27		var y = e.touches[0].clientY;
28		//装子弹
29		if(Math.sqrt((x - ReX)*(x - ReX) + (y - ReY)*(y - ReY)) <= ReR)
30		{
31			WS.send('reload');
32		}
33		//射击开始
34		if(Math.sqrt((x - SeX)*(x - SeX) + (y - SeY)*(y - SeY)) <= SeR)
35		{
36			WS.send('shoot_start');
37		}
38		event.preventDefault();
39	}
40	myCanvas.ontouchmove = function(e){
41		event.preventDefault();
42	}
43	myCanvas.ontouchend = function(e) {
44		WS.send('shoot_end');
45		event.preventDefault();
46	}
47	myCanvas.ontouchcancel = function(e) {
48		WS.send('shoot_end');
49		event.preventDefault();
50	}
51
52	var last_x = 0;
53	var last_y = 0;
54	if(!window.DeviceMotionEvent)
55	{
56		alert('你的浏览器不支持手机重力传感');
57	}
58	else
59	{
60		//方向获取
61		window.ondevicemotion = function(e) {
62			if(websocketBusyFlag) return;
63
64			var x = e.accelerationIncludingGravity.x;
65			var y = e.accelerationIncludingGravity.y;
66
67			var direction = null;
68			if(x > 7.5 && x > last_x)
69			{
70				direction = 'backward';	//减速
71				last_x = x;
72			}
73			else if(x < -1.0 && x < last_x)
74			{
75				direction = 'forward';	//加速
76				last_x = x;
77			}
78			else last_x = 0;
79
80			if(y < -3 && y < last_y)
81			{
82				direction = 'leftward';	//向左
83				last_y = y;
84			}
85			else if(y > 3 && y > last_y)
86			{
87				direction = 'rightward';	//向右
88				last_y = y;
89			}
90			else last_y = 0;

用户触摸浏览器的时候有时会被浏览器认为是在切换网页操作,我得阻止这种事情发生,玩游戏的时候切到其他网页那还得了啊,没法玩了。我要在每个touch事件上加上event.preventDefault()阻止touch事件上抛给浏览器,不让它切页面和其他操作。

python大哥会给我发“reset”信息是为了防止他没处理完信息时我再给他发信息,那我得弄个标识量websocketBusyFlag,我给python发信息的时候websocketBusyFlag就置1,下次再给python发信息的时候我要判断websocketBusyFlag,如果为1就不发了,为0就发。当接收到python发过来的“reset”时websocketBusyFlag置0。

第68行到90行是判断用户操作的方向。x、y是重力加速度在x和y方向的数值。最大9.8最小-9.8。我的思路是:当用户要向左,那么y就会变小,当小于一个阈值时那我就认为是“向左”操作。

但现在出现个情况:比如说“向左”是y<-3,也就是y=-4也是“向左”,y=-5也是“向左”,那么问题来了。如果此时y=-5,当用户想向右时,y的值应该是由-5逐渐增大,途中肯定会经过-4.5、-4。当经过-4.5的时候-4.5<-3,所以也会被认为是“向左”,但此时用户明明已经有向右的趋势了,虽然在现实生活中继续“向左”是没错的,但在游戏中这种体验不是很好,所以我给方向判断多加了个条件last_x、last_y。只有x、y满足了阈值和last_x、last_y两个条件才能被认定为完成某项操作。比方说y=-4,last_y=0,此时y<-3 和y<last_y,此时会被认定为“向左”,然后last_y会被更新last_y=-4,下次y=-3.5,虽然y<-3但y>last_y就不会认定为“向左”。

终于写完了,现在就把python大哥和websocket老兄一起叫过来,联调了~~~

来看看效果吧:





优酷视频:

http://v.youku.com/v_show/id_XMTgyNDUxMDg0NA==.html?spm=a2hzp.%208244740%20.0.0.5uY9nS&from=y1.7-1.2

本文详细代码及示例flash游戏:https://git.oschina.net/ginnywzj/python_websocket_html5

本文提及的知识点:

1、websocket:http://www.cnblogs.com/wei2yi/archive/2011/03/23/1992830.html

2、pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/

3、bottle:http://www.bottlepy.org/docs/dev/

4、python对应电脑键码:http://blog.sina.com.cn/s/blog_5eeb1e2f0101ax1x.html

5、html5 touch事件:https://developer.mozilla.org/en-US/docs/Web/API/Touch_events

6、gevent-websocket:https://bitbucket.org/noppo/gevent-websocket

7、html5重力感应:http://www.haorooms.com/post/html5_DeviceMotionEvent

后记:

在这之前我用过ajax实现手机做为电脑的游戏手柄。ajax不能全双工,只能客服端向服务器请求数据,服务器只能被动发数据,所以需要轮询的方式不停的发出请求,如果网络一卡或者请求速度过快,游戏根本操作不了。websocket是全双工通信方式,客服端和服务器能同时相互传数据。用websocket做手柄感觉稍微比ajax好些,但操作游戏还是不流畅,没有达到直接用电脑键盘的效果,原因可能是代码没有优化或者说websocket实时性也不是特别高。游戏对时间很敏感。无论是用ajax还是websocket都是通过模拟电脑按键的方式实现操控游戏,对小游戏尚可,大型游戏就行不通了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Python WebSocket Html5