114
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CA Tech LoungeAdvent Calendar 2023

Day 1

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

Last updated at Posted at 2023-11-11

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

やりたいこと

まずはHSP3の布教を一つまみ...

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

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

これが空文字列のスクリプトを「実行」した結果です。
image.png
「実行結果」となるウィンドウが現れます。

これがハロワです
image.png

こうやっていじることができます。
image.png

HSP3はWindows APIの操作を十分に簡略化したことが特徴です。(そのため、MacOSは使えない)
中3の僕はC++でウィンドウを表示するだけで1か月かかりました。参考

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

燃やされた子供心で、中2で下記のような格闘ゲームを作れてしまいました。(Windows API以外のロジックはフルスクラッチで、2万行のコードとなっていて、かなりイカれた中学生だと自省しております)
sasa_20231110_182210.gif

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

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

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

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

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

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

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

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

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

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

今回の自作ライブラリ

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

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

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

作った

まだまだ関数が完備とは言えませんが、ベースは作れました。
レポジトリと、チュートリアルサイトです。
(このチュートリアルサイトもVue3でそれなりに頑張ったので、こちらも後日記事にします)

仕様(簡単に)

ハロワ

こんな感じ

async function main() {
    DrawText("ageuoiehbuoefa", 100, 100)
}

image.png

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

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

色を変えるのは、HSP3setcolorと同じノリで。

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

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

Sleep関数

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

async function Sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms))
}

こう実装することで、

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のコードでもお見せしましたが、ゲームシステムは基本的に無限ループの中で、決まったフローを繰り返すことで動作しています。(「フレーム」というゲーム用語は、このループの一回一回を指しているわけです)

しかし、このようなスクリプトを書くと、

while(true){
    //フレーム処理
}

描画すらされずに、ブラウザがクラッシュします。

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

これを、こう書くと

while(true){
    //フレーム処理
    await sleep(1)
}

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

入力系統

HTMLで入力を扱おうとすると、HTMLイベントを扱わざるを得なくなり、だるい!
ので、隠蔽しました。

キー入力で文字が移動するスクリプト
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)を

ちなみに

大学の課題の物理シミュレーションの実装課題で、教員のBlenderファイルを無視して本ライブラリで「ニュートンのゆりかご」を実装したのがこちらです

(動作ページはREADMEのリンクから)

講座をした

東大GDSCにて、このライブラリを用いた初心者講座を行いました。
参加者は11名で、玄人が4人、別講座でPython入門だけはした方が6名、プログラミング全くの初心者が1名という構成でした。

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

講座で書いた内容がそのままこちらにあります

5時間ほどで、ハロワから始め、こちらの弾幕ゲームを作ることができました(github pagesに飛ぶ)

tama_20231112_062323.gif

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

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

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

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

最後に

いいね頂けると泣いて喜びます><

この記事を読んでいる方は次の記事を読んでいるかもしれません:

114
67
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
114
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?