{"id":4167,"date":"2025-03-04T11:10:32","date_gmt":"2025-03-04T02:10:32","guid":{"rendered":"https:\/\/ubun2m.com\/?p=4167"},"modified":"2025-03-04T11:48:52","modified_gmt":"2025-03-04T02:48:52","slug":"%e3%82%b7%e3%83%b3%e3%83%97%e3%83%ab%e3%81%aa%e3%82%aa%e3%83%b3%e3%83%a9%e3%82%a4%e3%83%b3%e3%82%b2%e3%83%bc%e3%83%a0%e3%81%ae%e4%bd%9c%e3%82%8a%e6%96%b9-%ef%bd%9e%e5%88%9d%e5%bf%83%e8%80%85%e5%90%91","status":"publish","type":"post","link":"https:\/\/ubun2m.com\/?p=4167","title":{"rendered":"\u30b7\u30f3\u30d7\u30eb\u306a\u30b9\u30de\u30db\u5bfe\u5fdc\u30aa\u30f3\u30e9\u30a4\u30f3\u30b2\u30fc\u30e0\u306e\u4f5c\u308a\u65b9"},"content":{"rendered":"\n<p>\u3000\u3053\u306e\u8a18\u4e8b\u3067\u306f\u3001HTML\u3001JavaScript\u3001Python\u3092\u4f7f\u3063\u305f\u30b7\u30f3\u30d7\u30eb\u306a\u30aa\u30f3\u30e9\u30a4\u30f32D\u6a2a\u30b9\u30af\u30ed\u30fc\u30eb\u98a8\u30b2\u30fc\u30e0\u306e\u30b5\u30f3\u30d7\u30eb\u3092\u7d39\u4ecb\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. \u30b2\u30fc\u30e0\u6982\u8981<\/h2>\n\n\n\n<p>\u3053\u306e\u30b2\u30fc\u30e0\u306f\u3001\u4ee5\u4e0b\u306e\u3088\u3046\u306a\u7279\u5fb4\u3092\u6301\u3063\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>2D\u6a2a\u30b9\u30af\u30ed\u30fc\u30eb\u98a8\u30b2\u30fc\u30e0<\/strong>\uff1a\u753b\u9762\u4e0a\u306b\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u304c\u63cf\u753b\u3055\u308c\u3001\u5de6\u53f3\u306e\u79fb\u52d5\u3084\u30b8\u30e3\u30f3\u30d7\u304c\u53ef\u80fd\u3067\u3059\u3002<\/li>\n\n\n\n<li><strong>\u30aa\u30f3\u30e9\u30a4\u30f3\u5bfe\u5fdc<\/strong>\uff1aSocket.IO \u3092\u5229\u7528\u3057\u3066\u8907\u6570\u306e\u30d7\u30ec\u30a4\u30e4\u30fc\u304c\u540c\u6642\u306b\u53c2\u52a0\u3057\u3001\u4e92\u3044\u306e\u52d5\u304d\u3092\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u3067\u5171\u6709\u3057\u307e\u3059\u3002<\/li>\n\n\n\n<li><strong>\u30b7\u30f3\u30d7\u30eb\u306a\u30de\u30c3\u30d7\u751f\u6210<\/strong>\uff1a\u30b5\u30fc\u30d0\u5074\u3067\u30e9\u30f3\u30c0\u30e0\u306a\u51f8\u51f9\u306e\u3042\u308b\u5730\u9762\u30c7\u30fc\u30bf\uff08\u30de\u30c3\u30d7\uff09\u3092\u751f\u6210\u3057\u3001\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u5074\u3068\u5171\u6709\u3057\u307e\u3059\u3002<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" width=\"996\" height=\"1318\" src=\"https:\/\/ubun2m.com\/wp-content\/uploads\/2025\/03\/Screenshot-2025-03-04-at-11.02.57.png\" alt=\"\" class=\"wp-image-4170\" style=\"width:460px;height:auto\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">2. \u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u5074\u30b3\u30fc\u30c9\u306e\u89e3\u8aac<\/h2>\n\n\n\n<p>\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u5074\u306f HTML \u3068 JavaScript \u3092\u5229\u7528\u3057\u3066\u3001\u30b2\u30fc\u30e0\u306e\u63cf\u753b\u3084\u30d7\u30ec\u30a4\u30e4\u30fc\u64cd\u4f5c\u3001\u30b5\u30fc\u30d0\u3068\u306e\u901a\u4fe1\u3092\u884c\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">HTML \u3068 CSS<\/h3>\n\n\n\n<p>\u307e\u305a\u306f\u3001HTML \u3068 CSS \u306b\u3088\u3063\u3066\u30b2\u30fc\u30e0\u753b\u9762\u306e\u30ec\u30a4\u30a2\u30a6\u30c8\u3084\u30b9\u30bf\u30a4\u30eb\u3092\u5b9a\u7fa9\u3057\u307e\u3059\u3002\u30ed\u30b0\u30a4\u30f3\u30aa\u30fc\u30d0\u30fc\u30ec\u30a4\u3001\u30b2\u30fc\u30e0\u30ad\u30e3\u30f3\u30d0\u30b9\u3001\u30c1\u30e3\u30c3\u30c8\u30a6\u30a3\u30f3\u30c9\u30a6\u3001\u30b8\u30e7\u30a4\u30b9\u30c6\u30a3\u30c3\u30af\u306a\u3069\u306e UI \u8981\u7d20\u3092\u914d\u7f6e\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-html\" data-lang=\"HTML\"><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=&quot;ja&quot;&gt;\n&lt;head&gt;\n  &lt;meta charset=&quot;UTF-8&quot;&gt;\n  &lt;title&gt;\u30aa\u30f3\u30e9\u30a4\u30f32D\u6a2a\u30b9\u30af\u30ed\u30fc\u30eb\u98a8\u30b2\u30fc\u30e0&lt;\/title&gt;\n  &lt;style&gt;\n    body { margin: 0; overflow: hidden; font-family: sans-serif; }\n    canvas { display: block; background: #87ceeb; }\n    #chat {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      width: 300px;\n      height: 200px;\n      background: rgba(255,255,255,0.8);\n      overflow-y: auto;\n      padding: 5px;\n      font-size: 14px;\n      display: none;\n    }\n    #chatInput {\n      position: absolute;\n      top: 220px;\n      left: 10px;\n      width: 300px;\n      box-sizing: border-box;\n      display: none;\n    }\n    #joystick {\n      position: absolute;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      width: 100px;\n      height: 100px;\n      background: rgba(0,0,0,0.2);\n      border-radius: 50%;\n      touch-action: none;\n      display: none;\n    }\n    #joystickInner {\n      position: absolute;\n      left: 50%;\n      top: 50%;\n      width: 40px;\n      height: 40px;\n      background: rgba(0,0,0,0.5);\n      border-radius: 50%;\n      transform: translate(-50%, -50%);\n    }\n    \/* \u30ed\u30b0\u30a4\u30f3\u30aa\u30fc\u30d0\u30fc\u30ec\u30a4 *\/\n    #loginOverlay {\n      position: absolute;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0, 0, 0, 0.7);\n      color: white;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      flex-direction: column;\n      z-index: 10;\n    }\n    #loginOverlay input {\n      padding: 10px;\n      font-size: 16px;\n      margin-bottom: 10px;\n    }\n    #loginOverlay button {\n      padding: 10px 20px;\n      font-size: 16px;\n    }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;!-- \u30ed\u30b0\u30a4\u30f3\u30aa\u30fc\u30d0\u30fc\u30ec\u30a4 --&gt;\n  &lt;div id=&quot;loginOverlay&quot;&gt;\n    &lt;h2&gt;\u30cb\u30c3\u30af\u30cd\u30fc\u30e0\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044&lt;\/h2&gt;\n    &lt;input type=&quot;text&quot; id=&quot;nicknameInput&quot; placeholder=&quot;\u4f8b\uff1aPlayer1&quot;&gt;\n    &lt;button id=&quot;loginButton&quot;&gt;\u30b9\u30bf\u30fc\u30c8&lt;\/button&gt;\n  &lt;\/div&gt;\n\n  &lt;canvas id=&quot;gameCanvas&quot;&gt;&lt;\/canvas&gt;\n  &lt;div id=&quot;chat&quot;&gt;&lt;\/div&gt;\n  &lt;input type=&quot;text&quot; id=&quot;chatInput&quot; placeholder=&quot;\u30c1\u30e3\u30c3\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u5165\u529b&quot;&gt;\n  &lt;div id=&quot;joystick&quot;&gt;\n    &lt;div id=&quot;joystickInner&quot;&gt;&lt;\/div&gt;\n  &lt;\/div&gt;\n\n  &lt;!-- Socket.IO \u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u30e9\u30a4\u30d6\u30e9\u30ea --&gt;\n  &lt;script src=&quot;https:\/\/cdn.socket.io\/4.5.4\/socket.io.min.js&quot;&gt;&lt;\/script&gt;\n  &lt;script&gt;\n    \/\/ \u3053\u3053\u304b\u3089JavaScript\u3067\u30b2\u30fc\u30e0\u30ed\u30b8\u30c3\u30af\u3092\u5b9f\u88c5\u3057\u307e\u3059\n    \/\/ DOM\u8981\u7d20\u306e\u53d6\u5f97\n    const canvas = document.getElementById(&#39;gameCanvas&#39;);\n    const ctx = canvas.getContext(&#39;2d&#39;);\n    const chat = document.getElementById(&#39;chat&#39;);\n    const chatInput = document.getElementById(&#39;chatInput&#39;);\n    const joystick = document.getElementById(&#39;joystick&#39;);\n    const joystickInner = document.getElementById(&#39;joystickInner&#39;);\n    const loginOverlay = document.getElementById(&#39;loginOverlay&#39;);\n    const nicknameInput = document.getElementById(&#39;nicknameInput&#39;);\n    const loginButton = document.getElementById(&#39;loginButton&#39;);\n\n    \/\/ \u30ad\u30e3\u30f3\u30d0\u30b9\u30b5\u30a4\u30ba\u306e\u8a2d\u5b9a\n    canvas.width = window.innerWidth;\n    canvas.height = window.innerHeight;\n\n    \/\/ \u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u753b\u50cf\u306e\u8aad\u307f\u8fbc\u307f\n    const characterImg = new Image();\n    characterImg.src = &quot;\/wp-content\/uploads\/2025\/03\/\u3075\u304f\u308d\u304f\u3058\u3085\u3055\u3093.png&quot;;\n    const charWidth = 50, charHeight = 50;\n\n    \/\/ \u81ea\u30ad\u30e3\u30e9\u3068\u4ed6\u30d7\u30ec\u30a4\u30e4\u30fc\u306e\u72b6\u614b\u3092\u7ba1\u7406\u3059\u308b\u305f\u3081\u306e\u5909\u6570\n    let myPlayer = { id: null, nickname: &quot;&quot;, x: 50, y: canvas.height - 150, vx: 0, vy: 0, direction: &quot;left&quot;, onGround: false };\n    let players = {};\n\n    \/\/ \u30b5\u30fc\u30d0\u30fc\u304b\u3089\u9001\u3089\u308c\u3066\u304f\u308b\u5171\u6709\u30de\u30c3\u30d7\u30c7\u30fc\u30bf\n    let mapData = [];\n\n    \/\/ \u30b8\u30e7\u30a4\u30b9\u30c6\u30a3\u30c3\u30af\u7528\u306e\u8a2d\u5b9a\u5909\u6570\n    let joystickStart = null;\n    let joystickDelta = {x: 0, y: 0};\n\n    \/\/ Socket.IO \u3092\u5229\u7528\u3057\u3066\u30b5\u30fc\u30d0\u30fc\u306b\u63a5\u7d9a\n    const socket = io();\n\n    \/\/ \u30ed\u30b0\u30a4\u30f3\u51e6\u7406\n    loginButton.addEventListener(&#39;click&#39;, function() {\n      const nickname = nicknameInput.value.trim();\n      if (nickname === &quot;&quot;) return;\n      \/\/ \u30b5\u30fc\u30d0\u30fc\u306b\u30ed\u30b0\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\n      socket.emit(&#39;login&#39;, { nickname: nickname });\n      myPlayer.nickname = nickname;\n      \/\/ \u30ed\u30b0\u30a4\u30f3\u753b\u9762\u3092\u96a0\u3057\u3001\u30b2\u30fc\u30e0\u306eUI\uff08\u30c1\u30e3\u30c3\u30c8\u3084\u30b8\u30e7\u30a4\u30b9\u30c6\u30a3\u30c3\u30af\uff09\u3092\u8868\u793a\n      loginOverlay.style.display = &#39;none&#39;;\n      chat.style.display = &#39;block&#39;;\n      chatInput.style.display = &#39;block&#39;;\n      joystick.style.display = &#39;block&#39;;\n    });\n\n    \/\/ Socket.IO \u306e\u5404\u7a2e\u30a4\u30d9\u30f3\u30c8\u30cf\u30f3\u30c9\u30e9\n    socket.on(&#39;connect&#39;, () =&gt; {\n      console.log(&#39;Socket.IO \u63a5\u7d9a\u6210\u529f:&#39;, socket.id);\n    });\n\n    socket.on(&#39;init&#39;, (data) =&gt; {\n      myPlayer.id = data.id;\n      console.log(&#39;\u521d\u671f\u5316\u60c5\u5831:&#39;, data);\n    });\n\n    socket.on(&#39;state&#39;, (data) =&gt; {\n      \/\/ \u5168\u30d7\u30ec\u30a4\u30e4\u30fc\u306e\u72b6\u614b\u3092\u66f4\u65b0\n      players = data.players;\n    });\n\n    socket.on(&#39;map&#39;, (data) =&gt; {\n      \/\/ \u30b5\u30fc\u30d0\u30fc\u304b\u3089\u53d7\u4fe1\u3057\u305f\u5171\u6709\u30de\u30c3\u30d7\u306e\u8a2d\u5b9a\n      mapData = data.map;\n    });\n\n    socket.on(&#39;chat&#39;, (data) =&gt; {\n      \/\/ \u30c1\u30e3\u30c3\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u753b\u9762\u306b\u8868\u793a\n      const msgDiv = document.createElement(&#39;div&#39;);\n      msgDiv.textContent = `${data.nickname}: ${data.message}`;\n      chat.appendChild(msgDiv);\n      chat.scrollTop = chat.scrollHeight;\n    });\n\n    \/\/ \u30c1\u30e3\u30c3\u30c8\u5165\u529b\u306e\u9001\u4fe1\u51e6\u7406\n    chatInput.addEventListener(&#39;keydown&#39;, function(e) {\n      if (e.key === &#39;Enter&#39;) {\n        const message = chatInput.value.trim();\n        if (message !== &quot;&quot;) {\n          socket.emit(&#39;chat&#39;, { message: message });\n          chatInput.value = &#39;&#39;;\n        }\n      }\n    });\n\n    \/\/ \u30b8\u30e7\u30a4\u30b9\u30c6\u30a3\u30c3\u30af\u306e\u30bf\u30c3\u30c1\u64cd\u4f5c\n    joystick.addEventListener(&#39;touchstart&#39;, function(e) {\n      e.preventDefault();\n      const touch = e.touches[0];\n      joystickStart = { x: touch.clientX, y: touch.clientY };\n    });\n    joystick.addEventListener(&#39;touchmove&#39;, function(e) {\n      e.preventDefault();\n      if (!joystickStart) return;\n      const touch = e.touches[0];\n      joystickDelta.x = touch.clientX - joystickStart.x;\n      joystickDelta.y = touch.clientY - joystickStart.y;\n      const maxDist = 30;\n      const dist = Math.sqrt(joystickDelta.x ** 2 + joystickDelta.y ** 2);\n      if (dist &gt; maxDist) {\n        joystickDelta.x = (joystickDelta.x \/ dist) * maxDist;\n        joystickDelta.y = (joystickDelta.y \/ dist) * maxDist;\n      }\n      joystickInner.style.transform = `translate(${joystickDelta.x}px, ${joystickDelta.y}px)`;\n    });\n    joystick.addEventListener(&#39;touchend&#39;, function(e) {\n      e.preventDefault();\n      \/\/ \u4e0b\u65b9\u5411\u306e\u30b9\u30ef\u30a4\u30d7\u3067\u30b8\u30e3\u30f3\u30d7\uff08\u5730\u9762\u306b\u3044\u308b\u5834\u5408\uff09\n      if (joystickDelta.y &gt; 20 && myPlayer.onGround) {\n        myPlayer.vy = -15;\n        myPlayer.onGround = false;\n      }\n      \/\/ \u5de6\u53f3\u79fb\u52d5\uff1a\u30aa\u30d5\u30bb\u30c3\u30c8\u306b\u5fdc\u3058\u3066\u901f\u5ea6\u3092\u8a2d\u5b9a\n      if (Math.abs(joystickDelta.x) &gt; 5) {\n        myPlayer.vx = joystickDelta.x * 0.5;\n        myPlayer.direction = joystickDelta.x &gt; 0 ? &quot;right&quot; : &quot;left&quot;;\n      } else {\n        myPlayer.vx = 0;\n      }\n      joystickStart = null;\n      joystickDelta = {x: 0, y: 0};\n      joystickInner.style.transform = `translate(0px, 0px)`;\n    });\n\n    \/\/ \u30b2\u30fc\u30e0\u30eb\u30fc\u30d7\u51e6\u7406\uff1a\u7269\u7406\u6f14\u7b97\u3001\u72b6\u614b\u306e\u9001\u4fe1\u3001\u63cf\u753b\u51e6\u7406\u3092\u884c\u3046\n    function gameLoop() {\n      \/\/ \u7269\u7406\u6f14\u7b97\uff1a\u91cd\u529b\u3084\u79fb\u52d5\u306e\u8a08\u7b97\n      myPlayer.vy += 0.8;\n      myPlayer.x += myPlayer.vx;\n      myPlayer.y += myPlayer.vy;\n\n      \/\/ \u5730\u9762\u3068\u306e\u885d\u7a81\u5224\u5b9a\uff08\u5171\u6709\u30de\u30c3\u30d7\u30c7\u30fc\u30bf\u3092\u5229\u7528\uff09\n      let groundY = baseGroundY;\n      if (mapData.length &gt; 0) {\n        for (let i = 0; i &lt; mapData.length - 1; i++) {\n          const p1 = mapData[i];\n          const p2 = mapData[i+1];\n          if (myPlayer.x + charWidth\/2 &gt;= p1.x && myPlayer.x + charWidth\/2 &lt;= p2.x) {\n            const t = (myPlayer.x + charWidth\/2 - p1.x) \/ (p2.x - p1.x);\n            groundY = p1.y * (1 - t) + p2.y * t;\n            break;\n          }\n        }\n      }\n      if (myPlayer.y + charHeight &gt; groundY) {\n        myPlayer.y = groundY - charHeight;\n        myPlayer.vy = 0;\n        myPlayer.onGround = true;\n      }\n\n      \/\/ \u30b5\u30fc\u30d0\u30fc\u3078\u81ea\u5206\u306e\u72b6\u614b\u3092\u9001\u4fe1\n      if (socket.connected) {\n        socket.emit(&#39;update&#39;, {\n          id: myPlayer.id,\n          x: myPlayer.x,\n          y: myPlayer.y,\n          vx: myPlayer.vx,\n          vy: myPlayer.vy,\n          direction: myPlayer.direction\n        });\n      }\n\n      \/\/ \u63cf\u753b\u51e6\u7406\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      \/\/ \u80cc\u666f\uff08\u7a7a\uff09\u306f canvas \u306e\u80cc\u666f\u8272\u3092\u305d\u306e\u307e\u307e\u4f7f\u7528\n\n      \/\/ \u5730\u9762\u306e\u63cf\u753b\uff08\u5171\u6709\u30de\u30c3\u30d7\uff09\n      if (mapData.length &gt; 0) {\n        ctx.fillStyle = &quot;#808080&quot;;\n        ctx.beginPath();\n        ctx.moveTo(0, canvas.height);\n        ctx.lineTo(mapData[0].x, mapData[0].y);\n        for (let i = 1; i &lt; mapData.length; i++) {\n          ctx.lineTo(mapData[i].x, mapData[i].y);\n        }\n        ctx.lineTo(canvas.width, canvas.height);\n        ctx.closePath();\n        ctx.fill();\n      }\n\n      \/\/ \u5404\u30d7\u30ec\u30a4\u30e4\u30fc\u306e\u63cf\u753b\n      for (let id in players) {\n        const p = players[id];\n        ctx.save();\n        if (p.direction === &quot;right&quot;) {\n          ctx.translate(p.x + charWidth\/2, 0);\n          ctx.scale(-1, 1);\n          ctx.drawImage(characterImg, -charWidth\/2, p.y, charWidth, charHeight);\n        } else {\n          ctx.drawImage(characterImg, p.x, p.y, charWidth, charHeight);\n        }\n        ctx.restore();\n      }\n\n      requestAnimationFrame(gameLoop);\n    }\n    gameLoop();\n\n    \/\/ \u753b\u9762\u30ea\u30b5\u30a4\u30ba\u306b\u5bfe\u5fdc\n    window.addEventListener(&#39;resize&#39;, function() {\n      canvas.width = window.innerWidth;\n      canvas.height = window.innerHeight;\n    });\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u5074\u30b3\u30fc\u30c9\u306e\u30dd\u30a4\u30f3\u30c8<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u30ed\u30b0\u30a4\u30f3\u51e6\u7406<\/strong>\uff1a\u30d7\u30ec\u30a4\u30e4\u30fc\u304c\u30b2\u30fc\u30e0\u306b\u53c2\u52a0\u3059\u308b\u969b\u3001\u30cb\u30c3\u30af\u30cd\u30fc\u30e0\u3092\u5165\u529b\u3059\u308b\u3053\u3068\u3067\u30ed\u30b0\u30a4\u30f3\u753b\u9762\u304c\u6d88\u3048\u3001\u30c1\u30e3\u30c3\u30c8\u3084\u30b8\u30e7\u30a4\u30b9\u30c6\u30a3\u30c3\u30af\u304c\u6709\u52b9\u306b\u306a\u308a\u307e\u3059\u3002<\/li>\n\n\n\n<li><strong>Socket.IO \u306b\u3088\u308b\u901a\u4fe1<\/strong>\uff1a\u30b5\u30fc\u30d0\u30fc\u3068\u306e\u63a5\u7d9a\u78ba\u7acb\u5f8c\u3001<code>login<\/code>\u3001<code>update<\/code>\u3001<code>chat<\/code> \u306a\u3069\u306e\u30a4\u30d9\u30f3\u30c8\u3092\u901a\u3058\u3066\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u901a\u4fe1\u3092\u884c\u3044\u307e\u3059\u3002<\/li>\n\n\n\n<li><strong>\u30b2\u30fc\u30e0\u30eb\u30fc\u30d7<\/strong>\uff1a<code>requestAnimationFrame<\/code> \u3092\u5229\u7528\u3057\u3066\u3001\u7269\u7406\u6f14\u7b97\uff08\u91cd\u529b\u3001\u79fb\u52d5\u3001\u885d\u7a81\u5224\u5b9a\uff09\u3068\u63cf\u753b\u51e6\u7406\u3092\u6bce\u30d5\u30ec\u30fc\u30e0\u5b9f\u884c\u3057\u3066\u3044\u307e\u3059\u3002<\/li>\n\n\n\n<li><strong>\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u753b\u50cf\uff1a<\/strong>\u3000\u300c<a href=\"https:\/\/dotown.maeda-design-room.net\/\" title=\"\">DOTOWN<\/a>\u300d\u69d8\u306e\u7d20\u6750\u3092\u304a\u501f\u308a\u3057\u3066\u3044\u307e\u3059\u3002<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"960\" height=\"720\" src=\"https:\/\/ubun2m.com\/wp-content\/uploads\/2025\/03\/\u3075\u304f\u308d\u304f\u3058\u3085\u3055\u3093.png\" alt=\"\" class=\"wp-image-4142\"\/><figcaption class=\"wp-element-caption\">\u3075\u304f\u308d\u304f\u3058\u3085\u3055\u3093<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">3. \u30b5\u30fc\u30d0\u30fc\u5074\u30b3\u30fc\u30c9\u306e\u89e3\u8aac<\/h2>\n\n\n\n<p>\u30b5\u30fc\u30d0\u30fc\u5074\u306f Python\u3001Flask\u3001Flask-SocketIO \u3092\u5229\u7528\u3057\u3066\u5b9f\u88c5\u3057\u3066\u3044\u307e\u3059\u3002\u3053\u3053\u3067\u306f\u3001\u30d7\u30ec\u30a4\u30e4\u30fc\u306e\u7ba1\u7406\u3084\u5171\u6709\u30de\u30c3\u30d7\u306e\u751f\u6210\u3001\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u304b\u3089\u306e\u72b6\u614b\u66f4\u65b0\u30fb\u30c1\u30e3\u30c3\u30c8\u306e\u4e2d\u7d99\u3092\u884c\u3063\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-python\" data-lang=\"Python\"><code>import random\nfrom flask import Flask, send_from_directory, request\nfrom flask_socketio import SocketIO, emit\n\napp = Flask(__name__, static_url_path=&#39;&#39;, static_folder=&#39;.&#39;)\napp.config[&#39;SECRET_KEY&#39;] = &#39;secret!&#39;\nsocketio = SocketIO(app, cors_allowed_origins=&quot;*&quot;)  # CORS \u3092\u8a31\u53ef\n\n# \u30b0\u30ed\u30fc\u30d0\u30eb\u306a\u30d7\u30ec\u30a4\u30e4\u30fc\u60c5\u5831\uff08\u5404\u30ad\u30fc\u306f\u63a5\u7d9a\u6642\u306e request.sid\uff09\nplayers = {}\n\n# \u30b5\u30fc\u30d0\u30fc\u5074\u3067\u751f\u6210\u3059\u308b\u30de\u30c3\u30d7\u30c7\u30fc\u30bf\uff08\u5730\u9762\u306e\u51f8\u51f9\u30dd\u30a4\u30f3\u30c8\u7fa4\uff09\nmap_data = []\npoint_interval = 100\n# \u3053\u3053\u3067\u306f\u4e00\u4f8b\u3068\u3057\u3066\u30ad\u30e3\u30f3\u30d0\u30b9\u5e45 1200px \u3092\u60f3\u5b9a\ncanvas_width = 1200\nbase_ground_y = 500  # \u5730\u9762\u306e\u57fa\u672c\u306e\u9ad8\u3055\nfor x in range(0, canvas_width + point_interval, point_interval):\n    # -10\u301c+10 \u306e\u30e9\u30f3\u30c0\u30e0\u306a\u30aa\u30d5\u30bb\u30c3\u30c8\u3092\u9069\u7528\n    offset = random.randint(-10, 10)\n    map_data.append({&#39;x&#39;: x, &#39;y&#39;: base_ground_y + offset})\n\n@app.route(&#39;\/&#39;)\ndef index():\n    return send_from_directory(&#39;.&#39;, &#39;index.html&#39;)\n\n@socketio.on(&#39;connect&#39;)\ndef handle_connect():\n    print(f&#39;Client connected: {request.sid}&#39;)\n    # \u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3078\u5171\u6709\u30de\u30c3\u30d7\u30c7\u30fc\u30bf\u3068\u73fe\u5728\u306e\u30d7\u30ec\u30a4\u30e4\u30fc\u72b6\u614b\u3092\u9001\u4fe1\n    emit(&#39;map&#39;, {&#39;map&#39;: map_data})\n    emit(&#39;state&#39;, {&#39;players&#39;: players})\n\n@socketio.on(&#39;login&#39;)\ndef handle_login(data):\n    &quot;&quot;&quot;\n    \u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u304b\u3089\u9001\u3089\u308c\u3066\u304f\u308b\u30c7\u30fc\u30bf\u4f8b:\n    { &quot;nickname&quot;: &quot;Player1&quot; }\n    &quot;&quot;&quot;\n    nickname = data.get(&quot;nickname&quot;, &quot;NoName&quot;)\n    # \u65b0\u898f\u30ed\u30b0\u30a4\u30f3\u6642\u306b\u521d\u671f\u4f4d\u7f6e\u3068\u30cb\u30c3\u30af\u30cd\u30fc\u30e0\u3092\u8a2d\u5b9a\n    players[request.sid] = {\n        &quot;nickname&quot;: nickname,\n        &quot;x&quot;: 50, \n        &quot;y&quot;: 300,\n        &quot;vx&quot;: 0,\n        &quot;vy&quot;: 0,\n        &quot;direction&quot;: &quot;left&quot;\n    }\n    print(f&#39;Client {request.sid} logged in as {nickname}&#39;)\n    # \u63a5\u7d9a\u3057\u305f\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3078\u521d\u671f\u60c5\u5831\u3092\u9001\u4fe1\n    emit(&#39;init&#39;, {&#39;id&#39;: request.sid, &#39;nickname&#39;: nickname})\n    # \u6700\u65b0\u306e\u30d7\u30ec\u30a4\u30e4\u30fc\u72b6\u614b\u3092\u5168\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u306b\u30d6\u30ed\u30fc\u30c9\u30ad\u30e3\u30b9\u30c8\n    socketio.emit(&#39;state&#39;, {&#39;players&#39;: players})\n\n@socketio.on(&#39;update&#39;)\ndef handle_update(data):\n    # \u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u304b\u3089\u306e\u72b6\u614b\u66f4\u65b0\uff08\u4f8b: { &quot;x&quot;: 100, &quot;y&quot;: 300, &quot;vx&quot;: 5, &quot;vy&quot;: 0, &quot;direction&quot;: &quot;right&quot; }\uff09\n    if request.sid not in players:\n        return\n    players[request.sid].update({\n        &quot;x&quot;: data.get(&quot;x&quot;, players[request.sid][&quot;x&quot;]),\n        &quot;y&quot;: data.get(&quot;y&quot;, players[request.sid][&quot;y&quot;]),\n        &quot;vx&quot;: data.get(&quot;vx&quot;, 0),\n        &quot;vy&quot;: data.get(&quot;vy&quot;, 0),\n        &quot;direction&quot;: data.get(&quot;direction&quot;, &quot;left&quot;)\n    })\n    # \u66f4\u65b0\u3055\u308c\u305f\u72b6\u614b\u3092\u5168\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u306b\u9001\u4fe1\n    socketio.emit(&#39;state&#39;, {&#39;players&#39;: players})\n\n@socketio.on(&#39;chat&#39;)\ndef handle_chat(data):\n    &quot;&quot;&quot;\n    \u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u304b\u3089\u306e\u30c1\u30e3\u30c3\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u4f8b:\n    { &quot;message&quot;: &quot;Hello everyone!&quot; }\n    &quot;&quot;&quot;\n    message = data.get(&quot;message&quot;, &quot;&quot;)\n    nickname = players.get(request.sid, {}).get(&quot;nickname&quot;, request.sid)\n    chat_data = {&quot;nickname&quot;: nickname, &quot;message&quot;: message}\n    print(f&quot;Chat from {nickname}: {message}&quot;)\n    socketio.emit(&#39;chat&#39;, chat_data)\n\n@socketio.on(&#39;disconnect&#39;)\ndef handle_disconnect():\n    print(f&#39;Client disconnected: {request.sid}&#39;)\n    if request.sid in players:\n        del players[request.sid]\n    socketio.emit(&#39;state&#39;, {&#39;players&#39;: players})\n\nif __name__ == &#39;__main__&#39;:\n    # \u5916\u90e8\u304b\u3089\u306e\u63a5\u7d9a\u3092\u8a31\u53ef\u3059\u308b\u305f\u3081\u306b\u30db\u30b9\u30c8\u3092 0.0.0.0\u3001\u30dd\u30fc\u30c85000\u3067\u8d77\u52d5\n    socketio.run(app, host=&#39;0.0.0.0&#39;, port=5000)\n<\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">\u30b5\u30fc\u30d0\u30fc\u5074\u30b3\u30fc\u30c9\u306e\u30dd\u30a4\u30f3\u30c8<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Flask \u3068 Socket.IO \u306e\u9023\u643a<\/strong>\uff1aFlask \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306b Socket.IO \u3092\u7d44\u307f\u8fbc\u307f\u3001\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u901a\u4fe1\u3092\u5b9f\u73fe\u3057\u3066\u3044\u307e\u3059\u3002<\/li>\n\n\n\n<li><strong>\u5171\u6709\u30de\u30c3\u30d7\u306e\u751f\u6210<\/strong>\uff1a<code>map_data<\/code> \u306b\u30e9\u30f3\u30c0\u30e0\u306a\u30aa\u30d5\u30bb\u30c3\u30c8\u3092\u6301\u3064\u5730\u9762\u306e\u30dd\u30a4\u30f3\u30c8\u7fa4\u3092\u751f\u6210\u3057\u3001\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3068\u5171\u6709\u3057\u307e\u3059\u3002<\/li>\n\n\n\n<li><strong>\u30d7\u30ec\u30a4\u30e4\u30fc\u7ba1\u7406<\/strong>\uff1a\u5404\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3054\u3068\u306b <code>request.sid<\/code> \u3092\u30ad\u30fc\u3068\u3057\u3066\u30d7\u30ec\u30a4\u30e4\u30fc\u60c5\u5831\u3092\u4fdd\u6301\u3057\u3001<code>login<\/code>\u3001<code>update<\/code>\u3001<code>disconnect<\/code> \u30a4\u30d9\u30f3\u30c8\u3067\u66f4\u65b0\u30fb\u524a\u9664\u3092\u884c\u3044\u307e\u3059\u3002<\/li>\n\n\n\n<li><strong>\u30c1\u30e3\u30c3\u30c8\u6a5f\u80fd<\/strong>\uff1a\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u304b\u3089\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u3051\u53d6\u308a\u3001\u5168\u54e1\u306b\u30d6\u30ed\u30fc\u30c9\u30ad\u30e3\u30b9\u30c8\u3057\u307e\u3059\u3002<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">4. \u30b2\u30fc\u30e0\u306e\u52d5\u4f5c\u78ba\u8a8d\u3068\u5c0e\u5165\u65b9\u6cd5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u5fc5\u8981\u306a\u74b0\u5883<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Python 3.x<\/li>\n\n\n\n<li>Flask \u304a\u3088\u3073 Flask-SocketIO \u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb<br>\u4f8b:<\/li>\n<\/ul>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-bash\" data-lang=\"Bash\"><code>pip install flask flask-socketio\n<\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">\u8d77\u52d5\u65b9\u6cd5<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30b5\u30fc\u30d0\u30fc\u5074\u306e\u30b3\u30fc\u30c9\uff08Python \u30d5\u30a1\u30a4\u30eb\uff09\u3092\u4fdd\u5b58\u3057\u307e\u3059\uff08\u4f8b\uff1a<code>server.py<\/code>\uff09\u3002<\/li>\n\n\n\n<li>\u30bf\u30fc\u30df\u30ca\u30eb\u3067\u4fdd\u5b58\u5148\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306b\u79fb\u52d5\u3057\u3001\u4ee5\u4e0b\u306e\u30b3\u30de\u30f3\u30c9\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002<\/li>\n<\/ol>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-bash\" data-lang=\"Bash\"><code>python server.py\n<\/code><\/pre><\/div>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30d6\u30e9\u30a6\u30b6\u3067 \u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u5074\u306eHTML\u3092\u958b\u304f\u3068\u3001\u30ed\u30b0\u30a4\u30f3\u753b\u9762\u304c\u8868\u793a\u3055\u308c\u307e\u3059\u3002<\/li>\n\n\n\n<li>\u30cb\u30c3\u30af\u30cd\u30fc\u30e0\u3092\u5165\u529b\u3057\u3066\u300c\u30b9\u30bf\u30fc\u30c8\u300d\u30dc\u30bf\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u3001\u30b2\u30fc\u30e0\u753b\u9762\u304c\u8868\u793a\u3055\u308c\u3001\u30b8\u30e7\u30a4\u30b9\u30c6\u30a3\u30c3\u30af\u64cd\u4f5c\u3084\u30c1\u30e3\u30c3\u30c8\u3067\u4ed6\u306e\u30d7\u30ec\u30a4\u30e4\u30fc\u3068\u9023\u643a\u3057\u305f\u30aa\u30f3\u30e9\u30a4\u30f3\u30b2\u30fc\u30e0\u304c\u958b\u59cb\u3055\u308c\u307e\u3059\u3002<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"\u30b7\u30f3\u30d7\u30eb\u306a\u30b9\u30de\u30db\u5bfe\u5fdc\u30aa\u30f3\u30e9\u30a4\u30f3\u30b2\u30fc\u30e0\u30b5\u30f3\u30d7\u30eb\" width=\"500\" height=\"375\" src=\"https:\/\/www.youtube.com\/embed\/1_p0pdDhHCw?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5. \u307e\u3068\u3081<\/h2>\n\n\n\n<p>\u3000\u4eca\u56de\u7d39\u4ecb\u3057\u305f\u30b5\u30f3\u30d7\u30eb\u306f\u3001\u30b7\u30f3\u30d7\u30eb\u306a\u304c\u3089\u3082\u30aa\u30f3\u30e9\u30a4\u30f3\u30b2\u30fc\u30e0\u306e\u57fa\u672c\u7684\u306a\u8981\u7d20\uff08\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u901a\u4fe1\u3001\u7269\u7406\u6f14\u7b97\u3001\u30de\u30c3\u30d7\u751f\u6210\u3001\u30c1\u30e3\u30c3\u30c8\u6a5f\u80fd\u306a\u3069\uff09\u3092\u542b\u3093\u3067\u3044\u307e\u3059\u3002\u521d\u5fc3\u8005\u306e\u65b9\u3067\u3082\u3053\u306e\u30b3\u30fc\u30c9\u3092\u901a\u3057\u3066\u3001HTML\/CSS\/JavaScript \u306b\u3088\u308b\u30d5\u30ed\u30f3\u30c8\u30a8\u30f3\u30c9\u306e\u69cb\u7bc9\u3068\u3001Python\/Flask \u3092\u4f7f\u3063\u305f\u30d0\u30c3\u30af\u30a8\u30f3\u30c9\u306e\u5b9f\u88c5\u65b9\u6cd5\u3092\u5b66\u3076\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\u305c\u3072\u3001\u5b9f\u969b\u306b\u30b3\u30fc\u30c9\u3092\u52d5\u304b\u3057\u3066\u307f\u3066\u3001\u5fc5\u8981\u306b\u5fdc\u3058\u3066\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3057\u306a\u304c\u3089\u81ea\u5206\u3060\u3051\u306e\u30b2\u30fc\u30e0\u3092\u4f5c\u3063\u3066\u307f\u307e\u3057\u3087\u3046\uff01<\/p>\n\n\n\n<div class=\"affiliate-box\"><div class=\"affiliate-containar\"><a href=\"https:\/\/amzn.to\/4brOhDM\" rel=\"nofollow\"><img decoding=\"async\" style=\"border: none;\" src=\"https:\/\/m.media-amazon.com\/images\/I\/818KskYApZL._SY522_.jpg\" target=\"_blank\"><\/a><div class=\"affiliate-content\"><a href=\"https:\/\/amzn.to\/4brOhDM\" rel=\"nofollow\">Python\u3067\u306f\u3058\u3081\u308b\u30b2\u30fc\u30e0\u5236\u4f5c \u8d85\u5165\u9580\u3000\u77e5\u8b58\u30bc\u30ed\u304b\u3089\u306e\u30d7\u30ed\u30b0\u30e9\u30df\u30f3\u30b0\uff06\u30a2\u30eb\u30b4\u30ea\u30ba\u30e0\u3068\u6570\u5b66 Kindle\u7248<\/a><ul class=\"affiliate-button\"><li><a href=\"https:\/\/amzn.to\/4brOhDM\" rel=\"nofollow\">Amazon<\/a><\/li><\/ul><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>\u3000\u3053\u306e\u8a18\u4e8b\u3067\u306f\u3001HTML\u3001JavaScript\u3001Python\u3092\u4f7f\u3063\u305f\u30b7\u30f3\u30d7\u30eb\u306a\u30aa\u30f3\u30e9\u30a4\u30f32D\u6a2a\u30b9\u30af\u30ed\u30fc\u30eb\u98a8\u30b2\u30fc\u30e0\u306e\u30b5\u30f3\u30d7\u30eb\u3092\u7d39\u4ecb\u3057\u307e\u3059\u3002 1. \u30b2\u30fc\u30e0\u6982\u8981 \u3053\u306e\u30b2\u30fc\u30e0\u306f\u3001\u4ee5\u4e0b\u306e\u3088\u3046\u306a\u7279\u5fb4\u3092\u6301\u3063\u3066\u3044\u307e\u3059\u3002 2. \u30af\u30e9\u30a4\u30a2\u30f3 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4142,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-4167","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-3"],"acf":[],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/ubun2m.com\/wp-content\/uploads\/2025\/03\/\u3075\u304f\u308d\u304f\u3058\u3085\u3055\u3093.png","_links":{"self":[{"href":"https:\/\/ubun2m.com\/index.php?rest_route=\/wp\/v2\/posts\/4167","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ubun2m.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ubun2m.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ubun2m.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ubun2m.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4167"}],"version-history":[{"count":0,"href":"https:\/\/ubun2m.com\/index.php?rest_route=\/wp\/v2\/posts\/4167\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ubun2m.com\/index.php?rest_route=\/wp\/v2\/media\/4142"}],"wp:attachment":[{"href":"https:\/\/ubun2m.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4167"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ubun2m.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4167"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ubun2m.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}