Hatena::ブログ(Diary)

MASAの日記 Twitter


ウェブサイト

2013-01-08

[]アセンブリを別のディレクトリから読み込む

C#でゲームを作ると、色々なライブラリアセンブリ(DLLファイル)が実行ファイルと同じディレクトリに置くことになって汚くなることがよくあります。

これを避けるには、ライブラリと本体のプロジェクトをひとつにまとめて出力されるファイルをひとつにする方法、ILMergeを使ってアセンブリをひとつのファイルに固める方法などがあります。しかしどちらの方法もパッチなどで部分的にプログラムを変更する場合に、更新の必要がないライブラリなどの部分も一緒に配布する必要があるため無駄な容量が必要になり、また外部のライブラリではライセンス的な問題が生じることもあります。

そこで持ち上がるのが、実行ファイルのディレクトリとは別のディレクトリに参照するアセンブリを格納しておくという方法です。この記事ではそれのやり方を紹介します。

使うのはAppDomainクラスのAssemblyResolveイベント(msdn)です。このイベントはアセンブリを読む込むのに失敗したときに呼び出されます。プログラムは標準だと実行ファイルのディレクトリランタイムとしてインストールされてるアセンブリディレクトリアセンブリを探しに行き(どちらが先なのかなどの詳しい仕様は今回は問題にならないので無視します)、そのどちらにも必要なアセンブリが見つからなかったときにこのイベントが呼び出されるわけです。

実際のコードはこちら。

static class Program
{
	static void Main(string[] args)
	{
		AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
		using (Game1 game = new Game1())
		{
			game.Run();
		}
	}
	static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
	{
		return System.Reflection.Assembly.LoadFrom("asm/" + new System.Reflection.AssemblyName(args.Name).Name + ".dll");
	}
}

CurrentDomain_AssemblyResolve関数で必要なアセンブリを返してやることで読み込みができます。この例では実行ディレクトリ以下のasmというディレクトリアセンブリを格納しています。こうすることで読み込みに失敗したあとにCurrentDomain_AssemblyResolve関数が呼ばれ、asmディレクトリに入っているアセンブリが読み込まれて動作するわけです。

このコードだとアセンブリのバージョンが錯綜する時に問題が起きるかもしれませんが、検証してないので自分で何とかしてください。

2013年6月12日 追記

「このように読み込んだアセンブリを呼び出すときはリフレクション使った呼び出しになるので速度が落ちるのではないか?」というツッコミがあったので検証してみたところ、アセンブリ内のクラスの静的関数を呼び出す分には速度に変化はありませんでした。簡単に試しただけですが、多分他のことをしても速度の低下はないでしょう。

2012-10-04 XNA4.0での加算合成のバグ

[]XNA4.0での加算合成のバグ

ゲーム作ってて気になったので。

XNA4.0には乗算済みアルファという機能(Game Studio 4.0で乗算済みアルファを使う)があり、アルファブレンディングで生じる問題が解決されています。

この機能の導入に伴い、SpriteBatchなどで半透明の色を指定する場合には

 Color.White * 0.8f

というように書くように求められています。

アルファブレンドではこれで問題なさそうなんですが、加算ブレンドを使うと問題が生じます。

まず、Color.White * 0.8fの中身はRGBA = 0.8, 0.8, 0.8, 0.8となります(すべての要素にそれぞれ掛けられる)。

BlendState.Additiveの中身は

AlphaBlendFunction = BlendFunction.Add,
ColorBlendFunction = BlendFunction.Add,
ColorDestinationBlend = Blend.One,
AlphaDestinationBlend = Blend.One,
ColorSourceBlend = Blend.SourceAlpha,
AlphaSourceBlend = Blend.SourceAlpha,

となっている(ちなみにMSDNのドキュメントに書いてあるのは誤植です)ので、計算式はソース(描画に使うテクスチャ)の色(アルファ込み)をsRGBA、アルファsA、デスティネーション(描画される画面)の色をdRGBA、結果をresultとすると

result = dRGBA + sRGBA * sA

となるわけです。

乗算済みアルファの処理がされたテクスチャの色は、テクスチャの色をtRGB、アルファtA、出力をt'RGBAとすると

t'RGBA = (tRGB * tA, tA)

となります。

SpriteBatchのピクセルシェーダーでは、テクスチャの色をt'RGBA、色の指定(SpriteBatchのDrawで指定する色のこと)をpRGBA、出力をsRGBAとすると

sRGBA = t'RGBA * pRGBA (それぞれの要素を掛ける)

となります。

で、これらの式をまとめると

result = dRGBA + (t'RGBA * pRGBA) * (t'A * pA)

となります。

ここで注目するのはpRGBAで、基本のブレンド色をbRGBA(ここでのAは殆どの場合1)、Aをブレンドしたい濃度とすると

pRGBA = bRGBA * A

なので(Color.White * 0.8fの部分のこと)

result = dRGBA + (t'RGBA * bRGBA * A) * (t'A * (bA * A))

result = dRGBA + t'RGBA * bRGBA * t'A * bA * A * A

となります。

無駄に長くなった気もしますが、つまりブレンドしたい濃度が2乗でかかってしまうというのが問題なわけです。濃度は0から1の範囲で指定するので、濃度1未満ならば常に意図した濃度よりも薄く出力されてしまうのです。これの原因はBlendState.AdditiveAlpha(Color)SourceBlendがBlend.SourceAlphaになっていることなので、正しい加算ブレンドを使いたい場合は組み込みのブレンドステートを使わずに

new BlendState()
{
	AlphaBlendFunction = BlendFunction.Add,
	ColorBlendFunction = BlendFunction.Add,
	ColorDestinationBlend = Blend.One,
	AlphaDestinationBlend = Blend.One,
	ColorSourceBlend = Blend.One,
	AlphaSourceBlend = Blend.One,
};

というのを使いましょう。

2012-09-13

[]Visual Studio 2012 ProfessionalでXNAを動かしてみた

Visual Studio 2012ではXNAが動かないと評判ですが、下記のURLインストール方法が書いてあったので試してみました。非公式の方法なので自己責任でお願いします。

How to install XNA game studio on Visual Studio 2012? - Stack Overflow

ここに書いてある通りでは出来ないというコメントもあるのでアレンジして行った方法を記しておきます。

(0. XNAインストール)

1. C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\XNA Game Studio 4.0のディレクトリをC:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\Microsoft\XNA Game Studio 4.0にコピー

2. 後者のディレクトリのextension.vsixmanifestを開き、10行目のSupportedProducts以下の部分を

<VisualStudio Version="11.0">

<Edition>Pro</Edition>

</VisualStudio>

に書き換える。Expressの場合はEditionに <Edition>Express_All</Edition>も加えると多分動く(確認はしていない)

3. 後者のディレクトリのProjectTemplates\CSharp\XNA Game Studio 4.0\以下の

  • CSWinPhoneRichApp-v4.0.zip
  • CSXnaWindowsPhoneGame-v4.0.zip
  • CSXnaWindowsPhoneLibrary-v4.0.zip
  • CSXnaXbox360Game-v4.0.zip
  • CSXnaXbox360Library-v4.0.zip

を削除。ProjectTemplates\CSharp\XNA Game Studio 4.0\1041以下にある同名のファイル5つも削除。

VBでのXNA開発を使わないならProjectTemplates\VisualBasicディレクトリもまるごと削除。使う場合は同じような要領でやれば大丈夫かと。

上記のURLには書かれていませんが、この作業をやらないとプロジェクトの新規作成をするときにエラーが出て作成ができなくなります。またこの作業によってXBox360やWindowsPhone上で動かすプロジェクトは諦めることになります。

4. このItemTemplates\VisualBasic\を削除する。これをやらないとファイルの新規作成でエラーが出るようだがよく分からない。

5. C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe に /setup とオプションを与えて実行

こうすることで動作するようになりました。2010で作ったソリューションを開くことができ、ビルドや起動もできます。

WindowsPhoneやXBox360向けXNAアプリケーションができるかは確認していません。また何か不具合が出る可能性もあります。自己責任で。

2012-04-12

[]アルファテスト

技術的なネタを書き留めるべきに思えるので書き始める。今日はXNA4.0でのアルファテストについて。

アルファテストとは、ピクセルアルファ値によってそのピクセルを描画するかどうかを判定するというもの。昔のXNAではグラフィックデバイスの設定として存在していたのだが、XNA4.0やDirectX10以降などではピクセルシェーダで実装したほうが速いということで設定項目が廃止された。使いたい時はAlphaTestEffectというものを使えという情報があちこちで見つかるが、カスタムシェーダで使うにはどうすればいいのかという情報はあまり見ない。

カスタムシェーダでの実装は実際簡単で、ピクセルシェーダ内でclip命令を呼ぶだけだ。clip命令は数値をひとつ取り、その値が0未満ならばそのピクセルを破棄する。破棄するというのはつまり、画面に描画されずかつデプスバッファなどにも書き込まれないということである。これを利用することで透明なポリゴンを描画するときにデプスバッファ関係で問題が起きるのを防ぐことができる(半透明を扱うのは無理)。

注意点としては、強調したように条件が「0以下」ではなく「0未満」であること。α=0を渡しても破棄されないので、条件式にはαから適当に小さい値を引いたものを渡す必要がある。

参照

AlphaTestEffectなどのHLSLコード no title

clip命令のリファレンス clip (DirectX HLSL)

2012-03-23

制作物

[] Windows Phone開発

Windows Phone向けにゲームを作ってみたので感想。

まずWindowsで動くように作ってからWindows Phoneに移植する形で開発した。色々あったライブラリ群をWindows Phone用に機能を限定して移植する作業も一緒に。ライブラリの大半のコードはそのままで動く、かと思いきや微妙に機能が削られている点が多くてなかなか面倒だった。

使えなかったものを挙げていくと、まずExpressionTreeのCompileメソッド。これはセキュリティとかを考えたら仕方が無いのかもしれないし、そもそも普通は使わない。

続いてデフォルト引数機能。何故か使えない。オーバーロード使ったほうがお行儀がいいってことなのか。

それからSortedDictionary。貧弱な環境のために少しでも速くしようと思って使おうとしたらなかった。非ジェネリックのコレクションクラスがごっそりなくなっていたのは分かるがこれは何故。

XNAでのプログラマブルシェーダ。これが使えないのが一番デカイ。でも仕方が無いんだろうな。

Enumの色々なメソッドも使えない。これはC#につい最近追加されたばかりの処理だった気がする。

ジェネリクスでの暗黙的な型変換も気が利かない。自前でキャストしてやればいいだけの話だが。

そしてさっきバグの原因になっていたのがfloatに大きい数を入れた時の扱い。floatで角度を扱うところにRandom.Nextの値を入れた所で判明。Windowsだとfloatに大きいintの値が入ってもなんとかしてくれるのだけど、WPだとNaNになってしまう。CPUアーキテクチャとかそこらへんの問題なのだろうか。

使えないと言えば、Main関数も存在しない。XNAだとGameクラスのコンストラクタをエントリポイントとして扱っているようだ。それはいいのだが、Windows用のGame1クラスのソースコードWP用プロジェクトに入れてビルドしたら何故かそのクラスを認識してくれなかった。

ファイル読み込みでFile.Openとか使うと怒られるのも忘れずに。TitleContainer使ってあげましょう。

と色々文句を垂れてみたけれどおおむね快適に開発できる。タッチ操作が面倒だと思っていたが、単純なシングルタッチ操作であればMouseクラスでLeftClickとPositionとして扱えるため何も書く必要がなかった。

作ったのはそのうちマーケットプレイスで公開する予定。ソースコードも公開するかも。