junkerstock
 vrx-test18 

<!DOCTYPE html>
<html>
<head>
<title>A-Frame - 3D巨大迷路 v18</title>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-look-at-component@0.8.0/dist/aframe-look-at-component.min.js"></script>
<script src="https://unpkg.com/aframe-troika-text/dist/aframe-troika-text.min.js"></script>

<script>
// --- SCRIPT BLOCK 1: Component Definitions ---
// --- ▼▼▼【v18 ダメージ処理用】このコンポーネント内を修正します ▼▼▼ ---
AFRAME.registerComponent('camera-relative-controls', {
schema: {
targetSpeed: { type: 'number', default: 5 },
acceleration: { type: 'number', default: 10 },
damping: { type: 'number', default: 8 },
brakingDeceleration: { type: 'number', default: 20 },
enabled: { type: 'boolean', default: true },
rotationSpeed: { type: 'number', default: 1.5 },
pitchLimit: { type: 'number', default: 85 },
verticalSpeed: { type: 'number', default: 30 },
groundY: { type: 'number', default: 0 },
ceilingY: { type: 'number', default: 6.4 }
},
init: function () {
this.keys = {};
this.leftThumbstickInput = { x: 0, y: 0 };
this.rightThumbstickInput = { x: 0, y: 0 };
this.currentVelocity = new THREE.Vector3();
this.ZERO_VECTOR = new THREE.Vector3();
this.cameraDirection = new THREE.Vector3();
this.cameraRight = new THREE.Vector3();
this.moveDirection = new THREE.Vector3();
this.desiredVelocity = new THREE.Vector3();
this.cameraWorldQuaternion = new THREE.Quaternion();
this.rigEl = this.el;
this.cameraEl = this.el.querySelector('[camera]');
this.isReady = false;
this.mazeData = null;
this.mazeGridSize = 0;
this.mazeCellSize = 0;
this.mazeOffset = 0;
this.goldenWallPositions = [];
this.goldenWallsScanned = false;

// --- ▼▼▼【v18 ダメージ処理用】ここから追加 ▼▼▼ ---
// 体力と、体力表示用のHUD要素を初期化します。
this.health = 100;
this.healthElement = document.getElementById('health-display');
// --- ▲▲▲【v18 ダメージ処理用】ここまで追加 ▲▲▲ ---

if (!this.cameraEl) { console.error('camera-relative-controls: カメラエンティティが見つかりません。'); }
this.el.sceneEl.addEventListener('loaded', () => {
this.leftHand = document.getElementById('leftHand');
if (this.leftHand) { this.leftHand.addEventListener('thumbstickmoved', this.onLeftThumbstickMoved.bind(this)); }
else { console.warn("camera-relative-controls: 左手コントローラー(#leftHand)が見つかりません。"); }
this.rightHand = document.getElementById('rightHand');
if (this.rightHand) { this.rightHand.addEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); }
else { console.warn("camera-relative-controls: 右手コントローラー(#rightHand)が見つかりません。"); }
});
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
window.addEventListener('keydown', this.onKeyDown);
window.addEventListener('keyup', this.onKeyUp);
},
remove: function () {
window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('keyup', this.onKeyUp);
if (this.leftHand) { try { this.leftHand.removeEventListener('thumbstickmoved', this.onLeftThumbstickMoved.bind(this)); } catch(e){} }
if (this.rightHand) { try { this.rightHand.removeEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); } catch(e){} }
},
worldToMazeCoords: function(worldX, worldZ) {
const i = Math.round(worldX / this.mazeCellSize + this.mazeOffset);
const j = Math.round(worldZ / this.mazeCellSize + this.mazeOffset);
return { i, j };
},
isNearGoldenWall: function() {
const playerPos = this.rigEl.object3D.position;
const detectionRange = (this.mazeCellSize / 2) + 1;
for (const wallPos of this.goldenWallPositions) {
const dx = Math.abs(playerPos.x - wallPos.x);
const dz = Math.abs(playerPos.z - wallPos.z);
if (dx <= detectionRange && dz <= detectionRange) {
return true;
}
}
return false;
},
onLeftThumbstickMoved: function (evt) { this.leftThumbstickInput.x = evt.detail.x; this.leftThumbstickInput.y = evt.detail.y; },
onRightThumbstickMoved: function (evt) { this.rightThumbstickInput.x = evt.detail.x; this.rightThumbstickInput.y = evt.detail.y; },
tick: function (time, timeDelta) {
if (!this.data.enabled) return;
if (!this.isReady) {
if (this.cameraEl && this.cameraEl.object3D && this.cameraEl.object3D.matrixWorld) { this.isReady = true; }
else { return; }
}
if (!this.cameraEl || !this.cameraEl.object3D || !this.rigEl || !this.rigEl.object3D) { return; }
if (this.isReady && !this.goldenWallsScanned) {
const allWalls = document.querySelectorAll('.maze-wall');
allWalls.forEach(wall => {
const material = wall.getAttribute('material');
if (material && material.color === '#FFD700') {
const wallPosition = new THREE.Vector3();
wall.object3D.getWorldPosition(wallPosition);
this.goldenWallPositions.push(wallPosition);
}
});
this.goldenWallsScanned = true;
console.log(`[Controls] Found ${this.goldenWallPositions.length} golden walls by scanning the scene.`);
}
const data = this.data;
const dt = timeDelta / 1000;
const position = this.rigEl.object3D.position;
if (this.rigEl.sceneEl.is('vr-mode')) {
if (Math.abs(this.rightThumbstickInput.x) > 0.1) { this.rigEl.object3D.rotation.y += -this.rightThumbstickInput.x * data.rotationSpeed * dt; }
if (Math.abs(this.rightThumbstickInput.y) > 0.1) { position.y -= this.rightThumbstickInput.y * data.verticalSpeed * dt; }
}
const cameraObject = this.cameraEl.object3D;
cameraObject.getWorldQuaternion(this.cameraWorldQuaternion);
this.cameraDirection.set(0, 0, -1).applyQuaternion(this.cameraWorldQuaternion); this.cameraDirection.normalize();
this.cameraRight.set(1, 0, 0).applyQuaternion(this.cameraWorldQuaternion); this.cameraRight.y = 0; this.cameraRight.normalize();
this.moveDirection.set(0, 0, 0);
if (this.keys['KeyW'] || this.keys['ArrowUp']) { this.moveDirection.add(this.cameraDirection); }
if (this.keys['KeyS'] || this.keys['ArrowDown']) { this.moveDirection.sub(this.cameraDirection); }
if (this.keys['KeyA'] || this.keys['ArrowLeft']) { this.moveDirection.sub(this.cameraRight); }
if (this.keys['KeyD'] || this.keys['ArrowRight']) { this.moveDirection.add(this.cameraRight); }
if (Math.abs(this.leftThumbstickInput.y) > 0.1) { this.moveDirection.add(this.cameraDirection.clone().multiplyScalar(-this.leftThumbstickInput.y)); }
if (Math.abs(this.leftThumbstickInput.x) > 0.1) { this.moveDirection.add(this.cameraRight.clone().multiplyScalar(this.leftThumbstickInput.x)); }
const isInputting = this.moveDirection.lengthSq() > 0.0001;
if (isInputting) { this.moveDirection.normalize(); }
let lerpFactor = data.damping;
if (isInputting) {
if (this.currentVelocity.dot(this.moveDirection) < -0.1) { this.desiredVelocity.set(0,0,0); lerpFactor = data.brakingDeceleration;
} else { this.desiredVelocity.copy(this.moveDirection).multiplyScalar(data.targetSpeed); lerpFactor = data.acceleration; }
} else { this.desiredVelocity.set(0,0,0); lerpFactor = data.damping; }
const effectiveLerpFactor = 1.0 - Math.exp(-lerpFactor * dt);
this.currentVelocity.lerp(this.desiredVelocity, effectiveLerpFactor);
if (this.currentVelocity.lengthSq() < 0.0001) { this.currentVelocity.set(0,0,0); }
if (this.currentVelocity.lengthSq() > 0) {
const deltaPosition = this.currentVelocity.clone().multiplyScalar(dt);
if (this.mazeData) {
const margin = 0.2;
const nextX = position.x + deltaPosition.x;
const signX = Math.sign(deltaPosition.x);
const { i: nextI_X, j: nextJ_X } = this.worldToMazeCoords(nextX + (margin * signX), position.z);
if (this.mazeData[nextI_X] && this.mazeData[nextI_X][nextJ_X] === 1) {
deltaPosition.x = 0;
if (!isInputting) { this.currentVelocity.x = 0; }
}
const nextZ = position.z + deltaPosition.z;
const signZ = Math.sign(deltaPosition.z);
const { i: nextI_Z, j: nextJ_Z } = this.worldToMazeCoords(position.x, nextZ + (margin * signZ));
if (this.mazeData[nextI_Z] && this.mazeData[nextI_Z][nextJ_Z] === 1) {
deltaPosition.z = 0;
if (!isInputting) { this.currentVelocity.z = 0; }
}
}
position.add(deltaPosition);
}
if (position.y < data.groundY) {
position.y = data.groundY;
if (this.currentVelocity.y < 0) { this.currentVelocity.y = 0; }
}
const isNearGold = this.isNearGoldenWall();
if (!isNearGold && position.y > data.ceilingY) {
position.y = data.ceilingY;
if (this.currentVelocity.y > 0) { this.currentVelocity.y = 0; }
}

// --- ▼▼▼【v18 ダメージ処理用】ここから追加 ▼▼▼ ---
// 敵との距離をチェックし、近ければ体力を減らす処理です。
const enemies = document.querySelectorAll('.enemy');
const playerPos = this.rigEl.object3D.position;
const damageDistance = 2.0; // この距離以内だとダメージを受ける
const damagePerSecond = 20; // 1秒あたりに受けるダメージ量
let isTakingDamage = false;

enemies.forEach(enemy => {
// 動作中の敵のみを対象とします
if (enemy.getAttribute('enemy-ai')?.enabled) {
const enemyPos = new THREE.Vector3();
enemy.object3D.getWorldPosition(enemyPos);
// プレイヤーと敵の距離を計算
if (playerPos.distanceTo(enemyPos) < damageDistance) {
isTakingDamage = true;
this.health -= damagePerSecond * dt; // 時間経過に応じてダメージ
}
}
});

// 体力が0未満にならないように調整
if (this.health < 0) {
this.health = 0;
}
// HUDの体力表示を更新
if (this.healthElement) {
this.healthElement.textContent = `体力: ${Math.floor(this.health)}`;
}
// 体力が0になったらゲームオーバー処理を呼び出す
if (this.health <= 0) {
// windowオブジェクト経由でグローバルな関数を呼び出します
if (window.triggerGameOver) {
window.triggerGameOver("体力切れ");
}
}
// --- ▲▲▲【v18 ダメージ処理用】ここまで追加 ▲▲▲ ---
},
onKeyDown: function (event) { if (!this.data.enabled) { return; } if (['KeyW', 'ArrowUp', 'KeyS', 'ArrowDown', 'KeyA', 'ArrowLeft', 'KeyD', 'ArrowRight'].includes(event.code)) { this.keys[event.code] = true; } },
onKeyUp: function (event) { if (this.keys[event.code] !== undefined) { delete this.keys[event.code]; } }
});
// enemy-ai コンポーネント (v17Aから変更なし)
AFRAME.registerComponent('enemy-ai', {
schema: {
type: { type: 'int', default: 1 },
speed: { type: 'number', default: 3 },
target: { type: 'selector', default: '#player' },
changeDirectionInterval: { type: 'number', default: 3000 },
enabled: { type: 'boolean', default: true }
},
init: function () {
this.direction = new THREE.Vector3(Math.random() - 0.5, 0, Math.random() - 0.5).normalize();
this.timeUntilChange = this.data.changeDirectionInterval;
this.hasMazeData = false;
},
setMazeData: function (mazeData, mazeCellSize, offset) {
this.mazeData = mazeData;
this.mazeCellSize = mazeCellSize;
this.mazeOffset = offset;
this.hasMazeData = true;
console.log(`Enemy ${this.el.id} received maze data.`);
},
worldToMazeCoords: function(worldX, worldZ) {
const i = Math.round(worldX / this.mazeCellSize + this.mazeOffset);
const j = Math.round(worldZ / this.mazeCellSize + this.mazeOffset);
return { i, j };
},
tick: function(time, timeDelta) {
if (!this.data.enabled) return;
if (!this.hasMazeData) return;
const dt = timeDelta / 1000;
const position = this.el.object3D.position;
if (this.data.type === 1) {
if (this.data.target) {
const targetPosition = this.data.target.object3D.position;
const targetPos2D = new THREE.Vector2(targetPosition.x, targetPosition.z);
const myPos2D = new THREE.Vector2(position.x, position.z);
const direction2D = new THREE.Vector2().subVectors(targetPos2D, myPos2D).normalize();
this.direction.set(direction2D.x, 0, direction2D.y);
}
} else {
this.timeUntilChange -= timeDelta;
if (this.timeUntilChange <= 0) {
this.direction.set(Math.random() - 0.5, 0, Math.random() - 0.5).normalize();
this.timeUntilChange = this.data.changeDirectionInterval;
}
}
const speed = this.data.speed;
let deltaPosition = this.direction.clone().multiplyScalar(speed * dt);
const margin = 0.5;
const nextX = position.x + deltaPosition.x;
const signX = Math.sign(deltaPosition.x);
const { i: nextI_X, j: nextJ_X } = this.worldToMazeCoords(nextX + (margin * signX), position.z);
if (this.mazeData[nextI_X] && this.mazeData[nextI_X][nextJ_X] === 1) {
deltaPosition.x = 0;
}
const nextZ = position.z + deltaPosition.z;
const signZ = Math.sign(deltaPosition.z);
const { i: nextI_Z, j: nextJ_Z } = this.worldToMazeCoords(position.x, nextZ + (margin * signZ));
if (this.mazeData[nextI_Z] && this.mazeData[nextI_Z][nextJ_Z] === 1) {
deltaPosition.z = 0;
}
if (deltaPosition.x === 0 && deltaPosition.z === 0 && (Math.abs(this.direction.x) > 0.01 || Math.abs(this.direction.z) > 0.01)) {
this.direction.set(Math.random() - 0.5, 0, Math.random() - 0.5).normalize();
}
position.add(deltaPosition);
}
});
</script>
<style>
#hud {
position: fixed;
bottom: 20px;
left: 20px;
color: white;
background-color: rgba(0, 0, 0, 0.4);
padding: 15px;
border-radius: 10px;
font-family: sans-serif;
font-size: 24px;
font-weight: bold;
z-index: 10;
}
#hud > div:not(:last-child) {
margin-bottom: 10px;
}
#game-over-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 0, 0, 0.5);
z-index: 20;
display: none;
}
</style>
</head>
<body>
<div id="hud">
<div id="time-display">残り時間: 300</div>
<div id="health-display">体力: 100</div>
</div>
<div id="game-over-screen"></div>

<a-scene id="myScene" vr-mode-ui="enabled: true" background="color: #87CEEB">
<a-entity id="rig" camera-relative-controls="targetSpeed: 10; acceleration: 12; damping: 10; groundY: 0; ceilingY: 6.4;">
<a-entity id="player" position="0 0 0">
<a-entity id="camera" camera="far: 20000;" look-controls="pointerLockEnabled: false; touchEnabled: false" position="0 1.6 0">
<a-entity id="mouseCursor" cursor="rayOrigin: mouse; fuse: false;" raycaster="objects: .maze-wall, a-sphere, .enemy;" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03;" material="color: black; shader: flat; opacity: 0.7;"></a-entity>
</a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left; model: true;"></a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right; model: true;" raycaster="objects: .maze-wall, a-sphere, .enemy;" laser-controls="hand: right; model: false; lineColor: white; lineOpacity: 0.75" ></a-entity>
</a-entity>
</a-entity>
<a-entity light="type: ambient; color: #888"></a-entity>
<a-entity light="type: directional; color: #FFF; intensity: 0.8; castShadow: true" position="-100 200 100"></a-entity>
<a-plane id="ground" position="0 0 0" rotation="-90 0 0" width="500" height="500" color="#7BC8A4" shadow="receive: true"></a-plane>
<a-entity id="maze-container"></a-entity>
<a-entity id="enemy-container"></a-entity>
<script>
// --- SCRIPT BLOCK 2: Main Scene Logic ---
document.addEventListener('DOMContentLoaded', function () {
const sceneEl = document.querySelector('a-scene');
if (sceneEl.hasLoaded) { initializeWorld(); } else { sceneEl.addEventListener('loaded', initializeWorld, {once: true}); }
const MAZE_GRID_SIZE = 49;
const MAZE_CELL_SIZE = 4;
const WALL_HEIGHT = 8;
// --- ▼▼▼【v18 ダメージ処理用】ここから変更 ▼▼▼ ---
// ゲームオーバー状態を管理するフラグ
let isGameOver = false;
// --- ▲▲▲【v18 ダメージ処理用】ここまで変更 ▲▲▲ ---
function initializeWorld() {
console.log("Generating maze data...");
const mazeData = generateMazeData(MAZE_GRID_SIZE);
const goldenWallPositions = [];
console.log("Creating 3D maze from data...");
create3DMaze(mazeData, goldenWallPositions);
const rigEl = document.getElementById('rig');
const controls = rigEl.components['camera-relative-controls'];
if (controls) {
controls.mazeData = mazeData;
controls.mazeGridSize = MAZE_GRID_SIZE;
controls.mazeCellSize = MAZE_CELL_SIZE;
controls.mazeOffset = (MAZE_GRID_SIZE - 1) / 2;
controls.goldenWallPositions = goldenWallPositions;
console.log("Maze data and golden wall positions have been set to the control component.");
}
setPlayerStartPosition(mazeData);
createSpheres();
spawnEnemies(mazeData);
initializeHUD();
}
function generateMazeData(gridSize) {
let ary = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
for (let i = 0; i < gridSize; i++) { ary[i][0] = 1; ary[i][gridSize - 1] = 1; ary[0][i] = 1; ary[gridSize - 1][i] = 1; }
for (let i = 1; i < (gridSize - 1) / 2; i++) {
for (let j = 1; j < (gridSize - 1) / 2; j++) {
const x = i * 2; const y = j * 2; ary[x][y] = 1;
const isStartArea = (i === 1 && j === 1);
let ranten;
if (isStartArea) { ranten = Math.random() < 0.5 ? 0 : 2; } else { ranten = Math.floor(Math.random() * 4); }
if (ranten === 0) { ary[x + 1][y] = 1; } else if (ranten === 1) { ary[x - 1][y] = 1; } else if (ranten === 2) { ary[x][y + 1] = 1; } else if (ranten === 3) { ary[x][y - 1] = 1; }
}
}
return ary;
}
function create3DMaze(mazeData, goldenWallPositions) {
const container = document.getElementById('maze-container');
if (!container) { return; }
const gridSize = mazeData.length;
const offset = (gridSize - 1) / 2;
const goalI = gridSize - 2;
const goalJ = gridSize - 2;
const maxDist = Math.sqrt(Math.pow(goalI - 1, 2) + Math.pow(goalJ - 1, 2));
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
if (mazeData[i][j] === 1) {
const wall = document.createElement('a-box');
const x = (i - offset) * MAZE_CELL_SIZE;
const y = WALL_HEIGHT / 2;
const z = (j - offset) * MAZE_CELL_SIZE;
wall.setAttribute('position', {x, y, z});
wall.setAttribute('width', MAZE_CELL_SIZE);
wall.setAttribute('height', WALL_HEIGHT);
wall.setAttribute('depth', MAZE_CELL_SIZE);
wall.setAttribute('shadow', 'cast: true; receive: false');
wall.classList.add('maze-wall');
if (Math.random() < 0.05) {
wall.setAttribute('material', { color: '#FFD700', metalness: 0.8, roughness: 0.2 });
if(goldenWallPositions) goldenWallPositions.push({x, y, z});
} else {
const dist = Math.sqrt(Math.pow(goalI - i, 2) + Math.pow(goalJ - j, 2));
const normalizedDist = dist / maxDist;
const hue = 240 * (1 - normalizedDist);
wall.setAttribute('material', { color: `hsl(${hue}, 70%, 50%)`, roughness: 0.8 });
}
container.appendChild(wall);
}
}
}
console.log(`3D maze with colored walls created. Found ${goldenWallPositions ? goldenWallPositions.length : 0} golden walls.`);
}
function setPlayerStartPosition(mazeData) {
const rigEl = document.getElementById('rig');
const gridSize = mazeData.length;
const offset = (gridSize - 1) / 2;
const startGridX = 1;
const startGridZ = 1;
const startWorldX = (startGridX - offset) * MAZE_CELL_SIZE;
const startWorldZ = (startGridZ - offset) * MAZE_CELL_SIZE;
rigEl.setAttribute('position', `${startWorldX} 0 ${startWorldZ}`);
}
function createSpheres() {
const sceneEl = document.querySelector('a-scene');
const numberOfSpheres = 30;
const spawnRadius = MAZE_GRID_SIZE / 2 * MAZE_CELL_SIZE * 1.1;
for (let i = 0; i < numberOfSpheres; i++) {
const sphereEl = document.createElement('a-sphere');
const radius = Math.random() * 3 + 0.5;
const angle = Math.random() * Math.PI * 2;
const distance = spawnRadius + Math.random() * 50;
const x = Math.cos(angle) * distance;
const z = Math.sin(angle) * distance;
const y = radius;
const color = `hsl(${Math.random() * 360}, 70%, 50%)`;
sphereEl.setAttribute('position', {x, y, z});
sphereEl.setAttribute('radius', radius);
sphereEl.setAttribute('color', color);
sphereEl.setAttribute('shadow', 'cast: true');
sceneEl.appendChild(sphereEl);
}
}
function spawnEnemies(mazeData) {
const enemyContainer = document.getElementById('enemy-container');
const numberOfEnemiesPerType = 50;
const validSpawnPoints = [];
for (let i = 0; i < MAZE_GRID_SIZE; i++) {
for (let j = 0; j < MAZE_GRID_SIZE; j++) {
if (mazeData[i][j] === 0) {
validSpawnPoints.push({i, j});
}
}
}
for (let type = 1; type <= 2; type++) {
for (let i = 0; i < numberOfEnemiesPerType; i++) {
if (validSpawnPoints.length === 0) {
console.warn("No more valid spawn points for enemies.");
break;
}
const spawnIndex = Math.floor(Math.random() * validSpawnPoints.length);
const spawnPoint = validSpawnPoints.splice(spawnIndex, 1)[0];
const offset = (MAZE_GRID_SIZE - 1) / 2;
const x = (spawnPoint.i - offset) * MAZE_CELL_SIZE;
const z = (spawnPoint.j - offset) * MAZE_CELL_SIZE;
const enemyEl = document.createElement('a-sphere');
enemyEl.id = `enemy-${type}-${i}`;
enemyEl.setAttribute('position', {x: x, y: 1, z: z});
enemyEl.setAttribute('radius', '0.5');
enemyEl.setAttribute('color', 'black');
enemyEl.classList.add('enemy');
enemyEl.addEventListener('componentinitialized', function (evt) {
if (evt.detail.name === 'enemy-ai') {
this.components['enemy-ai'].setMazeData(mazeData, MAZE_CELL_SIZE, offset);
}
});
enemyEl.setAttribute('enemy-ai', {
type: type,
target: '#player'
});
enemyContainer.appendChild(enemyEl);
}
}
console.log(`Spawned ${enemyContainer.children.length} enemies.`);
}

// --- ▼▼▼【v18 ダメージ処理用】ここから変更 ▼▼▼ ---
function initializeHUD() {
const timeElement = document.getElementById('time-display');
let remainingTime = 300;
const timer = setInterval(() => {
// ゲームオーバー状態ならタイマーも何もしない
if (isGameOver) {
clearInterval(timer);
return;
}

if (remainingTime > 0) {
remainingTime--;
timeElement.textContent = `残り時間: ${remainingTime}`;
} else {
timeElement.textContent = '残り時間: 0';
clearInterval(timer);
triggerGameOver("時間切れ");
}
}, 1000);
}
// triggerGameOver関数を、コンポーネントから呼び出せるようにグローバルスコープに配置します
window.triggerGameOver = function(reason) {
if (isGameOver) return; // ゲームオーバー処理は一度だけ実行
isGameOver = true;

console.log(`GAME OVER: ${reason}`);
const gameOverScreen = document.getElementById('game-over-screen');
if (gameOverScreen) {
gameOverScreen.style.display = 'block';
}
const rigEl = document.getElementById('rig');
if (rigEl && rigEl.components['camera-relative-controls']) {
rigEl.setAttribute('camera-relative-controls', 'enabled', false);
}
const enemies = document.querySelectorAll('.enemy');
enemies.forEach(enemy => {
if (enemy.components['enemy-ai']) {
enemy.setAttribute('enemy-ai', 'enabled', false);
}
});
}
// --- ▲▲▲【v18 ダメージ処理用】ここまで変更 ▲▲▲ ---
});
</script>
</body>
</html>


使用変数

-------( Function )
) { const playerPos = this.rigEl.object3D.position; const detectionRange = -------( Function )
allWalls
angle
ary
background
camera
cameraDirection
cameraEl
cameraObject
cameraRight
color
container
controls
create3DMaze -------( Function )
createSpheres -------( Function )
currentVelocity
cursor
damageDistance
damagePerSecond
data
deltaPosition
desiredVelocity
detectionRange
direction2D
direction
display
dist
distance
dt
dx
dz
eftThumbstickInput
enemies
enemy
enemyContainer
enemyEl
enemyPos
eraWorldQuaternion
erOfEnemiesPerType
ffectiveLerpFactor
gameOverScreen
generateMazeData -------( Function )
geometry
ghtThumbstickInput
goalI
goalJ
goldenWallsScanned
gridSize
hasMazeData
health
healthElement
height
hue
i
id
initializeHUD -------( Function )
initializeWorld -------( Function )
isGameOver
isInputting
isNearGold
isReady
isStartArea
isTakingDamage
j
keys
leftHand
length
lerpFactor
light
margin
material
maxDist
mazeCellSize
mazeData
mazeGridSize
mazeOffset
MAZE_CELL_SIZE
MAZE_GRID_SIZE
moveDirection
myPos2D
name
nextX
nextZ
normalizedDist
numberOfSpheres
offset
oldenWallPositions
onKeyDown
onKeyUp
playerPos
position
radius
ranten
raycaster
remainingTime
rigEl
rightHand
rotation
sceneEl
setPlayerStartPosition -------( Function )
shadow
signX
signZ
spawnEnemies -------( Function )
spawnIndex
spawnPoint
spawnRadius
speed
sphereEl
src
startGridX
startGridZ
startWorldX
startWorldZ
targetPos2D
targetPosition
textContent
time, timeDelta) { if -------( Function )
timeElement
timer
timeUntilChange
triggerGameOver
type
ui
validSpawnPoints
wall
wallPosition
WALL_HEIGHT
width
worldX, worldZ) { const i = Math.round -------( Function )
x
y
z
ZERO_VECTOR