がりらぼ

WindowsRuntimeの応援ブログ

DirectX2Dのテンプレを読み解いてみる

XNAが8では絶望的ということで、ついに昔挫折したDirectXへと手を出すことになるようです。
少しでも簡単になっているといいのですが、でもクラス名が文字分けされて、コードスニペットも充実しているはずなので、まだ読みやすいかと思います。

とりあえずDirect2DAppアプリケーションを新規プロジェクトで作ってみるとこんな感じ

資産ってオイ…
この中で重要なのはDirectXBaseとDirectXPageとSimpleTextRedererの3つだけ。
とりあえずデバッグしてみましょう。

Hello,DirectX!
DirectXPage.xamlを見てみましょう。

<TextBlock x:Name="SimpleTextBlock" HorizontalAlignment="Center" FontSize="42" Height="72" Text="Hello, XAML!" Margin="0,0,0,50"/>

TextBlockとかいつも見てるコードが見えますね。
今回のWindows8ゲームは、XAMLでの描画と、DirectXでの描画が共存しています。
今までメニュー画面とかつくるのだるかったので全くよいことですね。
RTMでMerketにあるマインスイーパとか見てもらうとわかるのですが、XAMLのUIが多く使われています。

UIには特化したXAMLで、高速レンダリングには、特化したDirectXで、それぞれに役割分担するのもいいですね。

ではソースコードを読んでいきましょう。
まずApp.xaml.cppです。
OnLaunchedメソッドがアプリの開始時に呼び出され、ここでXAMLUIのDirectXPageが呼び出されています。

void App::OnLaunched(LaunchActivatedEventArgs^ args)
{
	m_directXPage = ref new DirectXPage();

	if (args->PreviousExecutionState == ApplicationExecutionState::Terminated)
	{
		m_directXPage->LoadInternalState(ApplicationData::Current->LocalSettings->Values);
	}

	// ページを現在のウィンドウに配置し、アクティブであることを確認します。
	Window::Current->Content = m_directXPage;
	Window::Current->Activate();
}

次はDirectXPage.xaml.cppを読んでいきましょう。
DirectXPageクラスの役割は、一画面の中で60fpsぐらい定期的にレンダラーを呼び出して、更新処理と描画処理を呼び出しています。
また、ディスプレイの向きが変わった、UIボタンが押されたなど、さまざまなイベントを検知し、レンダラーに伝えています。
ここでのレンダラーとは、何かを描画するための一つの単位です。

まずコンストラクタですが、レンダラーをnewして、初期化しています。

m_renderer = ref new SimpleTextRenderer();

	m_renderer->Initialize(
		Window::Current->CoreWindow,
		SwapChainPanel,

		DisplayProperties::LogicalDpi
		);

そしてさまざまなDisplayなどのイベントを関連付け、
CompositionTargetというクラスで定期更新処理を関連付けています。

//OnRenderingを定期的に呼び出して更新処理
	m_eventToken = CompositionTarget::Rendering::add(ref new EventHandler<Object^>(this, &DirectXPage::OnRendering));

BasicTimerはゲームの時間をはかるためのものであって、更新呼び出しとかではありません。

その後、さまざまなイベントに対してレンダラーを読んでいます。
重要なのがOnRendering

void DirectXPage::OnRendering(Object^ sender, Object^ args)
{
	if (m_renderNeeded)
	{
		m_timer->Update();
		m_renderer->Update(m_timer->Total, m_timer->Delta);
		m_renderer->Render();
		m_renderer->Present();
		m_renderNeeded = false;
	}

	
}

レンダラーを更新とレンダリング(描画)しています。
Updateにさっき言ったBasicTimerを渡し、時間を計測しています。

あと、SaveInternalStateとLoadInternalStateこれはおそらく画面の状態を一時的に保存するものです。


次にSimpleTextRederer.cppを見ていきましょう。
これがレンダラーで、描画の一個単位となります。
この場合は、Hello,DirectX!を描画するためのレンダラーというわけです。
ヘッダファイルを見てみると、親クラスはDirectXBaseクラスです。
レンダリングに対する難しいとこらへんはこのクラスがしてくれているので、あとは継承して利用するだけです。

最後にSimpleTextRederer.cppを見ていきましょう。
DirectXBaseを継承し、DirectXPageより呼び出されるいろんな処理があります。
ここで重要なのがm_d2dContext
レンダリングオブジェクトというらしいですが、こいつが描画処理を行っています。

Renderメソッド内

m_d2dContext->BeginDraw();

	m_d2dContext->Clear(ColorF(BackgroundColors[m_backgroundColorIndex]));

	// 表示されるテキストを配置します。
	Matrix3x2F translation = Matrix3x2F::Translation(
		m_windowBounds.Width / 2.0f - m_textMetrics.widthIncludingTrailingWhitespace / 2.0f + m_textPosition.X,
		m_windowBounds.Height / 2.0f - m_textMetrics.height / 2.0f + m_textPosition.Y
		);

	// m_orientationTransform2D マトリックスは、ここで事後乗算されます。
	// これにより、テキストの方向を表示方向と正しく一致させます。
	// この事後乗算ステップは、スワップ チェーンのターゲット ビットマップに対して行われるすべての
	// 描画呼び出しで実行する必要があります。他のターゲットに対する呼び出しでは、この変換を
	// 適用する必要はありません。
	m_d2dContext->SetTransform(translation * m_orientationTransform2D);

	m_d2dContext->DrawTextLayout(
		Point2F(0.0f, 0.0f),
		m_textLayout.Get(),
		m_blackBrush.Get(),
		D2D1_DRAW_TEXT_OPTIONS_NO_SNAP
		);

	// D2DERR_RECREATE_TARGET を無視します。このエラーは、デバイスが失われたことを示します。
	// これは、Present に対する次回の呼び出し中に処理されます。
	HRESULT hr = m_d2dContext->EndDraw();
	if (hr != D2DERR_RECREATE_TARGET)
	{
		DX::ThrowIfFailed(hr);
	}

	m_renderNeeded = false;

基本的にはm_d2dContextインスタンスから、BeginDrawを呼び出して、Clearして、Drawして、EndDraw()まででレンダリングしています。

これがとUpdateがDirectXPageより定期的に呼び出され、更新と描画を実現しています。

SaveInternalStateを見てください

void SimpleTextRenderer::SaveInternalState(IPropertySet^ state)
{
	if (state->HasKey("m_backgroundColorIndex"))
	{
		state->Remove("m_backgroundColorIndex");
	}
	if (state->HasKey("m_textPosition"))
	{
		state->Remove("m_textPosition");
	}
	state->Insert("m_backgroundColorIndex", PropertyValue::CreateInt32(m_backgroundColorIndex));
	state->Insert("m_textPosition", PropertyValue::CreatePoint(m_textPosition));
}

Stateインスタンス内にKeyと値を保存しているようですね。
やはり一時的に状態保存が容易にできるようです。
以上でかなり大雑把にテンプレートを読み終わりました。
まとめるとこんな感じ。

ゲームの画面を作るのに、いちいちBase画面クラスを作って、ScreenManagerで管理するとかいう時代もついに終わるんですかね。
Windows8はとりあえずアプリ普及しなきゃいけないのでより簡単に開発するためにはこういう進化も必要なのかと思います。
今後はレンダラーを追加していって、Pageクラスから呼び出せば描画するものを増やすことができます。
まだ資料は少ないですが、Windows8でゲーム開発してみてはどうでしょうか。
下手したらXAMLだけでもつくれちゃいます。