junkerstock
 vrx-test27A 

<!DOCTYPE html>
<html>
<head>
<title>A-Frame - 3D巨大迷路 v27A</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 }
},
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;
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) {
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;
}

const nextY = position.y + verticalMovement;
const isNearGold = this.isNearGoldenWall();

const ceiling = isNearGold ? Infinity : data.ceilingY;

if (nextY > ceiling) {
position.y = ceiling;
} else if (nextY < data.groundY) {
position.y = data.groundY;
} else {
position.y = nextY;
}

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 (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]; } }
});

// enemy-ai コンポーネント
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: 15px;
border-radius: 10px;
font-family: sans-serif;
font-size: 24px;
font-weight: bold;
z-index: 10;
transition: height 0.3s ease-in-out;
}
#hud-pc > div:not(:last-child) {
margin-bottom: 10px;
}
#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;
}
#explanation-panel-pc {
padding-top: 10px;
border-top: 1px solid white;
font-size: 16px;
font-weight: normal;
line-height: 1.4;
}
#explanation-close-button-pc {
cursor: pointer;
background-color: #7f8c8d;
padding: 8px;
text-align: center;
border-radius: 5px;
margin-top: 10px;
}
#explanation-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="time-display-pc">残り時間: 150</div>
<div id="health-display-pc">体力: 100</div>
<div class="hud-buttons">
<div id="replay-button-pc" onclick="window.replayGame()">リプレイ</div>
<div id="explanation-button-pc">説明</div>
</div>
</div>
<div id="explanation-panel-pc" style="display: none;">
<p>3D巨大迷路へようこそ!<br>PC: WASDキーとマウスで移動<br>VR: 左スティックで移動、右スティックで視点・高さ変更<br>敵に注意し、ゴールを目指してください。</p>
<div id="explanation-close-button-pc">戻る</div>
</div>
</div>
<a-scene id="myScene" vr-mode-ui="enabled: true" background="color: #87CEEB">

<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;">
<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 id="vr-hud" position="0 0.05 -0.25" rotation="-60 0 0">
<a-plane id="hud-background" width="0.22" height="0.18" color="#FAFAFA" opacity="0.8" side="double"></a-plane>
<a-entity id="floor-display-vr"
troika-text="value: 1階; color: black; fontSize: 0.012; align: center; anchorX: center;"
position="0 0.06 0.01"></a-entity>
<a-entity id="score-display-vr"
troika-text="value: SCORE: 0; color: black; fontSize: 0.012; 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.012; align: center; anchorX: center;"
position="0 0.00 0.01"></a-entity>
<a-entity id="health-display-vr"
troika-text="value: 体力: 100; color: black; fontSize: 0.012; align: center; anchorX: center;"
position="0 -0.03 0.01"></a-entity>
<a-sphere
id="replay-sphere-vr"
class="clickable"
radius="0.02"
position="-0.05 -0.07 0.01"
material="color: red; shader: flat">
<a-entity
troika-text="value: リプレイ; color: white; fontSize: 0.01; align: center; anchor: center;"
position="0 0.03 0">
</a-entity>
</a-sphere>
<a-sphere
id="explanation-sphere-vr"
class="clickable"
radius="0.02"
position="0.05 -0.07 0.01"
material="color: #3498DB; shader: flat">
<a-entity
id="explanation-sphere-text-vr"
troika-text="value: 説明; color: white; fontSize: 0.01; align: center; anchor: center;"
position="0 0.03 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 id="explanation-panel-vr" position="0 0.05 -0.25" rotation="-60 0 0" visible="false">
<a-plane color="#333" opacity="0.9" width="0.25" height="0.15"></a-plane>
<a-entity troika-text="value: PC: WASDキーとマウスで移動\nVR: 左スティックで移動、右スティックで視点・高さ変更\n\n敵に注意し、ゴールを目指してください。; color: white; fontSize: 0.011; anchor: center; align: left; maxWidth: 0.23;"
position="0 0 0.01"></a-entity>
</a-entity>
</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 () {
// ▼▼▼【v26A 調整】再度モーションが動かない問題に対応。v25Bで動作していたロジックに戻します。▼▼▼
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);
}
// ▲▲▲【v26A 調整】ここまで ▲▲▲

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

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 explanationSphereVR = document.getElementById('explanation-sphere-vr');
const explanationSphereTextVR = document.getElementById('explanation-sphere-text-vr');
const explanationPanelVR = document.getElementById('explanation-panel-vr');

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 toggleExplanationPC() {
isExplanationOpen = !isExplanationOpen;
hudMainContentPC.style.display = isExplanationOpen ? 'none' : 'block';
explanationPanelPC.style.display = isExplanationOpen ? 'block' : 'none';
}

function toggleExplanationVR() {
isExplanationOpen = !isExplanationOpen;
explanationPanelVR.setAttribute('visible', isExplanationOpen);
explanationSphereTextVR.setAttribute('troika-text', 'value', isExplanationOpen ? '戻る' : '説明');
}

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

explanationButtonPC.addEventListener('click', toggleExplanationPC);
explanationCloseButtonPC.addEventListener('click', toggleExplanationPC);

startNewLevel();
}
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('vrm', 'src', vrmSrcPath);
vrmStartEl.setAttribute('position', { x: startWorldX, y: 0, z: startWorldZ - 1 });
}
if (vrmGoalEl) {
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; }
}
}

cons


使用変数

-------( Function )
) { if -------( Function )
) { this.health = 100; }, worldToMazeCoords: function -------( Function )
ary
ationCloseButtonPC
background
camera
cameraDirection
cameraEl
cameraObject
cameraRight
ceiling
class
color
controls
currentFloor
currentMazeSize
currentVelocity
cursor
damageDistance
damagePerSecond
data
deltaPosition
desiredVelocity
destinationCoords
destinationIndex
destWorldX
destWorldZ
detectionRange
direction2D
direction
display
dt
dx
dz
eftThumbstickInput
enemies
enemy
enemyContainer
enemyPos2D
enemyPos
eraWorldQuaternion
erOfEnemiesPerType
explanationPanelPC
explanationPanelVR
ffectiveLerpFactor
fromCoords) { if -------( Function )
gameTimer
generateMazeData -------( Function )
geometry
ghtThumbstickInput
goalI
goalJ
goalWorldX
goalWorldZ
goldenWallCoords
goToNextLevel -------( Function )
gridSize
ground
hasMazeData
health
healthElementPC
healthElementVR
height
hudMainContentPC
i
id
idWarpDestinations
innerHTML
isExplanationOpen
isGameOver
isInputting
isNearGold
isReady
isStartArea
isTakingDamage
j
keys
leftHand
length
lerpFactor
light
margin
material
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
mazeOffset
mazeResult
MAZE_CELL_SIZE
moveDirection
myPos2D
nationSphereTextVR
nextX
nextY
nextZ
offset
oldenWallPositions
onclick
onKeyDown
onKeyUp
opacity
playerCoords
playerPos2D
playerPos
position
radius
ranten
raycaster
remainingTime
replayButtonPC
rigEl
rightHand
rotation
sceneEl
score
scoreElPC
scoreElVR
scoreText
setupGame -------( Function )
shadow
side
signX
signZ
speed
src
startGridX
startGridZ
startNewLevel -------( Function )
startWorldX
startWorldZ
style
targetPos2D
targetPosition
text
textContent
tileTypeX
tileTypeX_enemy
tileTypeZ
tileTypeZ_enemy
time, timeDelta) { if -------( Function )
timeUntilChange
toggleExplanationPC -------( Function )
toggleExplanationVR -------( Function )
type
ui
updateScoreDisplay -------( Function )
verticalMovement
visible
vrm
vrmGoalEl
vrmSrcPath
vrmStartEl
WALL_HEIGHT
warpFloorEl
wavingMotion1
wavingMotion2
width
worldX, worldZ) { const i = Math.round -------( Function )
x
xplanationButtonPC
xplanationSphereVR
y
z
ZERO_VECTOR