From bd13a5aeb23a1897133c081ad50c0a3357212ce2 Mon Sep 17 00:00:00 2001 From: Cx330 <1487537121@qq.com> Date: Sat, 18 Apr 2026 16:46:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8E=A7=E5=88=B6=E7=95=8C?= =?UTF-8?q?=E9=9D=A2UI=E6=A0=B7=E5=BC=8F=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=A4=96=E7=95=8C=E6=89=8B=E6=9F=84=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/CSS/control.css | 191 ++++++++++++----- main/CSS/style.css | 10 + main/Javascript/control.js | 5 +- main/Javascript/joycon.js | 247 ++++++++++++++++++++++ main/Javascript/script.js | 57 +++++ main/__pycache__/motor.cpython-313.pyc | Bin 4033 -> 0 bytes main/__pycache__/motor.cpython-38.pyc | Bin 0 -> 3564 bytes main/camera_test.py | 278 +++++++++++++++++++++++++ main/index.html | 50 +++-- main/motor.py | 20 +- 10 files changed, 779 insertions(+), 79 deletions(-) create mode 100644 main/Javascript/joycon.js delete mode 100644 main/__pycache__/motor.cpython-313.pyc create mode 100644 main/__pycache__/motor.cpython-38.pyc create mode 100644 main/camera_test.py diff --git a/main/CSS/control.css b/main/CSS/control.css index 20f9626..5830af6 100644 --- a/main/CSS/control.css +++ b/main/CSS/control.css @@ -6,14 +6,16 @@ justify-content: center; min-height: 80vh; padding: 20px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); } .title { - font-size: 24px; + font-size: 28px; font-weight: bold; color: #333; margin-bottom: 40px; text-align: center; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); } /* 手柄风格布局 */ @@ -23,46 +25,66 @@ align-items: center; gap: 30px; width: 100%; - max-width: 350px; + max-width: 600px; } -/* 移动控制按钮区域 */ -.movement-controls { +/* 控制器布局 */ +.controller-layout { + display: flex; + align-items: center; + justify-content: space-around; + width: 100%; + padding: 30px; + background: linear-gradient(145deg, #e6e6e6, #ffffff); + border-radius: 20px; + box-shadow: 10px 10px 20px rgba(0, 0, 0, 0.1), -10px -10px 20px rgba(255, 255, 255, 0.7); +} + +/* 方向键区域 */ +.dpad { display: flex; flex-direction: column; align-items: center; - gap: 15px; - width: 100%; + gap: 10px; } -.movement-row { +.dpad-row { display: flex; - gap: 15px; - justify-content: center; + gap: 10px; } -/* 录制回放按钮区域 */ -.record-controls { +/* 中间录制回放控制 */ +.center-controls { display: flex; + flex-direction: column; gap: 15px; - justify-content: center; - width: 100%; - margin-top: 20px; + align-items: center; +} + +/* 右侧旋转控制 */ +.rotate-controls { + display: flex; + flex-direction: column; + gap: 15px; + align-items: center; } /* 按钮样式 */ .btn { - width: 120px; - height: 50px; + width: 70px; + height: 70px; border: none; - border-radius: 25px; - font-size: 16px; + border-radius: 15px; + font-size: 20px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7); + display: flex; + align-items: center; + justify-content: center; } .btn::before { @@ -81,93 +103,168 @@ z-index: 2; } +/* 方向按钮样式 */ .btn-forward { - background-color: #4CAF50; + background: linear-gradient(145deg, #4CAF50, #45a049); color: white; } .btn-backward { - background-color: #f44336; + background: linear-gradient(145deg, #f44336, #da190b); color: white; } .btn-left { - background-color: #2196F3; + background: linear-gradient(145deg, #2196F3, #1976D2); color: white; } .btn-right { - background-color: #FF9800; - color: white; -} - -.btn-rotate-left { - background-color: #9C27B0; - color: white; -} - -.btn-rotate-right { - background-color: #607D8B; + background: linear-gradient(145deg, #FF9800, #F57C00); color: white; } +/* 斜向按钮样式 */ .btn-forward-left { - background-color: #4CAF50; + background: linear-gradient(145deg, #4CAF50, #45a049); color: white; } .btn-forward-right { - background-color: #4CAF50; + background: linear-gradient(145deg, #4CAF50, #45a049); color: white; } .btn-backward-left { - background-color: #f44336; + background: linear-gradient(145deg, #f44336, #da190b); color: white; } .btn-backward-right { - background-color: #f44336; + background: linear-gradient(145deg, #f44336, #da190b); color: white; } +/* 旋转按钮样式 */ +.btn-rotate-left { + background: linear-gradient(145deg, #9C27B0, #7B1FA2); + color: white; +} + +.btn-rotate-right { + background: linear-gradient(145deg, #607D8B, #455A64); + color: white; +} + +/* 中心方块样式 */ +.btn-center { + background: linear-gradient(145deg, #666666, #444444); + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7); + cursor: default; +} + +.btn-center:hover { + transform: none; + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7); +} + +.btn-center:active { + transform: none; + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7); +} + +/* 录制回放按钮样式 */ +.btn-record { + background: linear-gradient(145deg, #FF5722, #E64A19); + color: white; + width: 120px; + height: 50px; + font-size: 16px; + border-radius: 25px; +} + +.btn-playback { + background: linear-gradient(145deg, #00BCD4, #0097A7); + color: white; + width: 120px; + height: 50px; + font-size: 16px; + border-radius: 25px; +} + +/* 按钮动画效果 */ .btn:hover { transform: translateY(-3px); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); + box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.15), -8px -8px 16px rgba(255, 255, 255, 0.8); } .btn:active { transform: translateY(0); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2), -3px -3px 6px rgba(255, 255, 255, 0.6); } /* 状态显示 */ .status { margin-top: 30px; - padding: 15px; - background-color: #f5f5f5; - border-radius: 10px; - min-height: 50px; + padding: 20px; + background: linear-gradient(145deg, #f5f5f5, #e0e0e0); + border-radius: 15px; + min-height: 60px; width: 100%; - max-width: 350px; + max-width: 400px; text-align: center; - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: inset 5px 5px 10px rgba(0, 0, 0, 0.1), inset -5px -5px 10px rgba(255, 255, 255, 0.7); } .status p { color: #666; margin: 0; - font-size: 16px; + font-size: 18px; + font-weight: 500; } /* 响应式设计 */ -@media (max-width: 400px) { +@media (max-width: 650px) { + .controller-layout { + flex-direction: column; + gap: 20px; + padding: 20px; + } + + .dpad-row { + gap: 8px; + } + .btn { + width: 60px; + height: 60px; + font-size: 18px; + } + + .btn-record, .btn-playback { width: 100px; height: 45px; font-size: 14px; } + .title { + font-size: 24px; + } +} + +@media (max-width: 400px) { + .btn { + width: 50px; + height: 50px; + font-size: 16px; + } + + .btn-record, .btn-playback { + width: 90px; + height: 40px; + font-size: 13px; + } + .title { font-size: 20px; } diff --git a/main/CSS/style.css b/main/CSS/style.css index 1e43f28..533da69 100644 --- a/main/CSS/style.css +++ b/main/CSS/style.css @@ -56,6 +56,16 @@ h1 { color: white; } +.btn-record { + background-color: #ff9800; + color: white; +} + +.btn-playback { + background-color: #2196f3; + color: white; +} + .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); diff --git a/main/Javascript/control.js b/main/Javascript/control.js index b1da833..96f5cc8 100644 --- a/main/Javascript/control.js +++ b/main/Javascript/control.js @@ -424,6 +424,8 @@ function stopMotor() { const timestamp = Date.now() - window.recordingStartTime; window.recordedActions.push({ direction: 'stop', timestamp }); console.log('Recorded action:', { direction: 'stop', timestamp }); + } else { + console.log('Not recording, action:', 'stop'); } // 发送停止请求到服务器 @@ -437,7 +439,8 @@ function stopMotor() { .then(response => response.json()) .then(data => { if (data.status === 'success') { - // 停止成功,不显示消息 + // 停止成功,显示已停止消息 + status.innerHTML = `
${data.message}
`; } else { status.innerHTML = `错误: ${data.message}
`; } diff --git a/main/Javascript/joycon.js b/main/Javascript/joycon.js new file mode 100644 index 0000000..88982bd --- /dev/null +++ b/main/Javascript/joycon.js @@ -0,0 +1,247 @@ +// 确保在DOM加载完成后执行 +window.addEventListener('DOMContentLoaded', function() { + console.log('DOMContentLoaded - Joycon.js executing'); + + // 游戏手柄相关变量 + let gamepad = null; + let lastDirection = 'stop'; + let isRecording = false; + let isPlaying = false; + let recordedActions = []; + let recordingStartTime = 0; + + // 按键状态标志(用于检测按键按下事件) + let recordButtonPressed = false; + let playbackButtonPressed = false; + + // 获取状态元素 + const status = document.getElementById('status'); + + // 检查浏览器是否支持游戏手柄API + if (!('getGamepads' in navigator)) { + status.innerHTML = '您的浏览器不支持游戏手柄API
'; + console.log('Gamepad API not supported'); + return; + } + + // 连接游戏手柄 + window.addEventListener('gamepadconnected', function(e) { + gamepad = e.gamepad; + status.innerHTML = `已连接游戏手柄: ${gamepad.id}
`; + console.log('Gamepad connected:', gamepad); + // 开始监听游戏手柄输入 + requestAnimationFrame(updateGamepad); + }); + + // 断开游戏手柄 + window.addEventListener('gamepaddisconnected', function(e) { + status.innerHTML = '游戏手柄已断开连接
'; + console.log('Gamepad disconnected:', e.gamepad); + gamepad = null; + }); + + // 游戏手柄输入更新函数 + function updateGamepad() { + if (!gamepad) { + requestAnimationFrame(updateGamepad); + return; + } + + // 获取游戏手柄状态 + const gamepads = navigator.getGamepads(); + gamepad = gamepads[gamepad.index]; + + if (!gamepad) { + requestAnimationFrame(updateGamepad); + return; + } + + // 处理摇杆输入 (通常是0号摇杆,左摇杆) + const leftStickX = gamepad.axes[0]; + const leftStickY = gamepad.axes[1]; + + // 处理按键输入 + const buttons = gamepad.buttons; + + // 摇杆死区 + const deadzone = 0.3; + + // 确定移动方向 + let direction = 'stop'; + + if (Math.abs(leftStickX) > deadzone || Math.abs(leftStickY) > deadzone) { + // 计算角度 + const angle = Math.atan2(leftStickY, leftStickX) * 180 / Math.PI; + + // 根据角度确定方向(修复:交换前后方向,修复:使用下划线分隔的方向值) + if (angle >= -22.5 && angle < 22.5) { + direction = 'right'; // 右 + } else if (angle >= 22.5 && angle < 67.5) { + direction = 'backward_right'; // 右后 + } else if (angle >= 67.5 && angle < 112.5) { + direction = 'backward'; // 后 + } else if (angle >= 112.5 && angle < 157.5) { + direction = 'backward_left'; // 左后 + } else if (angle >= 157.5 || angle < -157.5) { + direction = 'left'; // 左 + } else if (angle >= -157.5 && angle < -112.5) { + direction = 'forward_left'; // 左前 + } else if (angle >= -112.5 && angle < -67.5) { + direction = 'forward'; // 前 + } else if (angle >= -67.5 && angle < -22.5) { + direction = 'forward_right'; // 右前 + } + } + + // 处理旋转按钮 (X键和Y键) + if (buttons[2].pressed) { // X键 - 左旋转 + direction = 'rotate_left'; + } else if (buttons[3].pressed) { // Y键 - 右旋转 + direction = 'rotate_right'; + } + + // 处理录制按钮 (十字键上键) - 修复:检测按键按下事件 + if (buttons[12].pressed) { // 十字键上键 - 录制 + if (!recordButtonPressed) { + recordButtonPressed = true; + if (!isPlaying) { + if (isRecording) { + // 停止录制 + isRecording = false; + status.innerHTML = `录制完成,共记录 ${recordedActions.length} 个操作
`; + console.log('Recording stopped, actions:', recordedActions); + } else { + // 开始录制 + isRecording = true; + recordedActions = []; + recordingStartTime = Date.now(); + status.innerHTML = '开始录制...
'; + console.log('Recording started'); + } + } + } + } else { + recordButtonPressed = false; + } + + // 处理回放按钮 (十字键左键) - 修复:检测按键按下事件 + if (buttons[14].pressed) { // 十字键左键 - 回放 + if (!playbackButtonPressed) { + playbackButtonPressed = true; + if (!isRecording && !isPlaying && recordedActions.length > 0) { + isPlaying = true; + status.innerHTML = '开始回放...
'; + console.log('Starting playback'); + + // 按时间顺序回放操作 + let currentTime = 0; + const totalDuration = recordedActions[recordedActions.length - 1].timestamp; + + recordedActions.forEach((action, index) => { + if (index === 0) { + // 第一个操作立即执行 + console.log('Executing action immediately:', action); + controlMotor(action.direction); + } else { + // 计算与前一个操作的时间差 + const prevAction = recordedActions[index - 1]; + const delay = action.timestamp - prevAction.timestamp; + currentTime += delay; + + console.log('Scheduling action:', action, 'at delay:', currentTime); + setTimeout(() => { + console.log('Executing action:', action); + controlMotor(action.direction); + }, currentTime); + } + }); + + // 回放完成后,确保发送停止请求 + setTimeout(() => { + console.log('Playback completed, sending stop command'); + controlMotor('stop'); + status.innerHTML = '回放完成
'; + console.log('Playback completed'); + isPlaying = false; + }, totalDuration + 2000); + } + } + } else { + playbackButtonPressed = false; + } + + // 如果方向改变,发送控制命令 + if (direction !== lastDirection) { + // 如果正在录制,记录操作 + if (isRecording) { + const action = { + direction: direction, + timestamp: Date.now() - recordingStartTime + }; + recordedActions.push(action); + } + controlMotor(direction); + lastDirection = direction; + } + + // 继续监听 + requestAnimationFrame(updateGamepad); + } + + // 控制电机的函数(与control.js中的相同) + function controlMotor(direction) { + // 根据方向设置状态消息 + if (direction === 'stop') { + status.innerHTML = `正在停止...
`; + } else { + let actionText = ''; + switch (direction) { + case 'forward': actionText = '前进'; break; + case 'backward': actionText = '后退'; break; + case 'left': actionText = '左移'; break; + case 'right': actionText = '右移'; break; + case 'rotate_left': actionText = '左旋转'; break; + case 'rotate_right': actionText = '右旋转'; break; + case 'forward_left': actionText = '左前移动'; break; + case 'forward_right': actionText = '右前移动'; break; + case 'backward_left': actionText = '左后移动'; break; + case 'backward_right': actionText = '右后移动'; break; + default: actionText = '移动'; break; + } + status.innerHTML = `正在${actionText}...
`; + } + + // 记录操作(如果正在录制) + if (isRecording) { + const timestamp = Date.now() - recordingStartTime; + // 注意:这里不再重复记录,因为已经在updateGamepad中记录了 + console.log('Recorded action:', { direction, timestamp }); + } else { + console.log('Not recording, action:', direction); + } + + // 发送AJAX请求到后端服务器 + fetch('/control', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ direction: direction }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + status.innerHTML = `${data.message}
`; + } else { + status.innerHTML = `错误: ${data.message}
`; + } + }) + .catch(error => { + status.innerHTML = `通信错误: ${error.message}
`; + }); + } + + // 初始化 + status.innerHTML = '请连接游戏手柄...
'; + console.log('Joycon.js initialized'); +}); diff --git a/main/Javascript/script.js b/main/Javascript/script.js index 502ec25..fadfe1e 100644 --- a/main/Javascript/script.js +++ b/main/Javascript/script.js @@ -1,12 +1,25 @@ // 获取按钮和状态元素 const forwardBtn = document.getElementById('forwardBtn'); const backwardBtn = document.getElementById('backwardBtn'); +const recordBtn = document.getElementById('recordBtn'); +const playbackBtn = document.getElementById('playbackBtn'); const status = document.getElementById('status'); +// 录制相关变量 +let isRecording = false; +let recordedActions = []; +let recordingStartTime = 0; + // 实际控制函数 function controlMotor(direction) { status.innerHTML = `正在${direction === 'forward' ? '前进' : '后退'}...
`; + // 记录操作(如果正在录制) + if (isRecording) { + const timestamp = Date.now() - recordingStartTime; + recordedActions.push({ direction, timestamp }); + } + // 发送AJAX请求到后端服务器 fetch('/control', { method: 'POST', @@ -28,6 +41,50 @@ function controlMotor(direction) { }); } +// 录制按钮点击事件 +recordBtn.addEventListener('click', () => { + if (isRecording) { + // 停止录制 + isRecording = false; + recordBtn.textContent = '开始录制'; + status.innerHTML = `录制完成,共记录 ${recordedActions.length} 个操作
`; + } else { + // 开始录制 + isRecording = true; + recordedActions = []; + recordingStartTime = Date.now(); + recordBtn.textContent = '停止录制'; + status.innerHTML = `开始录制...
`; + } +}); + +// 回放按钮点击事件 +playbackBtn.addEventListener('click', () => { + if (recordedActions.length === 0) { + status.innerHTML = `没有可回放的操作
`; + return; + } + + status.innerHTML = `开始回放...
`; + + // 按时间顺序回放操作 + let lastTimestamp = 0; + recordedActions.forEach((action, index) => { + setTimeout(() => { + controlMotor(action.direction); + + // 最后一个操作完成后显示回放完成 + if (index === recordedActions.length - 1) { + setTimeout(() => { + status.innerHTML = `回放完成
`; + }, 2000); + } + }, action.timestamp - lastTimestamp); + + lastTimestamp = action.timestamp; + }); +}); + // 按钮点击事件 forwardBtn.addEventListener('click', () => { controlMotor('forward'); diff --git a/main/__pycache__/motor.cpython-313.pyc b/main/__pycache__/motor.cpython-313.pyc deleted file mode 100644 index 564929a45ff85d2932dd9182d35b9dabcf73fb2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4033 zcmc&$-E&h#6yJOA-sI-1=@+Ffq8$V^NGqQTh%!h~K5UV0