レス数が1スレッドの最大レス数(1000件)を超えています。残念ながら投稿することができません。
おちゃめくらぶ掲示板
-
プチコン3号のDEFで作った自作関数、命令の解説
プチコン3号のDEFで最近作った自作命令、自作命令がたまったのでそれのリストや使い方や
解説などをしてみるにょ。(自作関数のサイトでの正式公開は近いうちに行う予定)
作ったのは本体がスリープしたか否か(簡単に言うと本体の蓋を閉じたか否か)を
取得できるSLEEP()関数と1000分の1秒(1ミリ秒)単位の時間を取得可能なTIMER()関数、
1ミリ秒単位でウェイトが可能なMWAIT命令、BEEPを強制的に停止させるBEEP OFF命令
スライドパッドを十字ボタンの代わりに使える(8方向移動を行いBUTTOON関数における
十字ボタンと同じ値を返す)DSTICK()関数、ネームバトルゲームなどで利用できる発生する
値のバラツキを自在にコントロールできるADAM()関数、フェードイン、フェードアウトを
行うFADEIN、FADEOUT命令、画面に任意の色を重ねるSETCOLOR命令、画面を任意の色から
任意の色に変化させるVARCOLOR命令、変数の型(実数型、整数型、文字列)を取得できる
るSUFFIX()関数、文字列からボタン番号を返すBTNUM()関数、任意のボタン入力待ちを行う
BTWAIT命令などにょ。
◎SLEEP()関数
DEF SLEEP()
VAR CNT=MAINCNT
VSYNC
RETURN MAINCNT-CNT>1
END
https://twitter.com/ochame_nako/status/577079366506573824
◎TIMERSTART命令
DEF TIMERSTART
XON MIC:MICSTART 3,0,0
_TIMER1=0:_TIMER2=0
END
◎TIMER()関数
DEF TIMER()
_TIMER0=MICPOS
IF _TIMER0<_TIMER1 THEN INC _TIMER2,262112
_TIMER1=_TIMER0
RETURN FLOOR((_TIMER2+_TIMER0)/32.73)
END
◎MWAIT命令
DEF MWAIT W
VAR A,B
A=TIMER()
WHILE B-A<W
B=TIMER()
WEND
END
https://twitter.com/ochame_nako/status/577083372708593664
◎BEEPOFF命令
DEF BEEPOFF
FOR I=1 TO 8:BEEP,,0:NEXT
END
https://twitter.com/ochame_nako/status/579287498758598656
◎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.2THEN B=VAL("78043519"[A])+1
RETURN B
END
https://twitter.com/ochame_nako/status/572034389917368320
◎ADAM()関数
DEF ADAM(S,P,Q)
VAR A,I
RANDOMIZE 0,S*PO(2,31)
FOR I=1TO P:INC A,RNDF():NEXT
RETURN A/P*(1-Q)*RNDF()*Q
END
https://twitter.com/ochame_nako/status/572035121143926784
◎VARCOLOR命令
DEF VARCOLOR C1,C2,TM
SPSET 0,1480
SPSCALE 0,54,30
SPOFS 0,-16,0,-256
SPANIM 0,"C",1,C1,-TM,C2
END
◎SETCOLOR命令
DEF SETCOLOR C
VARCOLOR C,C,1
END
◎FADEIN命令
DEF FADEIN TM
VARCOLOR RGB(255,0,0,0),RGB(0,0,0,0),TM
END
◎FADEOUT命令
DEF FADEOUT TM
SPCOLOR 0 OUT C
VARCOLOR C,RGB(255,0,0,0),TM
END
https://twitter.com/ochame_nako/status/578954829558501376
◎SUFFIX()関数
DEF SUFFIX(V)
VAR A
A=V==0!=3
IF A THEN V=0.5:A=V*4-A
RETURN A
END
◎BTNUM()関数
DEF BTNUM(B$)
VAR B,I,K
FOR I=0TO LEN(B$)-1
K=INSTR("↑↓←→ABXYLR",B$[I])
B=B+POW(2,K)*(K>=0)
NEXT
RETURN B
END
◎BTWAIT命令
DEF BTWAIT B
VAR BT
IF SUFFIX(B)THEN BT=BTMUM(B)ELSE BT=B
REPEAT UNTIL BUTTON()!=BT
REPEAT UNTIL BUTTON()==BT
END
https://twitter.com/ochame_nako/status/579675181670477825
※各命令や関数の動作確認用サンプルプログラムはリンク先を参照
まず、SLEEP()関数について書いておくにょ。
プチコン3号では本体がスリープした時には動作が停止する仕様になっているにょ
そのためスリープしたかどうかは判断ができないにょ。
それを判断する方法として普通に思いつくのは内蔵時計機能を使ったものにょ。
内蔵時計は常時動作しているため時計の動きをループ内で監視しておいて前回の時刻から
2秒以上経過した場合にはスリープしたかどうかが判断可能になるにょ。
ただし、この方法だとスリープしたかどうかを判断するには本体を開いてから1〜2秒の
時間が必要になるにょ。
しかし、スリープから復帰した直後は処理落ちするというのを知っていれば内蔵時計を使わず
一瞬でスリープしたかどうかを判定可能になるにょ。
それがこのSLEEP()関数にょ。
プチコンmkIIでも似たようなルーチンを作ったけどプチコン3号は自作関数が使えるように
なったためスリープチェック専用ルーチンで判断するのではなくメインルーチンから常時
判断を可能にしたょ。
ただし、その判断を正常に行うためにはSLEEP関数の中にVSYNCを入れる必要があったにょ。
そのためSLEEP()関数を単純にメインルーチンに追加するのではなくメインルーチン内にある
VSYNC 1をSLEEP()関数に置き換えて使うことを想定しているにょ。
もしも、VSYNC 1をSLEEP()関数に入れない場合はあらかじめグローバル変数CNTを定義して
おいてCNT=MAINCNTとSLEEP()の間にVSYNCを入れる必要があるにょ。
それだと初心者だと使い方が分からない(もしくは使い方を誤る)場合があるし、関数その
ものの使い勝手もあまり良くないためこのような仕様になっているにょ。
これを使ってスリープしたか否かではなくスリープしている時間の方を知りたいという人も
いることだと思うにょ。
これは秒単位だと簡単にょ。
というのも、内蔵時計をループ内で監視しておいて単純にスリープ判定が行われた時に
前回との差分を計算すればいいにょ。(当然ながらその結果には±1秒の誤差がある)
このスリープ判定を瞬時に行うSLEEP()関数があるお陰で1〜2秒待つ必要はなく瞬時に
スリープしていた時間を知ることができるにょ。
では、秒単位よりさらに細かい値(フレーム単位やミリ秒単位)で知りたいという場合には
どうするかというと少し工夫が必要になるにょ。
まずは内蔵時計の秒数が変わる瞬間のMAINCNTの値を変数に入れておくにょ。
これはプログラムを起動時に1回だけ行ってもいいけどVSYNC 1はぴったり1/60秒ではない
ため起動している時間が長ければ長いほど誤差が出てくるにょ。
そのため1秒ごとに初期化処理を行うのが望ましいにょ。
スリープから復帰した直後のMAINCNTの値の差分を計算すれば良さそうだけどスリープ中は
MAINCNTの増加は停止しているため差分を計算しても意味はないにょ。
しかし、スリープ中はMAINCNTが停止ているということは次に秒数が変わる瞬間のMAINCNTの
値との差分を取れば1秒未満の部分のスリープ時間は分かるにょ。
あとは秒数の差分をそれに加算すれば1フレーム単位の値が求めるにょ。
といっても、スリープから復帰直後は処理落ちしているためそれで求めた値には数フレーム
(最大で5フレーム程度)の誤差があるにょ。
MAINCNTの代わりに下記のTIMER()関数を使えばミリ秒単位のスリープ時間は分かるものの
誤差が5フレームあったらミリ秒単位で結果を返してもあまり意味はないにょ。
1/1000秒単位(1ミリ秒単位)の時間を取得可能なTIMER()関数の説明をする前にプチコンは
初代からプチコン3号まですべてにおいて取得可能な最小単位が1フレーム(1/60秒)と
なっているにょ。
確かに初代プチコンではそれでも良かったけどプチコン3号(New3DSで実行時)には初代と
比べて45倍くらい高速になっているにょ。(プチコン3号の公式ベンチであるSPEED TESTを
初代プチコンに移植して比較した結果)
これは言い換えればプチコン3号の1フレームは初代プチコンの1秒に近い感覚であるという
ことにょ。
その結果1フレームが最小単位では大きすぎると感じてしまう人も少なくないにょ。
twitter上でも要望として出ていたので作ったのがこのTIMER()関数というわけにょ。
プチコンmkIIでもBGMPLAYを使って1フレームより短い時間の取得は可能だったけど処理の
重さや精度を考えるとほとんど使う意味は感じられなかったにょ。
プチコン3号ではマイク命令を使うことで負荷をほとんど掛けずにそこそこ高い精度で
mkIIと比べて遙かに簡単なやり方で1ミリ秒単位の時間を取得可能になるにょ。
精度を重視するならばサンプリング周波数は最大の32730Hzがベターにょ。
32730Hzで実行時にはMICPOSは約8秒でループしてしまうため最大8秒しかカウントが
できないにょ。
これではさすがに不便なので前回のMICPOSとの値を比較して小さくなっているならば
8秒経過したと判断してMICSIZEである262112という値をインクリメントしているにょ。
これだと8秒に1回はTIMER()関数を実行する必要があるけどループ内で常に実行して
いれば特に気にする必要はないにょ。(8秒を超えてしまったら正しくカウントが
できなくなるためその場合はTIMERSTARTで初期化して使うのがベター)
MICSIZEではなく262112という定数をインクリメントしているのはループ中に常に使用
することを想定しているため速度を重視した結果にょ。(まぁ500ナノ秒くらいの高速化
なのでほとんど気休め程度の高速化だけど)
TIMER()関数の使い方について書いておくにょ。
まず最初にグローバル変数_TIMER0、_TIMER1、_TIMER2を定義しておく必要があるにょ。
これはサンプルプログラム1行目に書いているようにVARで定義しておけばいいだけにょ。
VAR _TIMER0,_TIMER1,_TIMER2
OPTION STRICTを使用してない状態でグローバル変数をわざわざ定義するのはDEF内では
グローバル変数とローカル変数の両方が使えるためにょ。
そのグローバル変数とローカル変数の使い分けは下記の通りにょ。
◎VARで定義された変数の場合
DEF外で定義された変数・・・グローバル変数
DEF内で定義された変数・・・ローカル変数
◎VARで定義されていない変数の場合
DEF外でも使用されている変数・・・・グローバル変数
DEF内でしか使用されてない変数・・・ローカル変数
つまり、定義された場所や最初に使われた場所でローカル変数かグローバル変数かが
決まるということにょ。
したがって、VARで定義してなくても問題ないとはいえ、確実にグローバル変数として
処理をしたい場合(確実にローカル変数として処理をしたい場合)はVARを使って
明示的に処理をしておいた方がいいにょ。(初期バージョンのプチコン3号ではDEFで
使用できる変数の処理にバグがあったためVARで定義したらグローバル変数と同じ変数名の
ローカル変数が使えず事実上ローカル変数としてまともに機能しない状態だったけどそれは
ver.3.0.2で改善されている)
まぁ、DEF外ではローカル変数は使用できないため明示的に処理するというのはあくまで
DEF内に限った話だけどVARによる定義をすべての変数において行うつもりならば最初から
OPTION STRICTを使用しておくのがベターにょ。
TIMER()関数を使用するためにはまずTIMERSTART命令を実行する必要があるにょ。
これによってマイク命令を使用可能にしてカウンタに使用する変数の初期化処理を行って
いるにょ。
TIMER()関数ではこのTIMERSTART命令が実行されてからの時間を取得できるにょ。
そのためTIMERSTART命令は最初に使うか上記のように8秒を超えた場合にまたカウンタを
初期化したい場合に使用することになるにょ。
問題は測定精度だけど32730Hzのサンプリングであっても正確な値ではないにょ。
長い時間(数秒)であればある程度安定した値を示すけど1フレーム以下になると誤差が
それなりにあるにょ。
1フレームは概ね16.6ミリ秒だけどこのTIMER()関数では16ミリ秒前後の値を示すにょ。
17ミリ秒と15ミリ秒の頻度から考えると最大で±0.8ミリ秒くらいの誤差がありそうにょ。
1フレームより短い時間の測定だとさらに誤差が大きくなることが予想されるにょ。
秒単位の測定だと安定しているといってもそれはVSYNCと比較した場合にょ。
ただし、これも私の環境だと常に+0.3%くらいの誤差が発生しているためリスト内の
32.73は32.81の方が正確な値になるにょ。(これだとVSYNC 300が5001ミリ秒前後を示し
VSYNCと比較した場合の誤差は0.1%以下になる)
とはいえ、プチコン3号の場合はVSYNCそのものが正確な値ではないからTIMER()関数は
正確な値を示すと思わない方がいいにょ。(そもそも1フレームはぴったり1/60秒でも
ないわけだし)
MWAIT命令はTIMER()関数を流用して作ったものにょ。
MWAIT 10だと10ミリ秒のウェイトになるにょ。
1フレームより短いウェイトって使う機会があるのかと思うかもしれないけど表示を
あるていどゆっくり行いたいけどWAIT 1では1秒間に60回しか処理できなし、
FOR〜NEXTなどによるウェイトではNew3DSと旧3DSで速度差が出るため機種に依存せず
短いウェイトを行うというのはそれなりに意味はあると思うにょ。
ちなみにMWAIT 1000の実行時間をTIMER()関数で測定するとぴったり1000ミリ秒に
なるにょ。(まぁ当然だけど)
個人的には1フレームより細かい時間を取得しても使う用途が極めて限られると思うにょ。
それはプチコン3号が表示も入力も1フレーム単位で処理されているからにょ。
したがって、それより短いタイマーがあっても何に使うかは私は考えてしまうにょ。
例えば「300fpsのゲームを作りたい」という場合に有用かもしれないけど結局はボタン
入力や表示のタイミングをVSYNCに合わせる必要があるためあまり意味がないにょ。
それならば1フレームに5回実行して擬似的な300fpsと変わらないからね。
むしろ、そっちの方がタイマー制御する必要がない分だけ簡単だし、短くなるためメリット
だらけにょ。
仮に5回の処理を1/120秒(1フレームの半分)で実行できたとしたら擬似的な300fpsだと
挙動がおかしくなりそうだけどそんなことはなく1回の処理をぴったり1/300秒で終わらせた
場合とプチコン3号上では何ら違いはないにょ。
これは上記のように入力も表示も1フレーム単位で処理されているためにょ。
それでも、MWAITの方は機種依存のないウェイト(旧3DSとNew3DSで同じ長さのウェイト)と
いうことでそれなりに使えるのではないかと思うにょ。
次にBEEPOFF命令について書いておくにょ。
プチコンのBEEPは初代から各音色の高さは変えられるけど鳴る長さは変えることができない
けどそれは3号であっても同じにょ。
しかし、鳴る長さを長くすることはできなくても強制的に短くすることはできるにょ。
それが、BEEPOFFにょ。
原理はすごく単純で無音BEEP(音量0のBEEP)を8回鳴らしているだけにょ。
プチコンのBEEPは8音までしか同時に鳴らせないので無音BEEPを8回鳴らせばそれよりも前に
鳴らしているBEEPは強制的に鳴るのを終了させることができるにょ。
さて、これが何に使えるかというとまた微妙な感じにょ。
私が初代で作ったプチコンMMLではこのBEEPOFF命令と同等の機能を使ってスタッカート処理の
実現をしているにょ。
プチコンMML
http://ochameclub.web.fc2.com/petitcom/mml.htm
プチコンを初代から使っている人は分かると思うけど初代ではユーザーが作った曲を鳴らす
機能(MML演奏機能)は備わってなかったにょ。
そのため各ユーザーは自らの手でBEEP命令を駆使してMML演奏プログラムを作っていったにょ。
その1つが私が作った「プチコンMML」にょ。
これはすでに作ったOMPがポケコンで作ったものをプチコンに移植しただけであり処理速度と
プログラムリストの短さを重視したものとなっているためMMLそのものは使いやすいかどうかは
微妙なものになっているにょ。(とはいえ、ポケコンにおいては処理速度が速く短いという
のは極めて大きなメリットだった)
音階演奏ソフト OMP
http://ochameclub.web.fc2.com/petitcom/1page.htm#omp
とりあえず、このBEEPOFF命令の使用例としてはこんなものがあるにょ。
BEEP 78:WAIT 5:BEEPOFF
これを実行すると「や」という掛け声が鳴るにょ。
格ゲーで使えそうな掛け声が欲しいというのがMiiverseであったのだけどこんな感じで既存の
ボイス音を短くするだけでいろいろな言葉を発することができるにょ。
本来はできなかったBEEPを強制的に停止させるというのは便利そうだけど今ひとつ使用する
用途が思いつかないにょ。
単にBEEPで使用されている音色を短く使いたいというだけならばBEEPOFF命令は使用せずとも
プチコン3号の場合はBGMPLAYでBEEPで鳴らせる音色が揃っているため音長とQコマンドを駆使
すればいくらでも可能にょ。
BGMPLAY "@379Q4C"
プチコンmkIIでは逆にBGMPLAYで使用可能な音源はBEEPで鳴らすことができたのでBEEPの方が
圧倒的に音色の数で優位に立っていたにょ。
次にDSTICK()関数について書いておくにょ。
これはMiiverseでスライドパッドを十字ボタンの代用として使用したいという声があったので
作ったものにょ。
十字ボタンの代用にするならばまずは8方向移動をさせる必要があるにょ。
方向はATANで分かるためそれを単純にDEGを使い0〜360°に変換した後に45で割るという方法が
あるけどそれでは真横や真上(真下)への移動は非常に不安定になるにょ。
45°というのは基準点から±22.5°にする必要はあるにょ。
これは簡単で単純にDEGで0〜360の値にした後に22.5を足すか引けばいいだけにょ。
ただし、22.5を足す場合には360を超えたかどうかを判断する処理が必要になるにょ。
それは、「382.5」(360+22.5)を足して「MOD 360」とすればいいだけにょ。
この0〜360の値を45で割れば0〜7という値になるにょ。(実際はこの後FLOORを使って整数化
する必要性があるけど今回のプログラムでは小数部分が無視されるためそれは不要)
この0〜7という値が出てしまえば8方向移動は可能なのだけど0は右側で反時計回りに数字が
増えていくという現状の値は分かりやすいけど十字ボタンの代用にするには使い勝手が
悪いにょ。
というわけでBUTTON()関数における十字ボタンと同じ値を返すことにしたにょ。
それは8通りをIF文で判断する必要なんて全くなくて B=VAL("78043519"[A])+1 でいいにょ。
78043519って何のことか分からないけど1桁ずつ取り出して1をプラスすればこれは各方向の
BUTTON()関数が返す値であることが分かるにょ。
その1桁ずつ取り出すやりかたはmkIIまでだとMID$を使うのが普通だったけどプチコン3号は
文字列を配列のように使えるため1文字ずつ取り出すのは上記のように非常に簡単になって
いるにょ。
DSTICK()関数は返す値がBUTTON()関数と同じ値なのでB=BUTTON() OR DSTICK()とすることで
十字ボタン用として作ったプログラムがそのままでスライドパッドによる動作が可能に
なるにょ。
もちろん、十字ボタンとスライドパッドの好きな方を使えるにょ。
ただし、このプログラムでは十字ボタンとスライドパッドの両方を同時に操作するという
場合は考慮されてない(十字ボタンが上でスライドパッドが右ならば右上と判断されるのは
許容できても本来はできない上と下の同時押しもできてしまうため片方を無効化する必要が
ある)のでその処理をするならばBUTTON() AND 15の値が0かどうか、DSTICK()の値が0か
どうかを判断してやればいいだけにょ。
これはDSTICK()関数内でやるとせっかくの短い処理が冗長になってしまうため必要ならば
各自で実装して欲しいにょ。(同時に押した場合はスライドパッドと十字ボタンのどちらを
優先すべきか各自の判断に任せた方がいいし)
次にADAM()関数について書くにょ。
これは端的に言えば乱数のばらつき方をコントロールできる関数で例えばネームバトルゲーム
とか能力値自動生成のRPGなどを作る際に極端に強い(弱い)キャラの出る確率を減らす
ことができるにょ。
どういうことかというと普通にRND(100)で100通りの乱数を発生して値が大きい方が強いと
した場合には例えば能力値がだとすると能力値50の平均的な能力のキャラであっても能力値
MAXである99のキャラも同じ確率で出てしまうので能力値の高いキャラ(低いキャラ)は
それなりに出にくくした方が強いキャラが出来たときの喜びが段違いになるにょ。
この際に端の方だけ出にくくするというのは簡単そうで難しい(端の値を出にくくすると
中央付近の値しか出にくくなる)ためそれを可能にするのがADAM()関数にょ。
実はこれは昔ポケコン用につくったADAMルーチンを移植しただけにょ。
ネームバトル系ゲームの最終兵器!? ADAMの謎に迫る!!
http://ochameclub.web.fc2.com/E500/TECH/ADAM.HTM
上記ページの解説を読めばこのプログラムの意図や原理は大体分かってもらえるのではないか
と思うにょ。
図で描いているのはあくまで理想的な状況だけど実際に発生させた値を見ても中央付近が
概ね平らになってその周辺(特に端の部分)は出にくくなっているというのは分かると
思うにょ。
https://twitter.com/ochame_nako/status/572035927503060993
ちなみにこのADAM()関数ではシードの値(第1引数)は0〜1の値が選択できるにょ。
つまり、名前などから0〜1の値を計算してそれをADAM()関数に渡せば指定されたばらつき方を
行う0〜1の値を返すというものにょ。(最大値99ならば100倍して整数化すればいい)
1ページに投稿できる最大サイズを超えたので下記に続く
|
|
|
掲示板管理者へ連絡
無料レンタル掲示板