当サイトには広告が含まれています

Raspberry Pi 5でWebサーバーを立てゲームを作ろう!

PC

はじめに

Raspberry Piは、教育目的で設計された小型コンピュータです。

低電力であること、手のひらサイズでコンパクトであることが、最大の特徴です。そのため、連続稼働させる使い方がとても適しています。例えば、

  • Webサーバー(ウェブサーバー)この記事を読めばできます!
  • 簡易ゲームの公開この記事を読めばできます!
  • ファイルサーバーこの記事を読めばインストールだけでできます!
  • 録画サーバー

などがあります💡

今回、Vesonn JPの「Raspberry Pi5 16gb セット技適済み」を用いて、ローカルWebサーバー(ウェブサーバー)を立ち上げ、簡単なゲーム(ブロック崩し)を作ってみました!

ローカルではありますが、家族や友人とスコアアタックなど楽しめそうです!
作りこめば、対戦できるゲームを作ることもできるので遊びの可能性は無限大です!

実際にRaspberry Pi 5でWebサーバーを立て(左側)、ブロック崩しをウェブで動作(右側)させている様子

開封の儀

外箱を開けると、下記のようなセットが入っています。内容物は簡単に、「Raspberry Pi5 16gb本体」、「電源アダプタ―」、「HDMIケーブル」、「SDカード&リーダー」、「ケース&クーラー」等になります。そのため、すぐにインストールと電源につないでディスプレイ出力できます。さらに、ケースによる保護と冷却が行えます

ケースとクーラーはこれが入っています。

最初に、SDカードへRaspberry Pi OSをインストールしよう

まず、下記の付属のSDカードリーダーを用いて、お持ちのPCでOSをインストールします。OSは、Raspberry Piを動作させるために必要なシステムです。

PCから、Raspberry Pi OS – Raspberry Piにアクセスして、Raspberry Pi Imagerをダウンロードしてください(Download for Windows)。ダウンロードしたら、指示に従ってインストールしてください。

重要:「Would you like to apply OS customization settings?」のポップアップ時は、「設定を編集する」をクリックして、下記を設定されることをオススメします。

  • 一般 タブ
    • ホスト名(例:raspberrypi)
      • ユーザー名とパスワードを設定する
        • ユーザー名(例:risupuresso)
        • パスワード(解説:root権限に入る際に必要になりますので設定してください)
    • Wi-Fiを設定する
      • SSID(解説:通常はWi-Fiのルーターに記載があります)
      • パスワード(解説:通常はWi-Fiのルーターに記載があります)
      • Wi-Fiを使う国(例:JP)
    • ロケール設定をする
      • タイムゾーン(例:Asia/Tokyo)
      • キーボードレイアウト(例:jp)
  • オプション タブ
    • SSHを有効化する
      • パスワード認証を使う

上記の設定を行うことで、Raspberry Pi 5初回起動時の手間がなくなり、最初からWi-Fiに接続され、さらには、外部からSSH接続できるようになります。SSH接続できるということは、SFTPクライアントを使用すればファイルサーバーとして機能するということです。

最後に、microSDカードをRaspberry Pi 5本体に差し込みます。向きを間違えないよう注意ください

接続してRaspberry Piを起動しよう

下記のように接続していきます。電源アダプタをコンセントにまだ刺さないでください
具体的には、本体左下部に電源アダプタその右隣にmicroHDMIケーブル(反対側のHDMIはディスプレイに接続)、本体右上部にキーボード&マウスをUSB接続します。

あと一息です!電源アダプタをコンセントに繋ぐと、Raspberry Pi 5は自動で起動します

補足:ファイルサーバーを使うには? (SFTP) / SSH接続するには?

右上のWi-Fiマークにマウスカーソルを合わせると、IPアドレスが表示されます。SFTPクライアント(例えば、WindowsであればWinSCP、iPhoneであればDocuments by Readdleなど)から、Raspberry Pi 5のファイルにアクセス出来ます。

例えばDocuments by Readdleであれば、インストール時に設定した、ホスト:IPアドレスログイン:ユーザー名パスワードを入力すれば、Raspberry Pi 5のファイルにアクセスできます。

ターミナル(WindowsであればPowerShell、iPhoneであればiSHなど)からSSH接続する時は、下記のようなコマンドを使用してください(ユーザー名:risupresso、IPアドレス:192.168.0.5の場合)。

ssh risupresso@192.168.0.5

接続先のサーバーが正しいか確認される(fingerprint)ので問題なければyes、その後、パスワードを入力すると接続できます。

ローカルWebサーバーを立てて、簡単なゲーム(ブロック崩し)を公開しよう

まず、左上のターミナルのマーク[>_]をクリックし、ターミナルを起動してください。
/var/www/htmlディレクトリを作成します。

sudo mkdir -p /var/www/html

そのまま、作成したディレクトリに移動します。

cd /var/www/html

次に、index.htmlファイルを作成します。

sudo touch index.html

index.htmlファイルをテキストエディタで編集します。

sudo nano index.html

下記のコードをコピーして、右クリックでpaste(貼り付け)します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>ブロック崩しデモ</title>
  <style>
    body { margin:0; background:#222; color:#fff; text-align:center; }
    #gameCanvas {
      background: #000;
      display: block; margin: 0 auto;
      max-width: 100%; height: auto;
      touch-action: none;
    }
    #info { margin: 8px; font-size: 1rem; }
  </style>
</head>
<body>
  <h1>ブロック崩しデモ</h1>
  <div id="info">
    Level: <span id="level">1</span>
    Score: <span id="score">0</span>
    Lives: <span id="lives">3</span>
  </div>
  <canvas id="gameCanvas" width="800" height="600"></canvas>
  <script>
  (() => {
    const canvas = document.getElementById('gameCanvas');
    const ctx    = canvas.getContext('2d');
    const levelEl = document.getElementById('level');
    const scoreEl = document.getElementById('score');
    const livesEl = document.getElementById('lives');

    // ゲームステート
    let level = 1, score = 0, lives = 3;
    let bricksLeft = 0;            // ← 追加
    let ballX = canvas.width/2, ballY = canvas.height-30;
    let ballDX = 4, ballDY = -4, ballRadius = 8;
    const paddleHeight = 12, paddleWidth = 100;
    let paddleX = (canvas.width - paddleWidth) / 2;
    let rightPressed = false, leftPressed = false;

    const brickRowCountBase = 3;
    const brickColumnCount = 7;
    const brickWidth = 75, brickHeight = 20;
    const brickPadding = 10, brickOffsetTop = 50, brickOffsetLeft = 35;
    let bricks = [];

    // ブロック初期化
    function initBricks() {
      const rows = brickRowCountBase + level - 1;
      bricksLeft = rows * brickColumnCount;  // ← ここでセット
      bricks = [];
      for (let r = 0; r < rows; r++) {
        bricks[r] = [];
        for (let c = 0; c < brickColumnCount; c++) {
          const hue = Math.floor(360 * c / brickColumnCount);
          bricks[r][c] = {
            x:0, y:0, status:1,
            color: `hsl(${hue}, 70%, 60%)`
          };
        }
      }
    }

    function drawBricks() {
      for (let r = 0; r < bricks.length; r++) {
        for (let c = 0; c < brickColumnCount; c++) {
          const b = bricks[r][c];
          if (!b.status) continue;
          const x = c*(brickWidth+brickPadding) + brickOffsetLeft;
          const y = r*(brickHeight+brickPadding) + brickOffsetTop;
          b.x = x; b.y = y;
          ctx.fillStyle = b.color;
          ctx.fillRect(x, y, brickWidth, brickHeight);
        }
      }
    }

    function drawBall() {
      ctx.beginPath();
      ctx.arc(ballX, ballY, ballRadius, 0, Math.PI*2);
      ctx.fillStyle = '#fff';
      ctx.fill();
      ctx.closePath();
    }

    function drawPaddle() {
      ctx.fillStyle = '#0f0';
      ctx.fillRect(paddleX, canvas.height - paddleHeight - 10, paddleWidth, paddleHeight);
    }

    function drawInfo() {
      levelEl.textContent = level;
      scoreEl.textContent = score;
      livesEl.textContent = lives;
    }

    // ブロック衝突判定
    function collisionDetection() {
      for (let r = 0; r < bricks.length; r++) {
        for (let c = 0; c < brickColumnCount; c++) {
          const b = bricks[r][c];
          if (!b.status) continue;
          if (
            ballX > b.x && ballX < b.x + brickWidth &&
            ballY > b.y && ballY < b.y + brickHeight
          ) {
            ballDY = -ballDY;
            b.status = 0;
            bricksLeft--;      // ← カウントダウン
            score += 10;
            // 全破壊で次レベル
            if (bricksLeft === 0) {
              level++;
              ballDX *= 1.1; ballDY *= 1.1;
              initBricks();
            }
          }
        }
      }
    }

    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      drawBricks(); drawBall(); drawPaddle(); drawInfo();
      collisionDetection();

      // 壁バウンス
      if (ballX + ballDX > canvas.width - ballRadius || ballX + ballDX < ballRadius) {
        ballDX = -ballDX;
      }
      if (ballY + ballDY < ballRadius) {
        ballDY = -ballDY;
      }
      // パドル判定
      else if (ballY + ballDY > canvas.height - ballRadius - paddleHeight - 10) {
        if (ballX > paddleX && ballX < paddleX + paddleWidth) {
          const paddleCenter = paddleX + paddleWidth/2;
          const relX = (ballX - paddleCenter) / (paddleWidth/2);
          const maxA = 75 * Math.PI/180;
          const speed = Math.hypot(ballDX, ballDY);
          const ang = relX * maxA;
          ballDX = speed * Math.sin(ang);
          ballDY = -speed * Math.cos(ang);
        } else {
          lives--;
          if (!lives) {
            alert('Game Over');
            document.location.reload();
            return;
          } else {
            ballX = canvas.width/2; ballY = canvas.height-30;
            ballDX = 4; ballDY = -4;
            paddleX = (canvas.width - paddleWidth)/2;
          }
        }
      }

      // 位置更新
      ballX += ballDX;
      ballY += ballDY;

      // パドル移動
      if (rightPressed && paddleX < canvas.width - paddleWidth) paddleX += 7;
      if (leftPressed  && paddleX > 0)                  paddleX -= 7;

      requestAnimationFrame(draw);
    }

    // イベント登録(キーボード/タッチ/マウス)
    document.addEventListener('keydown', e => {
      if (e.key==='Right'||e.key==='ArrowRight') rightPressed = true;
      if (e.key==='Left' ||e.key==='ArrowLeft')   leftPressed  = true;
    });
    document.addEventListener('keyup', e => {
      if (e.key==='Right'||e.key==='ArrowRight') rightPressed = false;
      if (e.key==='Left' ||e.key==='ArrowLeft')   leftPressed  = false;
    });
    canvas.addEventListener('touchmove', e => {
      e.preventDefault();
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / rect.width;
      const tx = (e.touches[0].clientX - rect.left) * scaleX;
      paddleX = Math.max(0, Math.min(tx - paddleWidth/2, canvas.width - paddleWidth));
    }, { passive: false });
    document.addEventListener('mousemove', e => {
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / rect.width;
      const mx = (e.clientX - rect.left) * scaleX;
      if (mx > 0 && mx < canvas.width) paddleX = mx - paddleWidth/2;
    });

    // 初期化&開始
    initBricks();
    draw();
  })();
  </script>
</body>
</html>

Ctrl+OのあとEnterキーで保存してください。その後Ctrl+Xで、nanoを終了します。

最後に、Webサーバーを立ち上げます。例としてポート番号8000としています。

python3 -m http.server 8000

あとはブラウザで、http://IPアドレス:8000とすれば、同一ネットワーク内であればアクセスでき、簡単なゲーム(ブロック崩し)で遊ぶことができます!✨

iPhoneからアクセスした様子(IPアドレスは192.168.0.4ですが環境により異なります)。

まとめ

Vesonn JPの「Raspberry Pi5 16gb セット技適済みは、簡単にセットアップでき、実用性も高いので一台持っていると便利でしょう。

Webサーバー、ファイルサーバー、プログラミングなどが、省電力&コンパクトで行えます!✨

コメント

タイトルとURLをコピーしました