feat(html/js): setup canvas for both babylon and p5js
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,61 +1,68 @@
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body.maze-page {
|
||||
background:
|
||||
radial-gradient(circle at top, #1c2733 0%, #0a0f15 55%, #06080c 100%);
|
||||
color: #e8eef6;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#one {
|
||||
display: block;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
.maze-layout {
|
||||
width: min(1100px, calc(100vw - 24px));
|
||||
margin: 12px auto 20px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
#two {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
.panel {
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 16px;
|
||||
background: rgba(7, 12, 18, 0.72);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Options in index.html */
|
||||
.options {
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
.panel-label {
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: #93a4b8;
|
||||
}
|
||||
|
||||
.option {
|
||||
float: left;
|
||||
padding: 50px;
|
||||
#renderCanvas {
|
||||
width: 100%;
|
||||
height: min(62vh, 680px);
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: 100px;
|
||||
vertical-align: middle;
|
||||
line-height: 2;
|
||||
clear: both;
|
||||
margin: auto;
|
||||
#p5-panel {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8px 0 14px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: auto;
|
||||
margin-top: -50px;
|
||||
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
font-size: 25px;
|
||||
letter-spacing: 0.6px;
|
||||
word-spacing: 2px;
|
||||
color: #000000;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
font-style: normal;
|
||||
font-variant: small-caps;
|
||||
text-transform: none;
|
||||
#p5-panel canvas {
|
||||
width: min(100%, 1000px);
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.maze-layout {
|
||||
width: calc(100vw - 16px);
|
||||
margin: 8px auto 16px;
|
||||
}
|
||||
|
||||
#renderCanvas {
|
||||
height: 52vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
<title>Untitled Maze Game</title>
|
||||
</head>
|
||||
<body>
|
||||
<body class="maze-page">
|
||||
<main class="maze-layout">
|
||||
<section class="panel">
|
||||
<div class="panel-label">Babylon Scene</div>
|
||||
<canvas id="renderCanvas"></canvas>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-label">p5 Control Panel</div>
|
||||
<div id="p5-panel"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script type="module" src="/src/multi_sketch.js"></script>
|
||||
<canvas id="renderCanvas"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,16 +1,258 @@
|
||||
import * as BABYLON from "babylonjs";
|
||||
import { sketch } from "p5js-wrapper";
|
||||
|
||||
const sharedState = (window.mazeGameState ??= {
|
||||
config: {
|
||||
seed: Math.floor(Math.random() * 100000),
|
||||
level: 1,
|
||||
mazeWidth: 11,
|
||||
mazeHeight: 11,
|
||||
minChestDeadEnds: 2,
|
||||
},
|
||||
runtime: {
|
||||
runActive: false,
|
||||
hasKey: false,
|
||||
elapsedSeconds: 0,
|
||||
message: "Adjust settings, then start a run.",
|
||||
},
|
||||
});
|
||||
|
||||
const canvas = document.getElementById("renderCanvas");
|
||||
const engine = new BABYLON.Engine(canvas, true);
|
||||
|
||||
const scene = new BABYLON.Scene(engine);
|
||||
const camera = new BABYLON.ArcRotateCamera("cam", 0, 0, 10, BABYLON.Vector3.Zero(), scene);
|
||||
scene.clearColor = new BABYLON.Color4(0.05, 0.07, 0.1, 1);
|
||||
|
||||
const camera = new BABYLON.ArcRotateCamera(
|
||||
"cam",
|
||||
-Math.PI / 2,
|
||||
Math.PI / 2.4,
|
||||
10,
|
||||
BABYLON.Vector3.Zero(),
|
||||
scene,
|
||||
);
|
||||
camera.attachControl(canvas, true);
|
||||
|
||||
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
|
||||
new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
|
||||
|
||||
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);
|
||||
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);
|
||||
const sphereMaterial = new BABYLON.StandardMaterial("sphereMaterial", scene);
|
||||
sphereMaterial.diffuseColor = new BABYLON.Color3(0.2, 0.55, 0.95);
|
||||
sphereMaterial.emissiveColor = new BABYLON.Color3(0.05, 0.12, 0.2);
|
||||
sphere.material = sphereMaterial;
|
||||
|
||||
const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 14, height: 14 }, scene);
|
||||
const groundMaterial = new BABYLON.StandardMaterial("groundMaterial", scene);
|
||||
groundMaterial.diffuseColor = new BABYLON.Color3(0.12, 0.14, 0.17);
|
||||
groundMaterial.specularColor = BABYLON.Color3.Black();
|
||||
ground.material = groundMaterial;
|
||||
|
||||
engine.runRenderLoop(() => {
|
||||
const level = sharedState.config.level;
|
||||
sphere.rotation.y += 0.01;
|
||||
sphere.scaling.x = 1 + (level - 1) * 0.05;
|
||||
sphere.scaling.z = 1 + (level - 1) * 0.05;
|
||||
sphereMaterial.diffuseColor = sharedState.runtime.hasKey
|
||||
? new BABYLON.Color3(0.25, 0.8, 0.45)
|
||||
: new BABYLON.Color3(0.2, 0.55, 0.95);
|
||||
scene.render();
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
engine.resize();
|
||||
});
|
||||
|
||||
const panelSize = { width: 1000, height: 270 };
|
||||
const buttonRects = {};
|
||||
const sliderRects = {};
|
||||
let draggingSlider = null;
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
function valueToX(rect, min, max, value) {
|
||||
const ratio = (value - min) / (max - min);
|
||||
return rect.x + ratio * rect.width;
|
||||
}
|
||||
|
||||
function xToValue(rect, min, max, x) {
|
||||
const ratio = clamp((x - rect.x) / rect.width, 0, 1);
|
||||
return Math.round(min + ratio * (max - min));
|
||||
}
|
||||
|
||||
function resetRun(message) {
|
||||
sharedState.runtime.runActive = true;
|
||||
sharedState.runtime.hasKey = false;
|
||||
sharedState.runtime.elapsedSeconds = 0;
|
||||
sharedState.runtime.message = message;
|
||||
sharedState.config.level = 1;
|
||||
}
|
||||
|
||||
function restartLevel(message) {
|
||||
sharedState.runtime.hasKey = false;
|
||||
sharedState.runtime.elapsedSeconds = 0;
|
||||
sharedState.runtime.message = message;
|
||||
}
|
||||
|
||||
function randomizeSeed() {
|
||||
sharedState.config.seed = Math.floor(Math.random() * 100000);
|
||||
sharedState.runtime.message = `Seed set to ${sharedState.config.seed}.`;
|
||||
}
|
||||
|
||||
function drawButton(rect, label, active = false) {
|
||||
push();
|
||||
stroke(active ? "#7db4ff" : "#284055");
|
||||
strokeWeight(2);
|
||||
fill(active ? "#17314c" : "#101820");
|
||||
rect(rect.x, rect.y, rect.w, rect.h, 12);
|
||||
noStroke();
|
||||
fill("#eef5ff");
|
||||
textAlign(CENTER, CENTER);
|
||||
textSize(16);
|
||||
text(label, rect.x + rect.w / 2, rect.y + rect.h / 2 + 1);
|
||||
pop();
|
||||
}
|
||||
|
||||
function drawSlider(rect, label, value, min, max, suffix = "") {
|
||||
const knobX = valueToX(rect, min, max, value);
|
||||
push();
|
||||
fill("#dbe6f2");
|
||||
noStroke();
|
||||
textAlign(LEFT, BOTTOM);
|
||||
textSize(15);
|
||||
text(`${label}: ${value}${suffix}`, rect.x, rect.y - 8);
|
||||
stroke("#33495e");
|
||||
strokeWeight(6);
|
||||
line(rect.x, rect.y + rect.h / 2, rect.x + rect.w, rect.y + rect.h / 2);
|
||||
noStroke();
|
||||
fill("#79aef2");
|
||||
circle(knobX, rect.y + rect.h / 2, 20);
|
||||
pop();
|
||||
}
|
||||
|
||||
sketch.setup = function () {
|
||||
createCanvas(panelSize.width, panelSize.height).parent("p5-panel");
|
||||
pixelDensity(1);
|
||||
textFont("monospace");
|
||||
noLoop();
|
||||
};
|
||||
|
||||
sketch.draw = function () {
|
||||
background("#091018");
|
||||
|
||||
noStroke();
|
||||
fill("#dce7f4");
|
||||
textAlign(LEFT, TOP);
|
||||
textSize(18);
|
||||
text("Maze game settings", 18, 14);
|
||||
|
||||
fill("#91a4b8");
|
||||
textSize(12);
|
||||
text(
|
||||
"This panel controls run settings and mirrors the shared state for the Babylon scene.",
|
||||
18,
|
||||
38,
|
||||
);
|
||||
|
||||
const buttonY = 64;
|
||||
buttonRects.start = { x: 18, y: buttonY, w: 140, h: 38 };
|
||||
buttonRects.restart = { x: 168, y: buttonY, w: 140, h: 38 };
|
||||
buttonRects.seed = { x: 318, y: buttonY, w: 160, h: 38 };
|
||||
|
||||
drawButton(buttonRects.start, "Start run", sharedState.runtime.runActive);
|
||||
drawButton(buttonRects.restart, "Restart level");
|
||||
drawButton(buttonRects.seed, "Randomize seed");
|
||||
|
||||
sliderRects.width = { x: 18, y: 154, w: 420, h: 22 };
|
||||
sliderRects.height = { x: 18, y: 206, w: 420, h: 22 };
|
||||
sliderRects.deadEnds = { x: 500, y: 154, w: 420, h: 22 };
|
||||
|
||||
drawSlider(sliderRects.width, "Maze width", sharedState.config.mazeWidth, 9, 31, " cells");
|
||||
drawSlider(sliderRects.height, "Maze height", sharedState.config.mazeHeight, 9, 31, " cells");
|
||||
drawSlider(
|
||||
sliderRects.deadEnds,
|
||||
"Minimum chest dead-ends",
|
||||
sharedState.config.minChestDeadEnds,
|
||||
1,
|
||||
10,
|
||||
);
|
||||
|
||||
fill("#dce7f4");
|
||||
textSize(14);
|
||||
textAlign(LEFT, TOP);
|
||||
text(`Seed: ${sharedState.config.seed}`, 500, 196);
|
||||
text(`Level: ${sharedState.config.level}`, 500, 220);
|
||||
text(`Time: ${sharedState.runtime.elapsedSeconds.toFixed(1)}s`, 640, 220);
|
||||
text(`Key: ${sharedState.runtime.hasKey ? "yes" : "no"}`, 780, 220);
|
||||
|
||||
fill("#93a4b8");
|
||||
textSize(12);
|
||||
text(`Status: ${sharedState.runtime.message}`, 18, 242, 960);
|
||||
};
|
||||
|
||||
sketch.mousePressed = function () {
|
||||
const x = mouseX;
|
||||
const y = mouseY;
|
||||
|
||||
const isInside = (rect) => x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
|
||||
|
||||
if (isInside(buttonRects.start)) {
|
||||
resetRun("Run started.");
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInside(buttonRects.restart)) {
|
||||
restartLevel("Level restarted.");
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInside(buttonRects.seed)) {
|
||||
randomizeSeed();
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInside(sliderRects.width)) {
|
||||
draggingSlider = "width";
|
||||
sharedState.config.mazeWidth = xToValue(sliderRects.width, 9, 31, x) | 1;
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInside(sliderRects.height)) {
|
||||
draggingSlider = "height";
|
||||
sharedState.config.mazeHeight = xToValue(sliderRects.height, 9, 31, x) | 1;
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInside(sliderRects.deadEnds)) {
|
||||
draggingSlider = "deadEnds";
|
||||
sharedState.config.minChestDeadEnds = xToValue(sliderRects.deadEnds, 1, 10, x);
|
||||
redraw();
|
||||
}
|
||||
};
|
||||
|
||||
sketch.mouseDragged = function () {
|
||||
if (draggingSlider === "width") {
|
||||
sharedState.config.mazeWidth = xToValue(sliderRects.width, 9, 31, mouseX) | 1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
if (draggingSlider === "height") {
|
||||
sharedState.config.mazeHeight = xToValue(sliderRects.height, 9, 31, mouseX) | 1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
if (draggingSlider === "deadEnds") {
|
||||
sharedState.config.minChestDeadEnds = xToValue(sliderRects.deadEnds, 1, 10, mouseX);
|
||||
redraw();
|
||||
}
|
||||
};
|
||||
|
||||
sketch.mouseReleased = function () {
|
||||
draggingSlider = null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user