2012年10月4日木曜日

「ゲームを動かす技術と発想」感想

堂前 嘉樹 著「ゲームを動かす技術と発想」を読みました.

読み終えて,自分がゲームプログラマになる前にこの本が出ていれば良かったのにな,と思いました.

自分のように大学出身のプログラマだと,専門学校生のようにゲームプログラミングについての知識が豊富なわけでも無いので,専門分野から少しでも外れると用語が分からなかったりします.

この本は,ゲームプログラマとしてやっていく上で必要な基礎を一通り解説していて,しかも,何故そうなっているのか,など理由をしっかりと説明してくれているため,手っ取り早く基礎を押さえるのに良いと思います.また,そういったことを教える際の参考にもなると思います.

ゲームプログラマにとってプログラムを書くのも重要ですが,デザイナに技術上の制限を理解してもらった上で色々と工夫をしてもらえるように,ちゃんと理解してもらえる説明をすることも重要だと思っています.

先日,デザイナの方からモーションを付けたらモデルの表示がおかしくなった,と相談を受けました.どうも頂点カラーが出ていないとのこと.しかし,自分は頂点カラーとモーションの関係が分からなかったため,先輩に聞きに行きました.
答えとしては,モーションが入っているとマテリアルが変わるから,頂点カラーが使えない,ということでした.
おそらく先輩にとっては常識だったのでしょうが,自分もデザイナの方もそんなことは知りません.何故モーションが入っているとマテリアルが変わるのか,聞いてみても違うからなぁ,というくらいしか答えは返ってこず,結局デザイナの方にはそのまま伝えることに.デザイナの方は,使えないなら仕方無いけどね,と渋々他の方法で表現してくれることに.

で,ちょうどこの本を読んでいたら,モーションが入ると頂点カラーが使えない,というよりも使わない理由が書いていました.おそらく,この本に書いてあるような説明ができていれば,デザイナの方も納得した上で仕事をしてもらえていたと思います.

他にも,自分の知識不足から不要な手間を取らせてしまうことが多々ありました.分かってはいるけど,デザイナの方に分かってもらうための言葉を知らなかったりもありました.

この本の前書きには,ゲームプログラマを目指す人,ゲームプログラマと接する機会の多い人向けの本です,ゲームプログラマとのコミュニケーションを円滑にするアイテムになって欲しい,と書かれています.
自分としては,特にゲームプログラマとのコミュニケーションを円滑にするアイテムとして,この本は素晴らしいと思います.

とりあえず,困ったときのために,会社の机に置いておこうと思います.

------------------------------------------------------------------------------------------------------------
著者の堂前さんが,少々はネタバレもOKということだったので,その辺も考慮して,この本の良い点を挙げておきます.

第1章は,本当に基本の基本.コンピュータがどうやって動いているのか,という内容で,さすがにプログラマならこのくらいは知っておきましょう,というレベルです.
でも,最近はメモリを意識しなくてもやっていけるので,メモリにプログラムを読み込む,というのを理解せずにプログラマになる人もいるのかも.

第2章は,メモリ管理周りの話です.据え置き機と携帯機ではメモリ容量の単位が違って驚いた話や,過去と現在を比較してのコラムが面白いです.昔はこんな努力までしていた,とか.実際に仕事でやってきた経験に基づいた内容,という感じでCD-ROMなどのディスク上でのデータの配置にまで気をつかう,などは読んでいて面白かったです.

第3章は,CPUとGPUについて.この章も,本当に仕事中にこういうのが必要だったんだろうな,というのが伝わってくる内容です.こうするとメモリは節約できるけど,デバッグは面倒になったよ,とか,こういう手順だと描画処理が間に合わないから1フレームずらして,とか.この章に限らず,全体的に図が多く,分かりやすいです.

第4章は,コンピュータ上での数値の表現方法と演算方法について.これは,プログラマであれば聞いたことがある内容ばかりでしょうが,それをプログラマ以外に説明する場合に,参考になると思います.図の書き方を参考にしたいです.

第5章は,3Dを扱う上では必須の行列やクォータニオンといった数学について.5.4までは他にも色々と詳しい本があるので,そちらを読んだ方が良いでしょう.で,5.5からが個人的には良いな,と思っているところで,数式上こうなるよ,とかこうすると解けるよ,という解説は色々ありますが,何故こうする必要があるのか,これだけ容量を食うんだよ,と言った現実の問題も絡めた上での説明はあまり見たことが無い気がします.
あと,最後のまとめの,数学に対する姿勢も個人的に好きです.

第6章は,アニメーションとかモーションについて.普通に考えるとパラパラアニメの各コマ毎に全てのデータを持っていれば良いけど,それだと必要なデータのサイズがこんなことに.でも工夫すると,徐々に減っていってこれだけで実現できるよ,という説明の仕方は,何故そんなことをするの,と言う部分が分かってとても良いです.
あと,IK(Inverse Kinematics)の説明や,骨格データなども知らなかったので参考になりました.

今日はこの辺で.

全体的に,技術書にありがちな文章とコードが多くて図があまり無い,とかではなく,色々と分かりやすい図が多くて読みやすい気がします.

2012年6月28日木曜日

LINQ to XMLと属性とnullと

最近,LINQ to XMLを使う機会が多い.で,こんな感じのコードを書く.

XDocument document = XDocument.load("foo.xml");
var elements = from e in document.Descendants("bar")
                        where e.Attribute("hoge").Value == "foobar"
                        select e;

すると,適当な属性が無い場合null参照で怒られる.で、こう書き換えていた.


XDocument document = XDocument.load("foo.xml");
var elements = from e in document.Descendants("bar")
                        where e.Attribute("hoge") != null && e.Attribute("hoge").Value == "foobar"
                        select e;

でもこれ冗長だなぁ、と思っていたわけで.調べてみると、文字列へのキャストをした方が良い感じ.

XDocument document = XDocument.load("foo.xml");
var elements = from e in document.Descendants("bar")
                        let hoge = (string)e.Attribute("hoge")
                        where hoge != null && hoge == "foobar"
                        select e;

で、ふと思ったのが、文字列型の変数がnullの場合,比較したら落ちるのかな,と.

string a = null;
string b = "hello";
if(a != b){
    Console.WriteLine("not same");
}

ちゃんと比較できるっぽいので,こうすればいちいちnullチェックいらないんじゃね,ということで

XDocument document = XDocument.load("foo.xml");
var elements = from e in document.Descendants("bar")
                        let hoge = (string)e.Attribute("hoge")
                        where hoge == "foobar"
                        select e;

と,ここにたどり着いた.

2012年6月21日木曜日

OpenGL事始め3

背景を塗りつぶしたら,次は点でも表示してみよう.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    // 点を描画
    glBegin(GL_POINTS);
    {
        glVertex2f(0.f, 0.f);
    }
    glEnd();
    glFlush();
}

glBegin(描画方法)からglEnd()の間に点を描画したい座標をglVertex2f()で指定してやると,点が描画される.この場合,中心に白い点が描画される.ただし,凄く小さくて分かりづらい.

そこで,少しサイズを大きくしてみる.サイズの指定は,glPointSize();

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    // 点のサイズを指定
    glPointSize(32.f);
    glBegin(GL_POINTS);
    {
        glVertex2f(0.f, 0.f);
    }
    glEnd();
    glFlush();
}

四角くでかい点が出る.ちなみに,このglPointSizeの位置をBeginとEndの間に持ってきても上手く動かない.更に点に丸みを付けてみる.glEnableにGL_POINT_SMOOTHという引数を渡すだけ.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    glEnable(GL_POINT_SMOOTH);
    glPointSize(32.f);
    glBegin(GL_POINTS);
    {
        glVertex2f(0.f, 0.f);
    }
    glEnd();
    glFlush();
}

OpenGL事始め2

コードは書きつつも,アウトプットが無さ過ぎなのでちょっと書いておく.

OpenGL事始め1で書いたコードに追加する感じで.

背景を塗りつぶす
だいたいの入門書で取り合えず背景を特定の色で塗りつぶす方法が最初に紹介されている気がする.OpenGLだと,glClear関数を呼び出す.描画関係の処理はglutDisplayFunc()に渡した関数の中に書く.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glutMainLoop();

    return 0;
}

void display()
{
    // 色バッファを指定した色で埋める
    glClear(GL_COLOR_BUFFER_BIT);

    // 描画コマンド実行指示
    glFlush();
}

glClear関数は,いくつかのビットフラグになっている定数を指定することで,その定数で指定されたバッファの内容を指定された値で埋めてくれる.今回の場合,色が保存されているバッファを指定した色で埋めている.

最後のglFlush関数は呼び出しておかないと,描画が実行されなかったり.

glClearのデフォルト値は,RGBAで(0, 0, 0, 0)になっているので,何も指定しないと画面が黒になる.
色を指定する場合は,次のようにglClearColorにRGBAを指定する.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    // 青色で塗りつぶすようにする
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    // 色バッファを指定した色で埋める
    glClear(GL_COLOR_BUFFER_BIT);

    // 描画コマンド実行指示
    glFlush();
}

R(赤),G(緑),B(青),A(アルファ)を0.0fから1.0fの範囲で指定する.

ちなみに,glClearColorの位置をglutCreateWindowの前にすると落ちる.

2012年5月28日月曜日

WebGL+HTML5の読書感想

カットシステムから出版されたWebGL+HTML5 3DCGプログラミング入門 (著 松田晃一)を読んだので感想を.

まず,この本は初心者のみならず,初心者に説明する立場の人にも読んでもらいたい一冊です.

OpenGLやOpenGL Shader Languageを知らないという初心者でも、少しずつ新しい機能に触れていくスタイルのこの本であれば,じっくりと理解していくことができると思います.通常のOpenGLの入門書を読むのであれば,こちらを先に読んで勉強しても良いと思います.また,少々複雑な部分は提供されているライブラリで上手く隠されていて,まず試してみて,それから中を知る,という形なので,そういった形式が好きな方にもお薦めです.

そして,初心者に説明する立場の人にも是非読んで欲しいと思う理由は,解説で使われている図にあります.GLのコマンドによってシステム内でどのような変化が起きているのかを非常に分かりやすい図で説明してあり,細かい単位毎にシステム内の変化を追えるので,図を見るだけで何となくどういう動作なのか分かってきます.自分でドキュメントを書いたりする上でも色々と参考になりそうです.

約6,000円と少々高いですが,久々にこれは買って良かったと思えた一冊です.

2012年5月13日日曜日

Blender on Mac OS X

Blenderを触ってみよう,と思ったものの,持っている本の解説はWindows向け,手元にあるのはMac,ということで微妙に操作が違うので,基本操作周りをメモっておこうかと.
ちなみに,Blenderは2.63aを使用.

最初に
MBAには,中ボタンも無いしテンキーも無いので,⌘, (command + ,) で設定を開いて,inputタブを選び,Emulate 3 Button MouseとEmulate Numpadにチェックを入れ,下側のSave As Defaultを選ぶ.保存しておかないと毎回設定し直しになる.

拡縮
拡縮はピンチで行う.トラックパッドで2本の指の間を広げると拡大,縮めると縮小.

平行移動
平行移動は,トラックパッドに指を2本付けた状態で移動させるだけ.

回転
回転は,通常中ボタンマウスでやるらしいが,無いのでalt (option)を押しながら,平行移動と同じようにすると回転ができる.

全体表示
Homeボタンが無いので,fn+←が代わりらしい.

他にも基本操作でWindowsではこうだけど,と言うのがあったら追記していこうかな.

2012年4月29日日曜日

プログラミング原論読書会

昨日の話になりますが,プログラミング原論(Elements of Programming)の読書会に参加してきました.

引っかかってた部分が解消でき,他にも色々と分かって良かったです.

読書会には原著を持ってきている方もいて,見せてもらいましたが,思った通り翻訳で結構重要な部分のニュアンスが伝わっていない部分がありました.これは原著も必要だなぁ,ということでそのうち原著も買おうと思います.

最初の補題1.1と1.2って同じこと言っているんじゃないのか,という話があったんですが,確かに一意と一義って分かりにくい気がします.例としては,1の補数の場合,+0と-0の二つの値が抽象実態 0 に対応しているので,これは一意ではないけれど,2つ以上の解釈を持たないので,一義的で,この場合は表現等価性が等価性になる,ということじゃないかと.

他にも正則って何よ,ってことで,結局書いてある定義をそのまま受け入れようという話に.ただ,underlying_typeを実際の型,という表現はどうよ,とか思ったり.C++11のtype_traitsにもunderlying_typeがあるので,実際の型とか訳されないか心配ですね.

一回目は読書会ってどうやってやろうか,という感じだったので,試行錯誤でしたが,得る物もあったし,次回も決まったので楽しみですね.次回にはもう少し理解を深めておきたいところです.

2012年4月14日土曜日

OpenGLのAPIの変遷をまとめてみた

OpenGLの勉強に使う目的で,OpenGLの各APIがどのバージョンから存在しているのか,というのが分かる表を作ってみました.

OpenGL API Histories

仕様を読んで,拡張機能以外は一通り網羅したと思います.GLSLやGLESについては,暇があれば作ろうかなぁ,と.

作り終えてから,何故こんなものを作ったという感じがしないでも無いですが.ただ,何度も仕様を読み返したおかげもあって,とりあえずAPIの名前だけは結構覚えました.名前だけは・・・

今は,The C++ Standard Library: A Tutorial and Reference 2nd Editionが発送されるのをいまかいまかと待ちわびています.しばらくはこちら中心になるので,GLSLやGLESはしばらく作らないかも.

2012年3月25日日曜日

vim-itunes開発メモ1

最近,VimからiTunesを操作するためのプラグインを作り始めたので,その際に参考にしたことなどなどをメモ.とりあえず,VimからiTunesを操作する方法について,MacとWinでの方法を.Uniteとの連携はまた今度書く.

こちらからダウンロード可能.
vim-itunes

neobundle.vimを使って ryutorion/vim-itunes でもインストール可能.
現状,:Unite it_track で曲一覧を表示して,選ぶと曲が再生できるくらい.
あとは,ドキュメントに書いてあるようにトラックを進めたりする関数も用意しています.

VimからiTunesを操作する方法

Mac OS Xの場合

Mac OS Xの場合,AppleScriptを使ってiTunesを操作できます.例えば,
tell application "iTunes" to play (track 1 of user playlist 1)

これを,AppleScriptエディタで実行するか,osascript -e '上の内容'とコマンドラインで実行すると,ミュージックの1つ目の曲が再生されます.

で,このAppleScript,日本語情報が少ない.英語でもなかなか情報が見つからない.既存のスクリプトの使い回しならいいんだけれど,Uniteと連携するために情報引き出したりとかやり始めると途端に分からなくなる.

AppleScriptについて

AppleScriptについては,次のドキュメントを参考にしました.


このドキュメントをざっと読んでおかないと,AppleScript書くの大変でした.
ちなみに,iThiefというコマンドラインからAppleScriptを使ってiTunesを操作するものがあるようです.作り終わってから教えてもらいました.Uniteと連携するのにどっちにしろ独自実装必要だった気がしますが,先に知ってたら無駄に苦労しなくて済んだかも,という気がします.

そして,AppleScriptを書く上でAppleScriptエディタの辞書は必須です.Command+Shift+Oで開いて,アプリケーションを選ぶとAppleScriptから呼び出せるコマンドやプロパティなどが書かれています.これを見ながら必要なAPIを探して叩きまくるわけです.

後はひたすら欲しい情報を得るために試行錯誤です.

Windowsの場合

Windowsの場合,JScriptを使ってiTunesを操作します.VBScriptとかC#も使えるようですが,諸事情によりJScriptを使います.これまた,JScriptからiTunesを利用する方法でまとまった情報がなかなか見つからない.とりあえず,WSHの本を片手に,次のドキュメントを参考にしました.

iTunes COM for Windows SDK (ダウンロードページトップなので検索してください)

一応サンプルスクリプトがついているものの,名前の大文字小文字とかどうなっているの,とか分からなくて,とりあえずif(API名)で存在を確認しながら徐々に作っていきました.
ただ,ある程度はAppleScriptと同じインターフェースを採用しているようなので,調べるためのキーワードはある程度分かっていたのが幸いでした.

以上
AppleScriptはなかなか面白いので,そのうちまとめを書いても良いかもなぁ.

2012年3月16日金曜日

ゲームプログラマとしての一年目のまとめ

もうそろそろ社会人,そしてゲームプログラマになって1年目が終わるので,この1年を振り返りつつ,まとめてみようかと思います.

入社前

とりあえず,入社前のスペックを整理.どこが成長したか分かる・・・かも?

Cは,プログラミング言語Cを一通り読んで,適当に書き散らしている程度.
C++は,ある程度有名どころの書籍には目を通している程度.
Javaも触れなくはない.Android関係も多少は弄った.
Perlは,joinとmapとgrepがあれば大概の処理が書けるよね,と思う程度.
JavaScriptは,Definitive Guideで勉強した程度.
C#は,プログラミングC#を買ってざっと目を通した程度.
シェルスクリプトは,書けなくも無いような.
PowerShellも本は読んだよ,という程度.
VBAは,15年くらい前にVB触ってたし何とかなるはずという程度.
サーバー関連は,研究室で管理者やってた程度.Apacheとかも軽く触れるよ.

Luaは,本は読んだ.
四元数は,本を買った.ついでに三元数なんて怪しい本も買った.
関数型言語は,LispもMLもHaskellも触ったことはあるけど,程度.
iOS関係も本のコードを書き写してみた程度.


ゲームプログラミングというか,描画関係は本を読んではいたけど,実際にコードを書いたことはほとんど無いという状態.DirectXのコードをすらすら書くことはできないし,OpenGLのコードもまともに書けない.ましてやシェーダーなんて.
こんな自分は,周りのゲームプログラミングなら任せろって人たちについていけるのか不安でした.

4月,5月,6月

まずは研修ということで,プログラマだけで社内のコードを弄り回して慣れる,という作業をしていました.CUIで生きてきた自分にとって,Visual Studioはコードを書くのが面倒でした.Vim使いたいんですけど,って言って導入許可をもらうまでが苦でした.

この頃は,既存のコードの使い回しだったので,描画関係のコードを弄ることもなく,純粋に言語の知識の差や,他人のコードを読んで理解する力が重要だったと思います.あと,人数が少なかったので,こういうことやったよ,と周りと話をしながらコードを弄っていたのですが,これが後で同じようなことをするときに役立ちました.

7月,8月,9月

新人プログラマだけでなく,新人デザイナーも交えての開発が始まりました.

このタイミングから,バージョン管理などのためのサーバー管理なども必要になってきます.基本的な知識があったため,最初の立ち上げをスムーズに行うことができて良かったです.また,プログラマ同士で開発していたときは,こういう手順でやって,と伝えるだけだった作業も,デザイナーにとっては難しいというのもあり,バッチなどを作るようになりました.この頃は,PowerShellを使おうという発想が無かったので,普通のbatで書いていました.PowerShellを使おうと思えていたら,効率も違っただろうなぁ,と思います.

この時既に自分は,知識の幅的に周りのサポート担当の位置付けになっていて,システム部分やバグ修正などを担当していました.そのためもあって,直接デザイナーとやりとりをすることも少なく,作ったものが使いやすいかといった聞き取りもほぼ出来ていませんでした.また,誰も聞いてこないから,とドキュメントの作成もあまり行えていませんでした.この点は非常に問題で,後々実際に問題になってきます.

開発が進んでいくと,どうしても自分だけでやっていては間に合わなかったりする部分が出てきた.そのタイミングになってようやくドキュメントを作成し,周りに仕事を投げるということをし始めました.ただ,ドキュメントを書くということにも慣れていなかったので,おせじにも読みやすいものができあがったとは言いがたいです.それもあって,周りもあまりドキュメントを読んでいなかったように思います.まぁ,そもそもドキュメントを読まない人も結構いた気がしないでも無いですが.

また,デザイナーに提供していたツールなども使いにくい面もあったのか,あまり活用してもらえていなかった気がする.どちらも新人ということで,何が分からないのか分からないという状態だったのかもしれないけれど,もっと積極的に話をすべきだったと思います.

9月以降

実際の開発チームに配属される.新人同士で馴れ合いになってしまってた部分もあった気がするので,実際の開発はそんなに甘く無いだろうな,と最初はちょっと緊張していた.
しかし,実際には非常に親切に色々と教えてくれるため,すぐにチームに打ち解けることができました.このチームに入れて良かったなと思っています.

この頃から,PowerShell Scriptを書くようになりました.バッチとして配布しつつ,何かを特にインストールしてもらう必要が無く,かつ楽にXMLを弄れる,という理由からでした.しかし,LINQを使い始めた今となっては,PowerShellよりC#でさっと書いた方が楽かも,とも思います.ただ,やはりいくつかのコマンドを組み合わせていって一つの作業を達成する,という手順になれているので,PowerShellも便利です.

更に,デザイナーに使ってもらうツールでGUIが必要,ということでC#でWPFを使ってみました.色々面倒でしたが,なかなかに面白い経験になりました.作ったものをデザイナーの方に見てもらうと,こういうデザインにした方が良い,などアドバイスがもらえて良いですね.

新人同士でやっていた頃と違うなぁ,と感じたのは,プログラマ同士よりもデザイナーとのやりとりが多いなぁ,ということです.デザイナーの方から,このツールが使いにくいんだけど,こういう風にできないか,と言った提案がしっかりと出るので,それに答えるためにこちらも色々と考えて,と良い循環ができている気がします.

最近

ちょっとずつやっていた描画周りの勉強も徐々に自分の中で消化できてきたのか,最近は何となくですが,描画関係の問題が発生したときも対処方法などに気づけるようになってきました.最近は,OpenGLの仕様を読んだりしています.過去から徐々に進めていって,変化を把握したり.仕様を読んだ方が自分の勉強方法には合っている感じです.

ただ,まだまだドキュメントを作ったりといった部分が後回しになりがちなので,今後は作業効率を上げていき,ドキュメントを作る余裕も作りたいです.

ドキュメントについて最近気付いたのは,プログラマはどうしても専門的な言葉で,かつ,そのツールの個々の要素の解説という形で説明を書いてしまいがちです.でも実際には,こういうことをやりたい場合は,こういう手順でやれば良いよ,と言う逆引き的なものを多く用意した方が喜ばれるようです.プログラマ的には個々の要素が分かれば,それを組み合わせてやりたいことが実現できることが分かるよね,と思ってしまうのですが,そうでは無いようです.

まとめ

入社前は,ゲームプログラマは描画関係のコードをバリバリ書いているんだろうなぁ,と幻想を抱いていました.実際には,そういった見た目の部分の多くはデザイナーの手によるものでした.描画関係のコードが書けた方が良いけれど,書けなくても十分仕事はあります.むしろ,描画しか出来ないくらいなら,他のことを色々できた方が良さそうな感じです.もちろん,描画関係の知識はあった方がデザイナーと話が通じやすくなるので必要ですが.

それと,需要は重要ということを学びました.具体的にやりたいことが出てくると,今まで知識だけだったPowerShellやC#も色々触ってみて,周りに提供できる程度のクオリティのものは作れるようになりました.あと,これVimでもっと楽にできないかな,と思うことが増えたので,Vimも色々と覚えた部分が増えた気がします.

デザイナーと話して,需要を見つけ,勉強して,提供して,改善案を話し合い,新しい需要を見つけ,お互いに効率を上げていく,という良い循環を作るのが重要だと思います.

ここまで書いて読み返して,主に内向き,開発内な話で,ゲームのおもしろさ的な部分,つまり外に向けての話が無いなぁ,と思ったのですが,ぶっちゃけそんなものは作ってみないと分からない部分が多いと思うのです.実際チームに入って,作る前はこれで良さそう,と話していたものが作ってみるとそうでもなく,変更するということは何度かありましたし.

ゲームプログラマとして大事なことは,早く試せる形にして,意見を吸い出せる状態にして,おもしろさの部分を育てられるようにすることだと思います.そのために,早く試せる形にするための技術が必要なんだと思います.必要な技術や知識は日々変わっていくので,必要になったら勉強する,とかじゃなくいつか必要になったときのために,日々色々な技術に目を向けておくのが大事だと思います.

そもそも,この技術は必要でこの技術は必要無い,なんて判断できるほどの知識も経験も持ち合わせていないのだから,やれることは何でもやった方が良いのです.

無駄に長くなりましたが,思い返すと一年を通して色々と成長できた気がするので,今後も精進していきたいと思います.

2012年3月13日火曜日

OpenGL事始め1

OpenGLを弄るのに毎回調べ直している気がするので,まとめておこうかと.

仕様を手に入れる

ちょこちょこと気になると本当にそうなの,と調べてしまうので最初から仕様を手に入れておく.幸いOpenGLは仕様が公開されている.

OpenGL最新仕様
OpenGL Shader Language仕様
GLUT仕様

一通り揃えてiPadへ.

とりあえず,最小の部分から始めていこうかと.環境は,Mac OS X Lionで.Windowsでの手順もそのうち書こうかな.寒く無くなって,デスクトップに向かう気が起きたら.

OpenGLでウィンドウを表示する場合,基本的にはGLUTを利用することになる.まずは,このヘッダファイルを開けるかの確認.
// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    return 0;
}

このコードをビルドしてみる.

$ g++ -o main main.cpp

特に何もしなくても,Lionの場合開発環境さえ入っていればGLUTまで入っているみたい.で,GLUTの初期化をする.

// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    return 0;
}

そして,ビルド.

$ g++ -o main main.cpp

今度は失敗.GLUTライブラリをリンクしていないから仕方無い.で,Mac OS Xの場合便利なのが,-frameworkオプションで名前指定するだけで色々よしなにしてくれるってところ.

$ g++ -o main main.cpp -framework GLUT

これでビルドが通る.実行してみてもちゃんと特に何も起きずに終わる.GLUTの仕様によれば,glutInit()を実行する前に,glutInit系(glutInitWindowSizeなど)以外のglutの関数や,OpenGLの関数を呼び出してはいけない,とのこと.で,glutInitにargcとargvを渡す理由としては,いくつかのコマンドライン引数をGLUT側で処理してくれるから,ということ.例えば、-iconicでアイコン化(最小化?)で表示される,とか.

で,次はウィンドウを作ってみる.
// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("hello");
    return 0;
}

ビルドして実行すると,何事も無かったかのように終わる.GLUT仕様曰く,glutMainLoop()に入るまではウィンドウの表示状態が変わらない,とのこと.ということで,glutMainLoop()を追加する.

// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("hello");
    glutMainLoop();
    return 0;
}

すると,ウィンドウを描画するための関数が設定されていないよ,と文句を言われる.このウィンドウを描画するための関数は,glutDisplayFunc()で設定する.というわけで,さっそく登録してみる.仕様を見ながら関数の型だけ合わせて空の関数を作って登録する.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutDisplayFunc(display);
    glutCreateWindow("hello");
    glutMainLoop();
    return 0;
}

void display()
{}

しかし,これをビルドして実行しても,さっきと同じ文句を言われる.仕様を読むと,ウィンドウが作成されたときには,描画関数が設定されておらず,表示するまでに設定するのがプログラマの責任,と書いてある.つまり,glutCreateWindow()で作成してから,glutMainLoop()に入るまでの間にglutDisplayFunc()で描画関数を登録する必要がある,ということ.というわけで,順番を修正する.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("hello");
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

void display()
{}

とりあえず,これでビルドして実行すると,無事ウィンドウが表示される.
今日はこんなところかな.

2012年2月22日水曜日

neocomplcache-snippets-complete 色々対応してもらった

neocomplcache-snippets-completeのprev_wordって何,とか書いてたら,
この素晴らしいプラグインの生みの親の@ShougoMatsuさんが
さっとドキュメントを修正してくれた上に,この動作ってこれでいいの,とか
話していたのまで拾ってイイ感じに修正してくれた.神じゃなかろうか?

とりあえず,prev_word '^'を書いておくと,そのsnippetは行頭じゃないと
補完候補にも挙がりません.今まではsnippet名さえ合っていれば補完はされないけど
展開されていたのが,今回展開もされないようになりました.

ついでに,前に書かなかった他の便利そうな機能も書いておこう.

snippet c
abbr C++ Class
prev_word '^'
    /*
        @author `$USER`
     */
    class ${1} {};

とこんな感じに,``を使うと式を評価してくれてその結果に置き換えてくれる.
何というか,こうサポートしてもらえるともっと使いたいって気になるよね.

2012年2月21日火曜日

neocomplcache-snippets-completeいいよ

実は,neocomplcacheのsnippetを利用していなかったので,ふと思い立ちインストールして使ってみた.簡単な使い方が分かっただけでもこれ便利だわって感じ.

インストールと設定

neocomplcacheはインストールして設定済みの前提で,neobundle.vimでneocomplcache-snippets-completeをインストールする.
" .vimrc
NeoBundle 'Shougo/neocomplcache-snippets-complete'

" snippetの配置場所
g:neocomplcache_snippets_dir='~/.vim/snippets'

" キーマップ
imap <C-k> <plug>(neocomplcache_snippets_expand)
smap <C-k> <plug>(neocomplcache_snippets_expand)

Snippetの作成
g:neocomplcache_snippets_dirで設定した場所に,ファイルタイプ名.snipでsnippetを書いておくファイルを用意する.
とりあえず,C++のBoost.Test用のコードのsnippetを用意してみた.

snippet     batc
abbr        BOOST_AUTO_TEST_CASE
prev_word   '^'
    BOOST_AUTO_TEST_CASE(${1:TestName})
    {
        BOOST_FAIL("Fail");
    }

後は,C++のソースコードでbatcと入力して,を押せばsnippetが展開される.${数字}または${数字:デフォルト値?}で
プレースホルダを設定できて,で次へ次へと飛んでいけるみたい.

ただ,何故かVimを再起動しないと上手くsnippetを読み込んでくれなかった.

プレースホルダの同期
更にあるプレースホルダで設定した内容を他の部分に反映ということまでやってくれる.

snippet     c
abbr        C++ class
prev_word   '^'
    class ${1:name} {
    public:
        $1();
        $1($1 const &);
        ~$1();
    private:
    };

これで,クラス名を入力すると,自動的にコンストラクタ,コピーコンストラクタ、デストラクタが宣言される.
${数字}で入力した内容を$数字に反映できる.

これだけでも十分使えるよなぁ.後はsnippetを増やしていけば、本当に便利そう.何で今まで使ってなかったのか、と思うね.

ところで、このsnippetの文法というか構文というかは、neocomplcache-snippets-completeのドキュメントだけで完結しているのかな?
ざっと読んだ感じだとprev_wordって何か分からなかったんですが.

2012年2月12日日曜日

Boost.Test Step 5

後はファイルをテストスイート毎に分割できると良いかな,ってことで分割してみた.

とりあえず,メイン.たぶんこのファイル中のテストスイートも外に出せる.
// main.cpp
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>

bool isOdd(int n)
{
    return n % 2 != 0;
}

BOOST_AUTO_TEST_SUITE(OddTest)
BOOST_AUTO_TEST_CASE(oddTest)
{
    BOOST_CHECK_EQUAL(isOdd(0), false);
}
BOOST_AUTO_TEST_SUITE_END()

偶数判定用のテストスイートはa.cppというファイルに.
// a.cpp
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>

bool isEven(int n)
{
    return n % 2 == 0;
}


BOOST_AUTO_TEST_SUITE(EvenTest)
BOOST_AUTO_TEST_CASE(evenTest)
{
    BOOST_CHECK_EQUAL(isEven(0), false);
}
BOOST_AUTO_TEST_SUITE_END()

これをつないでビルドすれば実行できる.
$ g++ -o main main.cpp a.cpp -lboost_unit_test_framework
$ ./main --report_level=detailed
a.cpp:13: error: in "evenTest": check isEven(0) == false failed [true != false]

Test suite "Master Test Suite" failed with:
  1 assertion out of 2 passed
  1 assertion out of 2 failed
  1 test case out of 2 passed
  1 test case out of 2 failed

  Test suite "OddTest" passed with:
    1 assertion out of 1 passed
    1 test case out of 1 passed

    Test case "oddTest" passed with:
      1 assertion out of 1 passed

  Test suite "EvenTest" failed with:
    1 assertion out of 1 failed
    1 test case out of 1 failed

    Test case "evenTest" failed with:
      1 assertion out of 1 failed

基本的な使い方はこれで一通り把握できたかなぁ.しかし相変わらずドキュメントが読みにくかった.

Boost.Test Step 4

テストケースをいくつかまとめたものをテストスイート(この場合のスイートはsuiteであって,sweetじゃない.)と言う.
BOOST_AUTO_TEST_SUITE(スイート名),BOOST_AUTO_TEST_SUITE_END()で囲めば良い.

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>

bool isOdd(int n)
{
    return n % 2 != 0;
}

bool isEven(int n)
{
    return n % 2 == 0;
}

BOOST_AUTO_TEST_SUITE(OddTest)
BOOST_AUTO_TEST_CASE(oddTest)
{
    BOOST_CHECK_EQUAL(isOdd(0), false);
}
BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(EvenTest)
BOOST_AUTO_TEST_CASE(evenTest)
{
    BOOST_FAIL("Fail");
}
BOOST_AUTO_TEST_SUITE_END()

これを実行すると,こんな感じ.

$ ./main --report_level=detailed
Test suite "Master Test Suite" failed with:
  1 assertion out of 2 passed
  1 assertion out of 2 failed
  1 test case out of 2 passed
  1 test case out of 2 failed
  1 test case out of 2 aborted

  Test suite "OddTest" passed with:
    1 assertion out of 1 passed
    1 test case out of 1 passed

    Test case "oddTest" passed with:
      1 assertion out of 1 passed

  Test suite "EvenTest" failed with:
    1 assertion out of 1 failed
    1 test case out of 1 failed
    1 test case out of 1 aborted

    Test case "evenTest" aborted with:
      1 assertion out of 1 failed

オプションに,--report_level=detailedを入れないとここまで詳細に出ない.他にも自動的に組み込まれるオプションがいくつかある.--show_progress=yesでテストの進捗を表示したり.--helpで確認可能.

Boost.Test Step 3

とりあえず奇数判定を行う関数を実装し,適当にテストしてみる.

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE OddTest
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>

// 奇数判定を実装
// 奇数は2で割った余りが1になる数?
bool isOdd(int n)
{
    return n % 2 == 1;
}

BOOST_AUTO_TEST_CASE(oddTest)
{
    // 非負偶数の場合
    BOOST_CHECK_EQUAL(isOdd(0), false);
    BOOST_CHECK_EQUAL(isOdd(2), false);

    // 非負奇数の場合
    BOOST_CHECK_EQUAL(isOdd(1), true);
    BOOST_CHECK_EQUAL(isOdd(3), true);
}

実行結果はこんな感じ.

Running 1 test case...

*** No errors detected

正しく動作するものを書いたので,当然成功する.ところが,テストケースにちょっと判定文を追加すると,テストに失敗する.

BOOST_AUTO_TEST_CASE(oddTest)
{
    // 非負偶数の場合
    BOOST_CHECK_EQUAL(isOdd(0), false);
    BOOST_CHECK_EQUAL(isOdd(2), false);

    // 非負奇数の場合
    BOOST_CHECK_EQUAL(isOdd(1), true);
    BOOST_CHECK_EQUAL(isOdd(3), true);

    // 負偶数の場合
    BOOST_CHECK_EQUAL(isOdd(-2), false);
    BOOST_CHECK_EQUAL(isOdd(-4), false);

    // 負奇数の場合
    BOOST_CHECK_EQUAL(isOdd(-1), true);
    BOOST_CHECK_EQUAL(isOdd(-3), true);

}

出力結果は次のようになる.

Running 1 test case...
main.cpp:27: error: in "oddTest": check isOdd(-1) == true failed [false != true]
main.cpp:28: error: in "oddTest": check isOdd(-3) == true failed [false != true]

*** 2 failures detected in test suite "OddTest"

原因は奇数の定義を2で割った余りが1になるとしたこと.この割った余りを正数にするか,
負数にするかっていうのは言語によって違うのだけれど,この場合,2で割り切れなければ奇数,つまり2で
割った余りが0で無ければ良いので,関数を修正する.

bool isOdd(int n)
{
    return n % 2 != 0;
}

これで無事テストが通る.

Boost.Test Step2

とりあえず前回作ったファイルにテストケースを追加する.BOOST_TEST_MAINの代わりにBOOST_TEST_MODULEを定義する.

// 動的リンクを使用
#define BOOST_TEST_DYN_LINK
// テストモジュール名を定義
#define BOOST_TEST_MODULE OddTest
#include <boost/test/unit_test.hpp>
// BOOST_AUTO_TEST_CASE などに必要
#include <boost/test/unit_test_suite.hpp>

// とりあえず奇数判定のテストケースの枠だけ作って
// 失敗することを確認
BOOST_AUTO_TEST_CASE(oddTest)
{
    BOOST_FAIL("Fail");
}

実行結果はこんな感じ.

Running 1 test case...
main.cpp:11: fatal error: in "oddTest": Fail

*** 1 failure detected in test suite "OddTest"

どうでも良いことなんだけど,テストケース,という用語に違和感を覚えないでも無い.一揃いの入力をテストケース,その集合をテストセットと言うような研究を何年も見てきたからだと思う.ここで言うテストケースってテストセットのことだよね,という気がしてならない.まぁ,本当にどうでも良いことなんだけどね.

Boost.Test Step 1

とりあえず,Boost.Testの使い方を適当にまとめておこうかと.何ステップになるか分からないけれど.

最小(?)のファイル

// main.cpp
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>

とりあえず,この3行の入ったコードをビルドすると最低限の処理が組み込まれる(コメントは除く).
なお,#defineは#includeの前に書いておかないと意味が無いので注意.

$ g++ -o main main.cpp -lboost_unit_test_framework
なお,Boostがインストールされている場所に,INCLUDEやLIBRARY_PATH,LD_LIBRARY_PATH,DYLD_LIBRARY_PATHといった環境変数が設定されているという前提。
Lionで実行したときは,DYLD_LIBRARY_PATHが適切に設定されていなくて動的ライブラリを見つけられずに上手く動かなかった.

これを実行すると,次のようにエラーが報告される.
$ ./main
Test setup error: test tree is empty

まだ何も登録していないので当然と言えば当然である.とりあえず,これでBoost.Testを使う準備はできたかな.

Boost.勉強会 #8 大阪に参加しました.

昨日Boost.勉強会 #8 大阪に参加してきました.
近くで開催されるということと,ゲーム開発会社が協賛ということで,色々と楽しみにしながら参加しました.

Test Driven Development
今回個人的に注目していたのは,テスト駆動開発(Test Driven Development, TDD)を
ゲーム開発で実践している,という話が聞けるということと,そのために使うBoost.Test
についての話が聞けるということでした.

結論から言うと,聞きたかったこととは若干外れていたかな,という感じです.テスト駆動開発については今から始めるということで,色々とライブラリが紹介されたのは良かったのですけど,個々のライブラリの比較はまだ出来てないのかな,と.今後実践していった結果のノウハウが紹介されることに期待です.

Boost.Testについては,ドキュメントの翻訳,という感じでした.ある程度経験を交えてかみ砕いているもののところどころで,よく分からないです,が入るのでいまいちただしいのかどうか分からなかったです.

メモリアロケータ
これも注目していた話の一つです.いくつかのメモリの管理手法が紹介され,その違い,使用されている具体例,実際に計測してみての速度差を見せる,と言った内容が分かりやすく,しかもテンポ良く説明しているので分かりやすかったです.ソースコードも公開されているので,後から勉強もしやすく良かったです.

Vim
今回目立ったのは,発表者の多くがVimを使っていたということでしょうか.
やはりVimは素晴らしいですね.人によってはほとんどの作業がVimの上で完結するという.
皆さんもVimを使いましょうw

その他の発表について
凄いけどついて行けない部分も,というのが正直な感想だったり.

全体的に
全体的に,参加者が固定されてきているのかなぁ,という感じがしました.もう何回も開催されている勉強会なので,顔なじみがいるのは当然のことですが,一番後ろの席に座っていると,前半分と後ろ半分の温度差が激しいなぁ,と感じました.発表者に絡んだネタも前半分だけに受けていたり.
ちょっとグダグダだったりするのも仕方ないと言えば仕方無いのですけれど,当日にも発表資料を作っているというような状況が当たり前のように受け入れられるのは良いのかな,と感じました.
どちらかというと勉強しに行く場,というよりもそういう人たちに会って直接話をして,ネタを集める場なのかなぁ,という感じでした.

とりあえず,Boost.Testについては気になったのでもうちょっと自分でも調べて行こうかなぁ,というつもりです.

2012年1月15日日曜日

IDirect3D9::CreateDevice

描画のためにIDirect3DDevice9オブジェクトを作ろうとすると,ウィンドウが必要になる.すると途端に面倒になる気がするのは何故だろう.面倒というよりも,自分はDirectXを弄りたいのだけれど,Win32 APIも弄らないといけないという部分が面倒なのかもしれない.とりあえず,生成するだけ生成してみた.

#include <sdkddkver.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <tchar.h>
#include <sstream>
typedef std::basic_ostringstream<TCHAR> tostringstream;

#include <d3d9.h>

#include <memory>

template <typename T>
struct release_deleter {
    typedef T * pointer;

    void operator()(T * p) const
    {
        p->Release();
    }
};
typedef std::unique_ptr<IDirect3D9, release_deleter<IDirect3D9> > d3d_ptr;

LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
    static TCHAR CLASS_NAME[] = TEXT("hello");

    //=======================================================================
    // ウィンドウの作成
    //=======================================================================
    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.hInstance = hInstance;
    wcx.lpfnWndProc = WindowProc;
    wcx.style = CS_HREDRAW | CS_VREDRAW;
    wcx.lpszClassName = CLASS_NAME;
    RegisterClassEx(&wcx);

    HWND hWnd = CreateWindow(
        CLASS_NAME,
        TEXT("hello"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, 
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        return -1;
    }                                                     

    // IDirect3D9オブジェクトの生成
    d3d_ptr pD3D(Direct3DCreate9(D3D_SDK_VERSION));

    // エラーチェック
    if(pD3D == NULL){
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    // IDirect3DDevice9オブジェクトの生成
    D3DPRESENT_PARAMETERS d3dpp = { 0 };
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
    // d3dpp.hDeviceWindow = hWnd; // コメントアウトを外して,

    LPDIRECT3DDEVICE9 pD3DDevice = NULL;
    HRESULT result = pD3D->CreateDevice(
        D3DADAPTER_DEFAULT,
        D3DDEVTYPE_HAL,
        hWnd, // ここをNULLにする
        D3DCREATE_SOFTWARE_VERTEXPROCESSING,
        &d3dpp,
        &pD3DDevice
    );
    if(result != D3D_OK){
        MessageBox(
            NULL,
            TEXT("IDirect3DDevice9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    pD3DDevice->Release();
   
    return 0;
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg){
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

D3DPRESENT_PARAMETERSで色々と設定できるみたいだけれど,これ必須の設定とそうでない設定がどれなのかがいまいち分からない.そういうのもドキュメントに書いていて欲しいなぁ,と思う今日この頃.

2012年1月12日木曜日

IDirect3D9::GetDeviceCaps

アダプタに対して,指定した種別のデバイスの情報を取得する.ただ,取得できる情報が多すぎるので表示機能ではなく,ブレイクポイントをはるためだけの命令を挟む.そこにブレイクポイントをはって,後はデバッガで中身を見る.D3DCAPS9の中はもうちょっとじっくりドキュメントを読まないと使えなさそう.

#include <sdkddkver.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <tchar.h>
#include <sstream>
typedef std::basic_ostringstream<TCHAR> tostringstream;

#include <d3d9.h>

#include <memory>

template <typename T>
struct release_deleter {
    typedef T * pointer;

    void operator()(T * p) const
    {
        p->Release();
    }
};

typedef std::unique_ptr<IDirect3D9, release_deleter<IDirect3D9> > d3d_ptr;

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    // IDirect3D9オブジェクトの生成
    d3d_ptr pD3D(Direct3DCreate9(D3D_SDK_VERSION));

    // エラーチェック
    if(pD3D == NULL){
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    UINT n = pD3D->GetAdapterCount();
    for(UINT i = 0; i < n; ++i){
        D3DCAPS9 caps;
        pD3D->GetDeviceCaps(i, D3DDEVTYPE_HAL, &caps);
        caps.AdapterOrdinal += 1; // ブレイクポイント用
        pD3D->GetDeviceCaps(i, D3DDEVTYPE_REF, &caps);
        caps.AdapterOrdinal += 1; // ブレイクポイント用
    }
   
    return 0;
}

IDirect3D9::GetAdapterIdentifier

ドライバ名とかグラフィックスボードの名前まで取得可能.そして,そこまでの情報を利用する必要があるのかは良く分からない.PCゲームはあまりやらないからなぁ.

#include <sdkddkver.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <tchar.h>
#include <sstream>
typedef std::basic_ostringstream<TCHAR> tostringstream;

#include <d3d9.h>

#include <memory>

template <typename T>
struct release_deleter {
    typedef T * pointer;

    void operator()(T * p) const
    {
        p->Release();
    }
};

typedef std::unique_ptr<IDirect3D9, release_deleter<IDirect3D9> > d3d_ptr;

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    // IDirect3D9オブジェクトの生成
    d3d_ptr pD3D(Direct3DCreate9(D3D_SDK_VERSION));

    // エラーチェック
    if(pD3D == NULL){
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    UINT n = pD3D->GetAdapterCount();
    for(UINT i = 0; i < n; ++i){
        D3DADAPTER_IDENTIFIER9 identifier;
        pD3D->GetAdapterIdentifier(
            i,
            0,  // D3DENUM_WHQL_LEVELを指定するとネットから情報取得?
            &identifier
        );

        // GetAdapterIdentifier()の結果を文字列に
        tostringstream msg;
        msg << "Adapter " << i << '\n';
        msg << "Driver           : " << identifier.Driver << '\n';
        msg << "Description      : " << identifier.Description << '\n';
        msg << "DeviceName       : " << identifier.DeviceName << '\n';
        msg << "DriverVersion\n";
        msg << "    Product      : " << HIWORD(identifier.DriverVersion.HighPart) << '\n';
        msg << "    Version      : " << LOWORD(identifier.DriverVersion.HighPart) << '\n';
        msg << "    SubVersion   : " << HIWORD(identifier.DriverVersion.LowPart) << '\n';
        msg << "    Build        : " << LOWORD(identifier.DriverVersion.LowPart) << '\n';
        msg << "VendorId         : " << identifier.VendorId << '\n';
        msg << "DeviceId         : " << identifier.DeviceId << '\n';
        msg << "SubSysId         : " << identifier.SubSysId << '\n';
        msg << "Revision         : " << identifier.Revision << '\n';
        // msg << "DeviceIdentifier : " << identifier.DeviceIdentifier << '\n';
        msg << "WHQLLevel        : " <<
            (identifier.WHQLLevel >> 16) << TEXT("年") << 
            ((identifier.WHQLLevel & 0xFFFF) >> 8) << TEXT("月") <<
            (identifier.WHQLLevel & 0xFF) << TEXT("日\n");

        MessageBox(
            NULL,
            msg.str().c_str(),
            TEXT("結果"),
            MB_OK
        );
    }
    
    return 0;
}

IDirect3D9::EnumAdapterModes

EnumAdapterModesは,指定されたアダプタ,フォーマットで指定された番号のモードに関して,幅,高さ,リフレッシュレート,フォーマットを,最後の引数に渡したポインタが指す変数に返す.しかし,日本語のドキュメントが最後の引数が配列,と書いていたけど,英語だと単なるポインタになっているので,誤訳だと思われる.

#include <sdkddkver.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <tchar.h>
#include <sstream>
typedef std::basic_ostringstream<TCHAR> tostringstream;

#include <d3d9.h>

#include <memory>

template <typename T>
struct release_deleter {
    typedef T * pointer;

    void operator()(T * p) const
    {
        p->Release();
    }
};

typedef std::unique_ptr<IDirect3D9, release_deleter<IDirect3D9> > d3d_ptr;

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    // IDirect3D9オブジェクトの生成
    d3d_ptr pD3D(Direct3DCreate9(D3D_SDK_VERSION));

    // エラーチェック
    if(pD3D == NULL){
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    UINT n = pD3D->GetAdapterCount();
    for(UINT i = 0; i < n; ++i){

        UINT m = pD3D->GetAdapterModeCount(i, D3DFMT_X8R8G8B8);
        for(UINT j = 0; j < m; ++j){
            D3DDISPLAYMODE DisplayMode;
            pD3D->EnumAdapterModes(i, D3DFMT_X8R8G8B8, j, &DisplayMode);
            
            // EnumAdapterModes()の結果を文字列に
            tostringstream msg;
            msg << "Adapter " << i << " : Mode " << j << '\n';
            msg << "Format      : " << DisplayMode.Format << '\n';
            msg << "Width       : " << DisplayMode.Width << '\n';
            msg << "Height      : " << DisplayMode.Height << '\n';
            msg << "RefreshRate : " << DisplayMode.RefreshRate << '\n';

            /* 結果の表示は大量に出ることもあるのでコメントアウト
            MessageBox(
                NULL,
                msg.str().c_str(),
                TEXT("結果"),
                MB_OK
            );
            */
        }
    }
    
    return 0;
}

こっそり,IDirect3D9オブジェクトの解放をuniqu_ptrにやらせるように変更してみたり.

2012年1月10日火曜日

IDirect3D9::GetAdapterModeCount

ディスプレイモードの数を返すIDirect3D9::GetAdapterModeCount.最初の引数は,0以上でIDirect3D9::GetAdapterCount()が返す値未満の値を渡す.第二引数には,D3DFORMATの値を渡す.ただ,D3DFORMATの値ならどれでも良いのかが,ドキュメント読んだだけではいまいち分からない.

#include <sdkddkver.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <tchar.h>
#include <sstream>
typedef std::basic_ostringstream<TCHAR> tostringstream;

#include <d3d9.h>

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    // IDirect3D9オブジェクトの生成
    LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);

    // エラーチェック
    if(pD3D == NULL){
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    UINT n = pD3D->GetAdapterCount();
    for(UINT i = 0; i < n; ++i){
        // GetAdapterModeCount()の結果を文字列に
        tostringstream msg;

        msg << "Adapter " << i << '\n';
        msg << "D3DFMT_X8R8G8B8     : " << pD3D->GetAdapterModeCount(i, D3DFMT_X8R8G8B8) << '\n';

        MessageBox(
            NULL,
            msg.str().c_str(),
            TEXT("結果"),
            MB_OK
        );
    }
    
    // 後始末
    pD3D->Release();

    return 0;
}

IDirect3D9::GetAdapterCount

Direct3DCreate9()でIDirect3D9オブジェクトを生成したので,そのオブジェクトを使ったコードを書いていってみよう.他のオブジェクトを必要とせずに書けるのはどこまでだろう.

とりあえず,今回はGetAdapterCount().その名の通りアダプタの数を取得する.アダプタというのは,出力先と考えれば良いっぽい.

#include <sdkddkver.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <tchar.h>
#include <sstream>
typedef std::basic_ostringstream<TCHAR> tostringstream;

#include <d3d9.h>

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    // IDirect3D9オブジェクトの生成
    LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);

    // エラーチェック
    if(pD3D == NULL){
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    // GetAdapterCount()の結果を文字列に
    tostringstream msg;
    msg << "GetAdapterCount() == " << pD3D->>GetAdapterCount();

    MessageBox(
        NULL,
        msg.str().c_str(),
        TEXT("結果"),
        MB_OK
    );
    
    // 後始末
    pD3D->Release();

    return 0;
}

複数ディスプレイがある場合に,どのディスプレイに出力するか選べるとかに使うんだろうか? 実は使い道が分かっていない.

2012年1月9日月曜日

Direct3DCreate9

思いつきで,DirectX 9から順に11に向かって,それぞれの関数なんかを対象に,それを利用したコードを書いてみようかと.というわけで,まずはDirect3DをDirectX 9で使う場合に必須の,Direct3DCreate9のコードを書いてみた.

#include <sdkddkver.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <tchar.h>

#include <d3d9.h>

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    // IDirect3D9オブジェクトの生成
    LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);

    // エラーチェック
    if(pD3D != NULL){
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成しました."),
            TEXT("成功"),
            MB_OK
        );
    }else{
        MessageBox(
            NULL,
            TEXT("IDirect3D9のオブジェクトを生成できませんでした."),
            TEXT("失敗"),
            MB_OK
        );
        return -1;
    }

    // IDirect3D9オブジェクトの後始末
    pD3D->Release();

    return 0;
}

Direct3DCreate9の引数は必ずD3D_SDK_VERSIONにしなくてはならない,とのこと.
Direct3DCreate9Exという関数もあるけれど,これはVista以降でないと使えないので注意,ということ.
こっちはまた今度書いてみよう.