
Among Us Scratch の解析6:回転するクルー達
今回は、ゲームが始まるまで後ろでくるくると回り続けているクルー達の解析をする。
このクルー達は、プレイが始まるまでの賑やかしなので、無くてもいいものだが、無いと次のような大変寂しい intro 画面になる。

プレイするまでの画面としては寂しいし、何よりプレーヤーの気分が盛り上がらない。動きのある次の画面のほうが楽しそうだ。

Intro:FlyingPlayers スプライト
くるくると回るクルー達は Intro:FlyingPlayers という名前のスプライトで実装されている。

Intro:FlyingPlayers スプライト:コスチューム
Intro:FlyingPlayers スプライトは、7つのコスチュームがある。

コスチュームには1~7までの名前がついている。
スクラッチは、コスチュームを2種類の描き方で作ることができる。ひとつはビットマップで、もう一つはベクターだ。Among Us Scratch のコスチュームはベクターで描かれているようだ。次は黒いクルーのコスチュームの中の絵を選択した写真で、絵が「線」や「線で囲まれた面」で描かれていることがわかる。

ベクターとビットマップの違いを比較したのが次の写真だ。左が Among Us Scratch のベクターで描かれたもの、右はそれをビットマップに変換したものだ(スクラッチで変換は簡単にできる)。ビットマップのほうはピクセルが見える。

ベクターのコスチュームは拡大に強いし、大きな画面で遊んでもきれいに見えることから、スクラッチでアプリケーションを作る際は好んで使われる。新規のコスチュームも、ベクターを使うのがディフォルトの設定となっている。
Intro:FlyingPlayers スプライト:コード全体
次の写真は、Intro:FlyingPlayers スプライトのコード全体だ。くるくるまわしているだけにしては少し多めにも見える。

Intro:FlyingPlayers スプライト:開始プログラム
Intro:FlyingPlayers スプライトの開始時「旗が押されたとき」のスクリプトは次の写真のものだけだ。「クローンを2つつくる」した後、「クローンを作り続ける」。

開始時の処理その1:クローンを2つ作る
まず、最初の2コードでクローンを2つ作る。

最初から画面をにぎやかすために2つ余計に作っているようだ。ここを増やすと最初からたくさんクルーが出せる。次の画面は試しで8個クローンしてみたところだが、かなりごちゃごちゃした感じになる。

開始時の処理その2:クローンを作り続ける
クローンを2つ作った後、次の写真の「ずっと」ループで、一切時間おきにクローンを続けるコードが動作する。

modus 変数で、次の写真に挙げるintro 画面、joining 画面、pre-waiting 画面、online 画面のときだけクローンは作られる(pre-waiting画面は現時点では何画面か不明)。
■ intro:最初の画面

■ joining:findで選んだゲームへ入ろうとしている最中の画面

■ pre-waiting:なんの画面か不明
■ online:host、public、join by code からゲームを選ぶ画面

1回クローンすると「4秒待つ」ので、4秒おきに1つクローンが増える。ぼーっとしているとどんどん増えてしまうはずなので、どこかで削除しなければならないはずだ。
Intro:FlyingPlayers スプライト:作ったクローンを消す
次の写真の6つのスクリプトは、connected 等の画面モードでクローンを削除する。

「旗が押されたとき」のスクリプトによって、 intro、joining、pre-waiting、online 画面でしかクローンは行われないので、上の6つのスクリプトのイベント画面に来ると、くるくる回る「クローン」たちはすべて消える。削除される画面は次の6つだ。参考に画面モード modus 変数も表示している。
■ connected:誰かが作ったゲームに入った後の画面。
(写真とれない...)
■ freeplay:一人で練習プレイしている画面

■ find:プレイできるゲームを探している画面。

■ failed:エラー画面
この画面は画面のモードとしては作られていないので、modus 変数はエラーの出た場所によって違う。

■ before.createnewgame:新しい公開ゲームを作る直前の画面
modus 変数の値 cngとイベント名の違うタイプの画面。

これらのスクリプトでクローンが削除されても、本体スプライトは消えない。画面が intro 等に戻ればまたクローンが始まる。
また、次の2つのスクリプトは、削除する前にスプライトのスクリプトの動作も停止している。

how to play イベントは、intro 画面で HOW TO PLAY ボタンを押したときの次の写真の説明画面モードへ行くときに送られる。

この画面も背景にくるくると回るクルーはいない。余談だが、イベント名は how to play なのにmodus は tutorial なので、モード切替のルールから少し外れて気持ちが悪い。
もう一つのスクリプト、crashed イベントは、特殊なエラーを表示する画面モードになるときに送られるもので、この画面もクルーは表示されない。
ここでみた画面ごとにクローンを消すスクリプトは、本当にこれで全部の画面をカバーできているのか不安になる。これまでの解析でも「これとそれとあの画面だったら~をする」のようなコードが散見されるし、新しい画面モードを追加したら、あちこち追加修正が必要になりそうだ。
しかし、スクラッチがビジュアルプログラミング環境であることや、検索機能が貧弱であることを考えると、状況を改善する修正もまた難しそうだ。
ここで見たスクリプトの削除は画面が変わった時だけなので、intro画面などでずっと待っていたりするとクローンは減らない。増え続けて処理速度などに影響が出ないようにするには、別の削除方法は必要だ。
Intro:FlyingPlayers スプライト:アニメーション
Intro:FlyingPlayers スプライトは、アプリケーション開始時の intro 画面にきたとき(intro イベント時)に、アニメーションの処理をする次のスクリプトが動作する。スクリプトは、intro 画面になったときの処理と、その後続く処理の2つがある。

intro画面になった時の処理
次の写真は、最初の intro 画面になった時の処理部分を拡大したものだ。クルーをいつもバラバラな動きにするために乱数を使って3つのものを設定している。

「①大きさとコスチューム」と「③最初の位置」の設定は、スプライトの機能そのままだ。表示して見えるようにしたら、大きさやコスチュームは乱数で適当に設定する。登場する位置も画面上の中央周辺をランダムに選んで設定している(X=0,Y=0が画面の中央)。
「②動き」はスプライト変数を設定しているだけなので、これのみでクルーが動くことはない。動く処理はこの後の「ずっと続く処理」が、次の3つの変数の値に沿って処理する。
me:direction:スプライトの向き(角度)
dir_move:スプライトの動く方向(角度)
dir_show_ch:スプライトの回転する速さ
この中で、dir_move と dir_show_ch は使用しているが、 me:direction は用途不明の謎変数だ。いつものごとく予想でしかないが、以前は使っていたが今は未使用で放置されているものかもしれない。
(intro画面になった後)ずっと続く処理
設定に従って動かすのは、次の写真の上の処理のあとに続くコード部分だ。「ずっと」を使ってスプライトを動かしている。
「隠す」についてはいったんおいといて、おおまかには「①動かす処理」と「②表示する画面だけの処理」の2つあり、これを「ずっと」繰り返す。

①動かす処理
動きの処理の中では、クルー達がほかのスプライト(ボタン等)を邪魔しないように「最背面へ移動する」をして、常にほかのものより下に表示されるようになっている。

スプライトが移動するコードは、その下の二つだ(次の写真)。

最初に設定した dir_move の方向へスプライトを向けたあと、スプライトの向いている方向に動かす「1歩動かす」する。
スプライトの向きは上を0としてー180から180の角度で指定する。intro画面になった時の処理の部分の次のコードで乱数設定されている。

次の動画は、「~度に向ける」と「~歩動かす」をずっとで繰り返すコードでスプライトを動かしている。コードとスプライトの向き動きの関係がわかりやすいと思う。

「~度に向ける」の向きは「0度」=上向きなのだが、この矢印のコスチュームは右向きにしないと、向けた方向に絵が正しく向いてくれない。

スプライトの向いている方向にコスチュームを向けたければ、コスチュームは必ず「右向き」にするのがスクラッチのルールだ。スクラッチを作った人の気持ちも理解はできるのだが、「右が上」というのは少しモヤモヤする。
また、画面はX軸正が右、Y軸正が上なのに、回転は時計回りなのも独特だ。上から時計回り、という設計の決断は勇気ある行動だったのではないかと感心したりする。
上の矢印のような「向いた方向に動きそう」なモノはこれでいい具合に見えるが、クルーは「くるくる回りながら」向きとは関係のない一定の方向へと移動する。次の動画はクルー1つだけを動かした例だが、回転と移動方向は無関係だ。

このために、移動の次にもう一つのコードがある。

移動のためにスプライトを移動方向に向けてしまったので、回転している向きに「再度向けなおし」している。その下の「dir_show を dir_show_ch づつ変える」コードで、dir_show は dir_show_ch だけ増える(dir_show_ch が負なら減る)。つまり、クルーは最初に設定した dir_show_ch の速さで回転する。
ところで、この dir_show 変数は、どこにも値の初期化コードがない。先ほど説明した intro画面になった時の処理では何もしていないし、Intro:FlyingPlayers スプライトのコード中にもない。最初の値が何であっても、多少最初の向きが異なるだけなので大した問題にはならないが、それでも初期化漏れはあまり良いコードではない。
②表示する画面だけの処理
Intro:FlyingPlayers スプライトのアニメーションコードにはもう一つ次の処理が含まれている。

「もし」は、おそらくクルーが出てくる画面=intro、joining、pre-waiting、online のどれかかどうかの判定をしている。その中では、次の2つをしている。
stars イベントを送る。
このイベントは何か全くわからないが、ためしでこのコードだけ消してみたのが次の動画だ。背景に流れていた星が無くなってしまう。

どこかでこの stars イベントをうけて星をアニメーション表示させているのだろうが、Intro:FlyingPlayers スプライトにはない。今後の解析課題だ。
スクラッチのループとアニメーションフレーム
今回の解析を含め、これまでは、「ずっと」ループは無限に処理を続けるコードとしてきたが、すこし漠然としすぎていた。「ずっと」も含めてスクラッチのループは次の2つのどちらで動いているのだろうか。
予想1:他のスクリプトの処理は無視して、とにかく最速で「ずっと」処理し続けている。
予想2:ループ中のどこかで必ず一度休憩が入る。その間にほかのスクリプトや画面の更新(アニメーションの表示)をする。
動作を実際に確認しただけで仕様や説明は見つからないが、ややこしいことに、スクラッチは「両方」の動作を勝手に切り替えて動作するようだ。
次の動画は予想1の「最速」の例だ。カウンタという変数を1づつ増やすだけだが、1秒でほぼ100万ふえるので、1ループ=1ミリ秒で動いていることになる。

ところが、このスクリプトにもう一つ、コスチュームを設定する次の写真の左のようなスクリプトを追加すると、カウンタの上がり方は極端に遅くなる。おおむね、アニメーションの1コマ=1フレームに1回処理している程度だ。

違いは、ループ中にコスチュームの変更をしているスクリプトがあることだ。
詳細は不明だが、スクラッチはプロジェクト中のどこかで動きや見た目のコード(青と紫のコードブロック)が動くと、プロジェクト全ての処理をアニメーションフレームにあわせて動かすようになる。

よって、上の動画の左側「コスチュームを~にする」のスクリプトを停止すれば、カウンタを変えているスクリプトはアニメーションフレームに縛られずに動作する。
次の動画はそれを試したものだ。左のスクリプトの黄色い枠がなくなってコスチューム処理を停止した瞬間にカウンタが高速に増えはじめ、再度動かすとカウンタはゆっくりになる。ややこしいが面白い。

スクラッチでは、「動き」や「見た目」の処理がまったくない最速動作状態は「シングルフレーム」モードと呼ばれる。詳細を解説した資料等は見つからなかったが、手練れのスクラッチャー達にはこの切り替わりのふるまいは常識らしい。
また、アニメーションフレームに合わせて動作する場合、ループの一番下でフレームが進むようだ。1フレームごとに「ずっと」ループ内部は一番上から一番下まで順に実行される。ループの途中でフレームが変わることは無い。これも手練れには常識のようで、この後、 Intro:FlyingPlayers スプライトでも利用されている。
どのタイプのコードが、シングルフレームモードのきっかけになるのかもわかっていない。「シングルフレーム」モードについて何か良い解説等あればお知らせいただけると嬉しいです。
また、細かいが、プロシージャや関数に相当するスクラッチの「ブロック定義」では、この動作を設定する機能として「画面を再描画せずに実行する」という選択がある。Among Us Scratch でも使用していそうな設定だ。
(画面の再描画=アニメーションフレームの更新)

Intro:FlyingPlayers スプライト:表示、非表示の切り替え
Among Us Scratchの、動きや見た目のコードを含む「ずっと」ループなら、アニメーションフレームに合わせて動作しているはずだ。すると、次の無限ループにあった「隠す」と「表示する」も意図がわかるようになる。

ずっと「隠す」しているのだからいつもは隠れているが、「もし」によって intro 画面等の場合だけ「表示する」して「隠す」を上書きしてしまう。ループの途中でアニメーションフレームが変わることは無く、「表示する」は必ず最後に実行されているから、結局次の動作になる。
■ intro 等の画面ならスプライトは「表示する」
■ それ以外の画面ならスプライトは「隠す」
Intro:FlyingPlayers スプライト:クローンの処理
Intro:FlyingPlayers スプライトはクローンを作るが、クローンした直後は intro イベントが送られるわけではないので、上のアニメーション処理のスクリプト自体は起動しない。
クローンにアニメーションをさせるために、クローン専用の次の写真のスクリプトがある。

大きく4つの部分にわかれているが、アニメーションの処理については、先ほどの intro イベントで始まるスクリプトに似ている。
①大きさとコスチューム

4つのコードのうち、大きさとコスチューム設定はスプライト本体と同じく乱数で設定している。コスチュームは本体が1~5番に対して、クローンは1~7番を設定している。何もせずに待っていると6,7番の「正面向きコスチューム」が出てくる、という細かい演出に見える。

「幽霊の効果を0にする」は不要そうに見える。このあとアニメーションする「ずっと」ループで効果を少しづつ減らして「だんだん現れる」ようになっているのだが、最初が0=効果ナシなら意味がない。最初を100にしてみたがあまり変化はみられないが、最初は画面外に配置されているからかもしれない。
②最初の位置と動く方向
①大きさとコスチュームの設定後、位置と動く方向をランダムに決める。本体スプライトと違い、クローンは最初は画面外にいて「外から画面内にやってくる」ようにアニメーションするので、位置と方向決めは少し複雑だ。
次の写真は②最初の位置と動く方向のコードの拡大図だが、3つの場合に分けて処理されている。

3つの場合はランダムに選択されている。現れる場所もランダムだ。移動方向は、スプライト本体の場合と同じく dir_move スプライト変数の方向へ移動する。
■ A:画面外の左の方に現れる。3分の1の確率。
X=ー340、Yはー250~250。画面の左端のさらに外側で、画面の上下よりさらに外側の座標におかれる。参考に、次の写真は画面の隅の座標をスプライトをおいて表示してみたものだ。

dir_move は画面左端から現れるようにするため、20度~160度=右上から右下方向になっている。次の動画は20度~160度まで矢印コスチュームのスプライトで確認してみたものだ。

■ B:画面外の右の方に現れる。3分の1の確率。
Aのケースとほぼ同じだが、X=340と右端の外、dir_move もAとは反対にー20度~ー160度になっている。
■ C:画面外の上の方に現れる。3分の1の確率。
A,Bの場合と違い、Y=250で一番上よりさらに外側、X=ー340~340で左右よりさらに外側の範囲となっている。
どの場合でも、クローンは画面の少し外の方に配置される。スクラッチの画面座標系は、次の写真のように、Xはー240から240まで、Yはー180から180までだが、A,Bの左右の画面外の場合は、Xがー340か340、Cの上の画面外の場合はY=250で、画面の端よりかなり外側ということがわかる。
ところで、なぜ「画面がの下の方に現れる」場合が無いのだろうか。下から出てくるのはイマイチ絵面が気に入らなかったとか...?

③回転速度
アニメーション前の最後の処理は、スプライト本体と同じく回転の速さとして使うスプライト変数 dir_show_ch に、やはりランダムな値を設定する。ランダムなので回転速度がクローンによってバラバラで、より面白い動きになる。

④「ずっと」ループのアニメーション処理
設定の処理が終わった後は、「ずっと」ループでクローンをアニメーションする処理になる。コードは次の写真のように3つの部分からできている。

最初の表示の設定部分の処理は、「幽霊効果を減らして、次第に現れるようにする」、「表示する」、「スプライトがほかの表示を邪魔しないように、一番下に表示されるようにする」、の3つだ。
しかし、最初の設定で幽霊効果は0=効果ナシなので、ここの幽霊効果の減少は意味はない。最初に表示設定しているので「表示する」も意味のないコードになっている。
よって、ここで意味があるのは「最背面へ移動する」コードだけだ。わかりづらくなっているだけの少し残念なコードではあるが、過去には役立っていた遺産かもしれない。
次に移動と回転の処理をする。このコードはスプライト本体とほぼ同じことをしている。dir_show スプライト変数の未初期化まで同じなのは問題だが...。
最後に intro イベントのスクリプトにはなかった次のコードがある。

絶対値関数を使って、X座標はー331~331の外、かつ、Y座標はー251~251の外、という条件で画面外かどうか調べ、そのときクローンを削除して不要にスプライトが増えないようにしている。クローンはまっすぐに動くので画面外へ出たら画面内へ戻ってくることは無く、削除しないと見えないクローンだらけになってしまう。
Y座標の251という値は、最初の「②最初の位置と動く方向」で使った上下のY座標がー250か250だから、それより外側なら「初期値より外に出た」とわかるし適切な値と思う。
一方、X座標の331という値は、「②最初の位置と動く方向」で画面左右から出るときのXがー350か350なことを考えると少し微妙ではる。Y座標の検査は安全だから気にするほどではないかもしれない。
Intro:FlyingPlayers スプライト:イベントのコード動作中に再度イベントが送られてきたらどうなるか?
クローンは、次のことから intro イベントを受け取ることは無い。
■ intro 画面でクローンされても、intro イベントはもう送られた後。
■ intro 画面に戻る画面では必ず削除される。FREE PLAY や HOW TO PLAY 画面等。
しかし、クローン元である Intro:FlyingPlayers スプライトはずっと存在している。つまり、intro 画面に戻ってくるたびに intro イベントスクリプトが呼ばれてしまいそうだ。
intro イベントスクリプトが何度も実行されると、アニメーションの「ずっと」がたくさん実行されて、回転や移動がどんどん早くなってしまう。スクラッチは、このような場合を回避するために、イベントのスクリプトは動作が終わるまでは再度呼ばれないようになっている。
次の動画は、スペースキーを押したら5秒間カウンタを1秒おきにアップする例だ。またスペースキーを押したら1秒ボールを表示するようにしている。スペースキーを何度押しても、5秒間動く方のスクリプトが何度も呼ばれて、カウンタは増え方が速くなるようなことはない。

Intro:FlyingPlayers スプライト:いつもの
もうおなじみになってしまったが、set_volume イベントに応じてボリューム設定をするスクリプトは、Intro:FlyingPlayers スプライトにも存在した。

なんであちこちにあるのだろうか...。
別のプロジェクトにスプライトやコードをコピーする
今回の解析では、Intro:FlyingPlayers スプライトだけを観察しやすくするために、スプライトの書き出し+アップロード機能を使い、Among Us Scratch の Intro:FlyingPlayers スプライト だけ別のプロジェクトに持ってきた。

書き出し機能は、コスチュームだけではなく、スクリプトやその変数、イベントなどのうち使用しているものだけを抽出してくれるので、一部だけ拾って解析するのに便利だ。
スクリプト1つだけ、とか、コード数ブロックだけコピーするというような細かい機能もほしい。
Intro:FlyingPlayers スプライト:まとめ
Intro:FlyingPlayers スプライトは、画面モードにまたがって存在するので、画面モードに応じて削除したりクローンしたり少し動作は細かかった。stars という背景の星を動かすイベントについてはこれからの解析課題となった。
また、スクラッチ画面の座標系やイベントが多重に実行されないことなどを確認した。少しスクラッチに詳しくなれたかもしれない。
細かい不要コードがあったり、set_volume イベントを処理する無駄なスクリプトがあるのは少し気になるが、長く作り続けていればこのぐらいは仕方ないことかもしれない。