文档章节

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

e哥
 e哥
发布于 2016/11/18 22:42
字数 3252
阅读 573
收藏 4
点赞 1
评论 0

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

       一看标题,你肯定会想,现在手机做游戏手柄的软件不是很多嘛,你的是不是比别人家要好啊?如果你问我这个问题,那我可以明确告诉你,本文不是做一个出色的游戏手柄而是通过做它来了解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都是通过模拟电脑按键的方式实现操控游戏,对小游戏尚可,大型游戏就行不通了。

© 著作权归作者所有

共有 人打赏支持
e哥
粉丝 7
博文 4
码字总数 7777
作品 0
深圳
小时候的感觉——小米蓝牙手柄评测

经过了2个晚上“雷电”的折磨,我也稍稍地开始对着满屏的子 弹有点手顺了,可以说说我对小米蓝牙手柄的感受了。(感谢@超重的小熊爱吃冰淇淋 提供测试样品) 游戏手柄的故事是这样的,首先,...

delxu ⋅ 2015/04/11 ⋅ 0

小鸡手柄和劲玩X3蓝牙手柄对比

这个时代变化真快。 智能手机的性能越来越强,现在已经具有2GB(甚至3GB)的内存和几十GB的闪存存储空间了。因此,在手机上通过模拟器来玩那些小时候家用游戏机上的游戏早已不是难事了。And...

delxu ⋅ 2014/07/13 ⋅ 0

Pico Neo VR一体机基础版测评:消费级的VR一体机这样玩

2017年到2018年,市面上出现多款VR一体机。在不少业内人士看来,VR一体机操作简便,性能可以满足一般消费者的需求,是VR向消费级市场普及的关键。 近日,雷锋网编辑拿到了Pico Neo VR一体机基...

李诗 ⋅ 04/28 ⋅ 0

Android 4.0 将支持 USB 游戏手柄

Android 4.0 将支持USB游戏手柄 和Android 3.0一样,Android 4.0将支持连接USB游戏手柄。Google的框架开发工程师Romain Guy在Twitter上确认了这个消息。同时Android 4.0也支持HDMI输出,这就...

虫虫 ⋅ 2011/10/25 ⋅ 17

变身游戏利器?这些古怪的功能型手机壳,让你惊讶脑洞还能这样开

不得不说,3D打印技术的普及,让手机壳这门脑洞玩法,有了更多的民间元素,而且中,必须要有的关键词是功能拓展。 否则,一个花哨的手机壳,就只是一个保护罩而已,无法惊艳。 比如2016年夏天...

张书乐 ⋅ 04/26 ⋅ 0

牛蛙/android-remote-control-computer

安卓控制电脑 项目是2012年4月份在校时候做的一个比赛项目 也是学习android后自己唯一做过的东西 以后就再没和android打过交道了 博文地址: http://blog.csdn.net/lujianing2011/article/d...

牛蛙 ⋅ 2014/06/22 ⋅ 0

Pico新公布的 Inside-Out 一体机也定价 3999,这是跟 HTC Vive 商量好的么?

回到 5 月份,Pico 在自家公司举办开放日,推出一体机 Pico Goblin 小怪兽,配备 3DoF 手,售价 1899 元。Pico 的 CEO 周宏伟曾多次表示 VR 一体机是未来。半年多后,赶在 2018 年元旦前,他...

田苗 ⋅ 2017/12/26 ⋅ 0

安卓手机控制电脑源代码开源

安卓控制电脑 项目介绍:http://www.oschina.net/p/android-remote-control-computer osc@git地址:http://git.oschina.net/lujianing/android-remote-control-computer 实现功能: 鼠标控制......

蛙牛 ⋅ 2014/06/22 ⋅ 5

Bluetooth :Connection refused问题

我先说一下我主要的工作,我在开发安卓蓝牙APP,目的是为了实现:在安卓电视上安装此APP,然后通过这个APP连接到蓝牙游戏手柄上。 跟大多数人一样,主要的问题出现在蓝牙的连接上。 在蓝牙连...

Jevons_z ⋅ 2014/05/27 ⋅ 2

安卓控制电脑

实现功能: 鼠标控制模式: 鼠标的移动 鼠标左键/右键点击 鼠标滑轮 文件的拖动 自定义音量键功能 键盘输入模式: 向电脑发送文字(中英) 回车 退格 以及DOS下的输入 电脑方向键的控制 WSAD...

蛙牛 ⋅ 2014/06/22 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring表达式语言(SpEL)

1、SpEL引用 Spring EL在bean创建时执行其中的表达式。此外,所有的Spring表达式都可以通过XML或注解的方式实现。下面将使用Spring表达式语言(SpEL),注入字符串,整数,Bean到属性。 SpEL的...

霍淇滨 ⋅ 22分钟前 ⋅ 0

Gradle使用阿里云镜像

gradle 生命周期中有一个初始化( Initialization )的过程,这个过程运行在 build script 之前,我们可以在这个地方做一点系统全局的设置,如配置仓库地址。 你可以在以下几个位置实现仓库地址...

明MikeWoo ⋅ 31分钟前 ⋅ 0

appium+python3.6

1.安装jdk1.8(不知道为啥只识别1.8,1.10不识别,所以为了少折腾,迁就安装1.8) http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 配置 JAVA_HOME:...

Kampfer ⋅ 49分钟前 ⋅ 0

详解Apache 日志分割教程

一、日志切割 安装cronolog CentOS 5.3中编译安装Apache日志默认是不切割的,需要用用工具Cronnolog进行日志切割。 1.下载及安装 wget http://cronolog.org/download/cronolog-1.6.2.tar.gz ...

dragon_tech ⋅ 52分钟前 ⋅ 0

Keepalived介绍

负载均衡器(Load Balancer, LB )是一组能够将IP数据流以负载均衡形式转发到多台物理服务器的集成软件。有硬件负载均衡器和软件负载均衡器之分,硬件负载均衡器主要是在访问网络和服务器之间...

寰宇01 ⋅ 52分钟前 ⋅ 0

java8-Collections and Streams

stream和集合的区别是什么? 1.在计算的时候处理不同, 2.every element should be computed in the memory and then to be part of collections stream Stream apis filter with a predica......

writeademo ⋅ 57分钟前 ⋅ 0

Confluence 6 重新获得附件指南

每一个文件在恢复上传到 Confluence 的时候必须单独重命名,你可以通过下面说明的 3 个方法中选择一个进行操作: 选择 A - 通过文件名恢复附件 如果你知道你需要恢复的每一个文件名,尤其是你...

honeymose ⋅ 今天 ⋅ 0

【每天一个JQuery特效】根据状态确定是否滑入或滑出被选元素

主要效果: 本文主要采用slideToggle()方法实现以一行代码同时实现以展开或收缩的方式显示或隐藏被选元素。 主要代码如下: <!DOCTYPE html><html><head><meta charset="UTF-8">...

Rhymo-Wu ⋅ 今天 ⋅ 0

度量.net framework 迁移到.net core的工作量

把现有的.net framework程序迁移到.net core上,是一个非常复杂的工作,特别是一些API在两个平台上还不能同时支持。两个类库的差异性,通过人工很难识别全。好在微软的工程师们考虑到了我们顾...

李朝强 ⋅ 今天 ⋅ 0

请不要在“微服务”的狂热中迷失自我!

微服务在过去几年一直是一个非常热门的话题(附录1)。何为“微服务的疯狂”,举个例子: 众所周知,Netflix在DevOps上的表现非常棒。Netfix可以做微服务。因此:如果我做微服务,我也将非常...

harries ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部