[项目] 体感跃动:基于物体追踪的HTML小鸟躲避游戏
在开始之前我想说:大家一定一定一定一定一定要更新固件,一步一步的跟着官方教程来,别想自己琢磨,我的一大半时间都在反思哪里程序编错了。
【演示视频】
【项目背景】
deep seek制作HTML网页炉火纯青了已经,我们不妨用它做个小游戏。提示词很简单,我稍后附上。
体感交互为传统游戏带来了全新的操控维度与沉浸体验。本项目灵感源于经典的“Flappy Bird”游戏,但摒弃了传统的键盘或触摸操作,创新性地利用二哈识图2(HuskyLens)AI视觉传感器与行空板K10,将现实世界中物体的移动实时映射为游戏内角色的控制。玩家通过移动一个被追踪的实物(如彩色积木、特定卡片),即可控制屏幕中的小鸟上下飞行、躲避障碍。该项目不仅是一个有趣的游戏,更是一个融合了计算机视觉、嵌入式硬件通信(串口)和现代Web前端技术(Web Serial API)的综合性实践案例,生动展示了如何将AI识别能力转化为直观的互动应用。
【功能和亮点】
1. 创新的体感操控:完全脱离传统输入设备,通过追踪真实物体的垂直(Y轴)移动来控制游戏角色,实现“所见即所控”的自然交互。
2. 硬件与Web的无缝集成:利用行空板K10作为串口网关,接收二哈识图2的识别数据,并通过Web Serial API与浏览器中的HTML5游戏进行实时通信,构建了从硬件传感器到网页应用的完整链路。
3. 完整的游戏化设计:游戏具备完整的逻辑,包括随机生成的障碍物、递增的游戏速度、得分与最高分记录系统,以及游戏状态(开始、暂停、结束)管理,提供流畅的游戏体验。
4. 实时数据可视化仪表盘:游戏界面侧边实时显示来自硬件的串口原始数据、解析后的物体Y坐标、映射后的小鸟位置百分比等关键信息,便于调试和观察数据流转。
5. 多重控制模式与兼容性:在串口控制之外,保留了键盘(空格键/方向键)和鼠标/触摸控制作为备用方案,确保在各种环境下均可游玩,提升了项目的可用性和演示稳定性。
6. 响应式前端界面:采用现代CSS技术构建,界面美观且适配不同尺寸的屏幕,从桌面浏览器到移动设备都能获得良好的视觉体验。
7. 模块化与可扩展的代码结构:JavaScript代码将游戏逻辑、渲染绘图、串口通信等模块清晰分离,并提供了易于修改的数据解析函数,方便开发者适配不同的硬件数据格式或扩展新功能。
【硬件清单】
步骤1 二哈识图2更新固件并设置:
1.更新固件:
https://wiki.dfrobot.com.cn/_SKU_SEN0638_Gravity_HUSKYLENS_2_AI_Camera_Vision_Sensor#7.%20%E5%9B%BA%E4%BB%B6%E6%9B%B4%E6%96%B0
2.开启二哈识图2,选择“物体追踪”功能。
将镜头对准你想要用作控制器的物体(确保颜色或形状与背景对比明显),触摸屏框选目标物对其进行识别学习。
确保其输出信息中包含物体框的中心点Y坐标。
步骤2 软件环境准备
软件环境准备:
在一台安装有Google Chrome 的电脑上,将本项目提供的HTML文件保存至本地。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>体感控制:小鸟躲避障碍物</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial Rounded MT Bold', 'Arial', sans-serif;
}
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #fff;
min-height: 100vh;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.header {
text-align: center;
margin-bottom: 20px;
width: 100%;
max-width: 800px;
}
h1 {
color: #4cc9f0;
text-shadow: 0 0 10px rgba(76, 201, 240, 0.5);
margin-bottom: 10px;
font-size: 2.5rem;
}
.subtitle {
color: #b8b8d1;
margin-bottom: 15px;
font-size: 1.1rem;
}
.game-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 800px;
gap: 20px;
}
.control-panel {
background: rgba(30, 30, 46, 0.8);
border-radius: 15px;
padding: 20px;
width: 100%;
border: 2px solid #4cc9f0;
box-shadow: 0 0 15px rgba(76, 201, 240, 0.3);
}
.serial-controls {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
align-items: center;
}
.serial-info {
background: rgba(20, 20, 35, 0.9);
border-radius: 10px;
padding: 15px;
margin-top: 10px;
}
.info-line {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid #33334d;
}
.info-line:last-child {
margin-bottom: 0;
border-bottom: none;
}
.info-label {
color: #b8b8d1;
}
.info-value {
color: #4cc9f0;
font-weight: bold;
}
button {
background: linear-gradient(to right, #4361ee, #3a0ca3);
color: white;
border: none;
padding: 12px 24px;
border-radius: 50px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(67, 97, 238, 0.3);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(67, 97, 238, 0.5);
background: linear-gradient(to right, #4895ef, #4361ee);
}
button:active {
transform: translateY(1px);
}
button:disabled {
background: #555;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
#gameCanvas {
border-radius: 10px;
border: 3px solid #4cc9f0;
box-shadow: 0 0 20px rgba(76, 201, 240, 0.4);
background-color: #0d1b2a;
display: block;
}
.instructions {
background: rgba(30, 30, 46, 0.8);
border-radius: 15px;
padding: 20px;
width: 100%;
margin-top: 10px;
border-left: 5px solid #f72585;
}
.instructions h3 {
color: #f72585;
margin-bottom: 10px;
}
.instructions ol {
padding-left: 20px;
line-height: 1.6;
}
.instructions li {
margin-bottom: 8px;
}
.instructions code {
background: rgba(20, 20, 35, 0.9);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
color: #90e0ef;
}
.status {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
background-color: #f72585;
}
.status.connected {
background-color: #4ade80;
box-shadow: 0 0 8px #4ade80;
}
.game-stats {
display: flex;
justify-content: space-around;
width: 100%;
margin-top: 10px;
}
.stat-box {
background: rgba(30, 30, 46, 0.8);
border-radius: 10px;
padding: 15px;
text-align: center;
flex: 1;
margin: 0 5px;
border-top: 4px solid #4361ee;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: #4cc9f0;
}
.stat-label {
color: #b8b8d1;
font-size: 0.9rem;
margin-top: 5px;
}
@media (max-width: 768px) {
.serial-controls {
flex-direction: column;
align-items: stretch;
}
button {
width: 100%;
}
h1 {
font-size: 2rem;
}
#gameCanvas {
width: 95%;
height: auto;
}
}
</style>
</head>
<body>
<div class="header">
<h1>🐦 物体追踪体感小鸟游戏</h1>
<p class="subtitle">通过二哈识图2 + 行空板K10控制 | 使用Web Serial API读取串口数据</p>
</div>
<div class="game-container">
<div class="control-panel">
<h2>📡 串口连接控制</h2>
<div class="serial-controls">
<button id="connectBtn">
<span class="status" id="statusIndicator"></span>
连接串口
</button>
<button id="disconnectBtn" disabled>断开连接</button>
<button id="startGameBtn" disabled>开始游戏</button>
<button id="pauseGameBtn" disabled>暂停游戏</button>
<button id="resetGameBtn">重置游戏</button>
</div>
<div class="serial-info">
<div class="info-line">
<span class="info-label">连接状态:</span>
<span class="info-value" id="connectionStatus">未连接</span>
</div>
<div class="info-line">
<span class="info-label">串口数据:</span>
<span class="info-value" id="serialData">等待数据...</span>
</div>
<div class="info-line">
<span class="info-label">小鸟Y坐标:</span>
<span class="info-value" id="birdPosition">50%</span>
</div>
<div class="info-line">
<span class="info-label">物体追踪Y值:</span>
<span class="info-value" id="trackingY">-</span>
</div>
</div>
</div>
<canvas id="gameCanvas" width="800" height="500"></canvas>
<div class="game-stats">
<div class="stat-box">
<div class="stat-value" id="score">0</div>
<div class="stat-label">得分</div>
</div>
<div class="stat-box">
<div class="stat-value" id="highScore">0</div>
<div class="stat-label">最高分</div>
</div>
<div class="stat-box">
<div class="stat-value" id="obstaclesPassed">0</div>
<div class="stat-label">通过障碍</div>
</div>
<div class="stat-box">
<div class="stat-value" id="gameSpeed">1.0</div>
<div class="stat-label">游戏速度</div>
</div>
</div>
<div class="instructions">
<h3>🎮 游戏使用说明</h3>
<ol>
<li><strong>硬件连接</strong>: 确保二哈识图2已通过串口连接到行空板K10,并正确输出物体追踪的Y坐标数据。</li>
<li><strong>数据格式</strong>: 行空板K10需要通过串口输出Y坐标值(如 <code>Y:125</code> 或 <code>{"y": 125}</code>)。你可能需要根据实际情况修改代码中的<code>parseSerialData</code>函数。</li>
<li><strong>开始游戏</strong>: 点击"连接串口"选择行空板设备,连接成功后点击"开始游戏"。</li>
<li><strong>游戏控制</strong>: 移动被追踪的物体(如彩色小球)来控制小鸟上下飞行,躲避障碍物。</li>
<li><strong>游戏规则</strong>: 每通过一个障碍物得1分,游戏速度会逐渐增加。碰撞到障碍物或上下边缘则游戏结束。</li>
</ol>
<p style="margin-top: 10px; color: #f72585; font-weight: bold;">⚠️ 注意: Web Serial API仅在现代浏览器(Chrome/Edge 89+)中可用。如无法连接,请检查浏览器版本和权限设置。</p>
</div>
</div>
<script>
// 游戏状态变量
let game = {
running: false,
score: 0,
highScore: parseInt(localStorage.getItem('highScore')) || 0,
obstaclesPassed: 0,
speed: 1.0,
birdY: 250,
birdSize: 30,
birdVelocity: 0,
gravity: 0.2,
lift: -5,
obstacles: [],
obstacleWidth: 60,
gap: 150,
frameCount: 0,
obstacleFrequency: 120
};
// 串口相关变量
let port = null;
let reader = null;
let readCancelled = false;
let trackingY = 0;
let lastTrackingY = 0;
// DOM元素
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const connectBtn = document.getElementById('connectBtn');
const disconnectBtn = document.getElementById('disconnectBtn');
const startGameBtn = document.getElementById('startGameBtn');
const pauseGameBtn = document.getElementById('pauseGameBtn');
const resetGameBtn = document.getElementById('resetGameBtn');
const statusIndicator = document.getElementById('statusIndicator');
const connectionStatus = document.getElementById('connectionStatus');
const serialData = document.getElementById('serialData');
const birdPosition = document.getElementById('birdPosition');
const trackingYElement = document.getElementById('trackingY');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('highScore');
const obstaclesPassedElement = document.getElementById('obstaclesPassed');
const gameSpeedElement = document.getElementById('gameSpeed');
// 初始化游戏
function initGame() {
game.running = false;
game.score = 0;
game.obstaclesPassed = 0;
game.speed = 1.0;
game.birdY = canvas.height / 2;
game.birdVelocity = 0;
game.obstacles = [];
game.frameCount = 0;
updateStats();
draw();
}
// 更新统计显示
function updateStats() {
scoreElement.textContent = game.score;
highScoreElement.textContent = game.highScore;
obstaclesPassedElement.textContent = game.obstaclesPassed;
gameSpeedElement.textContent = game.speed.toFixed(1);
birdPosition.textContent = Math.round((game.birdY / canvas.height) * 100) + '%';
}
// 绘制游戏
function draw() {
// 清空画布
ctx.fillStyle = '#0d1b2a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制背景网格
drawGrid();
// 绘制小鸟
drawBird();
// 绘制障碍物
drawObstacles();
// 绘制游戏状态信息
drawGameInfo();
// 如果游戏运行中,更新游戏状态
if (game.running) {
updateGame();
requestAnimationFrame(draw);
}
}
// 绘制背景网格
function drawGrid() {
ctx.strokeStyle = 'rgba(76, 201, 240, 0.1)';
ctx.lineWidth = 1;
// 垂直线
for (let x = 0; x < canvas.width; x += 50) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
// 水平线
for (let y = 0; y < canvas.height; y += 50) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
}
// 绘制小鸟
function drawBird() {
// 小鸟主体
ctx.fillStyle = '#4cc9f0';
ctx.beginPath();
ctx.ellipse(100, game.birdY, game.birdSize, game.birdSize * 0.8, 0, 0, Math.PI * 2);
ctx.fill();
// 小鸟眼睛
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(115, game.birdY - 5, 8, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(118, game.birdY - 5, 4, 0, Math.PI * 2);
ctx.fill();
// 小鸟翅膀
ctx.fillStyle = '#4895ef';
ctx.beginPath();
ctx.ellipse(90, game.birdY + 10, 15, 10, Math.PI / 4, 0, Math.PI * 2);
ctx.fill();
// 小鸟嘴巴
ctx.fillStyle = '#f72585';
ctx.beginPath();
ctx.moveTo(130, game.birdY);
ctx.lineTo(150, game.birdY - 5);
ctx.lineTo(150, game.birdY + 5);
ctx.closePath();
ctx.fill();
}
// 绘制障碍物
function drawObstacles() {
game.obstacles.forEach(obstacle => {
// 上障碍物
ctx.fillStyle = '#4361ee';
ctx.fillRect(obstacle.x, 0, game.obstacleWidth, obstacle.top);
// 下障碍物
ctx.fillRect(obstacle.x, canvas.height - obstacle.bottom, game.obstacleWidth, obstacle.bottom);
// 障碍物边缘高光
ctx.fillStyle = '#3a0ca3';
ctx.fillRect(obstacle.x, obstacle.top - 10, game.obstacleWidth, 10);
ctx.fillRect(obstacle.x, canvas.height - obstacle.bottom, game.obstacleWidth, 10);
// 障碍物通过区域(间隙)
ctx.fillStyle = 'rgba(76, 201, 240, 0.2)';
ctx.fillRect(obstacle.x, obstacle.top, game.obstacleWidth, canvas.height - obstacle.top - obstacle.bottom);
});
}
// 绘制游戏信息
function drawGameInfo() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.font = '16px Arial';
ctx.fillText(`得分: ${game.score}`, 20, 30);
ctx.fillText(`最高分: ${game.highScore}`, 20, 60);
ctx.fillText(`速度: ${game.speed.toFixed(1)}x`, 20, 90);
// 显示追踪数据
ctx.fillText(`物体Y: ${trackingY}`, canvas.width - 150, 30);
ctx.fillText(`小鸟Y: ${Math.round(game.birdY)}`, canvas.width - 150, 60);
// 游戏状态提示
if (!game.running && game.score === 0) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center';
ctx.fillText('连接串口并点击"开始游戏"', canvas.width / 2, canvas.height / 2);
ctx.font = '18px Arial';
ctx.fillText('用追踪物体控制小鸟上下飞行', canvas.width / 2, canvas.height / 2 + 30);
ctx.textAlign = 'left';
}
if (!game.running && game.score > 0) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
ctx.font = 'bold 32px Arial';
ctx.textAlign = 'center';
ctx.fillText('游戏结束!', canvas.width / 2, canvas.height / 2 - 30);
ctx.font = '24px Arial';
ctx.fillText(`最终得分: ${game.score}`, canvas.width / 2, canvas.height / 2 + 10);
ctx.fillText('点击"重置游戏"重新开始', canvas.width / 2, canvas.height / 2 + 50);
ctx.textAlign = 'left';
}
}
// 更新游戏状态
function updateGame() {
game.frameCount++;
// 应用重力
game.birdVelocity += game.gravity;
game.birdY += game.birdVelocity * game.speed;
// 基于物体追踪控制小鸟
controlBirdFromTracking();
// 边界检测
if (game.birdY < game.birdSize) {
game.birdY = game.birdSize;
game.birdVelocity = 0;
}
if (game.birdY > canvas.height - game.birdSize) {
game.birdY = canvas.height - game.birdSize;
game.birdVelocity = 0;
}
// 生成新障碍物
if (game.frameCount % Math.floor(game.obstacleFrequency / game.speed) === 0) {
const top = Math.floor(Math.random() * (canvas.height - game.gap - 100)) + 50;
const bottom = canvas.height - game.gap - top;
game.obstacles.push({
x: canvas.width,
top: top,
bottom: bottom,
passed: false
});
}
// 更新障碍物位置
for (let i = game.obstacles.length - 1; i >= 0; i--) {
const obstacle = game.obstacles[i];
obstacle.x -= 3 * game.speed;
// 检测是否通过障碍物
if (!obstacle.passed && obstacle.x + game.obstacleWidth < 100) {
obstacle.passed = true;
game.score++;
game.obstaclesPassed++;
// 每得5分增加速度
if (game.score % 5 === 0) {
game.speed += 0.1;
}
updateStats();
// 更新最高分
if (game.score > game.highScore) {
game.highScore = game.score;
localStorage.setItem('highScore', game.highScore.toString());
updateStats();
}
}
// 检测碰撞
if (
100 + game.birdSize > obstacle.x &&
100 - game.birdSize < obstacle.x + game.obstacleWidth &&
(game.birdY - game.birdSize < obstacle.top ||
game.birdY + game.birdSize > canvas.height - obstacle.bottom)
) {
gameOver();
return;
}
// 移除屏幕外的障碍物
if (obstacle.x + game.obstacleWidth < 0) {
game.obstacles.splice(i, 1);
}
}
}
// 基于物体追踪控制小鸟
function controlBirdFromTracking() {
// 如果有有效的追踪数据,使用它控制小鸟
if (trackingY > 0) {
// 将追踪Y坐标映射到画布范围
// 假设追踪Y范围是0-240(二哈识图2常见范围),映射到画布高度
const mappedY = (trackingY / 240) * canvas.height;
// 平滑移动:小鸟向目标位置移动
const targetY = mappedY;
const diff = targetY - game.birdY;
// 根据距离调整移动速度
if (Math.abs(diff) > 5) {
game.birdVelocity = diff * 0.05;
} else {
game.birdVelocity *= 0.9; // 减速
}
// 更新显示
trackingYElement.textContent = trackingY;
}
}
// 游戏结束
function gameOver() {
game.running = false;
pauseGameBtn.disabled = true;
startGameBtn.disabled = false;
startGameBtn.textContent = '重新开始';
draw();
}
// 串口数据解析函数 - 根据你的行空板输出格式修改此函数
function parseSerialData(dataString) {
// 移除换行符和空格
dataString = dataString.trim();
// 示例解析逻辑 - 根据你的实际数据格式修改
// 格式1: "Y:125" (直接包含Y坐标)
if (dataString.startsWith('Y:')) {
const yValue = parseInt(dataString.substring(2));
if (!isNaN(yValue) && yValue >= 0 && yValue <= 240) {
return yValue;
}
}
// 格式2: "{"y": 125}" (JSON格式)
if (dataString.startsWith('{')) {
try {
const data = JSON.parse(dataString);
if (data.y !== undefined) {
const yValue = parseInt(data.y);
if (!isNaN(yValue) && yValue >= 0 && yValue <= 240) {
return yValue;
}
}
} catch (e) {
console.log('JSON解析错误:', e);
}
}
// 格式3: 纯数字 "125"
const yValue = parseInt(dataString);
if (!isNaN(yValue) && yValue >= 0 && yValue <= 240) {
return yValue;
}
return null; // 无法解析的数据
}
// 读取串口数据
async function readSerialData() {
if (!port || !port.readable) {
console.error('串口不可读');
return;
}
try {
reader = port.readable.getReader();
readCancelled = false;
while (!readCancelled && port.readable) {
try {
const { value, done } = await reader.read();
if (done) {
console.log('串口读取完成');
break;
}
if (value) {
// 将Uint8Array转换为字符串
const textDecoder = new TextDecoder();
const dataString = textDecoder.decode(value);
// 更新数据显示
serialData.textContent = dataString.length > 30 ?
dataString.substring(0, 30) + '...' : dataString;
// 解析数据获取Y坐标
const parsedY = parseSerialData(dataString);
if (parsedY !== null) {
trackingY = parsedY;
lastTrackingY = trackingY;
}
}
} catch (error) {
console.error('读取数据时出错:', error);
if (!readCancelled) {
await disconnectSerial();
}
break;
}
}
} catch (error) {
console.error('初始化读取器时出错:', error);
} finally {
if (reader) {
reader.releaseLock();
reader = null;
}
}
}
// 连接串口
async function connectSerial() {
try {
// 请求串口访问权限
port = await navigator.serial.requestPort();
// 打开串口,设置波特率(根据你的行空板设置调整)
await port.open({ baudRate: 115200 });
// 更新UI状态
statusIndicator.classList.add('connected');
connectionStatus.textContent = '已连接';
connectionStatus.style.color = '#4ade80';
connectBtn.disabled = true;
disconnectBtn.disabled = false;
startGameBtn.disabled = false;
console.log('串口连接成功');
// 开始读取数据
readSerialData();
} catch (error) {
console.error('连接串口时出错:', error);
alert('连接串口失败: ' + error.message);
await disconnectSerial();
}
}
// 断开串口连接
async function disconnectSerial() {
readCancelled = true;
if (reader) {
try {
await reader.cancel();
} catch (error) {
console.error('取消读取时出错:', error);
}
reader.releaseLock();
reader = null;
}
if (port) {
try {
await port.close();
} catch (error) {
console.error('关闭串口时出错:', error);
}
port = null;
}
// 更新UI状态
statusIndicator.classList.remove('connected');
connectionStatus.textContent = '未连接';
connectionStatus.style.color = '#f72585';
connectBtn.disabled = false;
disconnectBtn.disabled = true;
startGameBtn.disabled = true;
serialData.textContent = '等待数据...';
trackingYElement.textContent = '-';
console.log('串口已断开');
}
// 开始游戏
function startGame() {
if (!game.running) {
game.running = true;
startGameBtn.disabled = true;
pauseGameBtn.disabled = false;
// 如果游戏已结束,重置游戏
if (game.score > 0) {
initGame();
}
requestAnimationFrame(draw);
}
}
// 暂停游戏
function pauseGame() {
game.running = !game.running;
pauseGameBtn.textContent = game.running ? '暂停游戏' : '继续游戏';
startGameBtn.disabled = game.running;
if (game.running) {
requestAnimationFrame(draw);
}
}
// 事件监听器
connectBtn.addEventListener('click', connectSerial);
disconnectBtn.addEventListener('click', disconnectSerial);
startGameBtn.addEventListener('click', startGame);
pauseGameBtn.addEventListener('click', pauseGame);
resetGameBtn.addEventListener('click', initGame);
// 键盘控制备用方案(如果串口不可用)
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' || e.code === 'ArrowUp') {
game.birdVelocity = game.lift;
}
});
// 触摸控制(移动设备)
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
game.birdVelocity = game.lift;
});
canvas.addEventListener('mousedown', (e) => {
game.birdVelocity = game.lift;
});
// 初始化游戏
initGame();
updateStats();
// 检查浏览器是否支持Web Serial API
if (!('serial' in navigator)) {
alert('您的浏览器不支持Web Serial API。请使用Chrome 89+或Edge 89+版本。');
connectBtn.disabled = true;
connectBtn.textContent = '浏览器不支持';
}
console.log('游戏初始化完成。请确保行空板K10通过串口输出物体追踪Y坐标数据。');
console.log('根据你的行空板输出格式,可能需要修改代码中的parseSerialData函数。');
</script>
</body>
</html>步骤3 【程序编写】
【程序编写】
步骤4 运行方式:文件代码和脚本全部打包,快去试试吧
双击打开本地的HTML文件。
点击页面上“连接串口”按钮,在浏览器弹出的设备选择器中,选择对应的行空板K10串口。
连接成功后,移动被二哈识图2追踪的物体,即可开始控制小鸟。点击“开始游戏”按钮,挑战自我!
附件

返回首页
回到顶部

评论