junkerstock
 アリスの迷路 Ver1.003 

<!DOCTYPE html>
<html>
<head>
<title>アリスの迷路 Ver1.003</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 src="./js/aframe-vrm.js"></script>

<script>
// --- SCRIPT BLOCK 1: Component Definitions ---
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 },
gravity: { type: 'number', default: 0.5 }
},
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.mazeCellSize = 0;
this.mazeOffset = 0;
this.goldenWallPositions = [];
this.validWarpDestinations = [];

this.health = 100;
this.healthElementPC = document.getElementById('health-display-pc');
this.healthElementVR = null;

this.wasTakingDamage = false;

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);
},
setMazeInfo: function(mazeData, cellSize, offset, goldenWalls, warpDestinations) {
this.mazeData = mazeData;
this.mazeCellSize = cellSize;
this.mazeOffset = offset;
this.goldenWallPositions = goldenWalls;
this.validWarpDestinations = warpDestinations;
console.log(`[Controls] Maze info received. Golden walls: ${goldenWalls.length}, Warp destinations: ${warpDestinations.length}`);
},
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){} }
},
resetHealth: function() {
this.health = 100;
},
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() {
if (!this.goldenWallPositions) return false;
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;
},
warpPlayer: function(fromCoords) {
const warpSfx = document.querySelector('#warp-sfx-player');
if (warpSfx) warpSfx.components.sound.playSound();

if (this.validWarpDestinations.length === 0) {
console.warn("No valid warp destinations available.");
return;
}

const destinationIndex = Math.floor(Math.random() * this.validWarpDestinations.length);
const destinationCoords = this.validWarpDestinations[destinationIndex];

const destWorldX = (destinationCoords.i - this.mazeOffset) * this.mazeCellSize;
const destWorldZ = (destinationCoords.j - this.mazeOffset) * this.mazeCellSize;

this.rigEl.object3D.position.set(destWorldX, this.data.groundY, destWorldZ);

if (fromCoords) {
this.mazeData[fromCoords.i][fromCoords.j] = 0;
const warpFloorEl = document.getElementById(`warp-floor-${fromCoords.i}-${fromCoords.j}`);
if (warpFloorEl) {
warpFloorEl.parentNode.removeChild(warpFloorEl);
}
}

this.el.emit('player-warped');

console.log(`Warped to: (${destinationCoords.i}, ${destinationCoords.j})`);
},
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.healthElementVR) {
this.healthElementVR = document.getElementById('health-display-vr');
}

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

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);

const tileTypeX = this.mazeData[nextI_X] ? this.mazeData[nextI_X][nextJ_X] : 0;
if (tileTypeX === 1 || tileTypeX === 2) {
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));

const tileTypeZ = this.mazeData[nextI_Z] ? this.mazeData[nextI_Z][nextJ_Z] : 0;
if (tileTypeZ === 1 || tileTypeZ === 2) {
deltaPosition.z = 0;
if (!isInputting) { this.currentVelocity.z = 0; }
}
}
position.add(deltaPosition);
}

let verticalMovement = 0;
if (this.rigEl.sceneEl.is('vr-mode') && Math.abs(this.rightThumbstickInput.y) > 0.1) {
verticalMovement = -this.rightThumbstickInput.y * data.verticalSpeed * dt;
}

let newY = position.y + verticalMovement;

if (position.y > data.groundY) {
newY -= data.gravity * dt;
}

const isNearGold = this.isNearGoldenWall();
const ceiling = isNearGold ? Infinity : data.ceilingY;

position.y = Math.max(data.groundY, Math.min(newY, ceiling));

if (this.mazeData) {
const playerCoords = this.worldToMazeCoords(position.x, position.z);
if (this.mazeData[playerCoords.i] && this.mazeData[playerCoords.i][playerCoords.j] === 3) {
this.warpPlayer(playerCoords);
}
}

const enemies = document.querySelectorAll('.enemy');
const playerPos = this.rigEl.object3D.position;
const damageDistance = 4.0;
const damagePerSecond = 20;
let isTakingDamage = false;
const playerPos2D = new THREE.Vector2(playerPos.x, playerPos.z);

enemies.forEach(enemy => {
if (enemy.getAttribute('enemy-ai')?.enabled) {
const enemyPos = new THREE.Vector3();
enemy.object3D.getWorldPosition(enemyPos);
const enemyPos2D = new THREE.Vector2(enemyPos.x, enemyPos.z);
if (playerPos2D.distanceTo(enemyPos2D) < damageDistance) {
isTakingDamage = true;
this.health -= damagePerSecond * dt;
}
}
});

if (isTakingDamage && !this.wasTakingDamage) {
const damageSfx = document.querySelector('#damage-sfx-player');
if (damageSfx) damageSfx.components.sound.playSound();
}
this.wasTakingDamage = isTakingDamage;

if (this.health < 0) {
this.health = 0;
}
if (this.healthElementPC) { this.healthElementPC.textContent = `体力: ${Math.floor(this.health)}`; }
if (this.healthElementVR) { this.healthElementVR.setAttribute('troika-text', 'value', `体力: ${Math.floor(this.health)}`); }
if (this.health <= 0) {
if (window.triggerGameOver) {
window.triggerGameOver("体力切れ");
}
}

if (this.mazeData) {
const playerCoords = this.worldToMazeCoords(position.x, position.z);
const goalI = this.mazeData.length - 2;
const goalJ = this.mazeData.length - 2;

if (playerCoords.i === goalI && playerCoords.j === goalJ) {
console.log("Goal Reached! Proceeding to the next level.");
this.el.sceneEl.emit('reached-goal');
}
}
},
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]; } }
});

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);
const tileTypeX_enemy = this.mazeData[nextI_X] ? this.mazeData[nextI_X][nextJ_X] : 0;
if (tileTypeX_enemy === 1 || tileTypeX_enemy === 2) {
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));
const tileTypeZ_enemy = this.mazeData[nextI_Z] ? this.mazeData[nextI_Z][nextJ_Z] : 0;
if (tileTypeZ_enemy === 1 || tileTypeZ_enemy === 2) {
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-pc {
position: fixed;
bottom: 20px;
left: 20px;
color: white;
background-color: rgba(0, 0, 0, 0.4);
padding: 20px;
border-radius: 10px;
font-family: sans-serif;
font-size: 26px;
font-weight: bold;
z-index: 10;
min-width: 250px;
}
#hud-pc > div:not(:last-child) {
margin-bottom: 12px;
}
#replay-button-pc {
cursor: pointer;
background-color: #C0392B;
padding: 10px;
text-align: center;
border-radius: 5px;
}
#replay-button-pc:hover {
background-color: #A93226;
}
#explanation-button-pc {
cursor: pointer;
background-color: #2980B9;
padding: 10px;
text-align: center;
border-radius: 5px;
}
#explanation-button-pc:hover {
background-color: #2471A3;
}
#credits-button-pc {
cursor: pointer;
background-color: #27AE60;
padding: 10px;
text-align: center;
border-radius: 5px;
}
#credits-button-pc:hover {
background-color: #229954;
}
#explanation-panel-pc, #credits-panel-pc {
padding-top: 10px;
border-top: 1px solid white;
font-size: 18px;
font-weight: normal;
line-height: 1.5;
}
#explanation-close-button-pc, #credits-close-button-pc {
cursor: pointer;
background-color: #7f8c8d;
padding: 8px;
text-align: center;
border-radius: 5px;
margin-top: 10px;
}
#explanation-close-button-pc:hover, #credits-close-button-pc:hover {
background-color: #95a5a6;
}
.hud-buttons {
display: flex;
gap: 10px;
}
.hud-buttons > div {
flex-grow: 1;
}
</style>
</head>
<body>
<div id="hud-pc">
<div id="hud-main-content-pc">
<div id="floor-display-pc">1階</div>
<div id="score-display-pc">SCORE: 0</div>
<div id="hiscore-display-pc">HI SCORE: 0</div>
<div id="time-display-pc">残り時間: 150</div>
<div id="health-display-pc">体力: 100</div>
<div class="hud-buttons">
<div id="replay-button-pc" onclick="handleStartReplayClick()">スタート</div>
<div id="explanation-button-pc">説明</div>
<div id="credits-button-pc">Credits</div>
</div>
</div>
<div id="explanation-panel-pc" style="display: none;">
<p>
アリスの迷路 Ver1.003<BR><BR>
WASDとマウスで移動<br>
敵に注意しゴールを目指してください。<br>
敵はクリックで倒せますが、時間が20秒減ります。<br>
金の壁のまわりでは高く上昇できます。<br>
紫の床はワープなのでうまく使ってね。

</p>
<div id="explanation-close-button-pc">戻る</div>
</div>
<div id="credits-panel-pc" style="display: none;">
<p>

アリスの迷路 Ver1.003<BR><BR>
作成:setokem<BR>
使用AI:Gemini PRO 2.5<BR>
VRM作成soft:VRoid Studio 2.2.0<BR><BR>
BGM1:やすみじっかーん!  作曲:HiLi-ひぃ-様<BR>
Homepage:こんとどぅふぇ  https://conte-de-fees.com/<BR><BR>
BGM2:ダンジョン03b  効果音:システム41,22,18, 爆発06  作曲:森田交一様<BR>
Homepage:魔王魂  https://maou.audio/<BR>

</p>
<div id="credits-close-button-pc">戻る</div>
</div>
</div>
<a-scene id="myScene" vr-mode-ui="enabled: true" background="color: #87CEEB" cursor="rayOrigin: mouse">

<a-assets>
<audio id="bgm-main" src="./mp3/0057.mp3" preload="auto"></audio>
<audio id="bgm-dead" src="./mp3/md03b.mp3" preload="auto"></audio>
<audio id="sfx-click" src="./mp3/m41.mp3" preload="auto"></audio>
<audio id="sfx-damage" src="./mp3/m22.mp3" preload="auto"></audio>
<audio id="sfx-warp" src="./mp3/m18.mp3" preload="auto"></audio>
<audio id="sfx-enemy-destroy" src="./mp3/b06.mp3" preload="auto"></audio>
</a-assets>

<a-entity id="vrm-start" vrm="src:./vrm/tesA1_V0a.vrm" vrm-anim rotation="0 180 0"></a-entity>
<a-entity id="vrm-goal" vrm="src:./vrm/tesA1_V0a.vrm" vrm-anim rotation="0 45 0"></a-entity>

<a-entity id="rig" camera-relative-controls="targetSpeed: 10; acceleration: 12; damping: 10; groundY: 0; ceilingY: 6.4; gravity: 1.0;">
<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, .clickable;" 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 id="bgm-player" sound="src: #bgm-main; loop: true; positional: false; volume: 0.4;"></a-entity>
<a-entity id="dead-player" sound="src: #bgm-dead; loop: true; positional: false; volume: 0.5;"></a-entity>
<a-entity id="click-sfx-player" sound="src: #sfx-click; positional: false; volume: 0.8;"></a-entity>
<a-entity id="damage-sfx-player" sound="src: #sfx-damage; positional: false; volume: 1;"></a-entity>
<a-entity id="warp-sfx-player" sound="src: #sfx-warp; positional: false; volume: 0.9;"></a-entity>
<a-entity id="enemy-destroy-sfx-player" sound="src: #sfx-enemy-destroy; positional: false; volume: 0.7;"></a-entity>
</a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left; model: true;">
<a-entity id="vr-hud" position="0.02 0.04 -0.35" rotation="-60 0 0">
<a-plane id="hud-background" width="0.33" height="0.29" color="#FAFAFA" opacity="0.8" side="double"></a-plane>

<a-entity id="vr-hud-main-content">
<a-entity id="floor-display-vr"
troika-text="value: 1階; color: black; fontSize: 0.015; align: center; anchorX: center;"
position="0 0.11 0.01"></a-entity>
<a-entity id="score-display-vr"
troika-text="value: SCORE: 0; color: black; fontSize: 0.015; align: center; anchorX: center;"
position="0 0.07 0.01"></a-entity>
<a-entity id="hiscore-display-vr"
troika-text="value: HI SCORE: 0; color: black; fontSize: 0.015; align: center; anchorX: center;"
position="0 0.03 0.01"></a-entity>
<a-entity id="time-display-vr"
troika-text="value: 残り時間: 150; color: black; fontSize: 0.015; align: center; anchorX: center;"
position="0 -0.01 0.01"></a-entity>
<a-entity id="health-display-vr"
troika-text="value: 体力: 100; color: black; fontSize: 0.015; align: center; anchorX: center;"
position="0 -0.05 0.01"></a-entity>
</a-entity>

<a-entity id="vr-hud-explanation-content" visible="false">

<a-entity troika-text="value:

アリスの迷路 Ver1.003\n
左スティックで移動 右スティックで視点・高さ変更\n
敵に注意しゴールを目指してください。
敵はトリガーで倒せますが、時間が20秒減ります。\n
金の壁のまわりでは高く上昇できます。\n
紫の床はワープなのでうまく使ってね。\n
左右回転は、実は頭を向けるとはやく回転するよ。;

color: black; fontSize: 0.014; anchor: center; align: left; maxWidth: 0.3;"
position="0 0.03 0.01"></a-entity>

</a-entity>

<a-entity id="vr-hud-credits-content" visible="false">
<a-entity troika-text="value:

アリスの迷路 Ver1.003\n
作成:setokem 使用AI:Gemini PRO 2.5
VRM作成soft:VRoid Studio 2.2.0\n
BGM1:やすみじっかーん!
作曲:HiLi-ひぃ-様
Homepage:こんとどぅふぇ https://conte-de-fees.com/\n
BGM2:ダンジョン03b 効果音:システム41,22,18, 爆発06
作曲:森田交一様
Homepage:魔王魂 https://maou.audio/;

color: black; fontSize: 0.012; anchor: center; align: center; maxWidth: 0.34;"
position="0 0.03 0.01"></a-entity>
</a-entity>

<a-sphere id="replay-sphere-vr" class="clickable" radius="0.025" position="-0.1 -0.12 0.01" material="color: red; shader: flat">
<a-entity troika-text="value: スタート; color: white; fontSize: 0.012; align: center; anchor: center;" position="0 0.035 0"></a-entity>
</a-sphere>
<a-sphere id="explanation-sphere-vr" class="clickable" radius="0.025" position="0 -0.12 0.01" material="color: #3498DB; shader: flat">
<a-entity id="explanation-sphere-text-vr" troika-text="value: 説明; color: white; fontSize: 0.012; align: center; anchor: center;" position="0 0.035 0"></a-entity>
</a-sphere>
<a-sphere id="credits-sphere-vr" class="clickable" radius="0.025" position="0.1 -0.12 0.01" material="color: #27AE60; shader: flat">
<a-entity troika-text="value: Credits; color: white; fontSize: 0.012; align: center; anchor: center;" position="0 0.035 0"></a-entity>
</a-sphere>
</a-entity>
</a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right; model: true;" raycaster="objects: .maze-wall, a-sphere, .enemy, .clickable;" 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 wavingMotion1 = {
leftUpperArm: { keys: [ { rot: [0, 0, 65], time: 0 }, { rot: [0, 0, 63], time: 1 }, { rot: [0, 0, 65], time: 2 } ] },
rightUpperArm: { keys: [ { rot: [0, 0, -65], time: 0 }, { rot: [0, 0, -60], time: 1 }, { rot: [0, 0, -65], time: 2 } ] },
spine: { keys: [ { rot: [0, 8, 0], time: 0 }, { rot: [8, 0, -8], time: 1 }, { rot: [8, -8, 0], time: 2 }, { rot: [0, 0, 8], time: 3 }, { rot: [0, 8, 0], time: 4 } ] }
};
const wavingMotion2 = {
spine: { keys: [ { rot: [3, 5, 0], time: 0 }, { rot: [3, 5, 0], time: 1 }, { rot: [3, 5, 0], time: 3.5 }, { rot: [0, 0, 0], time: 4.5 } ] },
rightUpperArm: { keys: [ { rot: [0, 0, -65], time: 0 }, { rot: [0, 0, -40], time: 1 }, { rot: [0, 0, -65], time: 2 } ] },
leftUpperArm: { keys: [ { rot: [0, 0, 65], time: 0 }, { rot: [80, -20, -15], time: 1 }, { rot: [80, -20, -15], time: 3.5 }, { rot: [0, 0, 65], time: 4.5 } ] },
leftLowerArm: { keys: [ { rot: [0, 0, 0], time: 0 }, { rot: [0, -90, 0], time: 1 }, { rot: [0, -100, 0], time: 1.5 }, { rot: [0, -70, 0], time: 2.0 }, { rot: [0, -100, 0], time: 2.5 }, { rot: [0, -70, 0], time: 3.0 }, { rot: [0, -90, 0], time: 3.5 }, { rot: [0, 0, 0], time: 4.5 } ] }
};

const vrmStartEl = document.querySelector('#vrm-start');
const vrmGoalEl = document.querySelector('#vrm-goal');

if (vrmStartEl) { vrmStartEl.setAttribute('vrm-anim', 'idleMotion', wavingMotion1); }
if (vrmGoalEl) { vrmGoalEl.setAttribute('vrm-anim', 'idleMotion', wavingMotion2); }

const sceneEl = document.querySelector('a-scene');
let isGameOver = false;
let isGameStarted = false;
let gameTimer = null;
let score = 0;
let hiScore = 0;
let remainingTime = 0;
let currentMazeSize = 39;
let numberOfEnemiesPerType = 30;
let currentFloor = 0;

let sound = {};

const rightHand = document.getElementById('rightHand');
const replayButtonPC = document.getElementById('replay-button-pc');
const ground = document.getElementById('ground');
const rigEl = document.getElementById('rig');

const hudMainContentPC = document.getElementById('hud-main-content-pc');
const explanationButtonPC = document.getElementById('explanation-button-pc');
const explanationPanelPC = document.getElementById('explanation-panel-pc');
const explanationCloseButtonPC = document.getElementById('explanation-close-button-pc');
const creditsButtonPC = document.getElementById('credits-button-pc');
const creditsPanelPC = document.getElementById('credits-panel-pc');
const creditsCloseButtonPC = document.getElementById('credits-close-button-pc');

const vrHudMainContent = document.getElementById('vr-hud-main-content');
const vrHudExplanationContent = document.getElementById('vr-hud-explanation-content');
const vrHudCreditsContent = document.getElementById('vr-hud-credits-content');

function handleStartReplayClick() {
sound.click.playSound();
if (!isGameStarted) {
startGame();
} else {
replayGame();
}
}
window.handleStartReplayClick = handleStartReplayClick;

function startGame() {
if (isGameStarted) return;
isGameStarted = true;
console.log("Game is starting for the first time!");

sound.dead.stopSound();
sound.bgm.playSound();

const replayBtnPC = document.getElementById('replay-button-pc');
if (replayBtnPC) replayBtnPC.textContent = 'リプレイ';

const replaySphereVRText = document.querySelector('#replay-sphere-vr > a-entity');
if (replaySphereVRText) replaySphereVRText.setAttribute('troika-text', 'value', 'リプレイ');

replayGame();
}

function updateScoreDisplay() {
const scoreText = `SCORE: ${score}`;
const scoreElPC = document.getElementById('score-display-pc');
const scoreElVR = document.getElementById('score-display-vr');
if (scoreElPC) scoreElPC.textContent = scoreText;
if (scoreElVR) scoreElVR.setAttribute('troika-text', 'value', scoreText);
}

function updateHiScoreDisplay() {
const hiScoreText = `HI SCORE: ${hiScore}`;
const hiScoreElPC = document.getElementById('hiscore-display-pc');
const hiScoreElVR = document.getElementById('hiscore-display-vr');
if (hiScoreElPC) hiScoreElPC.textContent = hiScoreText;
if (hiScoreElVR) hiScoreElVR.setAttribute('troika-text', 'value', hiScoreText);
}

function checkAndSetHiScore() {
if (score > hiScore) {
hiScore = score;
localStorage.setItem('mazeHiScore', hiScore);
updateHiScoreDisplay();
}
}

function showPcPanel(panelName) {
sound.click.playSound();
hudMainContentPC.style.display = 'none';
explanationPanelPC.style.display = 'none';
creditsPanelPC.style.display = 'none';

if (panelName === 'main') {
hudMainContentPC.style.display = 'block';
} else if (panelName === 'explanation') {
explanationPanelPC.style.display = 'block';
} else if (panelName === 'credits') {
creditsPanelPC.style.display = 'block';
}
}

// ★ 変更点: 敵を破壊する関数を新しいアニメーションロジックに書き換え
function destroyEnemy(enemyEl) {
if (!enemyEl || enemyEl.isDestroying) return;

enemyEl.isDestroying = true; // 連続クリック防止フラグ

sound.enemyDestroy.playSound();

remainingTime -= 20;
console.log("Enemy destroyed! -20 seconds.");

if(enemyEl.hasAttribute('enemy-ai')) {
enemyEl.setAttribute('enemy-ai', 'enabled', false);
}

// setTimeoutを使って段階的にスケールを変更
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(1.25, 1.25, 1.25); }, 25);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(1.5, 1.5, 1.5); }, 50);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(1.75, 1.75, 1.75); }, 75);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(2, 2, 2); }, 100);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(2.5, 2.5, 2.5); }, 125);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(3, 3, 3); }, 150);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(3.5, 3.5, 3.5); }, 175);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(4, 4, 4); }, 200);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(5, 5, 5); }, 225);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(6, 6, 6); }, 250);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(7, 7, 7); }, 275);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(8, 8, 8); }, 300);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(10, 10, 10); }, 325);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(12, 12, 12); }, 350);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(14, 14, 14); }, 375);
setTimeout(() => { if(enemyEl.object3D) enemyEl.object3D.scale.set(16, 16, 16); }, 400);

// 最後に要素を削除
setTimeout(() => {
if (enemyEl.parentNode) {
enemyEl.parentNode.removeChild(enemyEl);
}
}, 425);
}

function setupGame() {
sound = {
bgm: document.querySelector('#bgm-player').components.sound,
dead: document.querySelector('#dead-player').components.sound,
click: document.querySelector('#click-sfx-player').components.sound,
enemyDestroy: document.querySelector('#enemy-destroy-sfx-player').components.sound
};

hiScore = localStorage.getItem('mazeHiScore') || 0;
updateHiScoreDisplay();

sceneEl.addEventListener('reached-goal', goToNextLevel);
rigEl.addEventListener('player-warped', () => {
score += 30;
updateScoreDisplay();
console.log("Warp bonus! +30 points. New score: " + score);
});

sceneEl.addEventListener('click', function (evt) {
const hitEl = evt.detail.intersectedEl;
if (isGameStarted && !isGameOver && hitEl && hitEl.classList.contains('enemy')) {
destroyEnemy(hitEl);
}
});

explanationButtonPC.addEventListener('click', () => showPcPanel('explanation'));
explanationCloseButtonPC.addEventListener('click', () => showPcPanel('main'));
creditsButtonPC.addEventListener('click', () => showPcPanel('credits'));
creditsCloseButtonPC.addEventListener('click', () => showPcPanel('main'));

initializePreGameUI();
}

function initializePreGameUI() {
rigEl.setAttribute('position', '0 0 0');
rigEl.setAttribute('rotation', { x: 0, y: 45, z: 0 });

const mazeContainer = document.getElementById('maze-container');
const enemyContainer = document.getElementById('enemy-container');
if (mazeContainer) mazeContainer.innerHTML = '';
if (enemyContainer) enemyContainer.innerHTML = '';

if (gameTimer) clearInterval(gameTimer);
const timeText = `残り時間: ---`;
const scoreText = `SCORE: 0`;
const floorText = `---階`;
const healthText = `体力: 100`;

const timeElPC = document.getElementById('time-display-pc');
const scoreElPC = document.getElementById('score-display-pc');
const floorElPC = document.getElementById('floor-display-pc');
const healthElPC = document.getElementById('health-display-pc');

if (timeElPC) timeElPC.textContent = timeText;
if (scoreElPC) scoreElPC.textContent = scoreText;
if (floorElPC) floorElPC.textContent = floorText;
if (healthElPC) healthElPC.textContent = healthText;

const timeElVR = document.getElementById('time-display-vr');
const scoreElVR = document.getElementById('score-display-vr');
const floorElVR = document.getElementById('floor-display-vr');
const healthElVR = document.getElementById('health-display-vr');

if (timeElVR) timeElVR.setAttribute('troika-text', 'value', timeText);
if (scoreElVR) scoreElVR.setAttribute('troika-text', 'value', scoreText);
if (floorElVR) floorElVR.setAttribute('troika-text', 'value', floorText);
if (healthElVR) healthElVR.setAttribute('troika-text', 'value', healthText);

updateHiScoreDisplay();

if (rigEl && rigEl.components['camera-relative-controls']) {
rigEl.setAttribute('camera-relative-controls', 'enabled', true);
}

if (vrmStartEl) vrmStartEl.setAttribute('visible', 'false');
if (vrmGoalEl) vrmGoalEl.setAttribute('visible', 'false');
}

function goToNextLevel() {
console.log("--- GOAL! ---");
score += remainingTime;
updateScoreDisplay();
console.log(`Time bonus! +${remainingTime} points. New score: ${score}`);
currentMazeSize += 10;
numberOfEnemiesPerType += 8;
startNewLevel();
}
function startNewLevel() {
currentFloor++;
console.log(`Starting Level (Floor ${currentFloor}) with Size: ${currentMazeSize}, Enemies per Type: ${numberOfEnemiesPerType}`);
const mazeContainer = document.getElementById('maze-container');
const enemyContainer = document.getElementById('enemy-container');
if (mazeContainer) mazeContainer.innerHTML = '';
if (enemyContainer) enemyContainer.innerHTML = '';
const controls = rigEl.components['camera-relative-controls'];

const mazeResult = generateMazeData(currentMazeSize, currentFloor);
const mazeData = mazeResult.maze;
const goldenWallCoords = [];

create3DMaze(mazeData, goldenWallCoords);

if (controls) {
controls.resetHealth();
controls.setMazeInfo(mazeData, MAZE_CELL_SIZE, (currentMazeSize - 1) / 2, goldenWallCoords, mazeResult.pathCoords);
}

setPlayerStartPosition(mazeData);

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;

const goalI = gridSize - 2;
const goalJ = gridSize - 2;
const goalWorldX = (goalI - offset) * MAZE_CELL_SIZE;
const goalWorldZ = (goalJ - offset) * MAZE_CELL_SIZE;

const vrmSrcPath = `./vrm/tesA${currentFloor}_V0a.vrm`;
console.log(`Loading VRM: ${vrmSrcPath}`);
if (vrmStartEl) {
vrmStartEl.setAttribute('visible', 'true');
vrmStartEl.setAttribute('vrm', 'src', vrmSrcPath);
vrmStartEl.setAttribute('position', { x: startWorldX, y: 0, z: startWorldZ - 1 });
}
if (vrmGoalEl) {
vrmGoalEl.setAttribute('visible', 'true');
vrmGoalEl.setAttribute('vrm', 'src', vrmSrcPath);
vrmGoalEl.setAttribute('position', { x: goalWorldX, y: 0, z: goalWorldZ });
}

if(!document.getElementById('spheres-container')) {
createSpheres();
}
spawnEnemies(mazeData);
initializeHUD();
}
if (sceneEl.hasLoaded) { setupGame(); } else { sceneEl.addEventListener('loaded', setupGame, {once: true}); }

const MAZE_CELL_SIZE = 4;
const WALL_HEIGHT = 8;

function generateMazeData(gridSize, floor) {
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; }
}
}

const pathCoords = [];
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
if (ary[i][j] === 1 && Math.random() < 0.05) {
ary[i][j] = 2;
}
if (ary[i][j] === 0) {
if (!((i === 1 && j === 1) || (i === gridSize - 2 && j === gridSize - 2))) {
pathCoords.push({i, j});
}
}
}
}

const mazeHalfSize = (gridSize - 1) / 2 * MAZE_CELL_SIZE;
const exclusionThreshold = mazeHalfSize * 2 * 0.9;

const filteredPathCoords = pathCoords.filter(coord => {
const x = (coord.i - (gridSize - 1) / 2) * MAZE_CELL_SIZE;
const z = (coord.j - (gridSize - 1) / 2) * MAZE_CELL_SIZE;
return (Math.abs(x) + Math.abs(z)) < exclusionThreshold;
});

for (let k = 0; k < (floor * 3) && filteredPathCoords.length > 0; k++) {
const randIndex = Math.floor(Math.random() * filteredPathCoords.length);
const coord = filteredPathCoords.splice(randIndex, 1)[0];
ary[coord.i][coord.j] = 3;
}

return { maze: ary, pathCoords: pathCoords };
}
function create3DMaze(mazeData, goldenWallCoords) {
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++) {
const tileType = mazeData[i][j];
const x = (i - offset) * MAZE_CELL_SIZE;
const z = (j - offset) * MAZE_CELL_SIZE;

if (tileType === 1 || tileType === 2) {
const wall = document.createElement('a-box');
const y = WALL_HEIGHT / 2;
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 (tileType === 2) {
wall.setAttribute('material', { color: '#FFD700', metalness: 0.8, roughness: 0.2 });
goldenWallCoords.push(new THREE.Vector3(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);
}
else if (tileType === 3) {
const warpFloor = document.createElement('a-plane');
warpFloor.id = `warp-floor-${i}-${j}`;
warpFloor.setAttribute('position', {x: x, y: 0.02, z: z});
warpFloor.setAttribute('width', MAZE_CELL_SIZE);
warpFloor.setAttribute('height', MAZE_CELL_SIZE);
warpFloor.setAttribute('rotation', '-90 0 0');
warpFloor.setAttribute('material', {color: 'purple', opacity: 0.7});
container.appendChild(warpFloor);
}
}
}

const goalMarker = document.createElement('a-plane');
const goalWorldX = (goalI - offset) * MAZE_CELL_SIZE;
const goalWorldZ = (goalJ - offset) * MAZE_CELL_SIZE;
goalMarker.setAttribute('position', {x: goalWorldX, y: 0.01, z: goalWorldZ});
goalMarker.setAttribute('width', MAZE_CELL_SIZE);
goalMarker.setAttribute('height', MAZE_CELL_SIZE);
goalMarker.setAttribute('rotation', '-90 0 0');
goalMarker.setAttribute('material', {color: 'red', opacity: 0.7});
container.appendChild(goalMarker);

console.log(`3D maze created. Found ${goldenWallCoords.length} 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}`);
rigEl.setAttribute('rotation', { x: 0, y: 45, z: 0 });
}
function createSpheres() {
const container = document.createElement('a-entity');
container.id = 'spheres-container';
const numberOfSpheres = 30;
const spawnRadius = currentMazeSize / 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: x, y: y, z: z});
sphereEl.setAttribute('radius', radius);
sphereEl.setAttribute('color', color);
sphereEl.setAttribute('shadow', 'cast: true');
container.appendChild(sphereEl);
}
sceneEl.appendChild(container);
}
function spawnEnemies(mazeData) {
const enemyContainer = document.getElementById('enemy-container');
const validSpawnPoints = [];
const gridSize = mazeData.length;
const startI = 1, startJ = 1;
const safeRadius = 3;

for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
if (mazeData[i][j] === 0 || mazeData[i][j] === 3) {
const distFromStart = Math.sqrt(Math.pow(i - startI, 2) + Math.pow(j - startJ, 2));
if (distFromStart > safeRadius) {
validSpawnPoints.push({i, j});
}
}
}
}
for (let type = 1; type <= 2; type++) {
for (let i = 0; i < numberOfEnemiesPerType; i++) {
if (validSpawnPoints.length === 0) { break; }
const spawnIndex = Math.floor(Math.random() * validSpawnPoints.length);
const spawnPoint = validSpawnPoints.splice(spawnIndex, 1)[0];
const offset = (gridSize - 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.isDestroying = false;
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.`);
}
function initializeHUD() {
if (gameTimer) { clearInterval(gameTimer); }

updateScoreDisplay();

const floorElementPC = document.getElementById('floor-display-pc');
const floorElementVR = document.getElementById('floor-display-vr');
const timeElementPC = document.getElementById('time-display-pc');
const timeElementVR = document.getElementById('time-display-vr');
const healthElementPC = document.getElementById('health-display-pc');
const healthElementVR = document.getElementById('health-display-vr');

const floorText = `${currentFloor}階`;
if (floorElementPC) floorElementPC.textContent = floorText;
if (floorElementVR) floorElementVR.setAttribute('troika-text', 'value', floorText);

remainingTime = 150;
const timeText = `残り時間: ${remainingTime}`;
if (timeElementPC) timeElementPC.textContent = timeText;
if (timeElementVR) timeElementVR.setAttribute('troika-text', 'value', timeText);

if (healthElementPC) healthElementPC.textContent = `体力: 100`;
if (healthElementVR) healthElementVR.setAttribute('troika-text', 'value', `体力: 100`);

gameTimer = setInterval(() => {
if (isGameOver) {
clearInterval(gameTimer);
return;
}
if (remainingTime > 0) {
remainingTime--;
const newTimeText = `残り時間: ${remainingTime}`;
if (timeElementPC) timeElementPC.textContent = newTimeText;
if (timeElementVR) timeElementVR.setAttribute('troika-text', 'value', newTimeText);
} else {
const newTimeText = '残り時間: 0';
if (timeElementPC) timeElementPC.textContent = newTimeText;
if (timeElementVR) timeElementVR.setAttribute('troika-text', 'value', newTimeText);
clearInterval(gameTimer);
triggerGameOver("時間切れ");
}
}, 1000);
}

function replayGame() {
console.log("Replaying game...");

checkAndSetHiScore();

isGameOver = false;

sound.dead.stopSound();
sound.bgm.playSound();

const controls = rigEl.components['camera-relative-controls'];
if (controls) {
controls.goldenWallPositions = [];
}

score = 0;
currentFloor = 0;
currentMazeSize = 39;
numberOfEnemiesPerType = 30;

if (ground) {
ground.setAttribute('color', '#7BC8A4');
}
if (rigEl && rigEl.components['camera-relative-controls']) {
rigEl.setAttribute('camera-relative-controls', 'enabled', true);
}
startNewLevel();
}
window.replayGame = replayGame;

window.triggerGameOver = function(reason) {
if (isGameOver) return;
isGameOver = true;
console.log(`GAME OVER: ${reason}`);

checkAndSetHiScore();

sound.bgm.stopSound();
sound.dead.playSound();

const allWalls = document.querySelectorAll('.maze-wall');
allWalls.forEach(wall => {
wall.setAttribute('material', { color: 'darkred' });
});

if (ground) {
ground.setAttribute('color', '#550000');
}
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);
}
});
}

if (rightHand) {
rightHand.addEventListener('triggerdown', function () {
const raycasterComponent = rightHand.components.raycaster;
const intersectedEls = raycasterComponent ? raycasterComponent.intersectedEls : [];

if (intersectedEls.length > 0) {
const hitEl = intersectedEls[0];
const hitElId = hitEl.id;

if (hitEl.classList.contains('enemy')) {
destroyEnemy(hitEl);
return;
}

if (hitElId === 'replay-sphere-vr' || hitElId === 'explanation-sphere-vr' || hitElId === 'credits-sphere-vr') {
sound.click.playSound();
}

if (hitElId === 'replay-sphere-vr') {
handleStartReplayClick();
return;
}

if (hitElId === 'explanation-sphere-vr' || hitElId === 'credits-sphere-vr') {
vrHudMainContent.setAttribute('visible', 'false');
}

if (hitElId === 'explanation-sphere-vr') {
vrHudExplanationContent.setAttribute('visible', 'true');
}
if (hitElId === 'credits-sphere-vr') {
vrHudCreditsContent.setAttribute('visible', 'true');
}
}
});

rightHand.addEventListener('triggerup', function () {
vrHudMainContent.setAttribute('visible', 'true');
vrHudExplanationContent.setAttribute('visible', 'false');
vrHudCreditsContent.setAttribute('visible', 'false');
});
}

const pcHud = document.getElementById('hud-pc');
if(pcHud) {
sceneEl.addEventListener('enter-vr', function () {
pcHud.style.display = 'none';
});
sceneEl.addEventListener('exit-vr', function () {
pcHud.style.display = 'block';
});
}
});
</script>
</body>
</html>


使用変数

-------( Function )
) { if -------( Function )
) { this.health = 100; }, worldToMazeCoords: function -------( Function )
allWalls
angle
ary
ationCloseButtonPC
background
camera
cameraDirection
cameraEl
cameraObject
cameraRight
ceiling
checkAndSetHiScore -------( Function )
class
color
container
controls
coord
create3DMaze -------( Function )
createSpheres -------( Function )
creditsButtonPC
creditsPanelPC
currentFloor
currentMazeSize
currentVelocity
cursor
damageDistance
damagePerSecond
damageSfx
data
deltaPosition
desiredVelocity
destinationCoords
destinationIndex
destroyEnemy -------( Function )
destWorldX
destWorldZ
detectionRange
direction2D
direction
display
dist
distance
distFromStart
dt
dx
dz
editsCloseButtonPC
eftThumbstickInput
enemies
enemy
enemyContainer
enemyEl
enemyPos2D
enemyPos
eraWorldQuaternion
erOfEnemiesPerType
exclusionThreshold
ExplanationContent
explanationPanelPC
ffectiveLerpFactor
filteredPathCoords
floorElementPC
floorElementVR
floorElPC
floorElVR
floorText
fromCoords) { const warpSfx = document.querySelector -------( Function )
gameTimer
generateMazeData -------( Function )
geometry
ghtThumbstickInput
goalI
goalJ
goalMarker
goalWorldX
goalWorldZ
goldenWallCoords
goToNextLevel -------( Function )
gridSize
ground
handleStartReplayClick -------( Function )
hasMazeData
health
healthElementPC
healthElementVR
healthElPC
healthElVR
healthText
height
hiScore
hiScoreElPC
hiScoreElVR
hiScoreText
hitEl
hitElId
hudMainContentPC
hue
i
id
idWarpDestinations
initializeHUD -------( Function )
initializePreGameUI -------( Function )
innerHTML
intersectedEls
isDestroying
isGameOver
isGameStarted
isInputting
isNearGold
isReady
isStartArea
isTakingDamage
j
k
keys
leftHand
length
lerpFactor
leStartReplayClick
light
margin
material
maxDist
mazeCellSize
mazeContainer
mazeData, cellSize, offset, goldenWalls, warpDestinations) { this.mazeData = mazeData; this.mazeCellSize = cellSize; this.mazeOffset = offset; this.goldenWallPositions = goldenWalls; this.validWarpDestinations = warpDestinations; console.log -------( Function )
mazeData
mazeHalfSize
mazeOffset
mazeResult
MAZE_CELL_SIZE
moveDirection
myPos2D
name
newTimeText
newY
nextX
nextZ
normalizedDist
numberOfSpheres
offset
oldenWallPositions
onclick
onKeyDown
onKeyUp
opacity
panelName
pathCoords
pcHud
playerCoords
playerPos2D
playerPos
position
preload
radius
randIndex
ranten
raycaster
raycasterComponent
remainingTime
replayBtnPC
replayButtonPC
replayGame -------( Function )
replayGame
replaySphereVRText
rHudCreditsContent
rigEl
rightHand
rotation
safeRadius
sceneEl
score
scoreElPC
scoreElVR
scoreText
setPlayerStartPosition -------( Function )
setupGame -------( Function )
shadow
showPcPanel -------( Function )
side
signX
signZ
sound
spawnEnemies -------( Function )
spawnIndex
spawnPoint
spawnRadius
speed
sphereEl
src
startGame -------( Function )
startGridX
startGridZ
startI
startJ
startNewLevel -------( Function )
startWorldX
startWorldZ
style
targetPos2D
targetPosition
text
textContent
tileType
tileTypeX
tileTypeX_enemy
tileTypeZ
tileTypeZ_enemy
time, timeDelta) { if -------( Function )
timeElementPC
timeElementVR
timeElPC
timeElVR
timeText
timeUntilChange
triggerGameOver
type
ui
updateHiScoreDisplay -------( Function )
updateScoreDisplay -------( Function )
validSpawnPoints
verticalMovement
visible
vrHudMainContent
vrm
vrmGoalEl
vrmSrcPath
vrmStartEl
wall
WALL_HEIGHT
warpFloor
warpFloorEl
warpSfx
wasTakingDamage
wavingMotion1
wavingMotion2
width
worldX, worldZ) { const i = Math.round -------( Function )
x
xplanationButtonPC
y
z
ZERO_VECTOR