add camera controll

This commit is contained in:
2026-04-29 15:59:34 +09:00
parent bb24d5df44
commit b13342f47e
6 changed files with 270 additions and 17 deletions

View File

@@ -1,17 +1,17 @@
<script>
import CameraControl from "./CameraControl.svelte";
import {
initializeGame,
updateGame,
getScore,
isGameOver,
resetGame
} from './game/game.js';
resetGame,
} from "./game/game.js";
let game;
let score = $state(0);
let highScore = $state(0);
let state = $state('start'); // start | playing | gameover
let state = $state("start"); // start | playing | gameover
window.setup = () => {
createCanvas(400, 800).parent(game);
@@ -24,7 +24,7 @@
score = Math.floor(getScore());
if (isGameOver()) {
state = 'gameover';
state = "gameover";
if (score > highScore) {
highScore = score;
}
@@ -35,7 +35,7 @@
function playGame() {
resetGame();
score = 0;
state = 'playing';
state = "playing";
loop();
}
@@ -56,18 +56,22 @@
<div class="game-wrapper">
<div bind:this={game}></div>
{#if state !== 'playing'}
{#if state !== "playing"}
<div class="overlay">
<div class="overlay-card">
<h2>{state === 'gameover' ? 'Game over!' : 'Start new game'}</h2>
{#if state !== "start"} <p>Score: {score}</p> {/if }
<h2>{state === "gameover" ? "Game over!" : "Start new game"}</h2>
{#if state !== "start"}
<p>Score: {score}</p>
{/if}
<button onclick={playGame}>Play</button>
</div>
</div>
{/if}
</div>
<aside class="right-panel">
<CameraControl />
</aside>
<aside class="right-panel"></aside>
</div>
</main>
@@ -155,4 +159,4 @@
.right-panel {
width: 220px;
}
</style>
</style>

147
src/CameraControl.svelte Normal file
View File

@@ -0,0 +1,147 @@
<script>
import {
initCameraControl,
updateCameraControl,
cameraInput,
} from "./game/cameraControl.js";
let videoContainer;
let enabled = $state(false);
let loading = $state(false);
let zone = $state("center");
let error = $state("");
async function enableCamera() {
try {
loading = true;
error = "";
const video = await initCameraControl();
videoContainer.innerHTML = "";
videoContainer.appendChild(video);
await video.play();
enabled = true;
loading = false;
requestAnimationFrame(updateLoop);
} catch (e) {
console.error(e);
error = "Camera could not be started";
loading = false;
}
}
async function updateLoop() {
if (!enabled) return;
await updateCameraControl();
zone = cameraInput.zone;
requestAnimationFrame(updateLoop);
}
</script>
<div class="camera-card">
<h2>Camera Control</h2>
<p>
Enable camera and move your head to control the game, or use the arrows.
</p>
<div class="camera-view">
<div class="video-holder" bind:this={videoContainer}></div>
{#if !enabled}
<button onclick={enableCamera} disabled={loading}>
{loading ? "Loading..." : "Enable camera"}
</button>
{/if}
<div class="zones">
<div class:active={zone === "left"}>GO LEFT</div>
<div class:active={zone === "center"}>STAY</div>
<div class:active={zone === "right"}>GO RIGHT</div>
</div>
</div>
{#if error}
<p class="error">{error}</p>
{/if}
</div>
<style>
.camera-card h2 {
margin: 0;
font-size: 28px;
font-weight: bold;
}
.camera-card {
background: rgba(255, 255, 255, 0.12);
border-radius: 16px;
padding: 16px;
color: white;
width: 320px;
height: 340px;
}
.camera-view {
position: relative;
width: 320px;
height: 240px;
background: #111;
border-radius: 16px;
margin-top: 0;
overflow: hidden;
}
.video-holder :global(video) {
width: 100%;
height: 100%;
}
.video-holder :global(video) {
width: 100%;
height: 100%;
object-fit: cover;
transform: scaleX(-1);
}
button {
position: absolute;
inset: 50% auto auto 50%;
transform: translate(-50%, -50%);
z-index: 5;
padding: 10px 16px;
border: none;
border-radius: 999px;
cursor: pointer;
font-weight: bold;
}
.zones {
position: absolute;
inset: auto 0 0 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
font-size: 11px;
font-weight: bold;
z-index: 4;
}
.zones div {
padding: 6px 0;
background: rgba(0, 0, 0, 0.45);
}
.zones .active {
background: rgba(255, 255, 255, 0.8);
color: #111;
}
.error {
color: #ffb3b3;
font-size: 13px;
}
</style>

90
src/game/cameraControl.js Normal file
View File

@@ -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;
}

View File

@@ -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;