MENU

ブラウザ上で動くjavascriptの作成に苦労した話

chat-gpt o3-miniとo1 Pro に泣きつきながらブラウザ上で動くブロック崩しゲームを作成した。

ブログ投稿記事内にゲームプログラムを埋め込めばいいのかな?と、ふんわりとした思いを持ち、勢いで今日中に頑張ってみようとトライすることにして、無勉強・無計画で着手を開始した。

最初はpythonスクリプトでの作成をお願いしたが、ウェブ上で設定を考慮しないとスクリプトタグが無効になるケースが多いなど乗り越えないといけない環境の問題が多そうだったでやめた。

次にjavaでの作成をお願いしたが、ブラウザ上で動かすならjavascriptではo3-miniに突っ込まれる。

「javaとjava scriptは全く違う言語だなんて初耳だな。」素人はそんなもんだ。ブラウザ上で稼働するのはjava script のほう。

o3-miniがjava scriptを生成した。

ワードプレス編集画面で「カスタムHTML」を選択してそこにjava scriptを直接埋め込んだ。

編集画面上でのプレビューは起動した。

が、「新しいタブでプレビュー」で開いたブラウザ画面上ではゲームのウィンドウの表示はあるが、ゲームが始まらない。

o3-miniは、ブラウザを開いたときに、canvasが描画される前にスクリプトが実行された可能性があるという。その可能性をつぶすために、Windows.onloadイベントでスクリプトをラッパする。こうすると、canvasが描画された後にスクリプトが実行されるようだ。

window.onload = function() {
  // ゲームの初期化コードをここに入れる
  // 例:ゲームループの開始やcanvas要素の取得など
};

しかし、ゲームが始まらないという事象は解消されてない。

o3-miniは、ブラウザ上でf12を押すと開発者ツールが開くから、そのメッセージを確認しろ、という。

コンソール画面には以下のエラーメッセージが。

  • The Content Security Policy (CSP) prevents the evaluation of arbitrary strings as JavaScript to make it more difficult for an attacker to inject unathorized code on your site.
  • Response should include ‘x-content-type-options’ header.

意味不明だ。複雑な問題になりそうなので、選手をo3-mini からo1-proにチェンジし、彼にどうすればいいか聞く

(前者について)「セキュリティのために、ブラウザが「インラインJavaScript」を実行不可としている。(「インラインJavaScript」を実行不可のためゲームが起動しない)」「インラインで記述すると悪意を持ったハッカーが改ざんできてしまうので、JavaScriptは基本的には外部ファイルに持たせてあげて、それを読み込む」

意味不明。僕の知能が追い付かない。

対策も教えてくれた。

WordPressにおける「外部JavaScriptの読み込み」については、慣れないうちは少し複雑に感じるかもしれません。結論から言うと、
JSファイルを外部に用意し
「functions.php」でそのJSを「読み込ませる」設定をして
実際の投稿(本文)には<canvas>などのHTML要素だけを書く
という流れが「WordPressの正攻法」として推奨されています。

百聞は一見にしかず。この話をAIに画像生成させ、私はその画像を見てビジュアルに理解することにした。

理解できねえ。

私はゲームをアップロードしてみたいだけなのであって、技術的な枠組みの理解に体力を使いたくない。

仕方がないのでAIの言いなりになって手を動かすことにした。

親テーマにfunctions.phpをおくと、テーマのアップデートが行われるたびに上書きでなくなっちゃうので、子テーマに置く必要がある(子フォルダみたいなもの?)とのこと。下記はディレクトリ構造。サーバーのコントロールパネルでファイルマネージャーを使うことでアプローチができる。

my-child-theme
├─ functions.php
└─ js/
└─ block-breaker.js

o1 Pro先生の知見でもって検索したところ、以下のページが引っかかった。

SWELLに自分のオリジナルCSSやJS(Javascript)を追加する方法

しかし、プラグインのCode Snippetというものを使用するほうがWordPress管理画面上で完結できるためメンタルの消耗はより少なそうだ。ダメだ、金がかかる。正攻法で行くしかない。

SWELL子テーマの導入時点でファイルマネージャーを覗くとswell_childフォルダがあり、functions.phpを確認できる。

ここにjsフォルダを追加 「+フォルダをクリックし、jsと名前を入れる。」

jsフォルダに入って、適当な名称.jsを作成

今回適当な名称.js (上) とfunctions.php(下)は以下の通り

document.addEventListener('DOMContentLoaded', function() {
  // Canvas と 2D コンテキストを取得
  var canvas = document.getElementById("myCanvas");
  var ctx = canvas.getContext("2d");

  // ゲームの基本変数の初期化
  var ballRadius = 10;
  var x = canvas.width / 2;
  var y = canvas.height - 30;
  var dx = 2;
  var dy = -2;
  var paddleHeight = 10;
  var paddleWidth = 75;
  var paddleX = (canvas.width - paddleWidth) / 2;
  var rightPressed = false;
  var leftPressed = false;
  var brickRowCount = 3;
  var brickColumnCount = 5;
  var brickWidth = 75;
  var brickHeight = 20;
  var brickPadding = 10;
  var brickOffsetTop = 30;
  var brickOffsetLeft = 30;
  var bricks = [];

  for (var c = 0; c < brickColumnCount; c++) {
    bricks[c] = [];
    for (var r = 0; r < brickRowCount; r++) {
      bricks[c][r] = { x: 0, y: 0, status: 1 };
    }
  }

  // キーボードイベントの設定
  document.addEventListener("keydown", keyDownHandler, false);
  document.addEventListener("keyup", keyUpHandler, false);

  function keyDownHandler(e) {
    if (e.key === "Right" || e.key === "ArrowRight") {
      rightPressed = true;
    } else if (e.key === "Left" || e.key === "ArrowLeft") {
      leftPressed = true;
    }
  }

  function keyUpHandler(e) {
    if (e.key === "Right" || e.key === "ArrowRight") {
      rightPressed = false;
    } else if (e.key === "Left" || e.key === "ArrowLeft") {
      leftPressed = false;
    }
  }

  // 衝突判定:ボールがブロックに当たった場合の処理
  function collisionDetection() {
    for (var c = 0; c < brickColumnCount; c++) {
      for (var r = 0; r < brickRowCount; r++) {
        var b = bricks[c][r];
        if (b.status === 1) {
          if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
            dy = -dy;
            b.status = 0;
          }
        }
      }
    }
  }

  // ボール描画
  function drawBall() {
    ctx.beginPath();
    ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
  }

  // パドル描画
  function drawPaddle() {
    ctx.beginPath();
    ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
  }

  // ブロック描画
  function drawBricks() {
    for (var c = 0; c < brickColumnCount; c++) {
      for (var r = 0; r < brickRowCount; r++) {
        if (bricks[c][r].status === 1) {
          var brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
          var brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
          bricks[c][r].x = brickX;
          bricks[c][r].y = brickY;
          ctx.beginPath();
          ctx.rect(brickX, brickY, brickWidth, brickHeight);
          ctx.fillStyle = "#0095DD";
          ctx.fill();
          ctx.closePath();
        }
      }
    }
  }

  // ゲームループ
  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawBricks();
    drawBall();
    drawPaddle();
    collisionDetection();

    // 壁との衝突判定
    if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
      dx = -dx;
    }
    if (y + dy < ballRadius) {
      dy = -dy;
    } else if (y + dy > canvas.height - ballRadius) {
      // パドルとの衝突判定
      if (x > paddleX && x < paddleX + paddleWidth) {
        dy = -dy;
      } else {
        alert("GAME OVER");
        document.location.reload();
        return;
      }
    }

    x += dx;
    y += dy;

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

    requestAnimationFrame(draw);
  }

  // ゲーム開始
  draw();
});
<?php

/* 子テーマのfunctions.phpは、親テーマのfunctions.phpより先に読み込まれることに注意してください。 */


/**
 * 親テーマのfunctions.phpのあとで読み込みたいコードはこの中に。
 */
// add_filter('after_setup_theme', function(){
// }, 11);


/**
 * 子テーマでのファイルの読み込み
 */
add_action('wp_enqueue_scripts', function() {
	
	$timestamp = date( 'Ymdgis', filemtime( get_stylesheet_directory() . '/style.css' ) );
	wp_enqueue_style( 'child_style', get_stylesheet_directory_uri() .'/style.css', [], $timestamp );

	/* その他の読み込みファイルはこの下に記述 */
	
	// ひゃっはあああ!記念すべきゲーム第一号だぁぁあっ!
    // ゲーム1 ブロック崩し
    wp_enqueue_script('game1', get_stylesheet_directory_uri() . '/js/game1.js', array(), '1.0', true);
    // ゲーム2 準備中

}, 11);

そして、SWELLの投稿編集画面では下記のようにカスタムHTMLブロックを選択しそこにjsファイルを読みこむコードを埋め込む

<!-- 例: カスタムHTMLブロック -->
<canvas id="myCanvas" width="480" height="320"></canvas>
<style>
  canvas {
    background: #eee;
    display: block;
    margin: 20px auto;
    border: 1px solid #000;
  }
</style>

これでようやっと、冒頭のブロック崩しゲームが表示されるようになった。どんだけ大変やねん。

(中の人は他に環境の問題があって、子テーマのfunctions.phpを読み込めず原因の解明と解決にo1 Pro と一緒に取り組んで3時間かかる。セキュリティ度外視でインラインポリシーを無効化すれば投稿記事内のカスタムHTML一か所だけ気にすれば済む、そんな誘惑と戦いながら頑張った。)

結論

AIに聞かずにサイト検索だけで情報を集めて施行錯誤するのは無理ゲー。手を動かすよりも、頭を使うよりも、まずAIに聞け。

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

目次