148 lines
3.2 KiB
Svelte
148 lines
3.2 KiB
Svelte
<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>
|