# 基于WebSocket和Python服务端的CSI检测可视化网页上位机 - 续 - Chrome小恐龙 ## 0. 前言 继[之前那个CSI可视化](csi.html)之后,我收到反馈说这个可视化还不够直观,能不能再直观一点。好吧,灵感来了,让CSI的读值去控制Chrome小恐龙。 --- ## 1. 系统框图 ![系统框图](csi.png) 系统框图没有变,仍然假设服务端通过串口收到CSI信息,然后通过WebSocket把CSI的结果传给客户端。这次只需要修改客户端,让Chrome小恐龙的网页去接收CSI的结果。 --- ## 2. 小恐龙的实现 网络上有不少对小恐龙源码的解析[[1](https://www.cnblogs.com/undefined000/p/trex_1.html)] [[2](https://github.com/wayou/t-rex-runner)],或者直接问AI也行。简而言之小恐龙游戏的核心逻辑是: * 碰撞检测机制用于判断是否Game Over * 按下空格时跳跃 * 速度(currentSpeed)是一个随时间增长的变量,它通过动画被用户感知到: * 小恐龙本身的绝对位置不变,改变背景景物的移动速度让人以为小恐龙在往前跑。背景景物的移动速度与currentSpeed成正比。 * 小恐龙两只脚交替的动画帧率越高,用户以为小恐龙跑得越快。帧率和currentSpeed的关系可以自定义函数来计算。 要让小恐龙可视化CSI的读值,首先要禁用碰撞检测,否则会严重影响游戏体验。然后有两种思路: 1. 用挥手(CSI读数为1)代替空格控制跳跃,**或** 2. CSI检测到用户动得越快(读数持续为1),小恐龙跑得越快 本文接下来会基于[[2](https://github.com/wayou/t-rex-runner)]的简化版代码做修改来实现这几件事。 ### 禁用碰撞检测 正常的代码会先进行碰撞检测,如果没有撞到才会更新下一帧。这里直接把`if (!collison)`后面那段整个注释掉,碰撞就不会有任何效果了。 注释掉这一段同时也会让速度不再随时间增长。 ``` javascript // Check for collisions. var collision = hasObstacles && checkForCollision(this.horizon.obstacles[0], this.tRex); if (!collision) { this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; if (this.currentSpeed < this.config.MAX_SPEED) { this.currentSpeed += this.config.ACCELERATION; } } else { this.gameOver(); } ``` ### CSI控制移动速度 在入口函数里,和Runner有关的函数都定义完之后,建立一个WebSocket连接并在回调函数里把currentSpeed设置成接收到的值。 ``` javascript var socket = new WebSocket("ws://localhost:4200"); socket.onmessage = function(event) { var data = JSON.parse(event.data); var currentSpeed=data.x * 12; window['Runner'].instance_.currentSpeed=currentSpeed; window['Runner'].instance_.tRex.updateMSPerFrame(currentSpeed); }; ``` 另外找到updateMSPerFrame这个函数,它的作用是修改小恐龙的脚蹬得有多快。这里靠目测拟合了一个msPerFrame关于currentSpeed的指数函数。 ``` javascript updateMSPerFrame: function (currentSpeed) { if (this.status === Trex.status.RUNNING) { //this.msPerFrame = 2000 - 550 * Math.sqrt(currentSpeed); this.msPerFrame = 2000 * Math.exp(-0.268 * currentSpeed); } }, ``` 修改后的代码在[git仓库](https://dizong.top/git/dinoUI.repo.git/)里。 ### CSI触发跳跃 这件事的本质是要用“CSI接收到1”这个事件代替“按下空格”。所以只需要把“按下空格”的响应函数复制到WebSocekt的回调函数里。 “按下空格”的响应函数如下,其中`if (!this.crashed && (Runner.keycodes.JUMP[e.keyCode] || e.type == Runner.events.TOUCHSTART))`的部分用来初始化游戏,以及让非跳跃状态的小恐龙开始跳跃。 ``` javascript onKeyDown: function (e) { // Prevent native page scrolling whilst tapping on mobile. if (IS_MOBILE && this.playing) { e.preventDefault(); } if (e.target != this.detailsButton) { if (!this.crashed && (Runner.keycodes.JUMP[e.keyCode] || e.type == Runner.events.TOUCHSTART)) { if (!this.playing) { this.loadSounds(); this.playing = true; this.update(); if (window.errorPageController) { errorPageController.trackEasterEgg(); } } // Play sound effect and jump on starting the game for the first time. if (!this.tRex.jumping && !this.tRex.ducking) { this.playSound(this.soundFx.BUTTON_PRESS); this.tRex.startJump(this.currentSpeed); } } if (this.crashed && e.type == Runner.events.TOUCHSTART && e.currentTarget == this.containerEl) { this.restart(); } } ``` 把这一段响应复制到WebSocket的回调函数里即可。 ``` javascript var socket = new WebSocket("ws://localhost:4200"); socket.onmessage = function(event) { var data = JSON.parse(event.data); // var currentSpeed=data.x * 12; // window['Runner'].instance_.currentSpeed=currentSpeed; // window['Runner'].instance_.tRex.updateMSPerFrame(currentSpeed); var runner=window['Runner'].instance_; if (!runner.crashed && data.x>0.5) { if (!runner.playing) { runner.loadSounds(); runner.playing = true; runner.update(); if (window.errorPageController) { errorPageController.trackEasterEgg(); } } // Play sound effect and jump on starting the game for the first time. if (!runner.tRex.jumping && !runner.tRex.ducking) { runner.playSound(runner.soundFx.BUTTON_PRESS); runner.tRex.startJump(runner.currentSpeed); } } }; ``` --- ## 参考资料 1. [Chrome自带恐龙小游戏的源码研究](https://www.cnblogs.com/undefined000/p/trex_1.html) 2. [简化版T-Rex Runner](https://github.com/wayou/t-rex-runner)