【プチコン4講座】プレイヤーをコントロールしてみよう
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は210日目になります。
第2回「プレイヤーを色々な位置に動かしてみよう」にてループを使ってスプライトを継続的に動かす方法を学びました。
自動で動いてしまったらゲームにはならないので、今回は条件式を駆使してコントロールできるようにしてみましょう。
まずは4方向の移動にチャレンジしてみます。4方向が出来たら後は斜めに移動させるだけで8方向移動が完成しますね。
それではプチコン4でSTG作りその第3回目始めましょう。
押されているボタンを調べてみよう
リキ、自動で動いてたらゲームって言えなくないか?
そうだね。自らの意思でコントロールしないと面白くないよね。今回はボタンの処理を学ぶよ。
Switchってコントローラーをまとめたり1つずつにしたり出来るよね?どれでも大丈夫なの?
プチコン側でどのタイプのコントローラーを使うのかを決めることが出来るんだ。今回は一人用シューティングの作り方だから2つもちタイプの操作でやっていくよ。
Switchに付けても取り外して両手でもってもいいのか?
そうだね。両手で持っていれば基本的には同じ操作が出来るし何よりそっちの方が個人的に教えやすいんだ。それじゃあ早速行くよ。次のコードをみてくれるかな。
ACLS
' ボタンの状態を格納する変数
var B
' ループ開始
loop
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
B = Button(0)
' 押されているボタンの情報を表示
PRINT B
VSYNC
endloop
もうコメントで答え書いちゃってるけど、一度動かしてみて。
なんだ?ボタンを押すたびに変な数字がでてくるぞ?
押されている状態がビット毎にセットされて返ってきてるんだけど……ちょっと難しいよね。
うん……全然わかんない
実はこのままだと少し使いづらいから加工してあげる必要があるんだ。これは公式リファレンスに書かれているものだよ
'ボタンに対応するビットをONにした値を作るには、1をボタンIDだけ左シフト(<<)すれば良い
'たとえば#B_RLEFTが押されているか調べたい場合
BTNBIT_RLEFT = 1 << #B_RLEFT
引用元
https://sup4.smilebasic.com/doku.php?id=reference:%E5%90%84%E7%A8%AE%E5%85%A5%E5%8A%9B
うーん……これは2進数の知識がないとちょっとなんでこうなるのかが理解できないかもしれないね。
今はこうやったらどのボタンが押されているのかが分かるようになるって覚えておいて。
うん、そうするわ。そうすればやりたいことは出来るんだから今悩む必要はないってことね。
そう、何度も言うけどプログラミングにおいてその考えってとってもとっても大切なんだ。
理解出来たところで結果は変わらないもんね。もちろん知ってた方が色々幅が広がっていいんだけど、
今は2進数を覚えたいんじゃなくてキャラクターを動かしたいだけだもの
定数について学ぼう
この「#B_RLEFT」ってなんなんだ?
それは定数って言ってあらかじめ設定された不変の値なんだ。変数は数が変わるけど、定数は最初に設定した数から変化出来ないって言う仕組みさ
変化出来ないって……それ変数の方が便利じゃないの?
例えばボタンを順番通りに押さないとゲームオーバーになるトラップがあったとして村人からその情報を聞けるとした場合、村人の言葉がなんらかのバグで変数が入れ替わって情報が変化しちゃったらプレイヤーは正解がわからないでしょ?
そ、それもそうね……
これは絶対この数値。それ以外はあり得ないっていうときに定数として定義して名前を付けておけばこれには何が入っているのかも検討が付きやすいしプログラミングをする上では結構重要な仕組みなんだ。そしてこの「#B_RLEFT」には「2」という数字が入っているよ。
ダイレクトモードで「PRINT #B_RLEFT」っと……あ、ほんとだ「2」ってでるわね。
ちなみに「1 << #B_RLEFT」をPRINTすると4って出るよ。
な、なんで4がでるんだ!?
2進数&ビットについて少しだけ学ぼう
ちょっとだけ2進数の話をすると、2進数って0と1だけでしか洗わせられないんだ。つまり0と1はそのまま0と1で表現できるよね?普段みんなが使っている数は10進数って言って10まできたら次は11になるでしょ?2進数も同じで0と1を使ったら次は繰り上がって「10」になるよ。これは「ジュウ」ではなく「イチゼロ」と読むのが正解。
じゃあ11が3だから……4は100よね?
うん、正解。話を戻すと「1 << #B_RLEFT」の「<<」はビットシフトといって左の値元に右の値分ビットをずらすんだ。ビットでずらすってことだから2進数で考えてね。ちなみに「>>」になったら右の方向にずらすって意味になるよ。
二進数は基本的に4つセットで扱われるんだ。その4つセットで0~15の数値を表すことができるよ。そして1桁1桁のことをビットというんだ。つまり4つで4ビットってことだね。
2進数の「1111」は10進数の「15」と同じだよ。分かりやすいように2進数=10進数早見表を作ったからちょっとみてみて。
なるほど、1111になったら次はもう増やせないからまた4つ桁を増やして次にいくのね。
そう、このように16になると次の4桁の方に繰り上がるんだ。話を戻すと、「1 << #B_RLEFT」の1は4桁で表すと「0001」だよね?#B_RLEFTは2という数値になっているからこれを日本語で表すと……
ちょっとまってくれ。2は「0010」にしなくていいのか?
うん。結局のところ0010は2だからね。2つズラすって意味では2だろうが0010だろうが同じなんだ。コンピューターはもちろん0010で見てるけど人間には2つズラすって言った方が伝わりやすいでしょ?
言われてみれば確かに2には変わりないな……話をさえぎって悪かったな。次に進めてくれ。
それじゃあ1を2ビットずらしたらどうなるかコードでみてみよう。
' 1は0001、一番右のビットだけ1になっていると良い表せる
' 0001 を 2ビットそのままずらすと以下になる
' 0100
' 0100 は 2進数で4となる
' 補足すると 0001を4ビットずらした場合は以下のようになる
' 0001 0000
' つまり1を4ビットずらすと16になる
' 更に補足すると 4bitしか扱えない機械だった場合左
' 0001 0000 左側の[0001]は扱えないので結果0となる
' 溢れてしまうことを「オーバーフロー」という
つまり「1 << #B_RLEFT」は4になるということだね。
すっごい!説明されるまで全く意味不明だったのにこの説明でモヤモヤが晴れた感じがするわ!
リキくんは大丈夫?
お、おう……な、なんとか……わかるぜ……
よくわからない場合はいつも通り、「こうしたらこうなる」理論で覚えていこうね。
それで、これでどうやってどのボタンが押されているってわかるの?
#B_RLEFTはつまり右のコントローラーの左側のボタン、つまり「Yボタン」だね。なぜ4がYボタンなのかは以下のコードを試してみるとわかるよ。
ACLS
' ボタンの状態を格納する変数
var B
' ループ開始
loop
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
B = Button(0)
' 押されているボタンの情報を表示
PRINT BIN$(B)
VSYNC
endloop
この状態でYボタンを押してみてよ。
さっきまでは凄いでかい数字だったけど今度は1と0がいっぱいでてきたぞ!
……あ!
マユミちゃんはわかったみたいだね。
へ?どういうことだよ!
リキ、この0と1の羅列の一番右の方をみてごらんなさいよ。
うーん?……あ”!
そう、左に余計な0と1があるけど一番最下位のところが「0100」になっているね。ちなみに「X=1」「B=10」「Y=100」「A=1000」ってなるんだ。
4桁を使ってABXYのどれがONなのかOFFなのかがこれで判るよね。でもこのままだと左にある余計な0と1が邪魔になってわかり辛いままなんだ。
できればその桁の位が1なのかを判別したいんだけど、それには比較演算子「==や>や<」のところに「AND」を使うんだよ。コードで書くと以下のようになるよ。
ACLS
' ボタンの状態を格納する変数
var B
' ループ開始
loop
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
B = Button(0)
' 押されているボタンの情報を表示
if (B AND 1 << #B_RLEFT) != 0 then
PRINT "Yボタンが押されたよ"
else
PRINT "Yボタンを押してね"
endif
VSYNC
endloop
条件式で〇〇且つ××の場合っていうときは「&&」を使うんだけど、2進数を比較したいときは「AND」って書くんだ。これはプチコン4の決まりだから覚えてね。
今回はもちろん2進数で比較したいから「AND」にしているよ。これをAND演算っていうんだけどイメージは算数で習った「ひっ算」の形にするとわかりやすいね。画像で笑わすと以下のようになるよ。
つまり「B AND 1 << #B_RLEFT」をするとYボタンが押されていたら「0100」が返ってくることになるよね。
次に比較するための演算子、「!=」は論理否定演算子といって==の逆で左と右が一致しない場合って意味になるんだ。日本語で言うと「左の値が0でなければ」だね。
トモカズ……楽しそうなとこ悪いんだけどさ……頭にはいってこねぇわ……
わ、私はなんとかついていけているわよ!
うーん、確かにちょっと楽しくないお勉強だったかもしれないね。かなり噛み砕いて説明したけどそれでも難しいと感じたらその時点で頭に入ってこないから無理に覚えなくていいんだよ。
とりあえずどのボタンを押しているか調べたい場合はこの形で調べればいいってことなんだよな?
他のボタンを調べたいときは「#B_RLEFT」を変更すればいいってことでいいのかしら。
うん、それで大丈夫だよ。定数に名前がついているのはわからない人のためでもあるんだ。「#B_RLEFT」っていうのは「B=ボタン」「R=右コントローラー」「LEFT=左ボタン」つまり右コントローラーのYボタンってことになるからAを押したい場合はどうすればいいでしょう?
わかったわ!「#B_RRIGHT」でAでしょ!?
正解!今回はスプライトを移動させたいから十字ボタンがある左側のコントローラーのボタンの状態を調べるから「#B_LUP」「#B_LDOWN」「#B_LLEFT」「#B_LRIGHT」を使っていくよ。
2進数の話をすると長ったらしく数学のお勉強みたいな状態になってしまうので
省略したかったのですが、RPG講座のときよりもわかりやすくしたかったので噛み砕いて説明してみました。
私自身2進数の取り扱いを完全に理解したというわけではないので、
詳しく2進数について知りたい人はググって詳しく解説しているサイトをみてもらったほうがいいでしょう。
今回プチコン4でボタンの押されている状態がちょっと複雑になってしまっているのは
Switchのコントローラーが分離出来てしまうせいかなとも思っています。
BIGの時はもうちょっとシンプルに書けた気がするんですが内部的には結局は2進数なので
2進数について理解しておけばプログラミング言語が変わろうが変わらない部分です。
プログラミングをする上で2進数は理解しておいて損はないので、
プログラミングが少し上達したら2進数をしっかり学んでみてみるとより理解度が高まります。
押されてるボタンに対して条件分岐をしてみよう
ボタン入力の説明がすごく長くなっちゃったね。でもゲームにおいて入力処理ってとても大切なことだから許して。
でもお陰でどうしたらどのボタンが押されているのかがわかったわ。これでキャラクターを動かせるようになったのね。
トモカズ!俺なりに書いてみたぜ!これで上下左右の認識が出来るだろ!?
ACLS
' ボタンの状態を格納する変数
var B
' ループ開始
loop
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
B = Button(0)
' 押されているボタンの情報を表示
if (B AND 1 << #B_LUP) != 0 then
PRINT "上"
elseif (B AND 1 << #B_LDOWN) != 0 then
PRINT "下"
elseif (B AND 1 << #B_LLEFT) != 0 then
PRINT "左"
elseif (B AND 1 << #B_LRIGHT) != 0 then
PRINT "右"
endif
VSYNC
endloop
おぉ!リキくん完璧だよ!凄いね!まだ「elseif」教えてないけどよくわかったね?
実践派のリキ様をなめんなよ!……まぁ公式リファレンス見てきただけなんだけどな(笑)
なによそれ、カンニングじゃない!ズルいじゃない!
ううん、全然良いんだよ。むしろ自分で調べた方が身に付きやすいんだ。人間必要なものを探すときは神経を集中させるから記憶にも残りやすいんだよ。
うっ、何も言い返せないわ……そういえば別にテストじゃないもんね。
へへっマユミはまだまだ頭が固いな!
見てなさいよ!同じ土俵に立てればリキなんかに負けないんだから!
自分のペースで大丈夫だよ。プログラミングはその時わかんなくても後から理解出来れば大丈夫。忘れちゃっても今の時代はいつでも検索できるから必要なものだけ調べる癖をつけておけば気付けば自分の力になっているんだ。
押された方向にキャラクターを動かしてみよう
それでこのリキが書いたコードのPRINT文をSPOFSに置き換えれば動けるのかしら?
そうだね。リキくん、その調子でSPOFSに置き換えてみてよ。
おう、任せとけ!
X座標とY座標の変数も忘れずに作ってね。
あぁ、わかってるって!……リキ、1つ聴いていいか?
うん、なんだい?
変数を増加させるのは「INC」ってならったけどよ、減らすのはどうするんだ?
あぁ、それなら「DEC」を使うかそのまま「INC」を使って変数の後に-1を与えてあげればいいよ。DECの方がきれいになるかな。
なるほどな。サンキュ!
ACLS
' ボタンの状態を格納する変数
var B
var X = 0
var Y = 0
' スプライトを定義
SPSET 0, 352
' ループ開始
loop
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
B = Button(0)
' 押されているボタンの情報を表示
if (B AND 1 << #B_LUP) != 0 then
DEC Y
SPOPS 0, X, Y
elseif (B AND 1 << #B_LDOWN) != 0 then
INC Y
SPOPS 0, X, Y
elseif (B AND 1 << #B_LLEFT) != 0 then
DEC X
SPOPS 0, X, Y
elseif (B AND 1 << #B_LRIGHT) != 0 then
INC X
SPOPS 0, X, Y
endif
VSYNC
endloop
うーん……
どうしたマユミ?俺の完璧なコードになんかイチャモンつけたいのか?
マユミちゃんはこのコードどう思う?
うん、DECとINCでプラスもマイナスもいい感じだし頭の中で車は動いているわ。でも……
じゃあ動かしてみるぞ!……問題なく動いたぞ!やっぱり完璧だな俺!
いい感じだね。ただ、この書き方だと1つ問題が出てくるんだ。マユミちゃん、このコードを書き替えてみてくれる?
え?えぇ……
ACLS
' ボタンの状態を格納する変数
var B
var X = 0
var Y = 0
' スプライトを定義
SPSET 0, 352
' ループ開始
loop
' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
B = Button(0)
' 押されているボタンの情報を表示
if (B AND 1 << #B_LUP) != 0 then
DEC Y
elseif (B AND 1 << #B_LDOWN) != 0 then
INC Y
endif
if (B AND 1 << #B_LLEFT) != 0 then
DEC X
elseif (B AND 1 << #B_LRIGHT) != 0 then
INC X
endif
SPOPS 0, X, Y
VSYNC
endloop
どうかしら?あんまり自信はないんだけど……
マユミちゃん、パーフェクトだよ!
え?本当?よかったぁ~
……そうか!上下と左右条件を切り離せばXはX、YはYで別々に処理できる……ってことは!
斜め移動もばっちりだね。Switchって十字キーじゃなくて十字ボタンになっちゃってるから両方押せるようになってるんだけど、
こういう2Dゲームって上下同時や左右同時に押して移動するってことはありえないでしょ?
私、要素がそろっていれば頭の中でシミュレートするのって結構得意なの。もちろん、トモくんが教えてくれなかったらシミュレートすらできなかったんだけど。
個人的にリキくんが書いてた無駄な処理を最適化したのが素晴らしいね。ゲームの面白さには直接関係はないんだけど、プログラミングって基本的に同じこと書くのはナンセンスなんだ。
もちろん、それが悪いってわけじゃないし動いてなんぼの世界だからリキくんのようにとにかく動かすっていうのも作る上ではとっても大事だよ。
貶されてんのか褒められてんのかよくわかんねーな……勉強になったぜ!マユミもありがとな!
べ、別に……私はゴチャゴチャしたのが嫌いなだけよ。
(リキくんが突っ走って、マユミちゃんがコントロールするって感じでいいコンビだね。教えてて楽しくなってきたよ。)
最後に分かりやすいようにPRINTで表示しつつ動かしてみました。
マユミちゃんのやったように無駄な記述を減らすのもプログラミングの1つの楽しみでもあります。
まずはリキくんのようにがむしゃらに書いて動かしてから、
無駄だなって思うところを削除して元々と同じ動きを維持すれば見やすいコードになるしサイズも軽くなります。
今の時代のPCではあまり気にしなくてもいいですが、昔のパソコンは容量が少なかったので最適化は必須だったと思います。
しかし最適化しすぎて見にくいコードになってしまったら本末転倒ですから、何事もほどほどにしておきましょう。
それでは今回やったことをおさらいしてみましょう。
シューティングゲーム作り講座第3回まとめ
- Button(n)
-
現在のボタンの状態を取得する関数。値を返してくれるので()を付けるのは必須です。
-
()の中のnは数字を入力しましょう。コントローラー1の場合は0を指定します。
-
他にも特定のボタンだけを監視したり別コントローラーの監視をしたりできますが、詳しくは公式リファレンスを見てください
- BIN$(n)
-
nに10進数の数字を与えると2進数の文字列に変換してくれます。
-
文字列として2進数で残したかったりする時に使うことが多いです。
- << or >>
-
ビットシフトです。「0010」の状態で左にビットシフトを2回すると「1000」になります。
-
二進数を理解していないと扱いが難しいので、今のところはあまり気にしなくても大丈夫です。
-
大文字で書いてますが半角英数で書いてください。
- 定数
-
一度決めたら変更できない入れ物。元々用意されている定数と、自分で用意することもできる。
-
自分で用意するときは頭にCONST を付けてから名前を「#」から始まる文字にしてください。
-
既に用意されている定数と被らないようにするためには公式リファレンスを確認しましょう。
- DEC
-
INCの逆版。使い方は殆ど一緒で何も指定しないと1つ減らす。
- if ~ elseif ~ endif
-
elseifを使うと条件式を複数作ることが出来る。
-
判断する順番はもちろん一番上からで手前のifの条件が満たない時に条件分岐が実行される。
自分でコントロールすることで一気にゲームに近づきました。
今回は2進数やビットシフト等、少し難しい内容が出てきましたが
トモカズくんの言うように仕組みそのものを完全に理解する必要は無く、道具をこうやって使えばこういう風に動くと覚えるのがベストです。
もし全然わからなかった場合はコードをそのまま打ち込んでどう動いているのか自分で調べると良いでしょう。
それでもわからなかった場合、とりあえず先に進めてみて再び戻ってきた時にすんなりと理解できるようになっているかもしれません。
次回は背景を表示してみましょう。真っ黒なままだと味気ないですからね。
宇宙空間っていう表現もありですけど学習のために背景を描写しましょう。
それでは。