From b13342f47e6bd0785965f20a6fed9d05953df9d3 Mon Sep 17 00:00:00 2001 From: Tomas Horsky Date: Wed, 29 Apr 2026 15:59:34 +0900 Subject: [PATCH] add camera controll --- package-lock.json | 11 ++- package.json | 3 +- src/App.svelte | 26 ++++--- src/CameraControl.svelte | 147 ++++++++++++++++++++++++++++++++++++++ src/game/cameraControl.js | 90 +++++++++++++++++++++++ src/game/player.js | 10 ++- 6 files changed, 270 insertions(+), 17 deletions(-) create mode 100644 src/CameraControl.svelte create mode 100644 src/game/cameraControl.js diff --git a/package-lock.json b/package-lock.json index b4b73a3..fd2feab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { - "name": "svelte-app", + "name": "nubzuki-jump", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "svelte-app", + "name": "nubzuki-jump", "version": "1.0.0", "dependencies": { + "@mediapipe/tasks-vision": "^0.10.35", "p5": "1.11.4", "p5-svelte": "^3.1.2", "p5play": "^3.8.14", @@ -85,6 +86,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.35", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.35.tgz", + "integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==", + "license": "Apache-2.0" + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", diff --git a/package.json b/package.json index a19bef6..152f09a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "svelte-app", + "name": "nubzuki-jump", "version": "1.0.0", "private": true, "type": "module", @@ -19,6 +19,7 @@ "svelte": "^5.55.5" }, "dependencies": { + "@mediapipe/tasks-vision": "^0.10.35", "p5": "1.11.4", "p5-svelte": "^3.1.2", "p5play": "^3.8.14", diff --git a/src/App.svelte b/src/App.svelte index b157f8b..3b055e6 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,17 +1,17 @@ + +
+

Camera Control

+

+ Enable camera and move your head to control the game, or use the arrows. +

+ +
+
+ + {#if !enabled} + + {/if} + +
+
GO LEFT
+
STAY
+
GO RIGHT
+
+
+ + {#if error} +

{error}

+ {/if} +
+ + diff --git a/src/game/cameraControl.js b/src/game/cameraControl.js new file mode 100644 index 0000000..22f5265 --- /dev/null +++ b/src/game/cameraControl.js @@ -0,0 +1,90 @@ +import { FaceDetector, FilesetResolver } from '@mediapipe/tasks-vision'; + +export const cameraInput = { + left: false, + right: false, + active: false, + zone: 'center', + x: 0.5 +}; + +let video; +let faceDetector; +let smoothedX = 0.5; + +// 🔥 TWEAK THESE FOR FEEL: +const SMOOTHING_FACTOR = 0.6; // Higher = more responsive/less lag (try 0.5 - 0.8) +const LEFT_THRESHOLD = 0.45; // Closer to 0.5 = smaller center zone +const RIGHT_THRESHOLD = 0.55; // Closer to 0.5 = smaller center zone + +export async function initCameraControl() { + if (cameraInput.active) return video; + + video = document.createElement('video'); + video.autoplay = true; + video.playsInline = true; + video.muted = true; + + const stream = await navigator.mediaDevices.getUserMedia({ + video: { width: 160, height: 120, frameRate: 15 } + }); + + video.srcObject = stream; + + const vision = await FilesetResolver.forVisionTasks( + 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm' + ); + + faceDetector = await FaceDetector.createFromOptions(vision, { + baseOptions: { + modelAssetPath: `https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.task`, + delegate: 'GPU' + }, + runningMode: 'VIDEO' + }); + + video.addEventListener('loadeddata', () => { + cameraInput.active = true; + startDetectionLoop(); + }); + + return video; +} + +function startDetectionLoop() { + const detect = (now) => { + const result = faceDetector.detectForVideo(video, now); + + if (result.detections.length > 0) { + const box = result.detections[0].boundingBox; + const centerX = box.originX + box.width / 2; + const normalizedX = centerX / 160; + + // Snappier smoothing + smoothedX += (normalizedX - smoothedX) * SMOOTHING_FACTOR; + cameraInput.x = smoothedX; + + // Updated Logic with smaller center zone + if (smoothedX < LEFT_THRESHOLD) { + updateZones(false, true, 'right'); // Mirrored + } else if (smoothedX > RIGHT_THRESHOLD) { + updateZones(true, false, 'left'); // Mirrored + } else { + updateZones(false, false, 'center'); + } + } else { + // Snap to center if face is lost + updateZones(false, false, 'center'); + } + + video.requestVideoFrameCallback(detect); + }; + + video.requestVideoFrameCallback(detect); +} + +function updateZones(l, r, z) { + cameraInput.left = l; + cameraInput.right = r; + cameraInput.zone = z; +} \ No newline at end of file diff --git a/src/game/player.js b/src/game/player.js index 7659a7d..7da86f1 100644 --- a/src/game/player.js +++ b/src/game/player.js @@ -1,4 +1,6 @@ import { PLAT_TYPE } from './constants.js'; +import { cameraInput } from './cameraControl.js'; + export function createPlayer() { const player = new Sprite(); @@ -44,13 +46,15 @@ export function updatePlayerPosition(player, platforms) { // Controls player.vel.x = 0; - if (keyIsDown(LEFT_ARROW)) { + if (keyIsDown(LEFT_ARROW) || cameraInput.left) { player.vel.x = -5; - } - if (keyIsDown(RIGHT_ARROW)) { + } else if (keyIsDown(RIGHT_ARROW) || cameraInput.right) { player.vel.x = 5; + } else { + player.vel.x *= 0.5; } + // Wrap horizontally if (player.x > width) { player.x = 0;