歌舞伎町 遊戯祭「KEREN」にシューティングゲームを展示しました

Created: 2025/11/23

自己紹介

初めまして、私は東京国際工科専門職大学 工科学部 情報工学科 1年の伊藤優和と申します。
私はデジタルエンターテイメント学科を有する本学でITの学部の所属ですが、ゲーム制作をしています。
情報工学科に在籍しておりますが、本学では学生の意欲次第で、ゲーム制作の多様な活動機会が得られました。その中で私が目指しているプランナーへの道、第一歩としてこの記事を残します。

📄 目次

1. 歌舞伎町 遊戯祭「KEREN」とは?

まずイベントについて軽く紹介いたします。本イベントは11月26日に歌舞伎町内のイベントで歌舞伎町商店街復興組合×東京国際工科専門職大学の共催で開催されました。メインカルチャーとサブカルチャーの混ざり合う、一夜限りのフェスティバルです。

歌舞伎町 遊戯祭「KEREN」

2. ゲーム制作概要

今回、どのようなゲームを作ったかというと、Web上で遊べるシューティングゲームです。メンバーは3人で制作し、約1ヶ月半で完成させました。
使用言語はHTML、CSS、JavaScriptです。

チームメンバー全員が、大学入学後にプログラミングを本格的に学び始めたばかりの初心者で構成されています。なので、これからプログラミングを始めるという方は是非参考にしてみてください。

この制作において、私の役割はプランナーとして参加しています。企画・シナリオ・UIデザイン・キャラクターデザインを担当し、音楽選定も担当しました。
音楽はフリー音源を 魔王魂 さんとSpringin‘さんから使わせていただきました。ありがとうございます。
 
また、ウェブ上でプレイできるようGitHub pageでゲームを公開しました。
是非、遊んでみてください。
Planet Seeker (Webでプレイ)

プレイ動画も私のチャンネルに限定公開しています。
Planet Seekerプレイリスト(YouTube)

2.1. ゲームデザイン

初めにメンバーと軽く話していると、星から星へと移住するという内容のシューティングゲームというアイデアが出たので、思い切ってそのアイデアを使ってみることにしました。

最初に私がイメージしたものをプロンプトにまとめGeminiに投げました。その中で、出来上がったものが、こちらです。

画像


このタイトルイメージからキャラクターのイメージを決め、画像を生成しました。

プレイヤー プレイヤー プレイヤー プレイヤー プレイヤー


このコバエちゃんは没案のボス

プレイヤー

全部は紹介できませんでしたが、リザルト背景、キャラアイコン、攻撃素材などゲームの画像素材はGeminiを使用しました。


私はまずこのゲームを形にするために、一通りアイデアを出しました。最初は1プレイで星を脱出せざる負えなくなった主人公が新たな星にたどり着く、そんな過程を描いたものでした。ですが、それを実際にその過程を制作しようと思うと、かなり難しくプロトタイプがほぼ完成してきた時期にメンバーと話し合い、3編に分けて実装することで形にすることにしました。

タイトル画面

今回のゲーム制作ではタイトルから作成しました。ということでこちらが書いたhtmlコードになります。

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
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Planet Seeker</title>
    <link rel="stylesheet" href="style/title.css">
    <link rel="icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
    <!-- ゲームタイトル画面 -->
    <div class="text-box">
        <h1>Planet Seeker</h1>
        <p>次の移住先に移動せよ!!!!</p>
        <label for="input_task">さあ、冒険の始まりだ!!!!</label><br>
        <button id="explanation_button" class="title_button">遊び方</button>
        <button id="debris_button" class="title_button">脱出編</button>
        <button id="UFO_button" class="title_button">UFO編</button>
        <button id="save_button" class="title_button">BOSS編</button>
    </div>

    <!-- 説明画面 -->
    <div id="explanation" class="screen hidden">
        <canvas id="explanationcanvas"></canvas>
        <h2>遊び方</h2>
        <h3>↑ ↓ → ← キーまたはADWSキーで移動</h3>
        <h4>スペースキーで攻撃</h4>
        <h5>脱出編はスコアアタック</h5>
        <h6>UFO編とボス編は敵を撃破するとクリア</h6>
        <button id="return_button" class="return_button">戻る</button>
    </div>

    <!-- 注意事項 -->
    <div id="error_warning" class="screen hidden">
        <h1>⚠ 注意事項 ⚠</h1>
        <p id="warning_message">
            タイトルとリザルト画面でリロードするとBGMが止まるから気をつけるんだぞ★
        </p>
        <button id="error_close_button" class="return_button">閉じる</button>
    </div>

    <!-- スクリプト -->
    <script src="str/title.js"></script>
</body>
</html>



工夫を凝らそうとしてしまい、長くなってしまったためcssは割愛します。
完成したものがこんな感じです。

画像


また、Web上での主なブラウザの自動再生ポリシーの問題の解決策としてこのような試みをしました。エラーが出た際、注意事項を表示させクリックさせることでBGMを流すようにしてみました。

js
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
const textBox = document.querySelector(".text-box");
const error_warning = document.getElementById("error_warning");

// BGMファイルを読み込む
const bgm = new Audio('assets/sounds/BGM/title.mp3');

// ループ再生を有効にする
bgm.loop = true;

// 音量を調整(0.0〜1.0)
bgm.volume = 0.5;

// ページ読み込み後に再生 w
window.addEventListener('load', () => {
  bgm.play().catch(err => {
    console.error("BGM再生エラー:", err);
    // 1. 表示させたい要素を取得
    const warningElement = document.getElementById("error_warning");

    // タイトル画面を非表示にする
    textBox.classList.add("hidden"); // hiddenクラスがあればCSSで非表示になるはず
    textBox.style.display = "none";

    // エラー画面を表示する
    error_warning.classList.remove("hidden");
    error_warning.style.display = "block"; 
  });
});



このような感じ
ウェブの仕様を逆手に取ったいい案だと思っています。

画像


ゲームはシナリオパート→バトルパート→リザルトと進行します。
脱出編では30秒間のスコアアタックUFO編とBOSS編ではタイムアタックになっています。
星を脱出した彼らがどのような運命が待ち受けるか、必見です!!!  画像

画像

画像

2.2. メッセージウィンドウ

ここでシナリオのメッセージウィンドウのコードを抜粋して紹介します。
Qiitaの記事を参考にさせていただき、Geminiでバイブコーディングを行い作成しました。
画面録画のGIF
※これはゲーム画面のものです。

html
1
2
3
<div id="textbox">   
    <p class="text_message_p"></p>
</div>
js
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
100
101
102
103
104
105
106
107
// --- 変数 ---
let currentMessageIndex = 0; // 現在のメッセージ番号
let isRevealing = false; // テキスト表示中か

// --- メッセージ配列 (textのみに簡略化) ---
const messages = [
  { text: "とうとう惑星の目の前に到着した。" },
  { text: "よく来たな。遠き星に住む生物よ。我はエクゾクォア、「外宇宙の審問官」である。" },
  { text: "名乗るがいい、ここがお前の墓場となる。" },
  { text: "俺の名は、ルミナス・ノア。星の命を、光で繋ぐ者、Planet Seekerだ。" },
];

// --- 関数: メッセージ表示を開始 ---
function initRevealTextMessage(message) { // window.initRevealTextMessageから変更
  // セレクタを取得。message.textがなければreturn
  const p = document.querySelector("#textbox .text_message_p");
  if (!p || !message.text) return;
  p.innerHTML = "";

  let chars = message.text.split("").map(char => {
    let span = document.createElement("span");
    span.textContent = char;
    p.appendChild(span);
    return {
      span,
      delayAfter: char === " " ? 0 : 60, // 文字間のディレイ
    };
  });

  isRevealing = true;
  // 1文字ずつ表示する再帰関数をキック
  revealTextMessage(chars, () => {
    isRevealing = false;
    // 表示完了時の処理(特になし)
  });
}

// --- 関数: 1文字ずつ表示 (再帰) ---
function revealTextMessage(list, onComplete) {
  // テキスト表示の強制スキップ機能は省略
  if (!isRevealing) { // 外部から isRevealing = false にされた場合、即座に終了
      return;
  }

  const next = list.splice(0, 1)[0];
  if (!next) {
    onComplete(); // リストが空になったら完了コールバックを呼ぶ
    return;
  }
  next.span.classList.add("revealed"); // CSSで文字を表示するクラスを付与

  if (list.length > 0) {
    setTimeout(() => {
      revealTextMessage(list, onComplete);
    }, next.delayAfter);
  } else {
    onComplete(); // 最後の文字を表示したら完了
  }
}

// ===============================================
// 会話送り処理 (共通関数)
// ===============================================
function proceedConversation() { // window.proceedConversationから変更
  // テキスト表示中はクリックで次のメッセージへ進まない(スキップ機能はここでは省略)
  if (isRevealing) {
    return;
  }
  // (ゲーム本編中や一時停止中のチェックも省略)

  currentMessageIndex++; // 次のメッセージへ

  // 1. 次のメッセージがある場合
  if (currentMessageIndex < messages.length) {
    // 次のメッセージを表示
    initRevealTextMessage(messages[currentMessageIndex]);
    return;
  }

  // 2. 最後のメッセージが終わった場合
  console.log("会話終了");
  // テキストボックスを非表示にする処理などを追加可能
  const textbox = document.getElementById("textbox");
  if (textbox) {
    textbox.style.display = "none";
  }
}


// ===============================================
// イベントリスナー (DOM読み込み完了時)
// ===============================================
window.addEventListener('DOMContentLoaded', () => {
  // 1. 最初のメッセージを表示
  if (messages.length > 0) {
    initRevealTextMessage(messages[currentMessageIndex]);
  }

  // 2. ウィンドウ全体へのクリックイベントとスペースキーイベントで会話を進める
  window.addEventListener("click", proceedConversation);
  window.addEventListener("keydown", (event) => {
    if (event.key === " " || event.code === "Space") {
      event.preventDefault(); // スペースキーのデフォルト動作(スクロールなど)を停止
      proceedConversation();
    }
  });
});
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
/* ページ全体のスタイル設定 */
body {
  background-color: #1a1a1a;
  height: 100vh;
  margin: 0;
  overflow: hidden;
  font-family: sans-serif;
}
/* メッセージボックス */
#textbox {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  height: 100px;
  background: rgb(55, 0, 255);
  color: white;
  padding: 20px;
  box-sizing: border-box;
  border: 2px solid white; 
  border-radius: 8px;
}

/* テキスト表示エリア */
.text_message_p {
  font-size: 20px;
  user-select: none;
  margin: 0;
  text-align: left;
  /*padding-left: 100px;  ここは話者名のスペースとして機能 */
}

/* テキスト送り用のスタイル */
.text_message_p span {
  opacity: 0; /* 非表示 */
  transition: opacity 0.2s ease;
}

.text_message_p span.revealed {
  opacity: 1; /* 表示 */
}



この機能は、ゲームのシナリオパートに臨場感を持たせ、プレイヤーが物語に入り込みやすくするための重要な演出です。テキストが一瞬で表示されるのではなく、まるでタイプライターで打っているように、1文字ずつ時間差で表示されます。

  1. CSSのopacity: 0(透明度)で全部隠します。

  2. JavaScriptのsetTimeout(時間差)と再帰関数を使って、文字ごとに時間差を作ります。

  3. CSSクラス(.revealed)をタグに順次付与することで表示の順番付けをする。

という仕組みです。

以上、JavaScriptで最低限動くメッセージボックスの内容をまとめました。よければノベルゲームなどのシナリオのあるゲーム制作に活用してみてください。

2.3. 話し足りない・・・けど

記事ですべてを書くと長くなってしまうので、タイトル画面とメッセージボックスで締めて、自分の担当外のところはメンバーに任せます。
本記事をお読みになられた方で、興味がある方はGithubからご確認ください。
(初めてのGit使用のため、コードの整理が行き届かない点はご容赦いただけますと幸いです。)

GitHub: Planet Seeker

3. まとめ

ゲーム制作は初めてな上にGitを初めて触るという経験もあり、かなり難航しました。実際にゲームの内容を考えると、この機能が欲しいということが後から出てくることもあり、デバッグなどの作業も増え、完成するのは本当にギリギリになりました。ゲームの延期ってこういう大変さがあって起こるんだなぁ…としみじみ思います。

私はゲームを作って遊んでもらった人に楽しんでもらうということまでが一セットであると私は思っているので、その部分が本当に悩みました。実際に作っている人はこれ面白いのかな?という問題と常にぶつかっています。その経験ができたのは今回の大きな収穫でした。また、HTMLやCSSなどにも触れホームぺージへの理解も深まりました。そして何より、イベントに参加し自分の企画で作ったゲームを説明してプレイしてもらうという経験ができてよかったです。

興味があれば、皆さんもチャレンジしてみましょう!!!

ここまで読まれた方、初めての記事をお読みいただき、ありがとうございました。
また次回お会いしましょう。では