Posts Tagged ‘ 問題回避

チップの登録機構

どうも、お久しぶりです。テストがあったり、私が所属してる情報工学科に革命がおこったりで色々ごたごたしてまして落ちつて作業できる時間が少なくて、更新できてなかったです。どんな革命が起きたかはここで語ることじゃないので、控えておきますが。

さてタイトルのことですがこれはかなり悩みに悩みました。
この前書いたとおりこのプロジェクトはプラグインに対応させるつもりです。ですのでできるだけ外から追加できる拡張性をもったコードを書かなければならず、 しかもこのチップの部分はこのプログラムの中核の部分なので手は抜きたくありませんでした。

デザインパターンをあたってみて、「あ、これ使えるじゃん!!ふむふむ、え~っとこうやって、これがこうで… で、肝心なここは……使えねぇ!!!!!」的なことの連発でした。

どうにかならないか困り果ててとつぶやいたら@haxeさんから回答がいただけました。

kassyi
@kassyi
@haxe
クラスを条件によって作り分けたいんです。例え話にすると調査員という仕事があり、Aは0歳から9歳までの朝食の食べる量を調査する、Bは14~50歳までの歩数を調査する。AとBが調査員というクラスを継承しているとすると、どうすればインスタンスを作り分けられるかって話です。
link
kassyi @kassyi @haxe 普通に考えれば
if(age >= 0 && age <= 9)
retrun new A();
else if(age >=14 && age<= 50)
retrun new B();
となると思うのですが、これだとプラグインなどで調査員を追加するのが難しくて。。
link
kassyi @haxe @kassyi 今WebRequestクラス見てるので、お勧めするのはその方法かなぁ。 http://msdn.microsoft.com/ja-jp/library/system.net.webrequest.aspx link
haxe @haxe @kassyi ちなみに標準機能はどこかで初期化するとして、利用者が派生クラスを追加する際に、app.configから登録できるようにしておくとさらに吉。 http://msdn.microsoft.com/ja-jp/library/42z9z1b9.aspx link

このやり取りのおかげ様でやっとこさ整合性のとれたコードを書くことができました。書いたコードをざっとまとめるとこんな感じです。

すべてのチップはこのCihpクラスを継承して表現されます。また継承されたクラスのコンストラクタはprotectedにして直接はインスタンスが作れないようにします。で、どこからインスタンスが作れるかというとChipクラスのCreate()です。そのCreate()の中で何をやってるかというと、登録された派生クラスのクリエイター対して「このIDのチップ扱える?」って順繰りに訪ねて行って扱えるなら「インスタンスを頂戴ね」ということでクリエイターにインスタンスを作らしています。

…ってなことで、なんとかえっちらおっちら拡張性のあるチップ登録機構を作ることができました。いやーまさかWebRequestクラスをまねできるとは思いませんでした…。.Netライブラリは当然プログラマーのプロの人が書いているので吸収できるところは吸収するようにしようと思いました。@haxeさんに、感謝。

RenderTarget2D.GetTexture()は同じアドレスを返す

RenderTarget2D.GetTexture()した結果を配列や二つの変数に代入し、あとからそのRenderTarget2Dをいじくり回すと両者が同じになるということについてです。

何のこっちゃさっぱりって人は、こちらのサンプルをどうぞ。XNA3.0で作成しています。

操作方法
左シフト->レンダリングターゲットからテクスチャを取得し左側のテクスチャに代入する。
右シフト->レンダリングターゲットからテクスチャを取得し右側のテクスチャに代入する。
そうするとソース上では片方しか描き換えてないのに、 両方書き換わってしまう。

私は最初これと同じようなことをしていて、この現象に出合い混乱して乱数クラスが狂ったのかと思いました。でもデバッガで追ってくと そうじゃなかったですね。ちゃんと乱数になってましたし、代入してるのも変数一つでした。で、ふと思いついたのが「ポインタ」って言葉。もしGetTexture()で取得できるオブジェクトのアドレスが同じならつじつまが通る…!

確かめてみようと思ってunsafeコード書いてみましたが、どうやら値型のアドレスは取得できても参照型のアドレスは取得できないようで、断念。

で、どうするか。下のような感じでディープコピーしてやればOKです。

GraphicsDevice.SetRenderTarget(0, null);
Texture2D newTex = new Texture2D(GraphicsDevice, tex1.Width, tex1.Height);
Color[] data = new Color[tex1.Width * tex1.Height];
target.GetTexture().GetData(data);
newTex.SetData(data);
tex1 = newTex;

ただひとつ問題なのがこの記事で検証されているようにXBOX360上ではGetData()/SetData()がパフォーマンスにかなり影響を与えるようです。特にGetData()が。今のところこれに対する対策は見つかってません…。描画処理をGPU上で全部やって最終的にテクスチャをもらうということぐらいしか思いつきません。ただそれだとfxファイルを書かなければならないですし、SpriteBatchなどで複雑な描画をしていた場合はとGPUには不向きかと。

うが~~~~!!!
XBOXでゲーム作りたいです…。

2009-12-16 01:18:49追記。

簡単な話でしたね。GetTexture()する数だけRenderTarget2Dを作ればいいんですね。
なぜ気がつかなかった!???


疲れてきてるな流石に…

RenderTarget2Dでエラー

バックバッファより大きいか、バックバッファより小さいRenderTarget2Dを作って、レンダーターゲットを切り替えてGraphicsDevice.Clear()もしくはspriteBatch.End()などをすると以下のようにエラーになったことがあると思います。

System.InvalidOperationExceptionエラー
アクティブなレンダー ターゲットと深度ステンシル サーフェイスは、ピクセル サイズとマルチサンプリング タイプがそれぞれ同じである必要があります。

具体的にはこんな感じのコードでエラーになるかと。

graphics.PreferredBackBufferHeight = 720;
graphics.PreferredBackBufferWidth = 1280;
var target = new RenderTarget2D(GraphicsDevice, 100, 100, 1, SurfaceFormat.Color);
//DepthStencilBuffer old = GraphicsDevice.DepthStencilBuffer;
//GraphicsDevice.DepthStencilBuffer = GfxComponent.CreateDepthStencil(target, DepthFormat.Depth24Stencil8Single);
GraphicsDevice.SetRenderTarget(0, target);
GraphicsDevice.Clear(Color.Brown);
GraphicsDevice.SetRenderTarget(0, null);
//GraphicsDevice.DepthStencilBuffer = old;
2009-12-31 追記

このままのコードだと、パフォーマンスが非常に悪くなるので、GfxComponent.CreateDepthStencilで作ったテクスチャは再利用するようにしたほうがいいです。


いままでは、このエラーを回避するにはレンダーターゲットの大きさを画面のサイズ(バックバッファ)と同じにするしかないと思ってました。でもエラーメッセージにはバックバッファなんて単語はひとつも入ってないんです。おかしいなぁと思って調べまくった結果、コメントアウトしてあることを追加すればOKでした。GfxComponentとかいうクラスはMSDNライブラリの「方法 : 深度テクスチャーを作成する」の「完全なサンプル コード」というところから落としてください。そのプロジェクトの中に入ってます。


以下、自分なりに解釈したこんなエラーになる理由です。

続きを読む