初心者向けゲームプログラミングJavaScriptライブラリを作り、講座をした

:::note
このライブラリのお話をしています(スター頂けると嬉しいです><)
https://github.com/konbraphat51/HotSoupScript
:::

## やりたいこと
### まずは`HSP3`の布教を一つまみ...
筆者は中学生の時に`HSP3`とともに育ちました。
`HSP3`は子供心を上手にくすぐったすごい言語です。
子供向け言語というと、「Scratch」のようなブロック型言語が思い浮かびますが、`HSP3`はゴリゴリのスクリプトベースのコンパイル式言語です。文法は`BASIC`と類似しています。
（解答のみ提出の時代の）情報オリンピックも`HSP3`で解いていました。

さあ、これの何がすごいのでしょうか？
何よりも、
**自分のスクリプトがグラフィカルに命宿る**
ところであると考えています。

これが**空文字列のスクリプト**を「実行」した結果です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/c6ea5df0-f3b5-d7a1-6b8a-b6081f90c10b.png)
「実行結果」となるウィンドウが現れます。

これがハロワです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/d77ee050-33f1-d40e-8ff6-31bd247d3d1b.png)

こうやっていじることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/afcba9f0-daa6-dc40-98af-3f37b6afc7b3.png)

`HSP3`はWindows APIの操作を十分に簡略化したことが特徴です。（そのため、MacOSは使えない）
中3の僕はC++でウィンドウを表示するだけで1か月かかりました。[参考](https://c-lang.sevendays-study.com/win32/day2.html)

言ってしまえばこの真っ白なウィンドウに文字書いたり図形書いたり画像貼ったりすることに帰着しますが、そのプリミティブな状態からどんどん自分の思い通りの世界が実現していくことがとても楽しく、中学生の頃は一日中ゲームを作ることだけを考えていました。

燃やされた子供心で、中2で下記のような格闘ゲームを作れてしまいました。（Windows API以外のロジックはフルスクラッチで、2万行のコードとなっていて、かなりイカれた中学生だと自省しております）
![sasa_20231110_182210.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/62f7286e-4227-85a7-7312-beda80b5f7cd.gif)

ちなみに、さきほどの静的な文字の貼り付けからこんなに動くようになったのはループで「描画→画面リセット→描画→...」を繰り返すことによって実装しています。

描画以外のロジックは全てフルスクラッチというわけですが、中1, 2のプログラミング初心者の能力で出来てしまえた理由は下記があるかと思います。

1. 描画APIの簡略化
2. プリミティブでややこしくない機能
3. シングルスレッドで一方通行で遂行

という3点にあるかと思います。

1.については先ほどの通り。

2.については、「なんでもかんでも代行すると逆にややこしくなる＋その仕組みを理解する学習コストがかかる」が問題になることかと思います。
例えば`Unity`などのゲームエンジンはオブジェクト管理、物理演算などを代行しますが、その仕様を理解するのが忙しい。分かれば便利なんだけど。特に`Unity`はオブジェクト指向（コンポーネント指向）の完成系じゃないですか。あんなの初心者には厳しい。
また、初心者が他人のコードを編集できるわけないので、もし既存のシステムが気に食わなければしんどい。
むしろ、全体のシステムを自分で組み上げた方が、大局観を把握できて小回りも簡単に効いてやりやすいのではないでしょうか。（少なくとも個人的にはそう思いました）


3.にも関わってくる話です。プログラミング初心者にはマルチスレッドはしんどいです。たとえば`JavaScript`の入力イベントシステム、`Unity`の`MonoBehaviour.Update()`呼び出し、など、処理がバラバラだと脳内の管理がしんどい。
プログラムは概念的にも一方通行の方が分かりやすい。

これが先ほど紹介した格闘ゲームのメインルーチン（1フレームごとの処理）ですが、これら全てが一方通行です。(オブジェクト指向を把握した今となってはなかなか違和感があるコードですが、初心者の暴走ということで。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/ba70adf2-4484-05d2-1b9c-f39f3d5ff1c9.png)
見知らぬスレッドシステムが自分のコードを拾いに来ることも、変なキー入力イベントが飛んでくるのを待ち構えるのではなく、自分で管理する。これがむしろ、初心者に適していると申します。

(もちろん、開発フローとしてはアジャイル開発的に、メインルーチンに段々機能をスモールステップで盛り込んでいく感じで、最初から大局観があったわけではありません)

まあ、教育全般に言えることですが、記憶消して再現性取らない限り何が有効か分かりませんけどね。

### 今回の自作ライブラリ
`HSP3`は優秀だけど、Windowsしか使えないのは今の時代致命的な問題、みんなに遊んでもらいたい。
そこで活路は最強のマルチプラットフォーム・プラットフォーム「ブラウザ」にあるわけです。
要するに、HTMLの`canvas`の上で、先述した3要件

1. 描画APIの簡略化
2. プリミティブでややこしくない機能
3. シングルスレッドで一方通行で遂行

を満たした`JavaScript`ライブラリを作らんと欲します。

## 作った
まだまだ関数が完備とは言えませんが、ベースは作れました。
[レポジトリ](https://github.com/konbraphat51/HotSoupScript)と、[チュートリアルサイト](https://konbraphat51.github.io/HSS_examples/)です。
（このチュートリアルサイトも`Vue3`でそれなりに頑張ったので、こちらも後日記事にします）

:::note
2023/11/14追記
記事書きました
[【Vue3】自作ライブラリをその場で実行できる多言語対応チュートリアルサイトを作った](https://qiita.com/konbraphat51/items/3919a4ff97e0a045545a)
:::

### 仕様（簡単に）
#### ハロワ
こんな感じ
```javascript
async function main() {
    DrawText("ageuoiehbuoefa", 100, 100)
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/d71dd9eb-1039-ccb1-5d90-c2d6e772fef3.png)

`async function main(){`はこの時点では必要なく、用意しなくとも動作しますが、後々出てくる`async Sleep()`の時の導入がだるくなるので、何も考えずに我慢してもらう感じで。

`DrawText()`の中身は`canvasの2Dコンテキスト.fillText()`そのままなのですが、HTMLの指定操作や、そもそものクラス概念（`.`直前まで）を隠蔽したいと思い、あえてグローバル関数として用意。

色を変えるのは、`HSP3`の`setcolor`と同じノリで。
```javascript
async function main() {
    //  デフォルトの色は黒です。
    DrawText("Black", 100, 100)

    //  SetColor関数で色を変更できます。
    SetColor("red")
    DrawText("Red", 100, 200)

    //  次のSetColor()が呼ばれるまで設定は続きます。
    DrawText("Red2", 200, 200)

    //  描画した後にSetColor()を呼んでも意味はありません。
    //  プログラムは上から下に流れます。
    DrawText("Green?", 100, 300)
    SetColor("green")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/c66ef688-8afa-41ed-3e67-7befa95ce73e.png)

色を指定する裏グローバル変数があり、それを操作する関数を叩いてから描画系の関数を叩くと変色する、という`HSP3`に準じた仕様に。
正直裏グローバル変数みたいな奇妙な概念を取り入れるか迷いましたが、描画関数に毎回色を引数として渡すと煩雑になる点、「プログラムが上から下に流れる」「指定を変えないと前回と同じ色で表示され、融通が利かない」という教育的価値を考え、採用しました。正直どうなんでしょうね。

#### Sleep関数
ゲームプログラミングをする上では、指定時間止めるという処理が、作品に時間概念を取り入れる上でかなり重要なのですが、意外と標準`JavaScript`には用意されていませんでした。

```javascript
async function Sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms))
}
```
こう実装することで、
```javascript
async function main() {
    // これはすぐに表示されます
    DrawText("1", 100, 100)

    // プログラムを1000ミリ秒(1秒)止めます
    // Sleepの前に"await"を付けるのを忘れないでください
    await Sleep(1000)

    // これは1秒後に表示されます
    DrawText("2", 100, 200)

    await Sleep(1000)
    SetFont("100px Arial")
    SetColor("red")
    DrawText("うおおおおおおおお", 100, 300)
}
```

こんな感じに利用できるように。

`while(true)`を無理に使ったBusy WaitだとHTMLイベントがこのスレッドに飛んでこなくなりますので、`Promise`を使うことで解決。その代わり、初心者に`await`を書かせることになりますが、こればかりは`JavaScript`が悪いということで（泣）
いちおう思考停止で`await`を書こう、という感じで非同期概念そのものを隠蔽しているつもりではありますが。

それと`Sleep()`の重要な役割として、 **`while(true)`の無限ループをブラウザをクラッシュさせることなく遂行させられる** ことにあります。

先ほどの`HSP3`のコードでもお見せしましたが、ゲームシステムは基本的に無限ループの中で、決まったフローを繰り返すことで動作しています。（「フレーム」というゲーム用語は、このループの一回一回を指しているわけです）

しかし、このようなスクリプトを書くと、
```javascript
while(true){
    //フレーム処理
}
```
描画すらされずに、ブラウザがクラッシュします。

これは描画処理のルーチンを始めブラウザ運行の処理に、システム実行の順番が回ってこないからでして、もちろんHTMLイベントも飛んできません。

これを、こう書くと
```javascript
while(true){
    //フレーム処理
    await sleep(1)
}
```

問題なく動作します。この`Sleep()`の時間中にブラウザ運行の処理がなされるわけですね。
初心者には「休憩時間」「冷却時間」という風に説明してます。

#### 入力系統
HTMLで入力を扱おうとすると、HTMLイベントを扱わざるを得なくなり、だるい！
ので、隠蔽しました。
```javascript: キー入力で文字が移動するスクリプト
async function main() {
    // 変数を作り、ついでに初期位置を設定
    var x = 100;
    var y = 100;

    // スピードを設定
    var speed = 5;

    // 文字の大きさをここで調整する
    SetFont("20px Arial")

    while (true) {
        // 前のフレームの描画を消す
        SetColor("white")
        DrawRect(0,0,GetCanvasSize()[0],GetCanvasSize()[1])

        // 文字を描画
        SetColor("black")
        DrawText("a", x, y)

        // キー入力によって移動
        if (GetKey("ArrowLeft")) {
            // "ArrowLeft"キーが押されたら、GetKey("ArrowLeft")は`true`になります
            // 対応するキーが押されたらこの部分が実行されます
            x -= speed;
        }
        if (GetKey("ArrowRight")) {
            x += speed;
        }
        if (GetKey("ArrowUp")) {
            y -= speed;
        }
        if (GetKey("ArrowDown")) {
            y += speed;
        }

        // 冷却時間
        await Sleep(10);
    }
}
```

この`GetKey()`関数が、その瞬間にキーが押されているかどうかを`boolean`で返しているわけです。

システム側が押されたかどうかを通知してくれるところを、わざわざこちらが毎フレーム確認しにいくという一見馬鹿らしいことをしていますが、**この「一方通行」性が初心者に思考の整理を簡単にできている** わけです。

[実装が気になる方はこちら(GitHub)を](https://github.com/konbraphat51/HotSoupScript/blob/main/Public/Inputs.js)

#### ちなみに
大学の課題の物理シミュレーションの実装課題で、教員のBlenderファイルを無視して[本ライブラリで「ニュートンのゆりかご」を実装したのがこちらです](https://github.com/konbraphat51/NewtonsCradle)

（動作ページはREADMEのリンクから）

## 講座をした
[東大GDSC](https://twitter.com/gdscjp)にて、このライブラリを用いた初心者講座を行いました。
参加者は11名で、玄人が4人、[別講座でPython入門だけはした方が6名](https://github.com/SuperHotDogCat/PYTHONWORKSHOP)、プログラミング全くの初心者が1名という構成でした。

全員が東大生というわけではないのですが、大半がそうなので、理解の速さなどにおいてはバイアスはないとは全くいえません。そこはご了承を。

[講座で書いた内容がそのままこちらにあります](https://github.com/konbraphat51/Dammaku_HSS/blob/main/Src/main.js)

[5時間ほどで、ハロワから始め、こちらの弾幕ゲームを作ることができました（github pagesに飛ぶ）](https://konbraphat51.github.io/Dammaku_HSS/)

![tama_20231112_062323.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/273c8876-ebc8-ae25-2aba-b0dbac71a762.gif)

初心者向けの講座ですので、かなりスローペースでの進行を心掛けましたが、ここまでいけてしまいました。事後アンケートでも速さについて「ちょっと遅い」が100%でしたので、速さはちょうどいいということですね。

一方的に教えるだけでなく、例えば「これにハイスコア機能を付け足してみてください」と丸投げしても全員できていたので、みんなで書いているコードが全員しっかり理解できていたかと思います。
やっぱり述べてきた3要件が初心者にも取り組みやすいということでしょうかね。

重要なのが、「楽しかった」100%だと思います（お世辞かもしれませんが泣）

大学生だとどうかと思いましたが、やっぱりどんな年でも自分の書いたコードで画面が動くというのは楽しいと思ってもらえるようですね。
僕はプログラミング中毒なので、コマンドプロンプトからの出力でも、なんなら出力しなくとも（？）楽しいのですが、初心者にとっては地味な作業をずっとしているという印象を持ちがち。そこで何かグラフィカルな作品を作るという体験はとてもプログラミングに対してポジティブな感情を呼び寄せることができたのではないでしょうか？
僕自身の思想としては、プログラミングを自己実現のビジネスツールではなく、クリエイティブな楽器として楽しむのが至幸だと考えていますので、皆様にもそう感じてもらえたら嬉しいな。

## 最後に
:::note
いいね頂けると泣いて喜びます><

この記事を読んでいる方は次の記事を読んでいるかもしれません:
* [初心者だらけでもとりあえず共同開発ができる手引き](https://qiita.com/konbraphat51/items/8793f03807c39573fac2)
* [【ゲーム物理】離散要素法DFMで簡単物理エンジン！①　基礎編](https://qiita.com/konbraphat51/items/157e5803c514c60264d2)
:::