プチコン3号入門講座

ボタンやスライドパッドでキャラを動かしてみよう


BUTTON()関数1つでどのボタンが押されているか分かる



 リアルタイムでキャラを動かすようなゲームではやはりボタンでキャラを操作したいところですね。プチコン3号にはどのボタンを押しているのか分かBUTTON()関数があります。
 例えば、B=BUTTON()とすることで変数Bに押しているボタンの情報が入ります。そのボタンの情報というのは下記の通りです。

押しているボタン
A
B
X
Y
L
R
BUTTON()関数の値
1
2
4
8
16
32
64
128
256
512
定数リテラル
#UP
#DOWN
#LEFT
#RIGHT
#A
#B
#X
#Y
#L
#R
[START]ボタンは取得できません。
 [SELECT]ボタンは1024を返しますが、スクリーンショットが保存されるため注意が必要です。(※ver.3.6.0以降)
 ちなみに、初期バージョンではプログラムを実行した段階ですでに押していた場合に限り[START][SELECT]ともに8192という値が取得できていましたが仕様が変更されました。


 この表の見方を簡単に説明すると例えばAの下の数字は16となっていますが、これはAボタンを押したときはBUTTON()関数の値が16になるということです。要するにAボタンを押しているときにB=BUTTON()を実行するとBの値は16になるということです。ちなみに何もボタンを押してなければ値は0になります。
 16とか32とかの数字を全部覚えるのは大変なのでプチコン3号 ver.3.2.0から定数リテラルというものが導入されました。これは#が頭に付いている値が固定の変数みたいなものです。「値が固定の変数」というと意味が分からないですが、すでにプチコン3号で多く用意されているシステム変数に近いものと思えば良いでしょう。
 定数リテラルがシステム変数と異なる部分はシステム上は完全に定数と同じ扱いということです。Aボタンを押したときのBUTTON()関数の値は16Aボタンを示す定数リテラルは#Aであるため#A16と全く同じ扱いになるわけです。(ボタン関係の定数リテラルはBUTTON()関数から得られる値を元に判定する場合のみ有効でDIALOG命令におけるボタン入力やBREPEAT命令の設定には対応していません)

 これは、ボタンを押したらそのBUTTON()関数の値を表示するプログラムです。

@LOOP
B=BUTTON()
PRINT "BUTTON()かんすう=";B
VSYNC
GOTO @LOOP

 様々なボタンを押して実際にどんな値が出るのかを確かめてみましょう。1つのボタンではなく複数のボタンを同時に押してみて値がどのように変化するのかを見ていくのもいいです。
 例えばAボタンとBボタンを両方押している時はいくつになったでしょうか?Aボタンが16Bボタンが32なのでその合計の48という値が表示されていると思います。

 これによってどのボタンを押したらどのような動作をさせるのかはIF命令で簡単に記述が可能です。

@LOOP
IF BUTTON()==16 THEN PRINT "Aボタンをおしたよ"
VSYNC
GOTO @LOOP

これは定数リテラルを使って記述するとこのようになります。
@LOOP
IF BUTTON()==#A THEN PRINT "Aボタンをおしたよ"
VSYNC
GOTO @LOOP

 16より#Aの方が「Aボタンが押されているかどうかを判定している」というのが分かりやすくなっていると思います。

 ボタンが押されているかどうかを判定するにはループを行いボタンを入力待ちの状態にする必要があります。そして、正しい判定ができるようにVSYNCも入れておきましょう。
 ABXYボタンを判定したい場合には下記のようにできます。

@LOOP
IF BUTTON()==16 THEN PRINT "Aボタンをおしたよ"
IF BUTTON()==32 THEN PRINT "Bボタンをおしたよ"
IF BUTTON()==64 THEN PRINT "Xボタンをおしたよ"
IF BUTTON()==128 THEN PRINT "Yボタンをおしたよ"
VSYNC
GOTO @LOOP

しかし、「VSYNCとWAITでタイミングを取れ」で書いたように同一フレーム内ではBUTTON関数の値は同一の値を返すためIF命令で毎回BUTTON()==値という形で判定する必要はなくそのフレームで最初に取得したBUTTON()の値を別の変数に入れておいてそれを元に判定を用いれば良いのです。



@LOOP
B=BUTTON()
IF B==16 THEN PRINT "Aボタンをおしたよ"
IF B==32 THEN PRINT "Bボタンをおしたよ"
IF B==64 THEN PRINT "Xボタンをおしたよ"
IF B==128 THEN PRINT "Yボタンをおしたよ"
VSYNC
GOTO @LOOP

 さて、ここで注意するのはABXYLRボタンの値が1248163264128256512になっているという点です。察しがいい人ならば1248というのは値が順に2倍になっていることに気づくでしょう。値が123456789ではないのは複数のボタンを同時に押している場合の判定を可能にするためです。(もしもBUTTON()関数においてAボタンが5Bボタンが6という値になるとしたらAB両方を押したら11という値になるけど足して11になる組み合わせは01111029384756127など多数あるためその値からどのボタンを同時に押しているのかを判断することはできない)
 複数のボタンが押されているかどうかは単純にどのボタンの値を足していけば良いです。例えばAボタンが16Bボタンが3216+32=48になるのでBUTTON()関数の値が48かどうかでA、Bボタンの両方が押されているかどうかが分かります

 IF B==48 THEN PRINT "AボタンとBボタンのりょうほうをおしたよ"

 これを定数リテラルを使って記述するとこうなります。

 IF B==#A+#B THEN PRINT "AボタンとBボタンのりょうほうをおしたよ"

 この場合もABボタン以外の他のボタンを押している状態であればその押したボタンの値も加わるため IF B==48 THEN 〜 では正しい判定はできません。十字ボタンの左を押しながらABボタンを押せばABボタンの合計値の48にさらに左ボタンの4を加えた52という値になります。
 それを簡単に正しく判定する方法は下記に書いています。


BUTTON()関数を使ってコンソールキャラを動かす



 せっかくなのでBUTTON()関数を使ってキャラを動かしてみましょう。まずは、簡単にできるコンソール画面(PRINT命令を使った表示)で試します。ここでは""(スペードマーク)を動かすことにしましょう。
 「キャラを動かす」というのはすでに書いているように「キャラの表示座標を変えて別の座標に表示する」というだけのことなのでどのボタンを押したらどのように座標が変わるのかを考えておく必要があります。
 十字ボタンを押して上下左右4方向に動かす場合を考えます。

 キャラを 上 に移動させる 十字ボタンの を押した時にY座標1小さくする
 キャラを 下 に移動させる 十字ボタンの を押した時にY座標1大きくする
 キャラを 左 に移動させる 十字ボタンの を押した時にX座標1小さくする
 キャラを 右 に移動させる 十字ボタンの を押した時にX座標1大きくする

 さて、これで4つのIF文で判定が可能なことが分かるのであとはそれを記述するだけです。

X=25:Y=15
@MAIN
B=BUTTON()
IF B==1 THEN Y=Y-1
IF B==2 THEN Y=Y+1
IF B==4 THEN X=X-1
IF B==8 THEN X=X+1
LOCATE X,Y:PRINT "♠"
VSYNC
GOTO @MAIN

 これは定数リテラルを使って記述するとこうなります。

X=25:Y=15
@MAIN
B=BUTTON()
IF B==#UP THEN Y=Y-1
IF B==#DOWN THEN Y=Y+1
IF B==#LEFT THEN X=X-1
IF B==#RIGHT THEN X=X+1
LOCATE X,Y:PRINT "♠"
VSYNC
GOTO @MAIN

 これを実行すると問題点がいくつかあることに気づくでしょう。

(1) キャラの移動した跡が残っている
(2) 端まで行ったらエラーが出る
(3) やたら動きが速い

(1)の問題はPRINT命令は別の座標に表示しなおしても元に表示されているものは表示されたままであるためです。これがスプライトならばキャラを動かすように作られているため移動した跡は残らず単純に表示座標を変えるだけで問題ありません。
 PRINT命令でキャラを動かすならば前回表示した部分を " "(スペース)で上書きして消去するか、毎回CLSで表示文字を消去すると良いです。つまり、「前の座標に表示しているキャラを消去 → 新しい座標にキャラを表示」を繰り返すだけです。

(2)の問題はLOCATEではX座標は049、Y座標は029の範囲しか指定できないためです。これは表示できる範囲外に表示されないようにすれば良いです。
 では、その具体的な対策法を書きます。例えば右ボタンを押した場合B=4の時)だと「Xの値が49未満(もしくは48以下)のときだけX座標を1大きくするという方法IF B==8 AND X<49 THEN X=X+1」と「Xの値が49より大きくなった場合にはX49を代入してX49を超えないようにするという方法IF B==8 THEN X=X+1:IF X>49THEN X=49があります。

(3)の動きがやたら速いのは1フレームで1キャラ分移動するためです。これは1秒間に換算すると60キャラ分となり画面の端から端まででもボタンを押して1秒足らずで到達してしまいます。これは普通のゲームのキャラ移動させる場合にはほとんど例がないくらい速いためVSYNCVSYNC 1)ではなくVSYNC 3にしていますが、速度は自分好みに変えても問題ありません。

 上記の(1)〜(3)の問題点を修正したのがこのリストになります。

X=25:Y=15
@MAIN
CLS
B=BUTTON()
IF B==1 AND Y> 0 THEN Y=Y-1
IF B==2 AND Y<29 THEN Y=Y+1
IF B==4 AND X> 0 THEN X=X-1
IF B==8 AND X<49 THEN X=X+1
LOCATE X,Y:PRINT "♠"
VSYNC 3
GOTO @MAIN

 これでBUTTON()関数を使ってコンソールキャラを動かす方法は分かったと思います。

 ちなみにこのプログラムを使えばRPGのコマンド選択のようなものも簡単に作れます。それは、「コマンド選択」というのはカーソルを移動させるだけのものだからです。

《 RPG風コマンド選択プログラム 》
CLS
Z=0
LOCATE 2,24:PRINT "こうげき"
LOCATE 2,25:PRINT "ぼうぎょ"
LOCATE 2,26:PRINT "まほう"
LOCATE 2,27:PRINT "アイテム"
LOCATE 2,28:PRINT "にげる"
@LOOP
B=BUTTON(2)
LOCATE 0,Z+24:PRINT " "
IF B==1 AND Z>0 THEN Z=Z-1
IF B==2 AND Z<4 THEN Z=Z+1
LOCATE 0,Z+24:PRINT ">"
VSYNC
IF B!=16 THEN @LOOP
BEEP 3
PRINT "コマンド";Z;"がせんたくされました"

 十字ボタンの上下でカーソル「>」を動かして「こうげき」「ぼうぎょ」「まほう」「アイテム」「にげる」を選択できます。決定はAボタンです。
 どれを選択したかで変数Zの値が決まります。「こうげき」ならばZ=0、「ぼうぎょ」ならばZ=1、「まほう」ならばZ=2、「アイテム」ならばZ=3、「にげる」ならばZ=4となります。
 ここでは、ボタンを押しっぱなしで一瞬で上下に移動してしまうのを防ぐため下記の押した瞬間の情報を取得するBUTTON(2)を使用しています。押しっぱなしでカーソルが移動できるようにするためにはBUTTON(0)を使い上手く速度調整をする必要があります。
 コンソールによるキャラ移動はどんな場合であっても 前の座標に表示しているキャラを消去 → 新しい座標を計算 → その座標にキャラを表示 とするだけで実現が可能であることが分かると思います。この順番を間違うと移動したはずなのに残像が残ってしまうなどの問題が起きる場合があります。

 さて、ここで上記の4方向移動プログラムにおいて4方向ではなく8方向に移動させたいという人もいると思います。
 しかし、それを行う場合、例えば「左上」に移動させるためには「左」と「上」の両方が同時に押されているかを判定する必要があります。BUTTON()関数の値は左が4、上が1なので左上はIF B==5 THEN X=X-1:Y=Y-1でできますね。(この場合も画面外に出ないように注意する)
 あと「右上」「左下」「右下」も同様に判定していきます。
 これで8方向に移動できるようになるのですが、上記の4方向移動と同じく十字ボタン以外のボタンを押している場合には正しく判定ができません。もしも、判定を行うならばプチコン3号で使用可能な全ボタンの組み合わせ(旧3DS本体で動作時でも全部で511通りの条件判断が必要)をIF命令で記述する必要がありとても現実的とは言えません。
 そこで必要になるのがビット演算子ANDです。


ANDを使って判定したいボタンのみを取り出して判定する



 さて、上記の「Aボタンが押されているかどうか」の判定は他のボタンを押している状態でAボタンを押す(例えば十字ボタンを押した状態でAボタンを押す)と正しく判定はできませんでした。

@LOOP
B=BUTTON()
IF B==16 THEN PRINT "Aボタンをおしたよ"
VSYNC
GOTO @LOOP

 この問題を解決するにはIF命令の条件式においてBB AND 16に変えてみてください。

@LOOP
B=BUTTON()
IF (B AND 16)==16 THEN PRINT "Aボタンをおしたよ"
VSYNC
GOTO @LOOP

 これを定数リテラルを使って記述するとこうなります。

@LOOP
B=BUTTON()
IF (B AND #A)==#A THEN PRINT "Aボタンをおしたよ"
VSYNC
GOTO @LOOP


 ここで注意が必要なのは「ビット演算子ANDよりも比較演算(==)の方が演算優先順位が高い」ということです。 比較の方が優先順位が高いというのは B AND 16==16 のようにカッコを省いて書くとB AND (16==16) と解釈されてしまうわけです。そのため IF (B AND 16)==16 THEN 〜 は、IF B AND 16==16 THEN 〜 のようにカッコを省略したら誤動作をしてしまうので注意してください。

 このプログラムを実行すると他のボタンを押していてもAボタンを押せばちゃんと判定が可能なことが分かります。ANDというのはすでにIF命令のところで出てきたものですが、これは実は「ビット演算子」と呼ばれているものです。
 詳しくは後述の「ボタン入力とビット演算」で書きますがIF (B AND 16)==16 THEN 〜は、簡単に説明すると「BUTTON()関数の値から16という値が含まれているかどうかを調べて16という値が含まれているならばTHEN以下を実行する」というものです。
 定数リテラルを使った方を見てみればB AND #Aで「Aボタンが押されているかどうか」を調べて、==#Aで「Aボタンが押されている場合」というのが一目瞭然だと思います。

 「16という値が含まれているか」というのがどういう意味かを少し説明しておきます。例えばA、B、Xボタンを同時に押している時はBUTTON()関数の値は16+32+64=112になります。ここで、DIRECTモードでPRINT 112 AND 16を実行すれば分かりますが、この値は16になります。これはBUTTON()関数の値が112の時は16という値が含まれているということです。言い換えれば、BUTTON()関数の値が112の時は112 AND 16の結果が16になるのでAボタン(値は16が押されていることが分かるわけです。
 B、X、Yボタンを同時に押した状態だと32+64+128=224になりますが、PRINT 224 AND 160になり、16ではないため)Aボタンが押されてないことがBUTTON()関数の値だけで分かります。
 これも定数リテラルを使えば簡単で例えばA、B、Xボタンを同時に押している時はBUTTON()関数の値は#A+#B+#Xになります。したがって、(変数BにBUTTON()関数の値が入っている場合ばB AND #Aでその中の#Aの値だけを取り出せるわけです。変数B#Aが含まれてない場合はB AND #Aとしても#Aの値を取り出せないため0になります。したがって、IF (B AND #A)==#A THEN 〜という条件は不成立になるわけです。

【 B AND 16 のイメージ 】




 同様にしてBボタンが押されているかどうかを調べたい場合はIF (B AND 32)==32 THEN 〜 とすれば良いし、AB両方のボタンが押されているかどうかを調べたい場合はIF (B AND 48)==48 THEN 〜 とすれば良いことが分かりますね。

【 B AND 48 のイメージ 】




 上記イメージ図も定数リテラルで考えればさらに分かりやすくなります。
 定数リテラルを使った場合はBボタンが押されているかどうかを調べたい場合はIF (B AND #B)==#B THEN 〜 とすれば良いし、AB両方のボタンが押されているかどうかを調べたい場合はIF (B AND (#A+#B))==#A+#B THEN 〜 とすれば良いです。AボタンとBボタンが押されているかどうかを調べてAボタンのみが押されているかを判断したい場合はIF (B AND (#A+#B))==#A THEN 〜 とすれば良いです。

 ANDを使うことで判定に必要なボタンが押されているか否かという情報のみ調べることができるため他のボタンが押されていたとしても正しく判定が可能になるというわけです。

 これさえ分かれば8方向移動させるのは出来たも同然です。

X=25:Y=15
@MAIN
CLS
B=BUTTON()
IF (B AND 1)==1 AND Y> 0 THEN Y=Y-1
IF (B AND 2)==2 AND Y<29 THEN Y=Y+1
IF (B AND 4)==4 AND X> 0 THEN X=X-1
IF (B AND 8)==8 AND X<49 THEN X=X+1
LOCATE X,Y:PRINT "♠"
VSYNC 3
GOTO @MAIN

 これを実行するとIF命令は4つしか使ってないのにちゃんと8方向移動が可能なことが分かります。これは十字ボタンの左上を押した時には「左ボタンが押されているかどうかの判定」と「上ボタンが押されているかどうかの判定」の2つのIF命令の条件が成立するためです。(左上を押したときはBUTTON()関数の値は5になるけど5 AND 1の値は1になるし、5 AND 4の値は4になり、BUTTON()関数が示す「5」という値にはには「1」も「4」も含まれているため)

 8方向ではなく「4方向移動にしたい」という場合は意図せず複数のIF命令の条件を満たして8方向移動になっては困りますが、基本さえ理解しておけば4方向も全く同じように書くことができます。(実はANDを使った場合は8方向より4方向の方が難しい)

X=25:Y=15
@MAIN
CLS
B=BUTTON()
IF (B AND 15)==1 AND Y> 0 THEN Y=Y-1
IF (B AND 15)==2 AND Y<29 THEN Y=Y+1
IF (B AND 15)==4 AND X> 0 THEN X=X-1
IF (B AND 15)==8 AND X<49 THEN X=X+1
LOCATE X,Y:PRINT "♠"
VSYNC 3
GOTO @MAIN

 十字ボタンの4方向の値を足したら1+2+4+8=15になります。(定数リテラルで記述するならば15の部分は(#UP+#DOWN+#LEFT+#RIGHT)になる)
 したがって、IF (B AND 15)==15 THEN 〜ならば十字ボタンを4方向すべてを同時に押しているときにTHEN以下が実行されるのが分かると思います。これは十字ボタンの構造上不可能であるため (B AND 15)==15という条件を満たすことはできませんが、 (B AND 15)==1ならばこの条件を満たすことは可能です。
B AND 15で十字ボタンのいずれかが押されているかが分かる(他のボタンを押している場合の影響を取り除くことができるので純粋に十字ボタンのみの判定が可能になる)ためその値が1ならば(他のボタンを押している状態であっても)上ボタンが押されていることが分かるわけです。
 B AND 15が4つも同じものが羅列されているのが気になるならばB=BUTTON() AND 15としておけば IF B==1 AND Y>0 THEN Y=Y-1 のようにすることができます。

 十字ボタンと同様の方法でABXYボタンのいずれかが同時押しするタイプのゲームでボタン入力判定を行う場合はその4つのボタンのBUTTON()関数の値の合計値は16+32+64+128=240ですが、B AND 240とすることでこの4つのボタンのいずれかのボタンが押されていることが分かります。ABを押しているかどうかを判断するならばIF (B AND 240)==48 THEN 〜 とすれば良いです。定数リテラルを使って記述するならばIF (B AND (#A+#B+#X+#Y))==#A+#B THEN 〜となり、ABXYボタンが押されているかを調べてAボタンとBボタンが押されている場合という条件を判断していることが一目で分かると思います。
 ABボタンが両方押されているかどうかはIF (B AND 48)==48 THEN 〜 とするのが分かりやすくて簡単なのですが、わざわざIF (B AND 240)==48 THEN 〜と記述するメリットは何かというと特定のボタンの組み合わせて同時に押す場合に上記の4方向移動のようにプログラム内で意図せず複数のIF命令での条件を満たさないようにすることができるということです。(例えばABXの3つのボタン、ABYの3つのボタン、ABXYの4つのボタンを押した時にはABボタンを押したと見なされてしまうためそれを除外するのが簡単にできるということ)
 この意味が良く分からないという人はIF (B AND 48)==48 THEN 〜のように使用している2つの数字を同じにしておけば判定したいボタンをすべて押しているかどうかの判定を簡単に行うことができます。(判定したいボタンのうち「すべて」ではなく「どれか1つでも」押されているかを調べたい時IF (B AND 48)!=0 THEN 〜のように「!=0」を付ければ判定ができる)

 「今回出てきたボタン入力の判定で使うANDIF命令において複数の条件を両方満たすかどうかを判断したいときのANDってどう違うの?」という疑問があるかもしれません。それは、根本的には同じものなのですが現時点で中途半端な説明をすると誤解を招いてしまうため今回は深く考えず IF (BUTTON() AND 判定したいボタンの値) == 判定したいボタンの値 THEN 〜 とすることで他のボタンを押している状態でも判定が可能になるということだけ覚えておいてください。(後述の「論理式で深まる条件判断」と「ボタン入力とビット演算」に詳しく書いている)

 ちなみによく見かける IF B AND 16 THEN 〜 のような書き方は特別な条件を満たす時だけに使える書き方です。その「特別な条件」というのが何かが分かって使うのであれば問題ないですが、そうでない限りは基本に忠実に IF (B AND 16)==16 THEN 〜 という書き方をするのをオススメします(その「特別な条件」は後述の「論理式で深まる条件判断」で解説しています)

【 比較演算を省略した場合のイメージ 】




 定数リテラルを使って記述すると比較演算を省略したらIF B AND #A THEN 〜Aボタンが押されているかを判定できIF B AND (#A+#B) THEN 〜ABボタンが押されているかを判定できるわけですが、この場合において前者はAボタンが押されているかどうかが正しく判定できますが、後者はABボタンのいずれかが押されているかを判定しているため両方押されているかの判定を行うことはできないというのは上記のイメージ図からも分かると思います。


BUTTON(1)、BUTTON(2)でボタンを押した瞬間を得る



 ボタンを押してキャラに様々な動作をさせる場合には十字ボタンのようにずっと押しっぱなしで移動を行う場合もあればシューティングゲームの弾の発射のように押した瞬間のみ反応すればいい場合もあります。
 そういう場合に便利なのがBUTTON(1)BUTTON(2)です。BUTTON(1)があらかじめ設定された自動連射機能付きでBUTTON(2)がボタンを押した瞬間のみ取得(手動連射)できます。これまでずっと使ってきた引数無しのBUTTON()BUTTON(0)と同等です。
 ちなみに連射設定はBREPEAT命令で各ボタンごとに設定できますが、このボタンIDはBUTTON()関数の値と異なっているという点に注意が必要です。

設定したいボタン
A
B
X
Y
L
R
ボタンID
0
1
2
3
4
5
6
7
8
9
定数リテラル
(なし)
(なし)
(なし)
(なし)
(なし)
(なし)
(なし)
(なし)
(なし)
(なし)
BREPEAT命令のボタンIDは定数リテラルでは設定されていません。
 定数リテラルは定数と全く同じ扱いなので#UPとすると1を指定したのと同じになります。つまり、BREPEAT #UP,10,5のようにすればボタンIDが1(つまり下ボタン)の設定ができるわけですが、上ボタンの設定をしているようにしか見えないため可読性を考えると本体の目的(#UPであればBUTTON()関数用)以外の定数リテラルは使用しない方が良いでしょう。


 例えばAボタンに連射設定を行う場合はボタンIDは4になるため連射が始まるまで10フレーム、連射は5フレーム間隔ならば BREPEAT 4,10,5 となります。この設定をすることで、B=BUTTON(1)とした場合にはAボタンをずっと押し続けるだけで開始10フレーム後、16フレーム後、22フレーム後、28フレーム後(以下6ずつ加算)に瞬間的にAボタンを押したと判断することが可能になります。(5フレーム間隔で設定しているのに6フレームずつ増えているのは「ボタンを押してない時間が5フレーム」という意味だから)

 では、BUTTON(2)を使って弾 " "(マイナス記号)を発射するプログラムを作ってみましょう。
 今回はプログラムを単純化するため画面内の弾の数は1つだけにします。
 「Aボタンを押したら弾を発射する」ための判定は IF (BUTTON(2) AND 16)==16 THEN 〜 となります。BUTTON()BUTTON(2)に変わっただけなので説明は不用ですね。

X=25:Y=15
@MAIN
CLS
B=BUTTON()
IF (B AND 1)==1 AND Y> 0 THEN Y=Y-1
IF (B AND 2)==2 AND Y<29 THEN Y=Y+1
IF (B AND 4)==4 AND X> 0 THEN X=X-1
IF (B AND 8)==8 AND X<49 THEN X=X+1
LOCATE X,Y:PRINT "♠"
IF (BUTTON(2) AND 16)==16 THEN BEEP 11:SX=X:SY=Y:F=1
IF F>0 THEN
 LOCATE SY,SY:PRINT "-":SX=SX+2
 IF SX>49 THEN F=0
ENDIF
VSYNC
GOTO @MAIN
※15行目はVSYNC 3ではなくVSYNC(もしくはVSYNC 1)にしておいてください。

 ごく簡単なプログラムですが10行目の解説をしておきます。
 Aボタンを押した場合には弾のX座標(変数SX)、Y座標(変数SY)に自機の座標を代入してフラグをONにしておきます。フラグというのは様々な状態を管理するためのものでショットフラグがONの時は弾を表示してそれを移動させることで「Aボタンを押したら弾を発射」という動作が可能になります。弾が画面から出たらフラグをOFFにします。
 このプログラムにおいてBUTTON(2)BUTTON()に変更すればBUTTON(2)BUTTON()とどのような違いがあるかが分かると思います。

 ここで15行目のVSYNCVSYNC 3に戻してみてください。するとAボタンを押した際に弾を発射されたりされなかったりでボタンに対する反応が非常に悪くなっていることに気づくと思います。それは「VSYNCとWAITでタイミングを取れ」で書いたようにBUTTON(1)やBUTTON(2)はVSYNC 1(60fps)の時でないと正常動作しないためです。BUTTON()関数は1フレームの間は同じ値を返すため60fpsより速いプログラムでもBUTTON(2)は正常動作しない)
 それについての詳細や対策プログラムについてはこちらの「60fps以外で動作時のBUTTON()関数」を参考にしてみてください。
 対策というのはリンク先でも書いているように「前回押して無くて今回押されたボタンが何か」というのを調べれば良いだけなのでビット演算についての詳しい知識がなくても今まで書いたことを少し応用するだけでできます。

X=25:Y=15
@MAIN
CLS
A=B:B=BUTTON()
IF (B AND 1)==1 AND Y> 0 THEN Y=Y-1
IF (B AND 2)==2 AND Y<29 THEN Y=Y+1
IF (B AND 4)==4 AND X> 0 THEN X=X-1
IF (B AND 8)==8 AND X<49 THEN X=X+1
LOCATE X,Y:PRINT "♠"
IF (A AND 16)!=16 AND (B AND 16)==16 THEN BEEP 11:SX=X:SY=Y:F=1
IF F>0 THEN
 LOCATE SY,SY:PRINT "-":SX=SX+2
 IF SX>49 THEN F=0
ENDIF
VSYNC 3
GOTO @MAIN

 変数Aに前回(3フレーム前)のBUTTON()関数の値が入っている場合には「前回Aボタンを押してないかどうか」というのは IF (A AND 16)!=16 THEN 〜 で判定が可能なので「前回Aボタンを押して無くて今回Aボタンを押しているかどうか」というのはIF (A AND 16)!=16 AND (B AND 16)==16 THEN 〜 で判定が可能です。ANDがたくさんあって分かりにくいという人は後述のように論理演算子&&を使えばIF !(A AND 16) && B AND 16 THEN 〜のようにすっきりとした記述も可能)

 このプログラムリスト内の青文字の変更によってBUTTON(2)と同様の機能(ただし、Aボタン限定)でありながらVSYNC 1(60fps)以外でも正常に動作するプログラムを作ることができます。
 それをAボタンのみに限定せずどんなボタンでも使えるようにしてさらに短くしたものがVSYNCのところで書いた A=B:B=BUTTON():C=NOT A AND B というものです。これを見てビット演算に興味が出た人は後述の「ボタン入力とビット演算」をご覧になってください。


 またBUTTON(2)を使えば何かボタンを押すまで入力待ちという感じのプログラムも簡単に作ることができます。

PRINT "なにかボタンをおしてください。
@LOOP
WAIT
IF BUTTON(2)==0 THEN @LOOP

 3行目のWAITWAIT 1と同等)は60fpsで動作させるために入れているというだけではなくすでにボタンを押している状態でこのプログラムを実行したときにも正常に動作するようにするためです。このプログラムを実行前からすでにボタンを押した状態になっている可能性を考えるとVSYNCだと確実に1フレームのウェイトが入る保証がないのでここではWAITにするのがベターです。(このプログラムをRUNではなく[START]ボタンで実行すればVSYNCWAITの違いが確認できる)

 これでBUTTON()関数の基本的な使い方は一通り理解できたことだと思います。


スライドパッドを使ってスプライトを動かす



 では、次にスライドパッドを使ってキャラを動かしてみます。スライドパッドが十字ボタンと異なるのは0か1かというデジタル的な値ではなくアナログ的な値を取得できるということです。この辺はあえて詳しく説明するまでもないですね。
 スライドパッドを使うにはSTICK OUTという命令を使います。STICK OUT X,Yとすることで、変数Xに「X座標方向にスライドパッドを動かした量」、変数Yに「Y座標方向にスライドパッドを動かした量」が入ります。
 では、どれくらい動かしたらどれくらいの値になるかを実際に確かめてみましょう。

@LOOP
STICK OUT X,Y
PRINT ”X=";X,"Y=";Y
VSYNC
GOTO @LOOP

 適当に動かすと変数X、変数Yともに-0.86+0.86くらいの値で変化していることが分かるでしょう。(この取得できる値は120倍すれば整数になるためスライドパッドは仮に絶対値0.875が最大ならば押した量を105段階で検知していることになる)
 ちなみにプチコン3号とプチコンBIGではSTICKの戻り値が多少異なり、プチコンBIGではX、Y方向ともに戻り値の最大は1.0になります。したがって、STICK(スライドパッド)の量によって動作が変化するプログラムの場合はプチコン3号とプチコンBIGでは挙動が異なるため注意が必要になります。(例えばX方向の値が0.9以上の場合に実行するようなプログラムの場合はプチコンBIGでは動作するけどプチコン3号では動作しない)
 X、Y方向の戻り値が移動速度に変換されるだけであればプチコン3号の方が1割程度動作が遅い(1フレーム当たりの移動量が小さい)くらいなので目立った不具合にはならず、そこまで気にするほどではありません。

 プチコン3号では、スライドパッドを真下にした時にはX=0、Y=-0.86くらいの値(約0.86はヘルプに記載されている値)になったと思います。BUTTON()関数ではどのボタンを押したらいくつになるのかが明確に決まっていましたが、STICK OUTで得られる値は本体の個体差によって微妙に変わってきます。したがって、自分の本体で0.86なったからといって他の本体でも0.86になるという保証はできません。(私のNew 3DS本体では最大0.875となったけどヘルプに記載されている0.86という値からはそんなに極端に変わることはないと思われる)
 したがって、真下を押しているかどうかをIF X==0 AND Y==0.86 THEN 〜 のように固定の値で判定してしまうとスライドパッドでは正常な判定ができなくなる恐れがあります。そこである程度の値を許容してやる必要があります。

 上下いくつくらいの値を許容するのかを決めてしまえばあとは絶対値を返すABS()関数で容易に実現できます。許容する値が0.1未満ならばIF ABS(X)<0.1 AND ABS(Y-0.86)<0.1 THEN 〜 となります。これで変数X-0.1<X<0.1の範囲、変数Y0.76<Y<0.96の範囲のときにTHEN以下を実行します。

 またスライドパッドを最大に動かした場合には円形で動作するためXYともに0.86付近の値になることはありません。スライドパッドがどの向きであっても概ねX*X+Y*Yの値は0.75くらいになります。(その値の平方根が0.860.87程度の値になる)



 次にスプライトキャラを動かしてみます。
ACLS
SPSET 0,1251
SPHOME 0,8,8
SX=200:SY=120
@MAIN
STICK OUT X,Y
SX=SX+X*4
SY=SY-Y*4
IF X==0 THEN SPCHR 0,1251
IF X>0 THEN SPCHR 0,1252+X*3
IF X<0 THEN SPCHR 0,1263+X*3
SPOFS 0,SX,SY
VSYNC
GOTO @MAIN
※スプライトキャラは画面の範囲外に表示してもエラーにはならないため画面外に出ないようにする判定は入れていない

 スライドパッドの動かした大きさや向きによって戦闘機の移動速度や移動する向きが変わります。またそれだけではなく戦闘機を旋回っぽくするためスライドパッドを左右に動かした量に応じて戦闘機のスプライトキャラを変えるようにしてみました。
 スライドパッドで移動させるときの注意点はX方向、Y方向の量は最大でも0.86程度でありその値を単純に加算すると移動が遅いため適当な倍数にしてやる必要がある(上記のサンプルでは4倍にしている)ということとY方向の移動量はプチコン3号の表示上のY座標とは正負の向きが逆になっているSTICK OUTによって得られるXY座標方向の移動量を加算するときはY方向はマイナスにする)ということです。
 これでスライドパッドを少し動かせばキャラが歩き多く動かせばキャラが走るプログラムとかも簡単に作れますね!

 STICK OUTによって得られるスライドパッドのX方向、Y方向の動かした大きさはY方向に関してのみプチコン3号の画面とは上記のように正負が逆なのには理由があります。それはその方がスライドパッドとの相性が良いからです。
 例えば押した向きにスプライトを回転させるプログラムを考える場合ATAN(Y,X)で倒した角度が「ラジアン」で求まります。スプライトの回転をさせるSPROTは角度が「ラジアン」ではなく「度」となっているためDEGを使ってラジアンから度へと変換する必要があります。つまり、DEG(ATAN(Y,X))でスライドパッドの向きを「度」で取得できるわけです。

《 スプライトをスライドパッドで回転させるサンプル1 》
ACLS
SPSET 0,0
SPHOME 0,8,8
SPSCALE 0,10,10
SX=200:SY=120
@MAIN
 STICK OUT X,Y
 A=DEG(ATAN(Y,X))
 SPROT 0,A
 VSYNC
GOTO @MAIN

 上記のサンプル1においてスライドパッドを動かしてない時は普通ですが、スライドパッドを少しでも動かすと「スライドパッドの回転とスプライトの回転は90度のずれがある」(真上に向けている時にスプライトは右を向く)ということと「回転が逆方向になっている」(スライドパッドを時計回りに回転させるとスプライトは反時計回りに回転する)ことに気づくでしょう。
 これはスプライトの回転の基準が時計の12時の方向であるのに対してスライドパッドの回転の基準(正しく言えばATAN関数の角度計算の基準)が時計の3時の方向にあり、画面とは正負が逆になっているためATANで計算される回転方向も正負が逆転してしまうためです。それならばスライドパッドの回転を90度マイナスして回転方向の正負を入れ替えてやればちょうど良くなりそうですが・・・。

《 スプライトをスライドパッドで回転させるサンプル2 》
ACLS
SPSET 0,0
SPHOME 0,8,8
SPSCALE 0,10,10
SX=200:SY=120
@MAIN
 STICK OUT X,Y
 A=DEG(ATAN(Y,X))-90
 SPROT 0,-A
 VSYNC
GOTO @MAIN
青字の部分はサンプル1から変更になった部分

 これを実際に動かすと確かにスライドパッドの回転とスプライトの回転が重なるようになったものの後から加えた-90のせいでスライドパッドを動かしてない時はスプライトが時計の9時の方向を向くようになってしまいました。
 これを防ぐにはIFを使ってIF X==0 AND Y==0 THEN A=0を加えてスライドパッドを動かしてない時はスプライトの回転角度を0度(時計の12時の方向にする)ということを行えば良いです。
 スライドパッドの正負が画面と逆になっているため非常に面倒になっているように感じますが実はスライドパッドとSPROTによる回転を合致させる場合にはATAN(Y,X)ではなくATAN(X,Y)とすればよいのです。つまり、X軸とY軸を入れ替えることで90度のずれが無くなるだけではなく正負の逆転も相殺されるため非常に使いやすくなります。

《 スプライトをスライドパッドで回転させるサンプル3 》
ACLS
SPSET 0,0
SPHOME 0,8,8
SPSCALE 0,10,10
SX=200:SY=120
@MAIN
 STICK OUT X,Y
 A=DEG(ATAN(X,Y))
 SPROT 0,A
 VSYNC
GOTO @MAIN
青字の部分はサンプル1から変更になった部分

 STICK OUTにおいて画面と正負が逆になっているのはこのようにスプライトの回転と親和性が非常に高くなるためだと思われます。これが正負が同じ無機だったとしたら90度のずれは上記のようにIF等をを使って強引に補正するしかなくなってしまいます。

 スライドパッドはアナログ的な操作に使うのが普通ですが、あえて十字ボタンの置き換えとして使いたい(デジタル的に使いたい)という人もいるでしょう。その際はキリの良い数に変換する必要があります。その処理はスティックを動かした角度を元に変換すれば良いので三角関数ATAN()を使えば可能です。ATANはX,Y座標を元に角度を取得する関数)

《 DSTICK()関数 》
DEF DSTICK()
 VAR A,B,SX,SY
 STICK OUT SX,SY
 A=(DEG(ATAN(SY,SX))+382.5)MOD 360/45
 IF SX*SX+SY*SY>0.2 THEN B=VAL("78043519"[A])+1
RETURN B
END
DEFは後述の「DEFで自作関数を作ろう」を参照

 自分のプログラムでDSTICK()を実行するとBUTTON()の十字ボタンと同じ値を返します。(例えば、上ならば1、右ならば8、右上ならば9となる)

 このDSTICK関数の簡単な解説を書いておきます。
 スティックには遊びを設けている(わずかにスティックを動かしただけでは0のままになるため誤動作防止に繋がる)ので最大稼働域の半分以上動作させることでその向きを取得が可能になっています。SQR(SX*SX+SY*SY)の値は約0.86SQR(0.2)が約0.44なので0.2でほぼ半分になっていてどの角度で倒しても同じように処理されるため角度によって動かしにくいとか言うことも無くなる
 このスティックを動かす量に遊びがあることで想定外の方向に動き出すということを防止しています。(約半分という遊びの量は誤動作防止を重視してやや大きめになっているためそれが気になる人は約3分の1となる0.1でも良い)

 上記のスライドパッドによるスプライト回転で書いたようにDEG(ATAN(SY,SX))で倒した角度が「度」で求められるのですが、得られる範囲は-180度〜+180度で負数を含みます。そのため382.5を足して360の剰余を取ることで0度〜359度に変換しています。382.5度というのは一見中途半端な値ですが、(DEG(ATAN(SY,SX))+360)MOD 360のようにキリの良い数字である360を足してしてしまうと真横ぴったりが基準になりそこからわずかに上下させることでキャラが動いてしまうためで8方向に思ったように動かすことができません。(8方向移動ではなくスライドパットを倒した角度を求めたい場合はこれで問題はない)
 そこで、22.5度(8方向は45度単位なのでその半分)基準点をずらすことで動かしたい方向にスムーズに動くようにしています。あとは得られた0〜359という値を0〜7に変換するため45で割っています。B=VAL("78043519"[A])+1はスティックを倒した時に8方向のどれかに相当する07という値を上記のボタン情報の値に変換するためのものです。(8方向だからIF命令を8個使って判定するというのも良いけれどこのように記述することでそれより遙かに短くなる)

 BUTTON()関数と併用して使う場合には B=DSTICK() OR BUTTON() とすれば良いです。斜め入力を多用するゲームにこのDSTICKを併用すれば操作性が格段に向上すると思います。(3DSでプチコンmkIIを使っていた人ならばスライドパッドでの移動を想定してもらえたら良い)
 なお、シンプルにするため十字ボタンとスライドパッドの両方を同時に押した場合やBUTTON(2)に相当するスライドパッドを押した瞬間の取得には対応していないので頑張って対応させてください。

 ちなみに角度を元に計算するのではなくSTICK OUTの戻り値から単純に計算することも可能です。

《 DSTICK()関数 別バージョン 》
DEF DSTICK()
 VAR A=0.2,X,Y
 STICK OUT X,Y
 RETURN (Y>A)+(Y<-A)*2+(X<-A)*4+(X>A)*8
END

 この別バージョンの方は遊びの大きさが上下方向と比べて斜め方向の方が大きくなっています。そのため方向によってスティック反応しやすさが異なり斜め方向へは入りにくくなっています。遊びの大きさを示しているA=0.2という値をA=0.6くらいにすればそれが顕著になります。A=0.7にすると斜め方向には全く入らなくなる)
 斜め方向には入りにくくしたいという意図があるならば8方向均一となる最初のバージョンではなくこの別バージョンの方がベターかもしれません。


拡張スライドパッドを使う



 3DSのオプションである拡張スライドパッドはプチコン3号で使うことが可能です。New 3DSでは標準で拡張スライドパッド相当の機能を内蔵していますがそれも拡張スライドパッドと同じ扱いで使用が可能です。

 拡張スライドパッドは標準では使えるように設定されてないためそれを使ったプログラムを使えるためには拡張スライドパッドが使えるように準備する必要があります。それは難しいことは全くする必要がなくXON EXPADと記述すれば良いだけです。

 これによってBUTTON()関数で拡張スライドパッドのZLボタンやZRボタンが押されているかが分かるようになります。

 ZRボタン ・・・ 2048 (定数リテラル #ZR 
 ZLボタン ・・・ 4096 (定数リテラル #ZL 

 次のものはボタンを押した時のBUTTON()関数の値を表示するプログラムです。

XON EXPAD
@LOOP
B=BUTTON()
PRINT "BUTTON()かんすう=";B
VSYNC
GOTO @LOOP

 ZRボタンを押したら2048ZLボタンを押したら4096、その両方を押したら2048+40966144と表示されるのが分かると思います。当然ながらそれ以外のボタンを押した時はその押した時の値も加算されていきます。
 拡張スライドパッドを装着していない状態で上記のプログラムは実行が可能ですが、その際は当然ながらZLボタンやZRボタンは存在しないためZLボタンやZRボタンを使うゲームでは正しい判断ができなくなってしまいます。それはシステム変数RESULTを使うことで改善ができます。

《 拡張スライドパッドが装着されているかどうかを判断するプログラム 》
XON EXPAD
EPAD=RESULT
IF EPAD==0 THEN PRINT "かくちょうスライドパッドをにんしきできませんでした"
IF EPAD==1 THEN PRINT "かくちょうスライドパッドをにんしきしました"

 システム変数RESULT処理が成功した場合には1、そうでない場合は0の値を取ります。この場合はXON EXPADを実行した時点で拡張スライドパッドが認識できていればRESULTには1が入るため変数EPADにはその1という値が入るためそれを元にすればプログラム内で拡張スライドパッドがある場合と無い場合の処理を行うだけで済みます。
 RESULTをそのまま使って判断すれば良いと考えるかもしれないですが、システム変数RESULTはそのXON EXPADが成功した場合だけではなくセーブやロードが成功した場合にも1を返すためプログラム内で随時変化していくため拡張スライドパッドの有無をゲーム内で判断するためには別の変数にその値を保存しておく必要があります。

 なお、拡張スライドパッドを使用しなくするためにはXOFF EXPADを実行します。

 拡張スライドパッドのボタンはBUTTON()関数で普通に取得できましたが、拡張スライドパッドのCスティックに関しては標準のスライドパッドの値を取得できるSTICK OUTではなくSTICKEX OUTを使います。

《 標準スライドパッドとCスティックの動作サンプルプログラム 》
ACLS
XON EXPAD
SPSET 0,290
SPSET 1,290
@LOOP
CLS
STICK OUT SX,SY
STICKEX OUT EX,EY
SPOFS 0,150+SX*120,120-SY*120
SPOFS 1,250+EX*120,120-EY*120
LOCATE 0,0:PRINT "SX=";SX
LOCATE 0,1:PRINT "SY=";SY
LOCATE 0,2:PRINT "EX=";EX
LOCATE 0,3:PRINT "EY=";EY
VSYNC
GOTO @LOOP

 このプログラムでは標準のスライドパッドと拡張スライドパッドのCスティックで上下左右にカーソルを動かすことができます。画面左上には取得された値が表示されるためスティックの可動範囲の確認に使ってください。


RETURN (プチコン3号講座のページにもどる) RETURN *MAIN (トップページにもどる)

inserted by FC2 system