junkerstock
 vrz60 

<!DOCTYPE html>
<html>
<head>
<title>VRテスト環境 Ver6.0 最初の配置もグリッド上に配置</title>
<script src="https://aframe.io/releases/1.5.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>
// --- Component: Player Controls ---
AFRAME.registerComponent('camera-relative-controls', {
schema: {
targetSpeed: { type: 'number', default: 2 },
acceleration: { type: 'number', default: 3 },
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: 2 },
groundY: { type: 'number', default: 0 },
ceilingY: { type: 'number', default: 50 },
gravity: { type: 'number', default: 0 }
},
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;

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)); }
this.rightHand = document.getElementById('rightHand');
if (this.rightHand) { this.rightHand.addEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); }
});

this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
window.addEventListener('keydown', this.onKeyDown);
window.addEventListener('keyup', this.onKeyUp);
},
remove: function () {
window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('keyup', this.onKeyUp);
if (this.leftHand) { try { this.leftHand.removeEventListener('thumbstickmoved', this.onLeftThumbstickMoved.bind(this)); } catch(e){} }
if (this.rightHand) { try { this.rightHand.removeEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); } catch(e){} }
},
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; }

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;
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);
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 && data.gravity > 0) {
newY -= data.gravity * dt;
}
position.y = Math.max(data.groundY, Math.min(newY, data.ceilingY));
},
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('hand-grab', {
schema: {
enabled: { type: 'boolean', default: false },
grabRadius: { type: 'number', default: 0.1 }
},
init: function () {
this.grabbedEl = null;
this.objectContainer = document.getElementById('object-container');
this.handPos = new THREE.Vector3();
this.objPos = new THREE.Vector3();
this.isAButtonPressed = false;

this.onAButtonDown = this.onAButtonDown.bind(this);
this.onAButtonUp = this.onAButtonUp.bind(this);
this.onTriggerDown = this.onTriggerDown.bind(this);
this.onTriggerUp = this.onTriggerUp.bind(this);
this.el.addEventListener('triggerdown', this.onTriggerDown);
this.el.addEventListener('triggerup', this.onTriggerUp);
this.el.addEventListener('abuttondown', this.onAButtonDown);
this.el.addEventListener('abuttonup', this.onAButtonUp);
},
remove: function () {
this.el.removeEventListener('triggerdown', this.onTriggerDown);
this.el.removeEventListener('triggerup', this.onTriggerUp);
this.el.removeEventListener('abuttondown', this.onAButtonDown);
this.el.removeEventListener('abuttonup', this.onAButtonUp);
},
onAButtonDown: function() {
this.isAButtonPressed = true;
},
onAButtonUp: function() {
this.isAButtonPressed = false;
},
onTriggerDown: function () {
if (!this.data.enabled || this.grabbedEl) return;

this.el.object3D.getWorldPosition(this.handPos);
const grabbableEls = this.objectContainer.children;
let closestEl = null;
let closestDistSq = this.data.grabRadius * this.data.grabRadius;

for (let i = 0; i < grabbableEls.length; i++) {
const el = grabbableEls[i];
el.object3D.getWorldPosition(this.objPos);
const distSq = this.handPos.distanceToSquared(this.objPos);
if (distSq < closestDistSq) {
closestDistSq = distSq;
closestEl = el;
}
}

if (closestEl) {
if (this.isAButtonPressed) {
const worldPos = new THREE.Vector3();
const worldQuat = new THREE.Quaternion();
closestEl.object3D.getWorldPosition(worldPos);
closestEl.object3D.getWorldQuaternion(worldQuat);

const copyEl = closestEl.cloneNode(true);
const originalType = copyEl.tagName.toLowerCase() === 'a-box' ? 'box' : 'sphere';
copyEl.setAttribute('id', `${originalType}-${window.createdObjectCounter++}`);

this.objectContainer.appendChild(copyEl);

const euler = new THREE.Euler().setFromQuaternion(worldQuat, 'YXZ');
const rotationDegrees = {
x: THREE.MathUtils.radToDeg(euler.x),
y: THREE.MathUtils.radToDeg(euler.y),
z: THREE.MathUtils.radToDeg(euler.z)
};
copyEl.setAttribute('position', worldPos);
copyEl.setAttribute('rotation', rotationDegrees);

this.grabbedEl = closestEl;
} else {
this.grabbedEl = closestEl;
}
this.el.object3D.attach(this.grabbedEl.object3D);
}
},
onTriggerUp: function () {
if (!this.data.enabled || !this.grabbedEl) return;

this.objectContainer.object3D.attach(this.grabbedEl.object3D);
window.alignSingleObject(this.grabbedEl);
this.grabbedEl = null;
}
});
</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: 300px;
}
#hud-pc > div:not(:last-child) { margin-bottom: 12px; }
.hud-buttons { display: flex; gap: 10px; }
.hud-buttons > div {
flex-grow: 1; cursor: pointer; padding: 10px; text-align: center; border-radius: 5px;
}
#replay-button-pc { background-color: #C0392B; }
#replay-button-pc:hover { background-color: #A93226; }
#create-box-button-pc { background-color: #2980B9; transition: background-color 0.3s; }
#create-sphere-button-pc { background-color: #27AE60; transition: background-color 0.3s; }

.color-palette-pc {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 5px;
margin-bottom: 10px;
}
.color-swatch-pc {
width: 100%;
height: 30px;
border-radius: 4px;
cursor: pointer;
border: 2px solid rgba(255, 255, 255, 0.5);
box-sizing: border-box;
}
.color-swatch-pc:hover {
border-color: white;
}
</style>
</head>
<body>
<div id="hud-pc">
<div id="hud-main-content-pc">
<div id="color-palette-pc" class="color-palette-pc"></div>
<div class="hud-buttons">
<div id="replay-button-pc" onclick="handleStartReplayClick()">スタート</div>
<div id="create-box-button-pc">四角</div>
<div id="create-sphere-button-pc">球</div>
</div>
</div>
</div>

<a-scene id="myScene" vr-mode-ui="enabled: true" background="color: #87CEEB" cursor="rayOrigin: mouse">
<a-assets></a-assets>

<a-entity id="rig" position="0 0 5"
camera-relative-controls="
targetSpeed: 2;
acceleration: 3;
">
<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;" raycaster="objects: .clickable, .grabbable;" 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" hand-controls="hand: left;">
<a-entity id="vr-hud" position="0.02 0.04 -0.35" rotation="-60 0 0">
<a-plane id="hud-background" width="0.4" height="0.22" color="#FAFAFA" opacity="0.8" side="double"></a-plane>

<a-entity id="color-palette-vr" position="0 0.04 0.01"></a-entity>

<a-box id="replay-button-vr" class="clickable" position="-0.15 -0.07 0.01"
width="0.07" height="0.03" depth="0.01" material="color: red; shader: flat"></a-box>
<a-entity id="replay-text-vr" troika-text="value: スタート; color: black; fontSize: 0.012; align: center; anchor: center;"
position="-0.15 -0.07 0.016"></a-entity>

<a-box id="create-box-button-vr" class="clickable" width="0.03" height="0.03" depth="0.03" position="-0.05 -0.07 0.01" material="color: #3498DB; shader: flat"></a-box>

<a-sphere id="create-sphere-button-vr" class="clickable" radius="0.015" position="0.05 -0.07 0.01" material="color: #27AE60; shader: flat"></a-sphere>

<a-box id="toggle-grab-mode-vr" class="clickable" position="0.15 -0.07 0.01"
width="0.07" height="0.03" depth="0.01" material="color: yellow; shader: flat"></a-box>
<a-entity id="grab-mode-text-vr" troika-text="value: ライン; color: black; fontSize: 0.012; align: center; anchor: center;"
position="0.15 -0.07 0.016"></a-entity>
</a-entity>
</a-entity>

<a-entity id="rightHand"
hand-controls="hand: right;"
laser-controls="hand: right; model: false;"
raycaster="objects: .clickable, .grabbable; lineColor: white; lineOpacity: 0.75"
hand-grab>
</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;" position="-100 200 100"></a-entity>
<a-plane id="ground" position="0 0 0" rotation="-90 0 0" width="500" height="500" color="#7BC8A4"></a-plane>
<a-entity id="object-container"></a-entity>
</a-scene>

<script>
// --- Main Scene Logic ---
document.addEventListener('DOMContentLoaded', function () {
const sceneEl = document.querySelector('a-scene');
let isGameStarted = false;
window.createdObjectCounter = 0;
let grabbedObject = null;
let draggedObject = null;
let dragDistance = 0;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const worldPosition = new THREE.Vector3();
let isLaserGrabMode = true;
let selectedColor = '#CCCCCC';

let isAButtonPressed = false;
let isShiftKeyPressed = false;

window.addEventListener('keydown', function(event) {
if (event.key === 'Shift') {
isShiftKeyPressed = true;
}
});
window.addEventListener('keyup', function(event) {
if (event.key === 'Shift') {
isShiftKeyPressed = false;
}
});

const rightHand = document.getElementById('rightHand');
const rigEl = document.getElementById('rig');
const objectContainer = document.getElementById('object-container');
const createBoxButtonPC = document.getElementById('create-box-button-pc');
const createSphereButtonPC = document.getElementById('create-sphere-button-pc');

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

function startGame() {
if (isGameStarted) return;
isGameStarted = true;
console.log("Game started!");
document.getElementById('replay-button-pc').textContent = 'リプレイ';
document.getElementById('replay-text-vr').setAttribute('troika-text', 'value', 'リプレイ');
replayGame();
}

function replayGame() {
console.log("Replaying game...");
objectContainer.innerHTML = '';
window.createdObjectCounter = 0;
if (rigEl && rigEl.components['camera-relative-controls']) {
rigEl.setAttribute('camera-relative-controls', 'enabled', true);
}
rigEl.setAttribute('position', '0 0 5');
rigEl.setAttribute('rotation', '0 0 0');
}

function createBox(position) {
const snapSize = 0.1;
const snappedPosition = {
x: Math.round(position.x / snapSize) * snapSize,
y: Math.round(position.y / snapSize) * snapSize,
z: Math.round(position.z / snapSize) * snapSize
};

const box = document.createElement('a-box');
box.setAttribute('id', `box-${window.createdObjectCounter++}`);
box.classList.add('grabbable');
box.setAttribute('position', snappedPosition);
box.setAttribute('width', 0.1);
box.setAttribute('height', 0.1);
box.setAttribute('depth', 0.1);
box.setAttribute('color', selectedColor);
objectContainer.appendChild(box);
}

function createSphere(position) {
const snapSize = 0.1;
const snappedPosition = {
x: Math.round(position.x / snapSize) * snapSize,
y: Math.round(position.y / snapSize) * snapSize,
z: Math.round(position.z / snapSize) * snapSize
};

const sphere = document.createElement('a-sphere');
sphere.setAttribute('id', `sphere-${window.createdObjectCounter++}`);
sphere.classList.add('grabbable');
sphere.setAttribute('position', snappedPosition);
sphere.setAttribute('radius', 0.05);
sphere.setAttribute('color', selectedColor);
objectContainer.appendChild(sphere);
}

function alignSingleObject(obj) {
if (!obj) return;
const pos = obj.getAttribute('position');
let snapSize = 1;
if (obj.tagName.toLowerCase() === 'a-box') {
snapSize = obj.getAttribute('width');
} else if (obj.tagName.toLowerCase() === 'a-sphere') {
snapSize = obj.getAttribute('radius') * 2;
}
const newPos = {
x: Math.round(pos.x / snapSize) * snapSize,
y: Math.round(pos.y / snapSize) * snapSize,
z: Math.round(pos.z / snapSize) * snapSize
};
obj.setAttribute('position', newPos);
obj.setAttribute('rotation', '0 0 0');
}
window.alignSingleObject = alignSingleObject;

function toggleGrabMode() {
isLaserGrabMode = !isLaserGrabMode;
console.log("Grab mode toggled. Laser enabled:", isLaserGrabMode);
const lineEl = rightHand.querySelector('[line]');
const modeTextEl = document.getElementById('grab-mode-text-vr');
if (isLaserGrabMode) {
rightHand.setAttribute('laser-controls', 'enabled', true);
rightHand.setAttribute('hand-grab', 'enabled', false);
if (lineEl) lineEl.setAttribute('visible', true);
if (modeTextEl) modeTextEl.setAttribute('troika-text', 'value', 'ライン');
} else {
rightHand.setAttribute('laser-controls', 'enabled', false);
rightHand.setAttribute('hand-grab', 'enabled', true);
if (lineEl) lineEl.setAttribute('visible', false);
if (modeTextEl) modeTextEl.setAttribute('troika-text', 'value', 'ハンド');
}
}

function selectColor(newColor) {
selectedColor = newColor;
createBoxButtonPC.style.backgroundColor = newColor;
createSphereButtonPC.style.backgroundColor = newColor;
document.getElementById('create-box-button-vr').setAttribute('material', 'color', newColor);
document.getElementById('create-sphere-button-vr').setAttribute('material', 'color', newColor);
}

function onMouseMove(event) {
if (!draggedObject) return;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
const camera = sceneEl.camera;
raycaster.setFromCamera(mouse, camera);
raycaster.ray.at(dragDistance, worldPosition);
draggedObject.object3D.parent.worldToLocal(worldPosition);
draggedObject.object3D.position.copy(worldPosition);
}

function onMouseUp() {
if (draggedObject) {
alignSingleObject(draggedObject);
}
draggedObject = null;
dragDistance = 0;
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
}

function setupGame() {
const paletteContainerPC = document.getElementById('color-palette-pc');
const paletteContainerVR = document.getElementById('color-palette-vr');
const PALETTE_COLS = 5;
const PALETTE_ROWS = 3;

for (let i = 0; i < PALETTE_ROWS * PALETTE_COLS; i++) {
const hue = Math.floor((i / (PALETTE_ROWS * PALETTE_COLS)) * 360);
const color = `hsl(${hue}, 90%, 60%)`;

const swatchPC = document.createElement('div');
swatchPC.classList.add('color-swatch-pc');
swatchPC.style.backgroundColor = color;
swatchPC.addEventListener('click', () => selectColor(color));
paletteContainerPC.appendChild(swatchPC);

const swatchVR = document.createElement('a-box');
swatchVR.classList.add('clickable');
swatchVR.setAttribute('color', color);
swatchVR.setAttribute('width', '0.06');
swatchVR.setAttribute('height', '0.03');
swatchVR.setAttribute('depth', '0.01');
const x = (i % PALETTE_COLS - (PALETTE_COLS - 1) / 2) * 0.08;
const y = (Math.floor(i / PALETTE_COLS) - (PALETTE_ROWS - 1) / 2) * -0.05;
swatchVR.setAttribute('position', `${x} ${y} 0`);
swatchVR.dataset.color = color;
paletteContainerVR.appendChild(swatchVR);
}
selectColor(selectedColor);

sceneEl.addEventListener('mousedown', function(evt) {
if (sceneEl.is('vr-mode') || !isGameStarted) return;
const intersectedEl = evt.detail.intersectedEl;
if (intersectedEl && intersectedEl.classList.contains('color-swatch-pc')) {
selectColor(intersectedEl.style.backgroundColor);
return;
}
if (intersectedEl && intersectedEl.classList.contains('grabbable')) {
if (isShiftKeyPressed) {
const worldPos = new THREE.Vector3();
const worldQuat = new THREE.Quaternion();
intersectedEl.object3D.getWorldPosition(worldPos);
intersectedEl.object3D.getWorldQuaternion(worldQuat);

const copyEl = intersectedEl.cloneNode(true);
const originalType = copyEl.tagName.toLowerCase() === 'a-box' ? 'box' : 'sphere';
copyEl.setAttribute('id', `${originalType}-${window.createdObjectCounter++}`);

objectContainer.appendChild(copyEl);

const euler = new THREE.Euler().setFromQuaternion(worldQuat, 'YXZ');
const rotationDegrees = {
x: THREE.MathUtils.radToDeg(euler.x),
y: THREE.MathUtils.radToDeg(euler.y),
z: THREE.MathUtils.radToDeg(euler.z)
};
copyEl.setAttribute('position', worldPos);
copyEl.setAttribute('rotation', rotationDegrees);

draggedObject = intersectedEl;
} else {
draggedObject = intersectedEl;
}

const intersection = evt.detail.intersection;
if (!intersection) return;
dragDistance = intersection.distance;
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
}
});

if (rightHand) {
rightHand.addEventListener('abuttondown', function() { isAButtonPressed = true; });
rightHand.addEventListener('abuttonup', function() { isAButtonPressed = false; });

rightHand.addEventListener('triggerdown', function () {
// --- 【バグ修正】処理の順番を整理し、UIクリックを常に最優先で判定する ---
const raycasterComponent = rightHand.components.raycaster;
if (!raycasterComponent) return;
const intersectedEls = raycasterComponent.intersectedEls;
if (intersectedEls.length === 0) return;

const hitEl = intersectedEls[0];

// 1. UI系のボタンクリックを先に処理する (モード不問)
if (hitEl.dataset.color) { selectColor(hitEl.dataset.color); return; }
if (hitEl.id === 'replay-button-vr') { handleStartReplayClick(); return; }
if (hitEl.id === 'toggle-grab-mode-vr') { toggleGrabMode(); return; }
if (hitEl.id === 'create-box-button-vr' || hitEl.id === 'create-sphere-button-vr') {
if (isGameStarted) {
const controllerPos = new THREE.Vector3();
rightHand.object3D.getWorldPosition(controllerPos);
if (hitEl.id === 'create-box-button-vr') createBox(controllerPos);
else createSphere(controllerPos);
}
return;
}

// 2. UIボタンでなければ、ラインモードの時だけ掴む処理を行う
if (isLaserGrabMode && isGameStarted) {
if (hitEl.classList.contains('grabbable') && !grabbedObject) {
if (isAButtonPressed) {
const worldPos = new THREE.Vector3();
const worldQuat = new THREE.Quaternion();
hitEl.object3D.getWorldPosition(worldPos);
hitEl.object3D.getWorldQuaternion(worldQuat);

const copyEl = hitEl.cloneNode(true);
const originalType = copyEl.tagName.toLowerCase() === 'a-box' ? 'box' : 'sphere';
copyEl.setAttribute('id', `${originalType}-${window.createdObjectCounter++}`);

objectContainer.appendChild(copyEl);

const euler = new THREE.Euler().setFromQuaternion(worldQuat, 'YXZ');
const rotationDegrees = {
x: THREE.MathUtils.radToDeg(euler.x),
y: THREE.MathUtils.radToDeg(euler.y),
z: THREE.MathUtils.radToDeg(euler.z)
};
copyEl.setAttribute('position', worldPos);
copyEl.setAttribute('rotation', rotationDegrees);

grabbedObject = hitEl;
} else {
grabbedObject = hitEl;
}
rightHand.object3D.attach(grabbedObject.object3D);
}
}
});

rightHand.addEventListener('triggerup', function () {
// ハンドモードの掴む解除は hand-grab コンポーネントが担当するため、ここではラインモードのみを処理
if (isLaserGrabMode && grabbedObject) {
objectContainer.object3D.attach(grabbedObject.object3D);
alignSingleObject(grabbedObject);
grabbedObject = null;
}
});
}

createBoxButtonPC.addEventListener('click', () => {
if (isGameStarted) {
const camera = document.getElementById('camera');
const worldPos = new THREE.Vector3();
const direction = new THREE.Vector3();
camera.object3D.getWorldPosition(worldPos);
camera.object3D.getWorldDirection(direction);
worldPos.add(direction.multiplyScalar(-0.5));
createBox(worldPos);
}
});

createSphereButtonPC.addEventListener('click', () => {
if (isGameStarted) {
const camera = document.getElementById('camera');
const worldPos = new THREE.Vector3();
const direction = new THREE.Vector3();
camera.object3D.getWorldPosition(worldPos);
camera.object3D.getWorldDirection(direction);
worldPos.add(direction.multiplyScalar(-0.5));
createSphere(worldPos);
}
});

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

if (sceneEl.hasLoaded) { setupGame(); } else { sceneEl.addEventListener('loaded', setupGame, {once: true}); }
});
</script>
</body>
</html>


使用変数

-------( Function )
) { isAButtonPressed = false; }); rightHand.addEventListener -------( Function )
) { isAButtonPressed = true; }); rightHand.addEventListener -------( Function )
) { this.isAButtonPressed = true; }, onAButtonUp: function -------( Function )
alignSingleObject -------( Function )
alignSingleObject
background
backgroundColor
box
camera
cameraDirection
cameraEl
cameraObject
cameraRight
class
closestDistSq
closestEl
color
controllerPos
controls
copyEl
createBox -------( Function )
createBoxButtonPC
createSphere -------( Function )
currentVelocity
cursor
data
deltaPosition
depth
desiredVelocity
direction
display
distSq
dragDistance
draggedObject
dt
eatedObjectCounter
eateSphereButtonPC
eftThumbstickInput
el
eraWorldQuaternion
euler
event) { if -------( Function )
evt) { if -------( Function )
ffectiveLerpFactor
geometry
ghtThumbstickInput
grabbableEls
grabbedEl
grabbedObject
handleStartReplayClick -------( Function )
handPos
height
hitEl
hue
i
id
innerHTML
intersectedEl
intersectedEls
intersection
isAButtonPressed
isGameStarted
isInputting
isLaserGrabMode
isReady
isShiftKeyPressed
key
keys
leftHand
length
lerpFactor
leStartReplayClick
light
lineEl
material
modeTextEl
mouse
moveDirection
newPos
newY
objectContainer
objPos
onAButtonDown
onAButtonUp
onclick
onKeyDown
onKeyUp
onMouseMove -------( Function )
onMouseUp -------( Function )
onTriggerDown
onTriggerUp
opacity
originalType
paletteContainerPC
paletteContainerVR
PALETTE_COLS
PALETTE_ROWS
pcHud
pos
position
radius
raycaster
raycasterComponent
replayGame -------( Function )
rigEl
rightHand
rotation
rotationDegrees
sceneEl
selectColor -------( Function )
selectedColor
setupGame -------( Function )
side
snappedPosition
snapSize
sphere
src
startGame -------( Function )
swatchPC
swatchVR
text
textContent
toggleGrabMode -------( Function )
ui
verticalMovement
width
worldPos
worldPosition
worldQuat
x
y
ZERO_VECTOR