*注意*
僕による僕のためのゲーム開発研究です。
第一目的がメモみたいな物なんで
初心者(僕)が憶測で色々物を言ってますので鵜呑みにしない事。
書き終わってから調べたりとか平気でします。
ここで考えてからそれを実装したりします。
あとVC++使ってます。
そろそろ公開すっか
2010-11-12
関数がエラーを返した時、なんどか試してくれるエラー対策関数
「エラーの時もう一度挑戦してみる」って言うエラーパターンを汎用的な関数で表現するには関数ポインタを使わないと難しい気がして関数ポインタをちょこっとだけやってみたんですけど
関数ポインタでも型を指定しないといけないので、その関数の引数を渡すのになんやかんやで実現は難しそうと思ったんですけど。
考えてみたらマクロはコンパイル前の文字列レベルでコードを置換してくれるんですよね。
なんとなく、マクロだからといっても関数は関数。関数内の出来事は周りに影響を与えないのが鉄則だろうと信じ込んでいたんで、マクロ関数の引数も型がないだけで普通の関数と変わらない動作をするもんだと思ってしまっていたんですけど。
マクロ関数の仮引数は内部で同じ名前の識別子があった場合、それを実引数に置換してからコンパイルしてくれます。
#include <stdio.h> #define defFUNC(paramA,paramB) \ {\ paramA = paramB;\ } void stdFUNC(int paramA,int paramB){ paramA = paramB; } #define FUNC defFUNC //#define FUNC stdFUNC int main(){ int argA = 0; int argB = 100; FUNC(argA , argB); printf("argA=%d\n",argA); printf("argB=%d\n",argB); return 0; }
これはコンパイル直前でmainはこんな置換されてからコンパイルされることになります。
int main(){ int argA = 0; int argB = 100; argA = argB;//ここがdefFUNC(argA , argB); printf("argA=%d\n",argA); printf("argB=%d\n",argB); return 0; }
なんで、最終的に実引数のargA の値も変わってしまってます。
いやぁ、あんまり気にしなかったけど強力ですね。
どうやらテキストレベルでコードを置換してるので、ここに関数を渡してやると、普通の関数では戻り値が渡されるわけですけど、関数の呼び出しの命令ごと渡せるんじゃないかということに今更ながら気づきまして
コレを利用して、失敗したらもう一回やってみるマクロ関数を作りました。
#include <stdio.h> #define _TRY(safeval,Trynum,func,funcsafe) {\ for(int i=0;((Trynum>i)&&(!safeval));i++){\ safeval = func==(funcsafe);\ }\ } int test(const int prm){ printf("%d回目。",prm+1); if(prm < 5){ printf("・・・しっぱい\n"); return -1; } printf("・・・せいこう!\n"); return 0; } int main(){ bool safe = test(-1)==0; _TRY(safe,10,test(i),0); if(safe ==true){ printf("成功したよっ\n"); }else{ printf("10回試したけど失敗したよ・・・\n"); } return 0; }
_TRY(safeval,Trynum,func,funcsafe)
safevalには最初にfalseが入ってるbool変数を渡します。別にいらないっちゃいらないんですけど、マクロ関数の戻り値的なのってどうやって出来るのわかんないので何度も試した挙句しっぱいしたのかどうかを受け取る変数になります。最初の試行が成功したかどうかを入れればお得だしと思って。
Trynumには試行回数を入れます。
funcには関数呼び出し命令を丸ごと入れます。普通の関数の引数ならコピー時に実行されて戻り値がコピーされるけれど、関数ポインタのように関数呼び出しそのものが入ります。
funcsafeには成功した時の関数の戻り値が入ります。この値と関数の戻り値を比べて成功したかどうかを判断します。
safevalに入れた変数をみて最終的に成功したのかどうかを判断できます。
マクロ関数の特徴として
置換された時点で整合性が保てればスコープ内に無い変数も引数に渡せます。
test(i)のiみたいに。
逆に一見正しそうでも置換された時点で整合性が保てなければエラーになっちゃいます。
safevalに定数を渡すとエラーになったりします。
なんで、言語のルールをひん曲げてしまうような関数が作れてしまうわけで、注意が必要なんですね。
実際引数に関数を渡す時のルールをひん曲げてしまってます。
誰が見ても理解できるコードではなくなってしまう危険をはらんでいるわけですね。
この辺に闇プログラマの入り口がありそうです。よろしくお願いします。
でもコレと同じ機能の関数を関数ポインタでやろうとしたら一気に難易度あがるんだぜ?そうでもないのかな?
ちょっと関数ポインタっぽくするようなマクロ関数で考えてる作戦はあるんだけど試してみようかな。
ともかくこれでブラックボックスな関数がエラーを返したときに、偶然なのか必然なのか分からないけどもう一度試してみる。そんな処理を1行でかけるようになりました。
ちなみに僕の作業範囲内ではこのブラックボックスな関数はDXライブラリの関数なんだけど、いまんとこ原因不明のエラーが返ってきた事は無いです。気づかないだけかもしれないけど。
(追記)
その作戦ですが20秒で実装できるくらい簡単なんで試したら出来ました。
#define _TRY(safeval,Trynum,funcsafe,func,...) {\ safeval=false; \ for(int i=0;((Trynum>i)&&(!safeval));i++){\ safeval = func(__VA_ARGS__)==funcsafe;\ }\ }
僕の実際にやってる実装では初回エラーの回数をカウントしてるので初回の試行は分けなければいけない仕組みにしてますけどsafeval=false;を加えておけば成否を受け取るためだけのbool変数を渡してもっと短くかけますね。Trynumに0を渡すと何もしてくれないですけど。
可変長引数は色々面倒そうなので避ける傾向にあるんですけど
マクロ関数の可変長引数は驚くほど簡単なんで関数の引数部分を分けるだけで出来ました。
マクロの可変長引数はなにも指定しないと動いてくれないとかなんとか聞きましたけど、僕の環境では...の部分を空っぽにしても動いてくれました。
funcに渡すのは関数名なので、コールバックに錯覚できますし
可変長の部分は、実際のコールバックでは引数はvoid*型のオブジェクトでやり取りするんだろうと思いますけど(知らないけど)
可変長引数で渡して内部でコールバックの引数を取得する方法もおそらく(僕には出来ないけど)実現できると思います。
なんでこのマクロ関数は普通の関数で(多分)表現できる範囲かと思います。
iという名前の識別子を渡すと狙った動作にならなくなりますが・・・
でもまぁこれで文法を著しく捻じ曲げるような事はなくなってると思うんですけどどうでせうか?
やっぱり戻り値は表現できなかったです。
2010-10-29
ペケポンガード(2)
パソコン初めて触ったときからペケポン言ってるけど、特殊な呼び方みたいですね。
WinProcとか調べてなんとかペケポンとAlt+F4の終了処理を分離できました。
多分コレで、うっかりペケポン押しても大丈夫だと思います。
うっかりAlt+F4押すような人にはあきらめてもらいます。
#include "DxLib.h" //メッセージボックス #define MB_Q(str) MessageBox( NULL, str , _T("確認") , MB_TOPMOST |MB_ICONQUESTION| MB_OKCANCEL ) //ウィンドウズへ渡すメッセージに割り込む //フックというらしいけどどの辺に割り込むのか怪しい LRESULT CALLBACK HookWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ int id; switch(msg){ case WM_SYSKEYDOWN://ALTを押してる時 //F4を押した瞬間 if(wParam == VK_F4){ DestroyWindow(hWnd); } break; case WM_CLOSE://「ウィンドウ閉じろ」という命令がきたとき id = MB_Q(_T("ペケポンが押されました。終了しますか?")); if(id == IDOK)//キャンセル押したら何もしない DestroyWindow(hWnd); break; //興味のないメッセージはウィンドウズ先生に任せる //いまのとこあってもなくても変わんない動作 // default: // return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; } //メイン関数 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { ChangeWindowMode(TRUE); //コレ設定しないと分岐作っても結局終了する。 SetWindowUserCloseEnableFlag(FALSE); DxLib_Init();//初期化 //割り込むメッセージの関数(プロシージャとか言うらしい)を仕込む。 SetHookWinProc(HookWinProc); SetDrawScreen( DX_SCREEN_BACK ) ; //メッセージ処理。GetWindowUserCloseFlagで分岐すべき処理はHookWinProcでやってるのでいらない。 while(DxLib::ProcessMessage() == 0){ DxLib::DrawString(100,100,_T("Alt+F4だと終了して、ぺけぽんだとメッセージ後終了テスト"),GetColor(255,255,255)); DxLib::ScreenFlip(); ClearDrawScreen( ) ; } DxLib_End() ;// DXライブラリ使用の終了処理 return 0 ;// ソフトの終了 }
簡単に説明すると
ウィンドウズへウィンドウ閉じなさい的な命令を通してプログラムが終了します。
Alt+F4が押されたときはこの命令をすっ飛ばして直接「実際にウィンドウを破棄するメッセージ」へ飛びます。
ウィンドウ閉じなさい的な命令をすっ飛ばしていいのか不安ですけど、
各解説サイトではこのウィンドウ閉じなさい命令をフックした内容は結局「実際にウィンドウを破棄するメッセージ」へ飛ばしてるだけだったんで、まぁいいのかなと。
またウィンドウ閉じなさい的メッセージは
今回WM_CLOSEで捕まえてますけど、
WM_SYSCOMMANDのパラメータSC_CLOSEでも捕まえられるんでどっちがいいのか分かりません。
またWM_SYSKEYDOWNを横取りしてる形なんだったら他のAlt+の命令が無効になっちゃうんじゃないかといった疑問もあります。
コレはずしてAlt押しながらF4以外を色々押したけど何もなかったので気にしなくてもいいのかもしれないけれど。
そしてSetWindowUserCloseEnableFlag(FALSE);を設定しておかないといくら弄っても結局終了しちゃいます。
終了しますか?⇒キャンセル
で結局終了しちゃうんでこの設定は必須っぽいです。
DxLibでもプロシージャを弄っていてそっち側で終了が呼ばれるんだと思いますが、どうでしょうか。
とりあえずコレが今の僕の最高解です。これできちんと正常に終了できるのか怪しいところですが確かめようがないのが難点。
ペケポンガード
RubyとかPythonとかなかったことになんねぇかなぁ・・・
DXライブラリで作ったアプリケーションはウィンドウバーの右上の閉じるボタンをクリックするか、Alt+F4を押すかすると、終了します。
サンプルで多いESCでの終了はアプリケーションで判断するけど、上記の処理はウィンドウズのメッセージで処理するので、より強制終了の意味合いが高い。そんな印象です。僕は。
ところが、コレをうっかり押してしまう悲劇もあるかと思います。
Alt+F4をうっかりで押してしまう間抜けはそういないと思いますが、右上のペケポンをうっかりクリックしてしまう間抜けは僕を始め多いかと思います。
ということで、この終了の処理をアプリケーション側で決めるためにウィンドウズのメッセージから切り離す関数がありました。いつも忘れてしまうのでメモ。
SetWindowUserCloseEnableFlag(FALSE);
でペケポンの処理を切り離す。
以降ペケポンが押されたときの処理は
int GetWindowUserCloseFlag( int StateResetFlag );
でペケポンが押されたときTRUEを返すので、ペケポンが押されたかどうかを判断する。
StateResetFlagはTRUEで何事もなかったかのように次からはペケポンが押されない限りはFALSEを返すようにし、
FALSEでペケポン押してからもずっとTRUEを返す。
単純に終了するかしないかのメッセージボックスだしたいだけなら
GetWindowUserCloseFlag( TRUE )
でOKなはず。
前の僕のソース見たらこうなってた。ループごとに呼び出す処理内。
//ペケポンとかの終了処理をチェック if(GetWindowUserCloseFlag( TRUE ) == TRUE){ int mb_mess = MB_Q( "ペケポンが押されました。終了しますか?"); if(mb_mess == IDOK) return 1; }
MB_QはDRAGMENTS LOAR と言うRPGがソース公開してたからみたら
メッセージボックスをマクロにして便利って書いてあったんで、真似した奴だったと思う。
とりあえずこれでいいんだけど、この関数だとAlt+F4押しても同様にメッセージボックスで終了するかどうか聞かれてしまうわけで、強力な強制終了の手段をなくしてしまうのは勿体無いわけで、Alt+F4は依然強力な終了手段として残しておくにはどうしたら良いのか・・・
WinProcがどうとかでやれるらしいけど、それがわかるんなら苦労はしないわな・・・
2010-10-17
泣いた
どれだけ探してもC++からruby使う方法を教えてくれるサイトはほとんど無いか、あっても序盤だけで以降はなかったことになってるのに
pythonはいっぱいあって笑った。
その後泣いた。
色々調べてみるとCからruby使うって言うのは誰もやらないみたい。
GCとかなんだとかで問題も多いけど、多言語との連携はrubyよりも大体pythonでやってるからみんなpythonでやってるみたい。
誰もrubyでやらないからそんなに教えてくれるところも無い。
ゲームスクリプト的には結局luaやれって話が多いみたいだけどまぁそれは良いんだ。
こうなったら意地でもruby使いたいんだけど、pythonもやってみようかな・・・なんつって。
休日つぶしてrubyやってんのにこれじゃ浮かばれねぇよ・・・
2010-10-16
やばい
RubyからCの関数を呼ぶのは良くやるみたいだけど
CからRubyを呼ぶのはあんまりやらないみたい。
CのAPIがあるからいけんだろうと思ってたけど、情報が少なすぎる。
僕が見つけてないだけなのか・・・
リファレンスは不親切すぎる。
語順に並べてあるだけだしもぉ。
どうしよう・・・
今やってること
- 主にDXライブラリの描画系の関数の速度テストに使用する速度計測プログラムをパラメータをrubyで定義して使う用に作り直す。
これからすること
- luaやXtalまたはそれ以外でもなんでも良いから、それで作られたゲームスクリプトのソース探してみる。
- 速度計測プログラムをちょっとずつrubyよりにしていく。
- 前に言ったpyファイルが使われてるゲームのソースのバックアップはとってたはずだから探してみて、膨大な量だけどどうやって使ってるのかちょっと探してのぞいて見る。
- Cからrubyを使用してるソースコードを探してみる。見当つかない。
- とっかかりとしてRubyからCの関数を呼ぶ方でなんか作ってみる。やるならC#かも。
まだあきらめないもんっ
ゲームスクリプトとしてのrubyの運用法を考える(2)
ゲームスクリプトを自作しようとするとパーサがどうとか構文解析がどうとかまったくもって理解ができなさそうなレベルの話が展開されます。
ある意味でデータを読み込んて加工した時点で解析してるわけなんで、とても低いレベルで僕も似たようなことをやっているのかもしれませんが。
そういうのがまずできないから僕はrubyにお願いしたわけなんですが、それでもまったく無視できないんだろうなとは思います。
rubyをやってみようと思っただけで僕はインタプリタの仕組みなんてまったく知りませんけどそれはちょっとまずいかもしれません。
前々回、STGで毎フレームすべての弾幕がスクリプトを読み込んでも速度に問題は無いか?なんて事を言いましたけど
C++からスクリプトを呼ぶわけなんですけど、あくまで予想ですが、
スクリプトはテキストで書かれているんで、まずテキストを読み込んで
これを誰かが解析して、さらに実行できる形式に変換して
そんで実行している。
こんな流れでしょうか?
これが正しいとすれば外部に最初から実行できる形式でおければ解析の手間が省ける気がするんですけど
中間コードって言うんでしょうか?バイナリだろうから配布時に中身が見られる心配もある程度減るわけで、さらに解析の手間が省けるからもう少し早くなるんじゃないかと思うんですけど
あさっての方向の意見なんでしょうか?
この辺はどうやって調べたら良いのか見当もつかないんだけど
できるだけスクリプトの実行回数を減らすような設計をするべきか
最初に使用するスクリプトの解析済みのバイトコードをメモリにキャッシュしてしまうとか、そういう方法があるならそういう設計にすべきか
そもそも、どっちにしても速度に違いはないのか
どうしたもんかな、調べるだけ調べてみるしかないよな・・・
ゲームスクリプトとしてのrubyの運用法を考える(1)
そもそもrubyはゲームスクリプトでもなければそれに特化したものでもないわけです。
大御所のluaとかXtalみたいなゲームに特化したスクリプトとの違いも僕には正直良くわかりません。
結構前にluaは挑戦したことがあるのだけれど、スタックで操作する感覚がいまいちピンとこなかったです。
luaに挫折した後Xtalとかに挑戦しなかったのはrubyかpythonをやってみたかったって言う理由以外にないです。
さらに言うとlua以外のゲームスクリプトはあんまり解説が多くないってのもあるんで、まずluaを覚えないといけないのかなと思いまして、lua挫折したのに・・・
前にみたあるゲームのソースでは.pyファイルが使われていて、中身はどうなってたのかしりませんが、インタプリタ言語をはじめて近くに感じた瞬間が出発点です。
なぜrubyなのかといいますと2Dと3Dで完全にカテゴライズされるけど、高さの概念がある2Dのゲームを作りたいのに2Dと3Dの中間のゲームについての議論が少ないと思って、確かにそういうゲーム自体あんまり無いですけど、色々探し回っていたらあるrubyユーザーの人が同じ事を言ってるのを見かけたからです。それだけです。別にRPGツクールで改造ruby使ってるからとかじゃないです。
わき道にそれましたが、ゲームスクリプトとしてrubyを使用するのに別に深い意味が無いんで、いざとなったら別の方法を模索する可能性はまだまだ十分にあるという事です。
まずゲームスクリプトにしてほしい事としてほしくない事を決めていきたいと思います。
rubyはそもそも完成されたプログラミング言語なんで言ってみればその気になればなんでもできるでしょう。rubyで作ったゲームをC++から呼ぶなんていうのは本末転倒なわけで、ゲームスクリプトの仕事、プログラムの仕事を明確に分けるべきだと思いました。
ぱっと思いつく限りゲームスクリプトの仕事は
- 台本
- 演出
- データ管理
- バランス管理
- AI
このくらいでしょうか?
rubyは何でもできすぎてしまうのでゲームスクリプトとしてはこれだけしかできないようになってほしいです。
制限を与えるわけだけどどうやって与えるのかはちょっと・・・
コマンドを関数で作ってそれをマクロにしてマクロ以外は使わないでください?的な?・・・
それはruby勉強しながらおいおい。
さらにプログラミングよりの話になってしまうけど、理想としては
「オブジェクトの生成は行わない」
これは前提にしたいです。
プログラムが、スクリプトから発生するオブジェクトを気にしないといけない状況は
なんとなくいやな予感がするので、
なんで、今のところスクリプト中に生成したオブジェクトはそのスクリプトが終わるまでに消滅するというルールを課しておきます。
スクリプトから受け取る変数なり値なりは、あらかじめプログラム側で作っておきます。
実際やってみないと良くわかりませんが・・・
プログラムはスクリプトを呼び出す事だけに専念してほしい感じです
あとは並列処理についてですか?
これは良くわかんないなぁ、必要になったら考える感じで。
RubyをC++で使ってみる(3)
結局C++への組み込み方はSQLiteと大体一緒でした。
ActiveScriptRubyが最初からライブラリとかをコンパイルしてあったんで
その差くらいしかありませんでしたね。
SQLiteのほうはまだ説明してないんですけど
ここを参考にためしにRubyスクリプトをC++から呼び出してみます。
#include <windows.h> #include"ruby.h" #pragma comment( lib, "msvcrt-ruby18.lib" ) void main() { // Rubyインタプリタの初期化 ruby_init(); ruby_init_loadpath(); // スクリプトをファイルから読み込んで実行 rb_load(rb_str_new2("test.rb"), 0); // Rubyインタプリタのクリーンアップ ruby_cleanup(0); }
ソースはほぼコピペです。こういうの初めてなんで問題あったらすいません。
platform SDK を使ってる人?
windowsプログラミングがあんまりわからないからDXライブラリを使ってる人と言い換えても良いかもしれません。つまり僕ですが
そういう人はruby.hの前にwindows.hをインクルードします。
プログラムに詳しい人はその人なりの解決法があると思います。
ちなみにruby.h単体だけのときのエラー箇所はsdkのwspiapi.hです。
テンプレート使ってるところでエラーでます。
で、pragma commentなり、プロジェクトのプロパティなりで、msvcrt-ruby18.libと
リンクします。このライブラリーは名前忘れたけどダイナミックリンクライブラリとリンクする雰囲気のスタティックリンクライブラリみたいなイメージです。僕は。
dllは使わないから良く知らないけど。
なんで、カレントになる所にmsvcrt-ruby18.dllをおいてください。
あとのruby用関数は上記のサイトなり、解説サイトなりを読むべきだと思います。
これから僕も読みに行きます。いい解説サイトがあったら教えてください。
def hello print 'こんにちは!' end hello
こっちは完全にコピペです。
test.rbで保存して、これもとりあえずカレントフォルダに。
これで試したら僕の環境ではちゃんとコンソール画面に
こんにちは!って表示したんで成功です。
やったね!
今後の予定は仕様やできることを調べながら、どういうゲームスクリプトにするかを
つめていきたいです。
たとえばSTGの弾幕を記述したスクリプトを毎フレーム弾幕の数だけ読み込むなんていう処理が軽量に行えるか否かでだいぶ仕様が変わっていくんじゃないかと思います
そういうところです。