今回は「ウサギの糞ではなくタピオカなのだ!タピオカで部屋を埋め尽くせ」というゲームをつくります。
どうしてこのゲームを作ることになったのか?
鳩でもわかるC# 管理人が応援している配信者にこんな方がいます。
飼い主さんはウサギ(オス 7歳)を飼っていてそのウサギさんが配信をしているというテイです。
ところで
ってウサギなの?
ホーランドロップイヤーという耳が垂れたタイプのウサギがいるそうです。
参考:ホーランド・ロップ
そしてこのウサギさん、清楚な配信をモットーとしているという割にはやたら「ウン◯」というワードを連呼するのです。
そこで今流行りの生成AI(ChatGPT)に「ウサギ小屋をみずからウ◯コまみれにするゲームを作りたいのですが、なにかアイデアはありますか?」と問うてみたところ、こんなクソゲーが生成されました。
このコードの解説はしません。あまりに生成物がひどすぎるからです。もっともプロンプトがひどいとAIはまともな仕事をしてくれないのですが・・・。
このクソゲーのダメな点として、
①マスのようなものがあるが意味をなしていない。
②敵役である飼い主さんの動きが単調すぎる。
③スペースキーを押しっぱなしにすると簡単に高得点がゲットできてしまう。
などがあります。
そこで改善点として
①すべてのマスを糞で埋め尽くせばステージクリアにする。
②飼い主さんの動きが糞に影響されるようにする。産みたての糞ほど吸引力を上げ、プレイヤーにはいかに戦略的に糞を配置するかを考えさせる。
飼い主さんの動きをコントロールできるようになると今度はいつまで経っても終わらないという別の問題がでてくるので、時間の経過とともに飼い主さんの視界を広くしていくことでプレイヤーにはいかにして短時間でステージクリアするかも考えさせるようにします。
糞には臭いがあり、時間の経過とともに消失する。飼い主さんは糞の臭いが強い地点に移動し、臭いが存在しない場合はプレイヤーを直接追いかけるようにすれば面白くなるのではないかと考えて以下のように作りました。
ちなみに配信者いわく これはウサギの糞ではなくタピオカである と言っています。
それでは作成していきましょう。
HTML部分
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ウサギの糞ではなくタピオカなのだ!タピオカで部屋を埋め尽くせ</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel="stylesheet" href="./style.css"> </head> <body> <div id = "container"> <div id = "field"> <div id = "field-header"> <p class = "h1-head">??の糞ではなくタピオカなのだ</p> <h1>タピオカで部屋を埋め尽くせ</h1> <div id = "field-header-left"> <div id = "score">Score</div> </div> <div id = "field-header-right"> <div id = "life"></div> </div> <div id = "field-header-both"> <div id = "distance"></div> </div> </div> <div id = "canvas-outer"></div> <div id = "start-buttons"> <p><label for="player-name">プレイヤー名:</label> <input id = "player-name" maxlength="32"></p> <p><button id = "start">START</button></p> <p><button id = "go-ranking" onclick="location.href = './ranking.html'">ランキング</button></p> </div> <div id = "control-buttons"> <button id = "left">←</button> <button id = "up">↑</button> <button id = "defecate">●</button> <button id = "down">↓</button> <button id = "right">→</button> </div> <div id = "volume"></div> <p>VOICEVOX:ずんだもん</p> </div> </div> <script src="./index.js"></script> </body> </html> |
style.css
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
body { background-color: black; color: white; } #container { margin: 40px auto 40px auto; width: 360px; } #field-header { margin-left: 10px; margin-right: 10px; margin-bottom: 0px; } .h1-head { color:aqua; font-size: large; margin-bottom: 0px; } h1 { color:aqua; font-size: x-large; margin-top: 0px; margin-bottom: 0px; } #field-header-left { float: left; font-size: large; width: 140px; margin-left: 10px; border: 0px #fff solid; } #field-header-right { float: right; font-size: large; text-align: right; width: 160px; margin-right: 10px; border: 0px #fff solid; } #field-header-both { text-align: right; clear: both; margin-right: 10px; } #score { font-size: large; } #distance { font-size: large; } #life { font-size: large; } #field { width: 360px; height: 600px; margin-left: auto; margin-right: auto; position: relative; border: 0px #1DA1F2 solid; } #start-buttons { position: absolute; left: 0px; top: 150px; width: 100%; text-align: center; } #start, #go-ranking { font-weight: bold; text-align: center; font-size: 18px; width: 280px; border: none; font-size: 16px; background-color: #1DA1F2; padding: 10px 32px; border-radius: 100vh; color: white; cursor: pointer; } #control-buttons { width: 100%; text-align: center; left: 0px; top: 380px; display: block; } #up, #left, #right, #down, #defecate { width: 50px; height: 60px; background-color: transparent; border: 2px #fff solid; margin-left: 5px; margin-right: 5px; font-size: 18px; color: #fff; font-weight: bold; } |
グローバル定数
グローバル定数は以下のとおりです。
index.js
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const ROW_COUNT = 9; // マスは9行9列 const COL_COUNT = 9; const CELL_SIZE = 36; // マスの描画サイズ const SMELL_MAX = 300; // 出したての糞の臭いの強さ const MARGIN = CELL_SIZE / 2; // マス等を描画するとき左上に余白を入れたい const CANVAS_WIDTH = 360; // canvasのサイズ const CANVAS_HEIGHT = 360; const $canvas_outer = document.getElementById('canvas-outer'); // canvasを生成・追加 const $canvas = document.createElement('canvas'); $canvas.width = CANVAS_WIDTH; $canvas.height = CANVAS_HEIGHT; $canvas_outer.appendChild($canvas); const ctx = $canvas.getContext('2d'); |
Cellクラスの定義
マスの状態の管理と描画処理のためにCellクラスを定義します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
class Cell { constructor(row, col){ this.Row = row; this.Col = col; this.X = col * CELL_SIZE + MARGIN; // 描画位置 this.Y = row * CELL_SIZE + MARGIN; this.IsDefecated = false; // マスの上に糞が置かれているか? this.Smell = 0; // マスの臭い } // マスの上に糞を置く Defecate(){ this.Smell = SMELL_MAX; if(!this.IsDefecated){ this.IsDefecated = true; return true; } return false; // すでに置かれているところに再度置かれた } // 糞が置かれている場合、臭いが減少していく Update(){ if(this.Smell > 0) this.Smell--; } // 糞を取り除く(当然臭いも消える) Reset(){ this.IsDefecated = false; this.Smell = 0; } // 描画処理(臭いが存在する場合はその値も描画する) Draw(){ ctx.fillStyle = '#adff2f'; ctx.fillRect(this.X, this.Y, CELL_SIZE, CELL_SIZE); ctx.strokeStyle = '#000'; ctx.strokeRect(this.X, this.Y, CELL_SIZE, CELL_SIZE); if(this.IsDefecated){ // 糞があるなら糞も描画 ctx.fillStyle = '#800'; ctx.beginPath(); ctx.arc(this.X + CELL_SIZE / 2, this.Y + CELL_SIZE / 2, 6, 0, Math.PI * 2); ctx.fill(); if(this.Smell > 0){ ctx.font = '20px Arial'; ctx.fillStyle = '#fff'; const v = Math.ceil(this.Smell / 60); ctx.fillText(v, this.X + CELL_SIZE / 2, this.Y + CELL_SIZE / 2); } } } } |
Playerクラスの定義
プレイヤーの状態の管理と描画処理のためにPlayerクラスを定義します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Player { constructor(){ this.X = 0; // 描画位置 this.Y = 0; this.Direct = 'N'; // 移動方向 this.NextDirect = 'N'; // 操作用のキーを押下時に方向転換可能な位置に移動したあとの次の移動方向 this.IsDead = false; this.Image = new Image(); this.Image.src = './images/player.png'; } // ゲーム開始時やミスからの復帰時の初期座標 Init(){ this.Direct = 'N'; this.NextDirect = 'N'; this.X = MARGIN; this.Y = MARGIN; } Draw(){ ctx.drawImage(this.Image, this.X, this.Y, CELL_SIZE, CELL_SIZE); } } |
Enemyクラスの定義
敵役の飼い主さんの状態の管理と描画処理をするためにEnemyクラスを定義します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Enemy { constructor(){ this.X = 0; // 描画位置 this.Y = 0; this.Direct = 'N'; // 移動方向 this.Image = new Image(); this.Image.src = './images/enemy.png'; } Init(){ this.Direct = 'N'; this.X = CELL_SIZE * 8 + MARGIN; this.Y = CELL_SIZE * 8 + MARGIN; } Draw(distance){ ctx.drawImage(this.Image, this.X - 4, this.Y - 4, CELL_SIZE + 8, CELL_SIZE + 8); // 飼い主さんの視界を円を描画することで可視化する ctx.strokeStyle = '#f00'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(this.X + CELL_SIZE / 2, this.Y + CELL_SIZE / 2, distance, 0, Math.PI * 2); ctx.stroke(); ctx.lineWidth = 1; } } |
長くなったので続きは次回とします。

