{"id":26115,"date":"2025-07-21T13:54:55","date_gmt":"2025-07-21T04:54:55","guid":{"rendered":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26115"},"modified":"2025-07-21T13:54:57","modified_gmt":"2025-07-21T04:54:57","slug":"versevr","status":"publish","type":"post","link":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26115","title":{"rendered":"VerseVR"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE html>\n&lt;html lang=\"ja\">\n&lt;head>\n  &lt;meta charset=\"UTF-8\" \/>\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n  &lt;title>Verse \u2013 \u30d7\u30ed\u30c0\u30af\u30b7\u30e7\u30f3\u7d1a\u30bd\u30fc\u30b7\u30e3\u30ebVR&lt;\/title>\n\n  &lt;!-- A-Frame Core -->\n  &lt;script src=\"https:\/\/aframe.io\/releases\/1.3.0\/aframe.min.js\">&lt;\/script>\n  &lt;!-- Networked-AFrame for multi-user sync -->\n  &lt;script src=\"https:\/\/unpkg.com\/networked-aframe@0.8.0\/dist\/networked-aframe.min.js\">&lt;\/script>\n  &lt;!-- GUI for VR buttons -->\n  &lt;script src=\"https:\/\/unpkg.com\/aframe-gui\/dist\/aframe-gui.min.js\">&lt;\/script>\n  &lt;!-- Environment presets -->\n  &lt;script src=\"https:\/\/unpkg.com\/aframe-environment-component\/dist\/aframe-environment-component.min.js\">&lt;\/script>\n  &lt;!-- Extras: teleport, locomotion, physics, super-hands -->\n  &lt;script src=\"https:\/\/unpkg.com\/aframe-extras@6.1.1\/dist\/aframe-extras.min.js\">&lt;\/script>\n  &lt;script src=\"https:\/\/unpkg.com\/aframe-super-hands-component@4.0.3\/dist\/aframe-super-hands.min.js\">&lt;\/script>\n  &lt;!-- Socket.IO for signaling -->\n  &lt;script src=\"https:\/\/cdn.socket.io\/4.5.0\/socket.io.min.js\">&lt;\/script>\n  &lt;!-- Simple-Peer for WebRTC voice chat -->\n  &lt;script src=\"https:\/\/unpkg.com\/simple-peer@9.11.0\/simplepeer.min.js\">&lt;\/script>\n\n  &lt;style>\n    body { margin: 0; }\n    #vr-scene { width: 100%; height: 100vh; }\n    .interactive:hover { animation: pulse 0.5s infinite alternate; }\n    @keyframes pulse { to { scale: 1.05; } }\n  &lt;\/style>\n&lt;\/head>\n&lt;body>\n\n  &lt;a-scene id=\"vr-scene\"\n           networked-scene=\"room: verse-room; serverURL: https:\/\/YOUR_SIGNAL_SERVER; app: verse-vr; debug: false\"\n           environment=\"preset: forest; groundColor:#445; skyColor:#889\"\n           extras=\"teleportControls: true; locomotionControls: true\"\n           physics=\"gravity: -9.8; debug: false\">\n\n    &lt;!-- Assets -->\n    &lt;a-assets>\n      &lt;audio id=\"click-sound\" src=\"click.mp3\">&lt;\/audio>\n      &lt;a-asset-item id=\"avatarModel\" src=\"avatar.glb\">&lt;\/a-asset-item>\n      &lt;img id=\"panel-bg\" src=\"panel-bg.png\" \/>\n    &lt;\/a-assets>\n\n    &lt;!-- Camera Rig -->\n    &lt;a-entity id=\"cameraRig\" position=\"0 1.6 0\"\n              locomotion-controls=\"fly:false; speed:4\"\n              teleport-controls=\"button: trigger; collisionEntities: #ground\">\n      &lt;a-entity camera look-controls>\n        &lt;a-cursor fuse=\"true\" fuseTimeout=\"300\" material=\"color:cyan; shader:flat\">&lt;\/a-cursor>\n      &lt;\/a-entity>\n      &lt;a-entity hand-tracking-controls=\"hand: left\" super-hands>&lt;\/a-entity>\n      &lt;a-entity hand-tracking-controls=\"hand: right\" super-hands>&lt;\/a-entity>\n    &lt;\/a-entity>\n\n    &lt;!-- Ground -->\n    &lt;a-plane id=\"ground\" position=\"0 0 0\" rotation=\"-90 0 0\" width=\"50\" height=\"50\" color=\"#444\" static-body>&lt;\/a-plane>\n\n    &lt;!-- Avatar Sync Template -->\n    &lt;template id=\"avatar-template\">\n      &lt;a-entity>\n        &lt;a-gltf-model src=\"#avatarModel\" scale=\"0.6 0.6 0.6\">&lt;\/a-gltf-model>\n      &lt;\/a-entity>\n    &lt;\/template>\n    &lt;a-entity networked-avatar networked=\"template:#avatar-template; attachTemplateToLocal:false\">&lt;\/a-entity>\n\n    &lt;!-- GUI Menu -->\n    &lt;a-gui-flex-container id=\"menu\" flex-direction=\"row\" justify-content=\"space-around\"\n                          panel-width=\"2\" panel-height=\"0.2\"\n                          component-padding=\"0.05 0.1\"\n                          position=\"0 2 -1.5\" material=\"src:#panel-bg; transparent:true\">\n      &lt;a-gui-button id=\"btnTextPost\" value=\"Text Post\" on-click=\"openTextInput()\" font-size=\"28px\" color=\"black\">&lt;\/a-gui-button>\n      &lt;a-gui-button id=\"btnVoicePost\" value=\"Voice Post\" on-click=\"startVoiceRecognition()\" font-size=\"28px\" color=\"black\">&lt;\/a-gui-button>\n      &lt;a-gui-button id=\"btnGPTChat\" value=\"GPT BOT\" on-click=\"callGPTBot()\" font-size=\"28px\" color=\"black\">&lt;\/a-gui-button>\n      &lt;a-gui-button id=\"btnLikeMode\" value=\"Like\/Delete\" on-click=\"toggleLikeMode()\" font-size=\"28px\" color=\"black\">&lt;\/a-gui-button>\n      &lt;a-gui-button id=\"btnSpawnCube\" value=\"Spawn Cube\" on-click=\"spawnCube()\" font-size=\"28px\" color=\"black\">&lt;\/a-gui-button>\n      &lt;a-gui-button id=\"btnVoiceChat\" value=\"Voice Chat\" on-click=\"toggleVoiceChat()\" font-size=\"28px\" color=\"black\">&lt;\/a-gui-button>\n    &lt;\/a-gui-flex-container>\n\n    &lt;!-- Containers -->\n    &lt;a-entity id=\"post-container\">&lt;\/a-entity>\n    &lt;a-entity id=\"ugc-container\">&lt;\/a-entity>\n\n  &lt;\/a-scene>\n\n  &lt;script>\n  \/\/ ----------- \u30c7\u30fc\u30bf -----------\n  let posts = JSON.parse(localStorage.getItem('posts')||'&#91;]');\n  let likeMode = false;\n  let recognition;\n  let socket = io('https:\/\/YOUR_SIGNAL_SERVER');\n  let peers = {};\n  let localStream;\n\n  \/\/ ----------- \u6295\u7a3f\u30ec\u30f3\u30c0\u30ea\u30f3\u30b0 -----------\n  function renderPosts() {\n    const container = document.getElementById('post-container');\n    container.innerHTML = '';\n    posts.forEach((p,i)=>{\n      const angle = (i\/posts.length)*Math.PI*2;\n      const x = Math.cos(angle)*2;\n      const z = Math.sin(angle)*2;\n      const postEl = document.createElement('a-entity');\n      postEl.classList.add('interactive');\n      postEl.setAttribute('geometry','primitive: plane; width:1.2; height:0.5');\n      postEl.setAttribute('material','color:#fff; shader:flat');\n      postEl.setAttribute('position',`${x} 1.3 ${z}`);\n      postEl.setAttribute('rotation',`0 ${-angle*180\/Math.PI+90} 0`);\n      postEl.setAttribute('text',`value:${p.content}; width:1.1; align:center; color:#000`);\n      \/\/ \u30af\u30ea\u30c3\u30af\u51e6\u7406\n      postEl.addEventListener('click', ()=>{\n        if(likeMode) {\n          p.likes = (p.likes||0)+1;\n          savePosts(); renderPosts();\n        }\n      });\n      \/\/ \u524a\u9664\u30dc\u30bf\u30f3\n      if(likeMode) {\n        const delBtn = document.createElement('a-entity');\n        delBtn.setAttribute('geometry','primitive: plane; width:0.2; height:0.1');\n        delBtn.setAttribute('material','color:#f88; shader:flat');\n        delBtn.setAttribute('position','0.55 -0.25 0.01');\n        delBtn.setAttribute('text','value:Del; width:0.2; align:center; color:#000');\n        delBtn.addEventListener('click', ()=>{ posts.splice(i,1); savePosts(); renderPosts(); });\n        postEl.appendChild(delBtn);\n      }\n      container.appendChild(postEl);\n    });\n  }\n  function savePosts(){ localStorage.setItem('posts', JSON.stringify(posts)); }\n\n  \/\/ ----------- \u30c6\u30ad\u30b9\u30c8\u6295\u7a3f -----------\n  window.openTextInput = ()=>{\n    const txt = prompt('\u6295\u7a3f\u5185\u5bb9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044');\n    if(txt){ posts.unshift({content:txt, likes:0}); savePosts(); renderPosts(); socket.emit('new-post', txt); }\n  };\n\n  \/\/ ----------- \u97f3\u58f0\u6295\u7a3f -----------\n  window.startVoiceRecognition = ()=>{\n    if(!('webkitSpeechRecognition' in window)) return alert('\u97f3\u58f0\u8a8d\u8b58\u975e\u5bfe\u5fdc');\n    recognition = new webkitSpeechRecognition();\n    recognition.lang='ja-JP'; recognition.interimResults=false;\n    recognition.onresult=e=>{ const txt=e.results&#91;0]&#91;0].transcript; posts.unshift({content:txt,likes:0}); savePosts(); renderPosts(); };\n    recognition.onerror=()=>alert('\u8a8d\u8b58\u30a8\u30e9\u30fc'); recognition.start();\n  };\n\n  \/\/ ----------- GPT BOT -----------\n  window.callGPTBot = async ()=>{\n    const key='YOUR_OPENAI_API_KEY';\n    const res=await fetch('https:\/\/api.openai.com\/v1\/chat\/completions',{ method:'POST',\n      headers:{'Content-Type':'application\/json','Authorization':`Bearer ${key}`},\n      body:JSON.stringify({ model:'gpt-4o-mini', messages:&#91;{role:'system',content:'\u3042\u306a\u305f\u306fVR BOT\u3067\u3059\u3002'}]\n        .concat(posts.slice(0,5).map(p=>({role:'user',content:p.content}))), max_tokens:50 })\n    });\n    const js=await res.json();\n    const txt=js.choices&#91;0].message.content.trim();\n    posts.unshift({content:`\ud83e\udd16 GPT: ${txt}`,likes:0}); savePosts(); renderPosts();\n  };\n\n  \/\/ ----------- \u3044\u3044\u306d\uff0f\u524a\u9664\u30e2\u30fc\u30c9 -----------\n  window.toggleLikeMode = ()=>{ likeMode=!likeMode; renderPosts(); };\n\n  \/\/ ----------- UGC: \u30ad\u30e5\u30fc\u30d6\u751f\u6210 -----------\n  window.spawnCube = ()=>{\n    const c=document.createElement('a-box');\n    c.setAttribute('class','interactive');\n    c.setAttribute('position','0 1 -1'); c.setAttribute('depth','0.5'); c.setAttribute('height','0.5'); c.setAttribute('width','0.5');\n    c.setAttribute('material','color:#4CC3D9'); c.setAttribute('dynamic-body','');\n    c.setAttribute('grabbable',''); c.setAttribute('stretchable','');\n    document.getElementById('ugc-container').appendChild(c);\n  };\n\n  \/\/ ----------- Voice Chat -----------\n  window.toggleVoiceChat = ()=>{ localStream?stopVoiceChat():startVoiceChat(); };\n  async function startVoiceChat() {\n    localStream = await navigator.mediaDevices.getUserMedia({audio:true});\n    socket.emit('join-voice');\n    socket.on('signal', ({ id, signal })=>{\n      if(!peers&#91;id]) createPeer(id, false);\n      peers&#91;id].signal(signal);\n    });\n    socket.on('user-joined', id=>{ createPeer(id, true); });\n    function createPeer(id, initiator) {\n      const peer = new SimplePeer({ initiator, trickle:false, stream:localStream });\n      peer.on('signal', signal=>{ socket.emit('signal',{ id: socket.id, to:id, signal }); });\n      peer.on('stream', stream=>{ const e=document.createElement('audio'); e.srcObject=stream; e.autoplay=true; document.body.appendChild(e); });\n      peers&#91;id]=peer;\n    }\n  }\n  function stopVoiceChat(){ Object.values(peers).forEach(p=>p.destroy()); peers={}; localStream.getTracks().forEach(t=>t.stop()); localStream=null; }\n\n  \/\/ ----------- Socket.IO \u30a4\u30d9\u30f3\u30c8 -----------\n  socket.on('new-post', txt=>{ posts.unshift({content:txt,likes:0}); savePosts(); renderPosts(); });\n\n  \/\/ ----------- \u521d\u671f\u30ed\u30fc\u30c9 -----------\n  document.querySelector('a-scene').addEventListener('loaded', ()=>{ renderPosts(); });\n\n  &lt;\/script>\n\n&lt;\/body>\n&lt;\/html>\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[4,6],"tags":[3],"class_list":["post-26115","post","type-post","status-publish","format-standard","hentry","category-programming","category-web","tag-programming"],"aioseo_notices":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26115","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=26115"}],"version-history":[{"count":1,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26115\/revisions"}],"predecessor-version":[{"id":26116,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26115\/revisions\/26116"}],"wp:attachment":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=26115"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=26115"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=26115"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}