feat(html/js): setup canvas for both babylon and p5js

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
pobadoba
2026-05-06 00:33:08 +09:00
parent 66f5f35a9e
commit 2acb724e1f
3 changed files with 309 additions and 48 deletions

View File

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