<!DOCTYPE html>
<html>
<head>
<title>アリスの迷路 Ver1.004</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;
// ★ 修正点: タイミング問題の解決。親から直接データを取得する
const enemyContainer = this.el.parentNode;
if (enemyContainer && enemyContainer.mazeData) {
this.mazeData = enemyContainer.mazeData;
this.mazeCellSize = enemyContainer.mazeCellSize;
this.mazeOffset = enemyContainer.mazeOffset;
this.hasMazeData = true;
// console.log(`Enemy ${this.el.id} received maze data.`);
} else {
console.error(`Enemy ${this.el.id} could not find maze data on its container!`);
}
},
// ★ 修正点: setMazeDataは不要になったので削除
worldToMazeCoords: function(worldX, worldZ) {
if (!this.hasMazeData) return {i: 0, j: 0};
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.004<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.004<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.004\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.004\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 = [];
// ★ 修正点: 敵を生成する前に、親コンテナに地図データを設定する
const offset = (mazeData.length - 1) / 2;
enemyContainer.mazeData = mazeData;
enemyContainer.mazeCellSize = MAZE_CELL_SIZE;
enemyContainer.mazeOffset = offset;
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(); // ★ 修正点: 引数なしで呼び出す
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() { // ★ 修正点: 引数を削除
const enemyContainer = document.getElementById('enemy-container');
const validSpawnPoints = [];
const gridSize = enemyContainer.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 (enemyContainer.mazeData[i][j] === 0 || enemyContainer.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 x = (spawnPoint.i - enemyContainer.mazeOffset) * enemyContainer.mazeCellSize;
const z = (spawnPoint.j - enemyContainer.mazeOffset) * enemyContainer.mazeCellSize;
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;
// ★ 修正点: componentinitializedイベントは不要に
enemyEl.setAttribute('enemy-ai', {
type: type,
target: '#rig' // ★ 修正点: ターゲットを#rigに変更
});
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 | |
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) { if -------( Function ) | |
x | |
xplanationButtonPC | |
y | |
z | |
ZERO_VECTOR |