文化祭で使うゴーストハントゲームをWebARで作った話

この記事は「[長野高専アドベントカレンダー2024](https://qiita.com/advent-calendar/2024/nnct)」の21日目の記事となっています！

まさか、まさかの**3回目の投稿**となっています。

友人が主催者になったので、がんばって書こうと思ったのですが、**途方もなく計画を間違えましたね。**

さて、タイトルは、 **「文化祭で使うゴーストハントゲームをWebARで作った話」** 。またの名を **「ボロボロのゲーム」** 。とくとご覧ください。

# はじめに言い訳

この記事を書き始めたのが、12月20日22:03ですね。。。え

**(追記: シンプルに、12/21の23:30に出しましたね。ぎりぎり。)**

なぜこんな時間になったのか。

なんか、**シンプルに体調が悪いんですよね**。

あと、学校が終わるタイミングと被って、寮の部屋の掃除とか、[前回の記事](https://3.14159.jp/posts/20241215_shuppankoshien/)の余韻とかで忙しかったんですよね。

最近は流行り病も怖いので、皆さんもご自愛くださいませ。


# 今年の文化祭何やった？

とは言っても、ある程度の記事は書きたいと思います。

さて、私はこれまで何かと工嶺祭(長野高専の文化祭)には、何かしらの形でかかわっていたのですが、振り返ってみたいと思います。

| 年 | 学年 | 何やった |
|-|-|-|
|2021年|1年| Webスタンプラリーアプリ開発コンテストに応募して採用され、運用|
|2022年|2年| 前年のスタンプラリーアプリを改善して、また運用 |
|2023年|3年| デジタルスタンプラリー廃止。しかし、開校60周年ということで、特別企画としてミッション形式のWebアプリを開発、運用（詳しくは[前年の記事](https://qiita.com/advent-calendar/2024/nnct)参照）|

ということで、これまですべて**実行委員会**の方で働いていたんですね。

さてさて。デジタルスタンプラリーは廃止になったし、特別企画は特にない。**クラス企画の方に全振りするか**。

ということで、これまで避けてきたクラス企画に全面的に協力することにしました。

当時、クラスで決まっていたことが、2つ。

1. 「~~ルイージマンション~~」のようなゲーム
1. 実物(お化け屋敷)というよりかはJ科らしくゲームっぽく

以上2点を満たすゲームを作ることになったんですが、誰が開発するか決まらず、夏休みを迎えようとしました。

そこで私がなんやかんやあり**開発を担当**することになりました。

# 作ったものの概略

タイトルにもある通り、WebARでゴーストハンティングゲーム（~~つまりルイージマンション~~）を作りました。

本当はローカルのアプリにすべきなんでしょうけど、自分がWeb開発畑の人間なので、WebARで開発することになりました。

仕様技術は、「Web上でARを使いたいならこれ！」と紹介されがちな、**[A-frame](https://aframe.io/)** と **[AR.js](https://ar-js-org.github.io/AR.js-Docs/)** というものです！

まさか、**Java生まれ、PHP育ち、Next.jsかじり中の私**(そんな人間がいるのか)。バニラのJavaScriptをゴリゴリ書くことになったのです。

構成としては、アプリの開発が私一人で、おばけのイラストや3Dモデルをクラスの皆さんに描いて頂いた感じです。

（お化けのイラストの掲載許可を取るのが~~めんどくさい~~大変なので、デバック画面を主とします）

あと、やりたかったこととしては、WebARながらも、ローカルで動かすために、**PWA(Progressive Web Apps)** という技術を使おうとしました。これは、Webサイトをアプリのように利用できるようにするものですが、WebARについて完全に理解していないのか、30分に1回くらい、データを取得しにっているらしく、30分でアプリが動作しなくなってしまいました。
そのため、PWAは諦めました。

また、今回は、**すべての人に端末を貸し出す形**で行いました。
建前は、 **お年寄りや中学生以下の子供にも対応できるようにする**ためです。
しかし、本音は、**ランキングを荒らすのが簡単なためでした。**

セキュリティ対策まで手が回らず、JavaScriptのコードを読めば、データベースを荒らすのが簡単にできてしまうクソシステムだったので、このような仕様にしました。

~~まぁこれもまたセキュリティ対策の一種~~（殴

# ダイジェスト

ということで、私の画像フォルダにあった数枚を元に、軽い解説をして終わりにしたいと思います。


こちらがデバックの画面になります。（しょぼい）

![Point Blur_20241220_222432.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3045371/0e52cda0-d410-066d-8a7a-30f3654cc773.jpeg)

ARマーカーの上に、画像や3Dモデルを表示させているだけですね。

これを画面中央の円の中に入れると捕まえられるといった感じです。



そして、得点の画面も作りました。
元ネタは、 **「[ザ・ベストテン](https://ja.wikipedia.org/wiki/%E3%82%B6%E3%83%BB%E3%83%99%E3%82%B9%E3%83%88%E3%83%86%E3%83%B3)」** (放送:1978年～1989年)の**パタパタ**(反転フラップ式案内表示機)のランキングボードをイメージして作りました。
果たして、高専生の中でわかった人がいるのでしょうか。。。

![Point Blur_20241220_223245.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3045371/1760a084-51a5-98e2-e747-d1060753193c.jpeg)

そして、協賛企業の方に作っていただいた迷路と、4J謹製のお化け屋敷らしい装飾をしてお客さんを迎えました。

![20241020_154401.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3045371/b4014ff0-25e3-60c6-02a2-9ca459c55e8b.jpeg)

何とかなってよかった。


# 見るに堪えないソースコード

一応、Qiitaという場なので、ソースコードを~~お見せ~~晒したいと思います。

いまはNext.jsを勉強しているんですが、この頃はjQueryしか知らない小童なので、以下のようにゴリ押しました。

まず、データを以下のようにJavaScriptでおばけのデータを定義しました。

| 名前 | 内容 |
| ----|-----|
|is3d|3Dモデルかどうか。true:3Dモデル　false:イラスト |
|path| 3Dモデルのファイル、あるいはイラストの画像ファイル |
|patternNum | ARマーカーとの対応する数字。ローカルでランダムに割り振る |
|catched | ユーザーが捕まえたかどうか |

そして、以下はおばけをWebAR上に配置するソースコードです。ソースコード内の`data`が上で設定したおばけのデータです。
この部分をイラストや3Dモデルのデータ数の回数分繰り返して、WebAR上に設置しています。

```js
if (data.is3d) {
    $("<a-asset-item>", {
        id: `${data.id}-item`,
        src: data.path
    }).appendTo('#read-assets');
    $(`<a-entity class="raycastable" cursor-listener id="${data.id}" gltf-model="#${data.id}-item" begin="show" rotation="${data.rotation}" scale="${data.scale} ${data.scale} ${data.scale}" position="${data.position}" cursor="rayOrigin: mouse"></a-entity>`).appendTo(marker);
} else {
    $("<img>", {
        id: `${data.id}-item`,
        src: data.path
    }).appendTo('#read-assets');
    $(`<a-image id="${data.id}" src="#${data.id}-item" cursor-listener width="4" height="4" begin="show" position="0.2 0.4 0.4" rotation="-90 0 0"></a-image>`).appendTo(marker);
}
if (data.id != "hazure") {
    $(`#${markerId}`)[0].addEventListener("markerFound", {item: data, handleEvent: removeObake_sub});
}
```



じつは懺悔しますが、良くない部分が一か所あります。

それは下から2行目、`addEventListener`の第一引数の`"markerFound"`。

直訳すると **「マーカーが見つかった」** 。。。

**「「あれ？『画面中央の円に入れて捕まえる』じゃなくない？」」**

これは、非常に苦渋の決断でした。

「画面中央の円に入れて捕まえる」の処理を忠実に実現するには、
```js
 $(`#${markerId}`)[0].addEventListener("click", {item: data, handleEvent: removeObake_sub});
 ```
 とすると良さそうです。

 開発段階では上手く動作したんですが、工嶺祭1日目に現場のクラスメイトから次々と報告が上がりました。

 **「表示はされるけど、捕まえられない！！」**

こうして何とか直そうとしたんですが、なかなか治らず。。。

2,3時間格闘の末、ふと我に返り、
**「ユーザーは画面にお化けが映ったら、必ずそれを読み込もうとするのでは」**
自分を天才だと思いましたね。

ということで、最初に示した`"markerFound"`になった訳です。

騙す様で申し訳ありませんでした。時効だと思うので懺悔させていただきます。



# おわりに

ちょっと上げて最後にすごく落とした記事ですが、これが僕の今年の工嶺祭でした。

さて、来年は5Jとして(進級できればの話ですが)の工嶺祭。どうなるんでしょうか！

では、ばいば～い！(終わり方が雑だな)