Scripts, scenes, bridges, bots and more
This commit is contained in:
120
game/index.html
120
game/index.html
@@ -19,9 +19,18 @@
|
||||
--display: 'DejaVu Sans', 'Liberation Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
--pad-y: 40px;
|
||||
--pad-x: 72px;
|
||||
/* Camera frame size — match this in OBS when positioning the camera source */
|
||||
--cam-w: 480px;
|
||||
--cam-h: 270px;
|
||||
/* Camera frame transform — auto-synced from OBS at runtime via
|
||||
GetSceneItemTransform + SceneItemTransformChanged. These defaults
|
||||
are only the first-paint fallback; once the WS connects the box
|
||||
snaps to wherever the Camera source actually is in the Game scene. */
|
||||
--cam-x: 1855px;
|
||||
--cam-y: 128px;
|
||||
--cam-w: 580px;
|
||||
--cam-h: 326px;
|
||||
/* Visual outset around the camera so the cyan border + corner ticks
|
||||
breathe and stay visible on all four sides (rather than getting
|
||||
overdrawn by the camera image). Applied symmetrically. */
|
||||
--cam-pad: 10px;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
@@ -55,32 +64,44 @@
|
||||
.hud > .group:nth-child(2) { justify-self: center; }
|
||||
.hud > .group:nth-child(3) { justify-self: end; }
|
||||
.hud .dim { color: var(--hud-dim); }
|
||||
.hud .group > span { display: inline-flex; align-items: center; gap: 10px; }
|
||||
.hud .led {
|
||||
display: inline-block;
|
||||
width: 10px; height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 10px var(--accent);
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
/* Small upward nudge: flex centers on line-box mid, but caps-only text
|
||||
reads centered around cap-height mid which sits a touch higher. */
|
||||
margin-top: -0.08em;
|
||||
animation: rec-blink 1.6s ease-in-out infinite;
|
||||
}
|
||||
/* The ▮/▯ glyphs in DejaVu Sans Mono are designed centered on x-height,
|
||||
not cap-height, so against all-caps SIGNAL their visual middle sits low.
|
||||
Lift by ~(cap-mid − x-mid) ≈ 0.10em to put the glyph center on cap center. */
|
||||
#signal {
|
||||
display: inline-block;
|
||||
transform: translateY(-0.10em);
|
||||
}
|
||||
@keyframes rec-blink {
|
||||
0%, 55% { opacity: 1; }
|
||||
65%, 100% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
/* ───────── Camera frame (top-right) ─────────
|
||||
Decorative only — drop your camera OBS source ON TOP of this frame
|
||||
(positioned to match --cam-w / --cam-h). The frame has its own dark
|
||||
backdrop + a centered placeholder so you can SEE where to put the
|
||||
camera; once the camera source covers it, the placeholder disappears. */
|
||||
/* ───────── Camera frame ─────────
|
||||
Decorative chrome drawn UNDER the OBS Camera scene item. The four
|
||||
--cam-* CSS vars are auto-synced from the Camera item's transform via
|
||||
OBS WebSocket (see syncCamera below), so wherever you drag/resize the
|
||||
camera in OBS, this frame snaps to match. The dark backdrop + centered
|
||||
placeholder show through only until the WS connects or while the camera
|
||||
is hidden. Requires the HUD browser source to render at canvas
|
||||
resolution (2560×1336) so 1 CSS px == 1 canvas px. */
|
||||
.camera-frame {
|
||||
position: absolute;
|
||||
top: calc(var(--pad-y) + 60px); /* clear of the HUD */
|
||||
right: var(--pad-x);
|
||||
width: var(--cam-w);
|
||||
height: var(--cam-h);
|
||||
top: calc(var(--cam-y) - var(--cam-pad));
|
||||
left: calc(var(--cam-x) - var(--cam-pad));
|
||||
width: calc(var(--cam-w) + var(--cam-pad) * 2);
|
||||
height: calc(var(--cam-h) + var(--cam-pad) * 2);
|
||||
pointer-events: none;
|
||||
background: rgba(6, 10, 18, 0.85);
|
||||
border: 1.5px solid rgba(79, 210, 255, 0.55);
|
||||
@@ -200,28 +221,6 @@
|
||||
100% { opacity: 1.00; }
|
||||
}
|
||||
|
||||
/* ───────── Subtle CRT overlays (lighter than Landing — game must stay readable) ───────── */
|
||||
#static {
|
||||
position: absolute; inset: 0;
|
||||
width: 100%; height: 100%;
|
||||
pointer-events: none;
|
||||
opacity: 0.022;
|
||||
mix-blend-mode: screen;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.scanlines {
|
||||
position: absolute; inset: 0;
|
||||
pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
rgba(0,0,0,0) 0px,
|
||||
rgba(0,0,0,0) 2px,
|
||||
rgba(0,0,0,0.13) 3px,
|
||||
rgba(0,0,0,0.13) 4px
|
||||
);
|
||||
opacity: 0.40;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -253,8 +252,8 @@
|
||||
<path d="M46 18 L60 10 L60 38 L46 30 Z" stroke="#4fd2ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<circle cx="14" cy="14" r="2" fill="#e63a2e"/>
|
||||
</svg>
|
||||
<span class="ph-text">— PLACE CAMERA HERE —</span>
|
||||
<span class="ph-coords">2008, 100 · 480 × 270</span>
|
||||
<span class="ph-text">— WAITING FOR CAMERA —</span>
|
||||
<span class="ph-coords">auto-syncs from OBS scene transform</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -284,9 +283,6 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<canvas id="static"></canvas>
|
||||
<div class="scanlines"></div>
|
||||
|
||||
<!-- Manifest from Project Loading. Same loading.js the Project Loading scene
|
||||
uses, so the Game scene picks up the game / subtitle / camera / mic the
|
||||
streamer already entered. -->
|
||||
@@ -344,23 +340,6 @@
|
||||
}
|
||||
tickElapsed(); setInterval(tickElapsed, 1000);
|
||||
|
||||
// ── Static noise (very subtle; gameplay must remain readable) ──
|
||||
const cv = document.getElementById('static'), ctx = cv.getContext('2d');
|
||||
const W = 320, H = 180;
|
||||
cv.width = W; cv.height = H;
|
||||
const img = ctx.createImageData(W, H);
|
||||
function drawNoise() {
|
||||
const d = img.data;
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
const v = (Math.random() * 255) | 0;
|
||||
d[i] = d[i+1] = d[i+2] = v;
|
||||
d[i+3] = 255;
|
||||
}
|
||||
ctx.putImageData(img, 0, 0);
|
||||
}
|
||||
drawNoise();
|
||||
setInterval(drawNoise, 1000 / 8);
|
||||
|
||||
// ── OBS WS: music sync + live camera visibility ──
|
||||
// URL params let you rename the camera source / scene without editing JS:
|
||||
// ?camera=Webcam&scene=Game
|
||||
@@ -376,7 +355,21 @@
|
||||
const npTitle = document.getElementById('np-title');
|
||||
const npTime = document.getElementById('np-time');
|
||||
const camEl = document.getElementById('cam');
|
||||
const rootStyle = document.documentElement.style;
|
||||
function setCameraVisible(visible) { camEl.classList.toggle('off', !visible); }
|
||||
// Map an OBS sceneItemTransform into the four CSS vars driving the frame.
|
||||
// Assumes alignment 5 (top-left) so positionX/Y is the top-left corner —
|
||||
// OBS's default for newly added items, and what the Camera source uses.
|
||||
function setCameraTransform(t) {
|
||||
if (!t) return;
|
||||
const w = (t.sourceWidth ?? 0) * (t.scaleX ?? 1);
|
||||
const h = (t.sourceHeight ?? 0) * (t.scaleY ?? 1);
|
||||
if (!(w > 0 && h > 0)) return;
|
||||
rootStyle.setProperty('--cam-x', `${t.positionX}px`);
|
||||
rootStyle.setProperty('--cam-y', `${t.positionY}px`);
|
||||
rootStyle.setProperty('--cam-w', `${w}px`);
|
||||
rootStyle.setProperty('--cam-h', `${h}px`);
|
||||
}
|
||||
|
||||
async function syncCamera(obs) {
|
||||
let camItemId = null;
|
||||
@@ -387,6 +380,9 @@
|
||||
const e = await obs.call('GetSceneItemEnabled',
|
||||
{ sceneName: CAM_SCENE, sceneItemId: camItemId });
|
||||
setCameraVisible(e.sceneItemEnabled);
|
||||
const t = await obs.call('GetSceneItemTransform',
|
||||
{ sceneName: CAM_SCENE, sceneItemId: camItemId });
|
||||
setCameraTransform(t.sceneItemTransform);
|
||||
} catch (err) {
|
||||
// Source not found — keep manifest value, don't block other features.
|
||||
console.warn(`[game] camera sync init failed (${CAM_SCENE}/${CAM_SOURCE}):`, err.message);
|
||||
@@ -394,10 +390,12 @@
|
||||
}
|
||||
obs.addEventListener('event', (ev) => {
|
||||
const d = ev.detail || {};
|
||||
if (d.eventType === 'SceneItemEnableStateChanged'
|
||||
&& d.eventData?.sceneName === CAM_SCENE
|
||||
&& d.eventData?.sceneItemId === camItemId) {
|
||||
if (d.eventData?.sceneName !== CAM_SCENE
|
||||
|| d.eventData?.sceneItemId !== camItemId) return;
|
||||
if (d.eventType === 'SceneItemEnableStateChanged') {
|
||||
setCameraVisible(d.eventData.sceneItemEnabled);
|
||||
} else if (d.eventType === 'SceneItemTransformChanged') {
|
||||
setCameraTransform(d.eventData.sceneItemTransform);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user