{"id":25865,"date":"2025-02-16T09:58:25","date_gmt":"2025-02-16T00:58:25","guid":{"rendered":"http:\/\/www.tyosuke20xx.com\/blog\/?p=25865"},"modified":"2025-02-16T09:58:27","modified_gmt":"2025-02-16T00:58:27","slug":"vrrpg-html","status":"publish","type":"post","link":"http:\/\/www.tyosuke20xx.com\/blog\/?p=25865","title":{"rendered":"VRRPG.html"},"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\">\n  &lt;title>VRRPG - \u62e1\u5f35\u7248 AR\/VR RPG&lt;\/title>\n  &lt;!-- A-Frame \u30e9\u30a4\u30d6\u30e9\u30ea -->\n  &lt;script src=\"https:\/\/aframe.io\/releases\/1.4.0\/aframe.min.js\">&lt;\/script>\n  &lt;!-- Particle system \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\uff08\u30d1\u30fc\u30c6\u30a3\u30af\u30eb\u6f14\u51fa\u7528\uff09 -->\n  &lt;script src=\"https:\/\/cdn.jsdelivr.net\/gh\/IdeaSpaceVR\/aframe-particle-system-component@master\/dist\/aframe-particle-system-component.min.js\">&lt;\/script>\n  &lt;style>\n    body { margin: 0; overflow: hidden; }\n    \/* \u5404\u7a2e\u30aa\u30fc\u30d0\u30fc\u30ec\u30a4 *\/\n    #mainMenuOverlay, #upgradeOverlay, #pauseOverlay, #gameOverOverlay {\n      position: absolute;\n      top: 0; left: 0;\n      width: 100%; height: 100%;\n      background: rgba(0,0,0,0.8);\n      color: #FFF;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      font-size: 48px;\n      z-index: 999;\n      display: none;\n      text-align: center;\n      flex-direction: column;\n    }\n    #mainMenuOverlay button, #upgradeOverlay button, #gameOverOverlay button {\n      font-size: 36px;\n      padding: 20px 40px;\n      margin-top: 20px;\n    }\n  &lt;\/style>\n&lt;\/head>\n&lt;body>\n  &lt;!-- \u30e1\u30a4\u30f3\u30e1\u30cb\u30e5\u30fc -->\n  &lt;div id=\"mainMenuOverlay\" style=\"display: flex;\">\n    &lt;div>\n      &lt;div>VRRPG - \u62e1\u5f35\u7248 AR\/VR RPG&lt;\/div>\n      &lt;button id=\"startButton\">Start Game&lt;\/button>\n    &lt;\/div>\n  &lt;\/div>\n  &lt;!-- \u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\u30b9\u30c8\u30a2 -->\n  &lt;div id=\"upgradeOverlay\">\n    &lt;div>\n      &lt;div>Upgrade Store&lt;\/div>\n      &lt;div>Press 1: Increase Sword Damage (+10) (Cost: 50 Score)&lt;\/div>\n      &lt;div>Press 2: Increase Max Health (+20) (Cost: 50 Score)&lt;\/div>\n      &lt;button id=\"closeUpgrade\">Close&lt;\/button>\n    &lt;\/div>\n  &lt;\/div>\n  &lt;!-- \u30dd\u30fc\u30ba\u30aa\u30fc\u30d0\u30fc\u30ec\u30a4 -->\n  &lt;div id=\"pauseOverlay\">Paused&lt;\/div>\n  &lt;!-- \u30b2\u30fc\u30e0\u30aa\u30fc\u30d0\u30fc\u30aa\u30fc\u30d0\u30fc\u30ec\u30a4 -->\n  &lt;div id=\"gameOverOverlay\">\n    &lt;div>\n      &lt;div>Game Over!&lt;\/div>\n      &lt;div id=\"finalScore\">Final Score: 0&lt;\/div>\n      &lt;button id=\"restartButton\">Restart&lt;\/button>\n    &lt;\/div>\n  &lt;\/div>\n\n  &lt;!-- AR\/VR\u30b7\u30fc\u30f3\uff1aXR\u30e2\u30fc\u30c9\u3092 AR \u306b\u8a2d\u5b9a\uff08Oculus\u30d1\u30b9\u30b9\u30eb\u30fc\u5229\u7528\uff09 -->\n  &lt;a-scene xr=\"mode: ar; referenceSpaceType: local-floor\">\n    &lt;!-- \u80cc\u666f\u97f3\u697d -->\n    &lt;a-entity id=\"bg-music\" sound=\"src: url(bg-music.mp3); autoplay: true; loop: true; volume: 0.3\">&lt;\/a-entity>\n    &lt;!-- \u74b0\u5883 -->\n    &lt;a-sky color=\"#88ccee\">&lt;\/a-sky>\n    &lt;a-plane position=\"0 0 0\" rotation=\"-90 0 0\" width=\"30\" height=\"30\" color=\"#77aa55\">&lt;\/a-plane>\n    &lt;a-light type=\"directional\" intensity=\"0.8\" position=\"0 10 5\">&lt;\/a-light>\n\n    &lt;!-- \u30d7\u30ec\u30a4\u30e4\u30fc\uff08\u30ab\u30e1\u30e9\u3001HUD\u3001\u30dd\u30fc\u30ba\u5bfe\u5fdc\uff09 -->\n    &lt;a-entity id=\"player\" weapon-switcher position=\"0 1.6 5\">\n      &lt;a-camera wasd-controls look-controls>\n        &lt;!-- \u53f3\u624b\uff1a\u30ab\u30e1\u30e9\u5185\u53f3\u4e0b\u306b\u56fa\u5b9a\u8868\u793a\uff08\u88c5\u5099\u54c1\uff09 -->\n        &lt;a-entity id=\"rightHand\" position=\"0.5 -0.3 -1\">&lt;\/a-entity>\n      &lt;\/a-camera>\n      &lt;!-- HUD -->\n      &lt;a-entity id=\"hud\" position=\"0 -0.5 -1.5\">\n        &lt;a-text id=\"scoreText\" value=\"Score: 0\" position=\"-1 0.7 0\" color=\"#FFF\" width=\"4\">&lt;\/a-text>\n        &lt;a-text id=\"healthText\" value=\"Health: 100\" position=\"-1 0.4 0\" color=\"#FFF\" width=\"4\">&lt;\/a-text>\n        &lt;a-text id=\"waveText\" value=\"Wave: 1\" position=\"-1 0.1 0\" color=\"#FFF\" width=\"4\">&lt;\/a-text>\n        &lt;a-text id=\"levelText\" value=\"Lv: 1 Exp: 0\" position=\"-1 -0.2 0\" color=\"#FFF\" width=\"4\">&lt;\/a-text>\n        &lt;a-text id=\"weaponText\" value=\"Weapon: None\" position=\"-1 -0.5 0\" color=\"#FFF\" width=\"4\">&lt;\/a-text>\n      &lt;\/a-entity>\n    &lt;\/a-entity>\n\n    &lt;!-- \u843d\u3061\u3066\u3044\u308b\u5263\uff08Sword\uff09 -->\n    &lt;a-entity id=\"sword\" position=\"0.3 1 -2\" sword-swing pickup>\n      &lt;!-- \u30d6\u30ec\u30fc\u30c9 -->\n      &lt;a-entity geometry=\"primitive: box; height: 1; width: 0.1; depth: 0.05\" \n                material=\"color: silver; metalness: 0.8; roughness: 0.2\" \n                position=\"0 0.5 0\">&lt;\/a-entity>\n      &lt;!-- \u30ac\u30fc\u30c9 -->\n      &lt;a-entity geometry=\"primitive: box; height: 0.2; width: 0.3; depth: 0.05\" \n                material=\"color: gold\" \n                position=\"0 0.05 0\">&lt;\/a-entity>\n      &lt;!-- \u30cf\u30f3\u30c9\u30eb -->\n      &lt;a-entity geometry=\"primitive: cylinder; radius: 0.05; height: 0.4\" \n                material=\"color: brown\" \n                position=\"0 -0.3 0\" rotation=\"90 0 0\">&lt;\/a-entity>\n      &lt;!-- \u56de\u8ee2\u30a2\u30cb\u30e1\u30fc\u30b7\u30e7\u30f3\uff08\u62fe\u308f\u308c\u308b\u307e\u3067\uff09 -->\n      &lt;a-animation attribute=\"rotation\" dur=\"3000\" fill=\"forwards\" to=\"0 360 0\" repeat=\"indefinite\">&lt;\/a-animation>\n    &lt;\/a-entity>\n\n    &lt;!-- \u9b54\u6cd5\u306e\u6756\uff08Magic Wand\uff09 \u203b \u672a\u4f7f\u7528 -->\n    &lt;a-entity id=\"magicWand\" geometry=\"primitive: cylinder; height: 0.8; radius: 0.05\" \n              material=\"color: purple; emissive: #aa00ff\" \n              position=\"0.3 1 -0.5\" rotation=\"0 0 0\" wand-fire visible=\"false\">&lt;\/a-entity>\n\n    &lt;!-- \u6575\u30b9\u30dd\u30fc\u30f3\u7528\u30a8\u30ea\u30a2 -->\n    &lt;a-entity id=\"enemy-spawn\">&lt;\/a-entity>\n\n    &lt;!-- \u30b5\u30a6\u30f3\u30c9\u8a2d\u5b9a -->\n    &lt;a-entity id=\"sword-sound\" sound=\"src: url(sword-swing.mp3); on: none\">&lt;\/a-entity>\n    &lt;a-entity id=\"pickup-sound\" sound=\"src: url(pickup.mp3); on: none\">&lt;\/a-entity>\n    &lt;a-entity id=\"wand-sound\" sound=\"src: url(wand-fire.mp3); on: none\">&lt;\/a-entity>\n\n    &lt;!-- \u30a4\u30f3\u30b9\u30c8\u30e9\u30af\u30b7\u30e7\u30f3\u8868\u793a\uff08\u521d\u56de\u306e\u307f\uff09 -->\n    &lt;a-entity id=\"instructions\" position=\"0 2 -3\">\n      &lt;a-text value=\"Controls: Oculus Touch \/ Gamepad \/ WASD+Mouse&amp;#10;\u30af\u30ea\u30c3\u30af\u3067\u5263\u3092\u62fe\u3044\u3001\u5263\u3092\u30af\u30ea\u30c3\u30af\u3067\u632f\u308b&amp;#10;P\u30ad\u30fc\u3067\u30dd\u30fc\u30ba \/ U\u30ad\u30fc\u3067\u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\" \n              align=\"center\" color=\"#FFF\" width=\"6\">&lt;\/a-text>\n    &lt;\/a-entity>\n\n    &lt;!-- \u30a6\u30a7\u30fc\u30d6\u7ba1\u7406 -->\n    &lt;a-entity wave-manager>&lt;\/a-entity>\n  &lt;\/a-scene>\n\n  &lt;script>\n    \/************ \u30b2\u30fc\u30e0\u30c7\u30fc\u30bf\u7ba1\u7406 ************\/\n    var gameData = {\n      score: 0,\n      playerHealth: 100,\n      wave: 1,\n      playerLevel: 1,\n      experience: 0,\n      currentWeapon: \"None\",\n      swordDamage: 50,\n      maxHealth: 100,\n      hasSword: false,\n      paused: false,\n      gameState: \"menu\" \/\/ \"menu\", \"playing\", \"paused\", \"gameover\"\n    };\n\n    \/************ HUD \u66f4\u65b0\u95a2\u6570 ************\/\n    function updateHUD() {\n      document.querySelector('#scoreText').setAttribute('value', 'Score: ' + gameData.score);\n      document.querySelector('#healthText').setAttribute('value', 'Health: ' + gameData.playerHealth);\n      document.querySelector('#waveText').setAttribute('value', 'Wave: ' + gameData.wave);\n      document.querySelector('#levelText').setAttribute('value', 'Lv: ' + gameData.playerLevel + ' Exp: ' + gameData.experience);\n      document.querySelector('#weaponText').setAttribute('value', 'Weapon: ' + gameData.currentWeapon);\n    }\n\n    \/************ \u30b2\u30fc\u30e0\u30aa\u30fc\u30d0\u30fc\u30c1\u30a7\u30c3\u30af ************\/\n    function checkGameOver() {\n      if (gameData.playerHealth &lt;= 0) {\n        gameData.gameState = \"gameover\";\n        document.getElementById('gameOverOverlay').style.display = \"flex\";\n        document.getElementById('finalScore').innerText = \"Final Score: \" + gameData.score;\n      }\n    }\n\n    \/************ \u7d4c\u9a13\u5024\u52a0\u7b97\uff06\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7 ************\/\n    function addExperience(exp) {\n      gameData.experience += exp;\n      if (gameData.experience >= 100) {\n        gameData.experience -= 100;\n        gameData.playerLevel++;\n        gameData.playerHealth = Math.min(gameData.maxHealth, gameData.playerHealth + 20);\n        openUpgradeStore();\n      }\n      updateHUD();\n    }\n\n    \/************ \u6575\u6483\u7834\u6642\u306e\u6f14\u51fa ************\/\n    function killEnemy(enemy) {\n      if (!enemy) return;\n      let healthBar = enemy.querySelector('.health-bar');\n      if (healthBar) { healthBar.parentNode.removeChild(healthBar); }\n      let explosion = document.createElement('a-entity');\n      explosion.setAttribute('particle-system', 'preset: dust; particleCount: 100; color: #FFAA00, #FF0000;');\n      explosion.setAttribute('position', enemy.getAttribute('position'));\n      enemy.parentNode.appendChild(explosion);\n      setTimeout(function(){ if(explosion.parentNode) explosion.parentNode.removeChild(explosion); }, 1000);\n      enemy.setAttribute('animation', 'property: scale; to: 0 0 0; dur: 500; easing: easeInOutQuad');\n      setTimeout(function(){ if(enemy.parentNode) enemy.parentNode.removeChild(enemy); }, 500);\n    }\n\n    \/************ \u30ab\u30e1\u30e9\u30b7\u30a7\u30a4\u30af ************\/\n    function cameraShake() {\n      let camera = document.querySelector('a-camera');\n      if (!camera) return;\n      let origPos = camera.getAttribute('position');\n      let shakePos = {\n        x: origPos.x + (Math.random()-0.5)*0.1,\n        y: origPos.y + (Math.random()-0.5)*0.1,\n        z: origPos.z\n      };\n      camera.setAttribute('position', shakePos);\n      setTimeout(function(){ camera.setAttribute('position', origPos); }, 100);\n    }\n\n    \/************ \u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\u30b9\u30c8\u30a2 ************\/\n    function openUpgradeStore() {\n      document.getElementById('upgradeOverlay').style.display = \"flex\";\n      gameData.paused = true;\n    }\n    function closeUpgradeStore() {\n      document.getElementById('upgradeOverlay').style.display = \"none\";\n      gameData.paused = false;\n    }\n    document.getElementById('closeUpgrade').addEventListener('click', closeUpgradeStore);\n\n    \/************ \u30e1\u30a4\u30f3\u30e1\u30cb\u30e5\u30fc\uff06\u30ea\u30b9\u30bf\u30fc\u30c8 ************\/\n    document.getElementById('startButton').addEventListener('click', function(){\n      document.getElementById('mainMenuOverlay').style.display = \"none\";\n      gameData.gameState = \"playing\";\n    });\n    document.getElementById('restartButton').addEventListener('click', function(){\n      window.location.reload();\n    });\n\n    \/************ \u30ad\u30fc\u64cd\u4f5c ************\/\n    document.addEventListener('keydown', function(e) {\n      if(e.key.toLowerCase() === 'p') {\n        gameData.paused = !gameData.paused;\n        document.getElementById('pauseOverlay').style.display = gameData.paused ? \"flex\" : \"none\";\n      }\n      if(e.key === 'u' &amp;&amp; gameData.gameState === \"playing\" &amp;&amp; !gameData.paused) {\n        openUpgradeStore();\n      }\n      if(document.getElementById('upgradeOverlay').style.display === \"flex\") {\n        if(e.key === '1') {\n          if(gameData.score >= 50) {\n            gameData.swordDamage += 10;\n            gameData.score -= 50;\n            updateHUD();\n          }\n        }\n        if(e.key === '2') {\n          if(gameData.score >= 50) {\n            gameData.maxHealth += 20;\n            gameData.score -= 50;\n            updateHUD();\n          }\n        }\n      }\n    });\n\n    \/************ pickup \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    AFRAME.registerComponent('pickup', {\n      init: function() {\n        let el = this.el;\n        el.addEventListener('click', function () {\n          if(gameData.paused || gameData.gameState !== \"playing\") return;\n          let player = document.querySelector('#player');\n          let playerPos = new THREE.Vector3();\n          player.object3D.getWorldPosition(playerPos);\n          let itemPos = new THREE.Vector3();\n          el.object3D.getWorldPosition(itemPos);\n          if(playerPos.distanceTo(itemPos) &lt; 2) {\n            if(!gameData.hasSword) {\n              let pickupSound = document.querySelector('#pickup-sound');\n              if(pickupSound &amp;&amp; pickupSound.components.sound) {\n                pickupSound.components.sound.playSound();\n              }\n              let rightHand = document.querySelector('#rightHand');\n              if(rightHand) {\n                rightHand.appendChild(el);\n                el.setAttribute('position', '0 0 0');\n              } else {\n                player.appendChild(el);\n                el.setAttribute('position', '0.3 0 -0.5');\n              }\n              gameData.currentWeapon = \"Sword\";\n              gameData.hasSword = true;\n              updateHUD();\n              console.log(\"Sword picked up!\");\n              el.removeAttribute('animation');\n            } else {\n              console.log(\"Already holding a sword.\");\n            }\n          }\n        });\n      }\n    });\n\n    \/************ enemy-ai \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    AFRAME.registerComponent('enemy-ai', {\n      schema: {\n        speed: {type: 'number', default: 0.02},\n        damage: {type: 'number', default: 5}\n      },\n      init: function() { this.attackCooldown = 0; },\n      tick: function(time, timeDelta) {\n        if(gameData.paused) return;\n        let player = document.querySelector('#player');\n        if(!player) return;\n        let enemy = this.el;\n        let enemyPos = enemy.object3D.position;\n        let playerPos = player.object3D.position;\n        let direction = new THREE.Vector3().subVectors(playerPos, enemyPos);\n        let distance = direction.length();\n        if(distance > 0.1) {\n          direction.normalize();\n          enemy.object3D.position.add(direction.multiplyScalar(this.data.speed * (timeDelta\/16)));\n        }\n        if(distance &lt; 1 &amp;&amp; this.attackCooldown &lt;= 0) {\n          gameData.playerHealth -= this.data.damage;\n          updateHUD();\n          cameraShake();\n          checkGameOver();\n          this.attackCooldown = 1000;\n        } else {\n          this.attackCooldown -= timeDelta;\n        }\n      }\n    });\n\n    \/************ enemy-health \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    AFRAME.registerComponent('enemy-health', {\n      schema: {\n        hp: {type: 'number', default: 100},\n        maxHp: {type: 'number', default: 100}\n      },\n      init: function(){\n        let bar = document.createElement('a-plane');\n        bar.setAttribute('class', 'health-bar');\n        bar.setAttribute('width', '1');\n        bar.setAttribute('height', '0.1');\n        bar.setAttribute('color', 'green');\n        bar.setAttribute('position', '0 0.8 0');\n        this.el.appendChild(bar);\n      },\n      updateHealthBar: function(){\n        let healthBar = this.el.querySelector('.health-bar');\n        if(healthBar) {\n          let hp = this.data.hp, max = this.data.maxHp;\n          let scaleX = Math.max(0, hp\/max);\n          healthBar.setAttribute('scale', `${scaleX} 1 1`);\n          let color = (scaleX > 0.5) ? \"green\" : (scaleX > 0.2 ? \"yellow\" : \"red\");\n          healthBar.setAttribute('color', color);\n        }\n      }\n    });\n\n    \/************ sword-swing \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    AFRAME.registerComponent('sword-swing', {\n      init: function(){\n        let sword = this.el;\n        let self = this;\n        sword.addEventListener('triggerdown', function(){ self.swing(); });\n        sword.addEventListener('click', function(){ self.swing(); });\n      },\n      swing: function(){\n        if(gameData.paused) return;\n        this.el.emit('swing');\n        let soundEl = document.querySelector('#sword-sound');\n        if(soundEl &amp;&amp; soundEl.components.sound){\n          soundEl.components.sound.playSound();\n        }\n        let swordPos = new THREE.Vector3();\n        this.el.object3D.getWorldPosition(swordPos);\n        let enemies = document.querySelectorAll('.enemy');\n        enemies.forEach(function(enemy){\n          let enemyPos = new THREE.Vector3();\n          enemy.object3D.getWorldPosition(enemyPos);\n          if(swordPos.distanceTo(enemyPos) &lt; 1){\n            let eh = enemy.getAttribute('enemy-health');\n            eh.hp -= gameData.swordDamage;\n            enemy.setAttribute('enemy-health', 'hp', eh.hp);\n            enemy.components&#91;'enemy-health'].updateHealthBar();\n            if(eh.hp &lt;= 0){\n              killEnemy(enemy);\n              gameData.score += 10;\n              addExperience(20);\n            } else {\n              enemy.setAttribute('material', 'color', '#ff4444');\n              setTimeout(function(){ enemy.setAttribute('material', 'color', '#66ff66'); }, 200);\n            }\n            updateHUD();\n          }\n        });\n      }\n    });\n\n    \/************ wand-fire \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    AFRAME.registerComponent('wand-fire', {\n      init: function(){\n        let wand = this.el;\n        let self = this;\n        wand.addEventListener('triggerdown', function(){ self.fire(); });\n        wand.addEventListener('click', function(){ self.fire(); });\n      },\n      fire: function(){\n        if(gameData.paused) return;\n        let wandSound = document.querySelector('#wand-sound');\n        if(wandSound &amp;&amp; wandSound.components.sound){\n          wandSound.components.sound.playSound();\n        }\n        let projectile = document.createElement('a-sphere');\n        projectile.setAttribute('radius', '0.1');\n        projectile.setAttribute('color', 'orange');\n        let startPos = new THREE.Vector3();\n        this.el.object3D.getWorldPosition(startPos);\n        projectile.setAttribute('position', startPos);\n        projectile.setAttribute('projectile', '');\n        this.el.sceneEl.appendChild(projectile);\n      }\n    });\n\n    \/************ projectile \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    AFRAME.registerComponent('projectile', {\n      schema: { speed: {type: 'number', default: 0.1}, damage: {type: 'number', default: 30} },\n      init: function(){\n        this.direction = new THREE.Vector3();\n        this.el.object3D.getWorldDirection(this.direction);\n      },\n      tick: function(time, timeDelta){\n        if(gameData.paused) return;\n        let distance = this.data.speed * (timeDelta\/16);\n        this.el.object3D.position.add(this.direction.clone().multiplyScalar(distance));\n        let projectilePos = new THREE.Vector3();\n        this.el.object3D.getWorldPosition(projectilePos);\n        let enemies = document.querySelectorAll('.enemy');\n        for(let i=0; i&lt;enemies.length; i++){\n          let enemy = enemies&#91;i];\n          let enemyPos = new THREE.Vector3();\n          enemy.object3D.getWorldPosition(enemyPos);\n          if(projectilePos.distanceTo(enemyPos) &lt; 0.5){\n            let eh = enemy.getAttribute('enemy-health');\n            eh.hp -= this.data.damage;\n            enemy.setAttribute('enemy-health', 'hp', eh.hp);\n            enemy.components&#91;'enemy-health'].updateHealthBar();\n            if(eh.hp &lt;= 0){\n              killEnemy(enemy);\n              gameData.score += 10;\n              addExperience(20);\n            } else {\n              enemy.setAttribute('material', 'color', '#ff4444');\n              setTimeout(function(){ enemy.setAttribute('material', 'color', '#66ff66'); }, 200);\n            }\n            updateHUD();\n            this.el.parentNode.removeChild(this.el);\n            return;\n          }\n        }\n        if(projectilePos.length() > 50){\n          this.el.parentNode.removeChild(this.el);\n        }\n      }\n    });\n\n    \/************ weapon-switcher \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    AFRAME.registerComponent('weapon-switcher', {\n      init: function(){\n        window.addEventListener('keydown', function(event){\n          if(event.key === '1'){\n            if(gameData.hasSword){\n              gameData.currentWeapon = \"Sword\";\n              document.querySelector('#rightHand').setAttribute('visible', 'true');\n              document.querySelector('#magicWand').setAttribute('visible', 'false');\n            }\n            updateHUD();\n          } else if(event.key === '2'){\n            gameData.currentWeapon = \"Magic\";\n            document.querySelector('#sword').setAttribute('visible', 'false');\n            document.querySelector('#magicWand').setAttribute('visible', 'true');\n            updateHUD();\n          }\n        });\n      }\n    });\n\n    \/************ wave-manager \u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8 ************\/\n    \/\/ \u6575\u304c\u5168\u6ec5\u3057\u305f\u3089\u6b21\u306e\u30a6\u30a7\u30fc\u30d6\u3092\u751f\u6210\u3002\u30a6\u30a7\u30fc\u30d6\u756a\u53f7\u304c5\u306e\u500d\u6570\u306e\u5834\u5408\u306f\u30dc\u30b9\u51fa\u73fe\u3002\n    AFRAME.registerComponent('wave-manager', {\n      tick: function(){\n        if(gameData.paused) return;\n        let spawnZone = document.querySelector('#enemy-spawn');\n        if(spawnZone.children.length === 0){\n          gameData.wave += 1;\n          updateHUD();\n          this.spawnWave();\n        }\n      },\n      spawnWave: function(){\n        let spawnZone = document.querySelector('#enemy-spawn');\n        if(gameData.wave % 5 === 0){\n          \/\/ \u30dc\u30b9\u30a6\u30a7\u30fc\u30d6\n          let boss = document.createElement('a-entity');\n          boss.classList.add('enemy');\n          boss.setAttribute('position', '0 1 -6');\n          boss.setAttribute('geometry', 'primitive: sphere; radius: 1');\n          boss.setAttribute('material', 'color: #aa0000; opacity: 0.9; transparent: true');\n          boss.setAttribute('animation__rotate', 'property: rotation; to: 0 360 0; dur: 6000; loop: true');\n          boss.setAttribute('enemy-ai', 'speed: 0.015; damage: 10');\n          boss.setAttribute('enemy-health', 'hp: 300; maxHp: 300');\n          spawnZone.appendChild(boss);\n        } else {\n          let enemyCount = 3 + gameData.wave - 1;\n          for(let i=0; i&lt;enemyCount; i++){\n            let enemy = document.createElement('a-entity');\n            enemy.classList.add('enemy');\n            let angle = Math.random() * Math.PI * 2;\n            let radius = 5 + Math.random() * 5;\n            let x = Math.cos(angle) * radius;\n            let z = Math.sin(angle) * radius;\n            enemy.setAttribute('position', `${x} 1 ${z}`);\n            enemy.setAttribute('geometry', 'primitive: sphere; radius: 0.5');\n            enemy.setAttribute('material', 'color: #66ff66; opacity: 0.8; transparent: true');\n            enemy.setAttribute('animation__wobble', 'property: scale; to: 1.1 0.9 1.1; dur: 1000; dir: alternate; loop: true');\n            enemy.setAttribute('enemy-ai', 'speed: 0.02; damage: 5');\n            enemy.setAttribute('enemy-health', 'hp: 100; maxHp: 100');\n            spawnZone.appendChild(enemy);\n          }\n        }\n      }\n    });\n\n    \/************ \u521d\u56de HUD \u66f4\u65b0 &amp; \u30a4\u30f3\u30b9\u30c8\u30e9\u30af\u30b7\u30e7\u30f3\u524a\u9664 ************\/\n    document.addEventListener('DOMContentLoaded', function(){\n      updateHUD();\n      setTimeout(function(){\n        let instructions = document.getElementById('instructions');\n        if(instructions){ instructions.parentNode.removeChild(instructions); }\n      }, 5000);\n    });\n  &lt;\/script>\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":[80,54],"tags":[3],"class_list":["post-25865","post","type-post","status-publish","format-standard","hentry","category-html","category-vr","tag-programming"],"aioseo_notices":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/25865","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=25865"}],"version-history":[{"count":1,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/25865\/revisions"}],"predecessor-version":[{"id":25866,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/25865\/revisions\/25866"}],"wp:attachment":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=25865"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=25865"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=25865"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}