資料館/CsharpE3D002

Last-modified: 2011-05-22 (日) 17:08:55

初めに

よし、じゃあさっそく、第0.1回目をやっていこう。え?第一回はいつ始まるのかって?
0.1回と銘打ったんだから、なんとなく察してくれ。
まあ実際のところ、HSPしかいままでやったことがない場合、開発環境を落としただけではスタートラインに立ててすらいないわけだ。サンプルの実行のし方すらおぼつかない人もいるのではないかな?
何事においても基礎学習は大事だ。と言っても、基本的な文法、考え方などはシューティングゲームを作っていきながら適宜解説した方がわかりやすい気がするので、先ずはスタートラインに立って構える、位を目標に、していく。
繰り返すが、この講座は、HSPユーザーが初めてC#に触ったウワァドキドキ*1、という状況を想定している。なおかつHSPがある程度できて、画像や音さえ全部そろっているならば、簡単なシューティングゲームくらい説明なしにこさえてしまう程度の素養があることが前提となる。*2
クラス云々、オブジェクト指向がどうの、ということは後でやると思うので、今日はとりあえず全体の流れを把握する感じでここを呼んでくれ。

この講座でやること

  • サンプルプログラムを実行してみる
  • サンプルプログラムを改変しながらVisual Studioの機能を触ってみる
  • サンプル、およびスケルトンプログラムを用いてプログラムの流れを把握する
  • スケルトンプロジェクトを基に新しいプロジェクトを作成して次回に備える

このぐらいか。なお、教材として、手前共の味噌を使用するので、落としてどっかその辺に解凍してください。

サンプルプログラムを実行してみる

兎角亀毛、サンプルプログラムを実行してみようじゃないか。ダウンロードしたファイルの中に、E3Dsample.slnというファイルがあるはずだから開いてくれ。出てないなんてことはまずないと思うが、画面にソリューションエクスプローラというものが出てくるはずだ。出てこない場合は、上の方のメニューの表示>ソリューションエクスプローラで出てくる。
サンプルは、各サンプル毎にプロジェクトという単位でまとめられている。各プロジェクトをそれぞれコンパイルすると、プロジェクトの数だけexeが出来上がることになるな。これらのプロジェクトを、ひとまとめにしたものがソリューションだ。一番上には、講座用に作ったスケルトンプロジェクトがあるぞ。C#とかは基本的に、画面に「Hello World」と文字を出すまでが長いので、あらかじめひな型を作っておくと楽になる。

e3dhsp3_0を見てみる

ソリューションの中に入ってるプロジェクトが見えたか?プロジェクト名はHSP版E3Dのサンプルのファイル名に対応しているので、中身もそのようになっている。この中の、e3dhsp3_1をみてみよう。ツリーになってるので閉じてたら展開してくれ。拡張子が.csのファイルが全部で3つ2つあるはずだ。

  • Easy3D.cs
    • Win32版easy3d.dllとデータのやり取りを行うプログラムで、変更してはならない
  • Form1.cs
    • 画面に表示するウィンドウ(フォーム)を定義している。プログラム側からいじるので変更しなくていい
      • ホントは必要ないので消すかも。Visual Studioのデフォルトに近付けたんだけどわかりにくいな
    • わかりにくいかもしれないし、不要なので消しました
  • Program.cs
    • 実際に行う処理などが書かれているので、ここを変更する

ライブラリは変更してはいけないので、Program.csを開いてみよう。C#のソースコードが表示されるはずだ。
これからこういったプログラムにチャレンジするわけなので、睨みつけて威嚇でもしておこう。そしたらプロジェクトをコンパイルし、実行してみよう。複数のプロジェクトがあると、HSPみたいに、F5を押して実行、としたときに、どれを実行するか設定しなきゃいけない。めんどかろう。先ずは動かしてみたいだろう。
プロジェクトを実行するには、各プロジェクトのツリーのルート、つまりプロジェクト名を右クリックし、出てくるメニューからデバッグ>新しいインスタンスを開始、を選択する。
00001.jpg

これで動作環境を満たしていればウィンドウが出てくるはずだ。ふた昔くらい前のネコミミが出てきてなにやらくねくねと面妖に踊りだせば成功だ。出てこない場合は、先ずはHSP版のEasy3Dが動作するか、そして、.netFramework2.0が入っているかを確認してくれ。少なくとも俺は動く。ちなみ.netFrameworkは2.0以上が入っていればいい。これは、プログラムの実行時にも必要なので、自分のプログラムを誰かに配布する際、そいつのパソコンにもインストールしてもらう必要がある。
ひとしきり不思議な踊りでMPを吸い取られたら、フォームのバツボタンを押して閉じるか、SHIFT+F5でデバッグは終了する。プロジェクトを右クリックした際、スタートアッププロジェクトに設定すれば、F5を押したときにデバッグが開始されるようになるぞ。

サンプルを改変しつつVisual Studioを触ってみる

よし、あらためてProgram.csを見てくれ。たかだかあれっぱかりの事をやるのに随分と長いコードを書かされたもんだな。しかしVisual Studioは、開発者がラクをするための全力の努力、英知の結晶である。人は怠ける為ならば全力で努力してその手法を開発するのだ。
さて、Visual Studioについている入力支援機能は、HSPのエディタには無いものだ。まずはこれをうまく使ってみよう。

キーワードの入力支援を使ってみる

プログラムは、Mainメソッドから始まる、という決まりがある。このメインメソッドの中で文字列型変数を一つ宣言してみよう。HSPとは異なり、使う前に変数を宣言する必要がある。
static void Main() という部分からMainメソッドが始まっている。その下に改行を一個入れて、sと入力してみよう。
00002.jpg

するとこのように、sで始まる、定義語がずらりと出てくる。説明つきで。これはCtrl+Spaceで意図的に呼び出すこともできる。この状態でキーの上下を押すことで、定義語を選択し、Enterを押せばそれが入力される。が、今回は文字列型であるstringを宣言したいので、このまま続けてt,rと続けて入力していく。
00003.jpg
するとstringが出てきたので、ここでEnterを押せばstringとエディタに入力される。慣れてくると長いメソッド名等もかなりの速度で入力することができるようになる。一応説明も見れる。
ちなみに文字列の宣言は、以下のようにする。
00004.jpg
各コードの最後は;(セミコロン)で終わる、というルールがあるので忘れないように。文法上の明らかな粗相があった場合、コンパイルする前に赤線で『間違ってんぞ』と言われたりすることもある。ではこの文字列変数を、タイトルバーに表示してみよう。コードを下っていくと少し下に

mainwindow.text="~~~";

という一文が見つかるはずだ。これがフォームのテキストを変更している。ここを文字列の代わりに、先ほどの宣言した文字列型変数に置き換えればいい。
00005.jpg
ここでもやはり入力支援が効く。ユーザーが宣言した変数も随時入力支援の候補となってゆく。楽この上ない。
変数は使用前に宣言しなければならない入力の支援機能がある、という二つの理由により、Visual Studio上ではスペルミスによるバグは極めて起こりにくい。HSPでは変数名をミスると新しい変数と認識されて、コンパイルエラーにならないからな。おお怖い。

メソッドの引数入力も支援してくれる

例えば、sigファイルを一つ読み込もうとしたとき。E3D.SigLoadの引数をド忘れすることはよくあることだ。*3HSPならF1を連打しながらソースを書く場面だが、Visual Studioでは入力支援機能としてツールチップが出る。
メソッド自体の説明
00006.jpg

そして入力時には、引数の説明までつく。最初の引数を入力しようとしたら
00007.jpg
このようになり、引数の入力が終わり、,(カンマ)で区切って次の引数を入力しようとすれば
00008.jpg
このように説明がつく。(クリックで拡大)
非常に便利だ。ちょっとしたド忘れ程度ならもはやヘルプファイルは無粋というものであろう。*4ライブラリの中に全部書かれてるので、これだけで書けるなら外部資料は不要である。

よしじゃあ、ちょっとした課題を出してみよう。サンプルのぬこはちょっとでかすぎて画面からはみ出てしまっている。カメラの位置を調節して、全体が画面に収まるようにしといてくれ。

プログラムの流れを把握していく

プログラムはMainメソッドから始まる事になっている。static void Main()から始まるモノがそうだ。

    static class Program
    {

        static int ScreenId = -1;//ウィンドウID
        static int modelId = -1;//モデルのID
        static int modelmotId = -1;//モデルのモーションID
        static int modelmotmaxframe = -1;//モーションの最大フレーム
        static int lightId = -1;//ライト

        static int nowfps = -1;//FPS
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            //3Dモデルに、ひとつだけモーションを読み込み、再生するサンプルです。
            Form mainwindow=new Form();
            mainwindow.ClientSize = new System.Drawing.Size(640, 480);
            mainwindow.Text = "Easy3D Wrapper 4CS sample";

            //ウィンドウハンドル取得
            Module[] ms = Assembly.GetEntryAssembly().GetModules();
            IntPtr hInstance = Marshal.GetHINSTANCE(ms[0]);
            //Easy3D初期化
            E3D.Init(hInstance, mainwindow.Handle, 0, 16, 0,\
 1, 1, 0, 0, ref ScreenId);

            E3D.EnableDbgFile();
            E3D.SetCameraPos(new Vector3(0.0f, 800.0f, -2000.0f));
//中略

初めの方を抜粋。
ここには基本的に、プログラム開始時に一回だけ呼ばれる処理を記述する。e3dhsp3_1の構造は

  • Program
    • Main
    • MainLoop
      • MainLoopの中のWhile

という構造になっている。変数にはHSPと異なりスコープがある。例をあげればMainの中で宣言された変数はMainの中でしか使えない、ということだ。Mainでキャラのモーション情報を初期化し、MainLoopでそれを利用する、というのが一般的なプログラムの流れであるので、MainとMainLoopにおいて変数をやり取りする何らかの手段が必要になってくる。手っ取り早いのは、MainでもMainLoopでも使える変数を宣言することだ。Mainより外、Programの部分でstaticで宣言されている変数がそれだ。こういう風にあらかじめ宣言しておくと、この変数についてはProgramの中でドコデも使える。解説書では良くメンバー変数とか呼ばれているんじゃないかと思う。今回はMainでのモデル読み込みと、MainLoopで描写するのに必要ないくつかの変数をあらかじめ宣言してある。あらかじめ、-1とか、適当な値を代入する必要があることに注意。これについては解説するかもしれないししないかもしれないが、とにかく必要なんだということで入れておいた方がベター。

初期化処理を見る

で、Mainの中ではまずフォームを一つ作り、ウィンドウサイズを640x480にする。タイトルバーの文字を適当なものに変える。その下のウィンドウハンドルの取得というのは、わかんなかったらこの2行をそのままコピペしてくれ。E3DInitで必要なウィンドウIDのようなものを得るのに必要な処理だ。そして、おなじみのE3DInitが呼び出される。環境が違うので、いくつか引数が変わっているが、入力支援機能を使えば各々の引数の意味などはわかると思う。が、ここで最後のScreenIdを受け取る際に、見慣れないキーワードであるrefがさりげなく登場している。これは参照を渡す事を意味する。HSPではなじみの無い表現だし、C#やC++でも値が一つならこういう手法は取らないこともあるが、値を受け取るためには原則refというキーワードが必要だということで今は覚えておいてくれ。他の引数はE3DInitにデータを与えるもので、受け取るScreenIdについてだけrefがつけられていることがわかるだろう。話が前後するが、refキーワードを用いて参照を利用するまでに、最低一回は何らかの代入(初期化)が行われている必要がある。これがややこしいので、最初に-1という値を代入していたわけ。
refについて説明したら、ライトの作成くらいまではスイっと読めるのではないかと思う。フィーリングで理解したら次へ進んでくれ。

描画用スレッドを作る

よしなに初期化処理が終わったら、描画用スレッドを作成する。恐ろしい事に、C#ではわりとホイホイスレッドを作成できる。*5何故スレッドを作るのかここで理解しておこう。
C#は、ウィンドウの移動や、クリックの処理などをイベントとして検出し、処理を行うようになっている。ウィンドウをドラッグする処理などはデフォルトで用意されているので、我々が通常感知する必要はないが、どこかでそれが定義されていると思ってくれ。
例えばの話、ゲーム中にタイトルバーがドラッグされたとしよう。ウィンドウの移動が始まるはずだ。
この時、たとえどんな処理をしていても、隙あらばウィンドウを移動する処理にジャンプする。そしてその間ゲームの処理は完全停止するのである。これはイクナイ場合があるだろう。たとえ一時停止不可能なハイスピードアクションゲームを作ったとしても、ウィンドウドラッグ一発で時を止められる。クイズの時間制限も糞も無くなり、ゲームバランスは崩壊するだろう。これを防ぐために、ウィンドウとゲームのループという二つのスレッドを作成する。描画用スレッドを定義し、スタートさせた後

Application.Run(mainwindow);

を使ってウィンドウを表示する。こうすることで、ウィンドウにはドラッグやその他の処理を受け持ってもらい、ゲームのループを独立させるという処理が可能になる。

スレッドの終了を定義する

Application.Run(mainwindow);が実行されると、以降プログラムは停止し、フォームの初期化が終わり次第イベントの待ち受けを開始する。*6フォームが閉じられると、以降の処理が行われる。この際、作ったスレッドは勝手に終わってくれないので、終了を通知する必要がある。この処理を行わないと、失われたウィンドウへ延々描画しようとしたり、ウィンドウを閉じても後ろでゲームの処理を続行していてCPUやメモリを食ったりといいことがない。なおゲーム終了時にセーブを行ったりしたい、という時には、ウィンドウが閉じた後でいいならこの辺りにぶち込む事になる。

描画スレッドについての補足

描画スレッドでは、基本的に無限ループを作りだし、ゲームの処理を行う。描画スレッドが終了してもウィンドウは閉じないし、ウィンドウが閉じても描画スレッドは終了しないので、お互いに確認しあって処理をする必要があるが、今回は書いてない。

ゲームの開発環境を整え、次回へ備えよう

ここまででサンプルプログラムの触り方くらいは説明したことになる。次回からはいよいよ実際にシューティングゲームを作っていく……などと言うと思ったか!!まだまだ学ばねばならんこともあるし、考えねばならんことも山積だ。次回は手を動かしつつ、骨組みの組み方を考えてみよう。
……だがそのためには、新しいソリューションの作成から始める必要があるな。

新しいソリューションの作成

1つのプログラムを作るのに、1つのプロジェクトが必要だ。今現在開いているE3DSamplexxというソリューションでプロジェクトを作成すると、もれなく君が作る予定のシューティングゲームもその一員となる。結構なことだが、俺が責任持てないのでやめて頂きたい。

なので次回からの作業用に、どっか適当な場所に、_SampleSkeletonフォルダをコピって置いておいてくれ。複数のプログラムを整理してまとめる必要がないなら、これだけで十分だ。インストーラーだとか、ゲームから呼び出す外部プログラム(アップデーターとか?)が必要になったら、そいつらを作ってソリューションにまとめるのもいいだろう。

結び

こんな感じで、絵や長文を交えながらマジで牛歩していくので、じっくりついてきてくれ。
意見要望感想等は、メールかTwitterが早い。


*1 細かい説明、正確な説明よりもフィーリングを重視する。
*2 シューティングのアルゴリズムだとか、プログラムの構成だとかをグダグダ説明するつもりがない
*3 少なくとも俺はいちいち覚えちゃいない。みんな覚えてるの?
*4 Easy3Dラッパープラグインには同梱してないしサンプルにも入れません
*5 作ったからにはきちんと制御する必要があるが……
*6 もちろん、スタートさせたスレッドは動いている