自作日本語プログラミング言語LazeとArduinoで手をかざしてパドルを操作できるPONGゲームを作った

#はじめに
今回は、[前回](https://qiita.com/coder6583/items/9adc2a7d5b55207c4b74)紹介したLazeでArduinoを制御できる機能を使ってゲームを作ってみました！ゲームの内容自体は何のひねりもない[PONGゲーム](https://www.ponggame.org/)ですが(逆に普通のに比べて性能が悪いかもしれません)、コントロールが自分の手なので、いつもと違う感覚で遊べるのでとても面白かったです。
前回同様、自作プログラミング言語Lazeでプログラムを書いています。ぜひこの記事を読んで面白そうと思ったら使ってみてください。[こちらのウェブサイト] (https://laze.ddns.net)ですぐにプログラミングを始めることができます。タイトルでは日本語プログラミング言語と書いてありますが、実は自由に文法が設定できて、どの言語にも対応することができます。
#ゲームの様子
![IMG_1957 (1)_3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2427444/9d804cde-789b-2330-7a0c-04f943f0ef62.gif)
どんどん球が速くなっていってとても楽しいです。
Qiitaの画像最大サイズのせいでビデオが小さくなってしまいました、ごめんなさい
#使ったもの
| 材料 | 値段 |
|:-----|:-----|
| [Arduino Uno](https://akizukidenshi.com/catalog/g/gM-07385/) | 3000円ぐらい |
|[RPR-220](https://akizukidenshi.com/catalog/g/gI-11401/)(フォトリフレクタ) x 2| 100円/個 |
| [8.2kΩ抵抗](https://akizukidenshi.com/catalog/g/gR-25822/) x 2| 100個で100円 |
| [180Ω抵抗](https://akizukidenshi.com/catalog/g/gR-25181/) | 100個で100円 |
| [C基板](https://akizukidenshi.com/catalog/g/gP-05294/) | 60円 |
| [Laze](https://laze.ddns.net)!! | 0円 |
##留意点
・今回はフォトリフレクタを使っていますが、別に距離が測れるセンサーであれば何でもいいと思います。今後個人的にやってみたいのは超音波センサーでこのゲームを作ってみるということです。手が動ける範囲が広くなっておもしろそう
・Arduino Unoを使っていますが、シリアル通信がUSB経由でできればどのマイコンでもいいと思います。個人的には、大好きなPICでやってみたいですが、ちょっと難しいかもしれませんね。
#回路
![DECBB5F0-32FF-4BE7-B4D1-D3749B7625E9.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2427444/9095b844-38c7-debb-c69a-825adc591421.jpeg)


#プログラム
```js
クラス:球
{
 	実数:PI=3.14159265358979323846264338;
	ベクトル2D:座標;
	ベクトル2D:スピード;
	実数:角度;
	実数:速さ;
	実数:一辺;
	実数:初期速さ;
	整数:青勝利数;
	整数:黄色勝利数;
	整数:打った方;
	関数:球(実数:初期速度, 実数:長さ) => (){
		打った方 = 0;
		角度 = 0.0;
		速さ = 初期速度;
		初期速さ = 初期速度;
		青勝利数 = 0;
		黄色勝利数 = 0;
		スピード.x = 初期速度 * cos(toRad(角度));
		スピード.y = 初期速度 * sin(toRad(角度));
		座標.ベクトル2D(0.0, 0.0);
		一辺 = 長さ;
	}
	関数:アップデート(ベクトル2D:板座標1, ベクトル2D:板座標2) => (){
		座標.x += スピード.x;
		座標.y += スピード.y;
		
		実数:maxY = 座標.y +　一辺/2;
		実数:maxX = 座標.x +　一辺/2;
		実数:minY = 座標.y - 一辺/2;
		実数:minX = 座標.x - 一辺/2;

		もし(minX > 1.0 || maxX < -1.0)ならば{
			もし(minX > 1.0)ならば{
				打った方 = 1;
				青勝利数 += 1;
				角度 = 180.0;
			}
			もし(maxX < -1.0)ならば{
				打った方 = 0;
				黄色勝利数 += 1;
				角度 = 0.0;
			}
			表示(青勝利数 * 100000 + 黄色勝利数);
			座標.x = 0.0;
			座標.y = 0.0;
			速さ = 初期速さ;
			スピード.x = 速さ * cos(toRad(角度));
			スピード.y = 速さ * sin(toRad(角度));
		}
		もし(maxY > 1.0 - 0.075 || minY < 0.075 - 1.0)ならば{
			角度 = -角度;
			スピードアップデート();
		}
		もし(maxX > 0.9 - 0.025 && 打った方 == 0)ならば{
			もし(minY < 板座標2.y + 0.40 && 板座標2.y - 0.40 < maxY)ならば{
				打った方 = 1;
				速さ *= 1.05;
				角度 = 180.0 - 角度 + jsRand() * 45 - 22.5;
				スピードアップデート();
			}
		}
		もし((-0.875) > minX && 打った方 == 1)ならば{
			もし(minY < 板座標1.y + 0.40 && 板座標1.y - 0.40 < maxY)ならば{
				打った方 = 0;
				速さ *= 1.05;
				角度 = 180.0 - 角度 + jsRand() * 45 - 22.5;
				スピードアップデート();
			}
		}
	}
	関数:スピードアップデート() => (){
		スピード.x = 速さ * cos(toRad(角度));
		スピード.y = 速さ * sin(toRad(角度));
	}
}

関数:実行() => () {
    ベクトル3D:背景色(0.0, 0.0, 0.5);
    シーン2D:ワールド(1.0, 背景色);
    Arduino設定(0x2A03, () => (){
			Arduinoアナログ入力ピン設定(0);
			Arduinoアナログ入力ピン設定(1);
    });
    ベクトル2D:座標1(-0.90, 0.0);
    ベクトル2D:座標2(0.90, 0.0);
    ベクトル3D:色1(0.2, 0.5, 0.7);
    ベクトル3D:色2(0.7, 0.5, 0.2);

    整数:id1 = ワールド.四角形追加(0.05, 0.30, 座標1, 色1, 1.0);
    整数:id2 = ワールド.四角形追加(0.05, 0.30, 座標2, 色2, 1.0);

	ベクトル2D:壁座標1(0.0, 1.00);
	ベクトル2D:壁座標2(0.0, -1.00);
	ベクトル3D:白(1.0, 1.0, 1.0);

	整数:壁id = ワールド.四角形追加(5.0, 0.075, 壁座標1, 白, 1.0);
	壁id = ワールド.四角形追加(5.0, 0.075, 壁座標2, 白, 1.0);

	ベクトル2D:真ん中(0.0, 0.0);
	壁id = ワールド.四角形追加(0.005, 2.0, 真ん中, 白, 1.0);
    
	球:ball(0.02, 0.075);

	整数:球id = ワールド.四角形追加(ball.一辺, ball.一辺, ball.座標, 白, 1.0);

    無限ループ{
        Arduinoデータ受信();
		実数:センサー値1 = Arduinoアナログ入力(1);
		実数:センサー値2 = Arduinoアナログ入力(0);

        座標1.y = 1.0 - センサー値1 * (0.01);
		もし(座標1.y > 1.0)ならば{
			座標1.y = 1.0;
		}
		もし(座標1.y < -1.0)ならば{
			座標1.y = -1.0;
		}
		座標2.y = (1.0 - センサー値2 * 0.01);
		もし(座標2.y > 1.0)ならば{
			座標2.y = 1.0;
		}
		もし(座標2.y < -1.0)ならば{
			座標2.y = -1.0;
		}

		ball.アップデート(座標1, 座標2);
        ワールド.スプライト配列.取得(id1) -> 座標設定(座標1);
        ワールド.スプライト配列.取得(id2) -> 座標設定(座標2);
        ワールド.スプライト配列.取得(球id) -> 座標設定(ball.座標);
        ワールド.描画();
    }
}
```
##解説
※※※このプログラムはきれいなプログラムではないので、マネはしない方がいいです。Lazeはこんなこともできるのかと思って読んでもらえればうれしいです。
### 1~79行目
ここでは球の速度、角度を保管したり、当たり判定、得点管理を行うクラスを定義しています。きれいなプログラムであればちゃんとゲームクラスを書いて、そこで当たり判定などをやると思うのですが、簡単なゲームなのでゆるしてください
###3~12行目
クラスのメンバーの定義を行っています。プログラムが日本語なのでそれぞれが何をするかはわかりやすいと思います。最後の`打った方`がわかりにくいかもしれないので説明すると、これは青と黄色のパドルのどっちが最後に打ったかを保管する変数です。最後の球を打ったプレイヤーと今打ったプレイヤーを比べることに使われていて、ボールが二連続で同じパドルに当たって変な方向に飛んでいくことを阻止しています。
###13~24行目
クラスのコンストラクタ、つまり初期設定を行っています。
ここも日本語なので特段わかりにくいものはないと思いますが、20行目、21行目の唯一英語である`toRad`はラジアンに変換を意味しています(Lazeの三角関数の引数の単位はラジアン)。22行目は球の座標を0.0, 0.0に設定しているのですが、これはスクリーンの真ん中となります。
###26~27行目
座標にスピードを足しています。本来であれば時間もかけるべきだと思うのですが、Lazeでは一ループの間隔が16.666..msと決まっているので、かける必要はないと判断しました(時間をスピードにかけるのは時間の間隔が決まっていないためスピードが一定でなくなってしまうから)。
###29~32行目
ここでは球(実際は四角形)の一番xとy座標が大きい座標、小さい座標を計算しています。これを用いて[AABB衝突判定](https://taiyakisun.hatenablog.com/entry/20120205/1328410006)の当たり判定を行います。
###34~51行目
ここでは球が画面外に出ていないかチェックしています。もし出ているのであれば、出た反対側のプレイヤーの点数が増え(37, 42行目)、球が初期位置(46, 47行目)、初期角度(38, 43行目)、初期速度(49, 50行目)に戻されます。
###52~55行目
球が壁に当たった時の処理を行っています。ボールが進んでいる角度の正負を逆にすることで入射角反射角の再現をしています。55行目では上の行で変えた角度を球の速度に反映させています。
###56~71行目
球とパドルの当たり判定と当たった時の処理を行っています。56, 64行目では球のx座標がパドルと一致しているか確認し、前回打ったプレイヤーが自分でないことを確認しています。57, 65行目で球のy座標がパドルと重なっていないか確認しています。もし重なっていた場合、球がパドルに当たったことになります。58, 66行目で最後にボールをプレイヤーが自分と変数を設定します。59, 67行目では球の速さを1.05倍しています(どんどん球は早くならないと面白くないですからね)。60, 68行目で球の角度を180から引いて反射させていますが、ランダムに最大45度までボールがさらに反射するようにしています。61, 69行目で上の行で変えた速さや角度を速度に反映させています。
###73~76行目
現在の球の設定された速さや角度を球の速度に反映させる関数です。
###80~81行目
80行目で0.0~1.0の範囲でRGBを定め、ワールドの背景色としていて、高さを1.0にしています。
###82~85行目
ここでArduinoを接続しています。0x2a03はArduino UNOのベンダーIDというもので、Arduino IDEやWindowsのデバイスマネージャーで確認することができます。Arduinoが使っているマイコンの種類によって違って、例えばArduino Leonardoだと0x2341になります。
Arduino設定の二個目の引数のアロー関数内では、接続し終わった後に実行するプログラムを書きます。このプログラムでは、センサーの値を受け取る0ピンと1ピンをアナログ入力と設定しています(アナログが10進法の値、デジタルが1か0か)。
###86~102行目
ここではパドルとフィールドを作っています。全て解説すると長ったらしくなってしまうので、パドルの部分だけ少し。86~87行目で両サイドのパドルの座標を、88~89行目で青色と黄色を定義しています。91~92行目で実際にワールドに定義した座標と色などで設定された四角形を追加しています。
###104行目
先ほど定義した球クラスの`ball`というオブジェクトを作っています。
0.02とは速くなる前の元のスピード、0.075は球の一辺の長さです。
###106行目
球を白い正方形としてワールドの真ん中に追加しています。
###109行目
8行目でArduinoからピンのデータを受信しています。ここが欠けるとArduinoからデータを受け取ることができないので、忘れないように。
###110~111行目
先ほどアナログ入力と設定した0ピン1ピンからセンサーの値を取り出し、変数に代入します。
###113, 120行目
受け取ったセンサーの値をパドルのy座標に変換しています。ぶっちゃけここら辺はあてずっぽうで係数をかけていっていい感じになったものを使っているのであまり説明することはありません。
###114~119行目, 121~126行目
パドルが画面外に出ないようにしています。
###128行目
先ほど説明した球の当たり判定などを行っている関数を呼び出し、球を動かしています。
###130~132行目
91, 92, 106行目で取得したパドルと球のIDを使ってそれぞれの新しい座標を設定しています。
###133行目
ワールドを描画!
#まとめ
今回は自作プログラミング言語LazeとArduinoで手でパドルを操作できるPONGゲームを作ってみました。いつもとは全然違う操作の難しさで友達とも楽しく遊ぶことができました。あまり難しくないので、読者さんも家にArduinoがあったりするならやってみたらどうでしょうか?
プログラミングに慣れている方であればこんなことはもう知ってるよ!っていうところがあったかと思いますが、最後まで読んでくれてありがとうございます。
まだこのゲームはLazeの可能性の端っこをかじっただけな気がします。これからもLazeでゲームを開発したり、おもしろいプロジェクトに取り組んでいきたいと思っているので、よければ[Laze公式Twitter](https://twitter.com/LazeProjectTeam)フォローお願いします!!