読者です 読者をやめる 読者になる 読者になる

がりらぼ

WindowsRuntimeの応援ブログ

WindowsPhone8.1ゲームプログラミング入門

C++ DirectX

はじめに

この記事はWindowsPhoneアドベントカレンダー2014の15日目の記事です。

今回WindowsPhoneアドベントカレンダーの記事を書こうと思ったとき、WindowsPhoneでゲームプログラミングをする記事を書こうと思いました。

そしてそういえば昔WindowsPhone アドベントカレンダーに参加してたなぁ..と思い出し、ググってみると、2011年にこんな記事でWindowsPhoneのアドベントカレンダーの記事を書いていました。

WindowsPhoneでゲームがつくりたいんだろ?基礎知識伝授するよ! - がりらぼWP7 ~WindowsPhoneプログラミング情報発信ラボ~

今から思うとひどい煽りタイトルだったなと..

さて、2011年、WindowsPhone7.x時代のゲームフレームワークといえばみんな大好きXNA Framework

f:id:garicchi:20141207162304j:plain

XNAとはC#Windows、WindowsPhone、XNAのゲームを開発できるすばらしいフレームワークであり、この時のアドベントカレンダーの記事もXNA Frameworkで書いていました。

しかし2014年、XNA Frameworkは開発終了しました。ご臨終です。

DirectX Toolkit

XNAが死んだ今、WindowsPhoneのゲームプログラミングの形としては3種類の選択肢があります。

Windowsのゲームを作るときは定番となっているDirectXでの開発方法です。DirectXはグラフィックAPIやサウンドAPI、入力APIなど、Windowsのゲーム開発の詳細なところまで記述できる分、言語はC++(SharpDXなどありますが)、難しいメソッドなど、初心者にはとっつきにくい形となっています。

  • MonoGame

C#クロスプラットフォームゲームを開発できるオープンソースプロジェクトMonoGameです。 XNAと似た構文で、iOSAndroid、WindowsPhoneなどのゲームが作れます。 ただしWindowsに関しては対応しているバージョンが8.1ではなく8なのでいろいろ大変です。

  • Unity

こちらもC#クロスプラットフォーム開発のゲームエンジンです。 ゲーム開発のめんどくさい部分をツール化することで最小限のコードで高度な3Dゲームを作ることができます。

本記事ではDirectXを使ったゲームプログラミング方法を紹介していきたいとおもいます。

DirectXは先ほど書いた通り、APIとして提供されるメソッドなどを直接呼び出そうとすると画像を描画するだけでも相当なコード分量が要ります。

1枚の画像を描画するためにいちいち長いコードを書いていてはいつまでたってもゲームが完成しません。そこでそれをラップしてXNAに似た記法で書けるようにしたライブラリがDirectX Toolkitです。

DirectX Tool Kit - Home

本記事ではDirectX Toolkitを使っていきます。

ちなみにアドベントカレンダーの都合上、WindowsPhone8.1ゲームプログラミング入門ということにしていますがWindowsストアアプリにも対応しています。

あとVisualStudio2013を想定しています。VisualStudioCommunityでも実行可能ですが、プロジェクトテンプレートの置き場所が違うかもしれません。

環境構築

DirectXToolkitを使ったとしても、ゲーム作成は初心者にはハードルが高いです。

そこで、ゲーム画面管理やゲームオブジェクト管理をすでに実装したプロジェクトテンプレートを作成したので今回はこれを使います。

garicchi/DxGameTemplate · GitHub

GitHubにおいているのですがいらないバイナリファイルとかたくさんおいてしまったのでファイルサイズは合計1Gあります。ダウンロードすると時間がかかりまくるのでテンプレートファイルだけ落としましょう。

/Templatesディレクトリに移動すると、2つのzipファイルがあります。

f:id:garicchi:20141211110453p:plain

それぞれ、WindowsストアむけプロジェクトテンプレートとWindowsPhone向けプロジェクトテンプレートです。

DXToolkit_Standard_WindowsPhone.zipをクリックして、ViewRawを押すとダウンロードできます。

DXToolkit_Standard_Windows.zipもついでに落としときましょう。

f:id:garicchi:20141211110503p:plain

次にエクスプローラーで

C:\Users\{UserName}\Documents\Visual Studio 2013\Templates\ProjectTemplates\Visual C++ プロジェクト

のフォルダに先ほどの2つのzipファイルをおいて、解凍します。

f:id:garicchi:20141211110854p:plain

あとはVisualStudio2013を開いて、新規プロジェクトでVisualC++のところを見ると、先ほど解凍したフォルダの名前のプロジェクトテンプレートが追加されていると思います。

f:id:garicchi:20141211111122p:plain

デバッグして実行してみましょう。

プロジェクトにはDirectXToolkitというライブラリが必要ですがビルドするとNugetが自動でインストールしてくれます。

ゲームを実行できました。

f:id:garicchi:20141211111433p:plain

ゲーム画面にオブジェクトを配置する

このゲームライブラリではゲームにおけるさまざまな物をゲームオブジェクトとして定義しています。

ゲームオブジェクトはGameObjectクラスを継承します。

例えばTextureRendererなどは2D画像を描画するゲームオブジェクトです。

Screenフォルダ内のTitleScreen.hを見てみましょう。

class TitleScreen:public ScreenBase{
public:
    TitleScreen(const shared_ptr<DeviceResources>& deviceResources);

    void CreateResources();
    void ReleaseResources();
    ScreenBase* Update(const StepTimer& timer, const GameInput& input);
    void Render();
private:
    shared_ptr<TextureRenderer> texture1;
};

TitleScreenクラスにはコンストラクタと、CreateResources、ReleaseResources、Update、Renderメソッドが存在しています。

CreateResourcesメソッドはリソースの確保を、ReleaseResourcesはリソースの解放を、Updateはゲームオブジェクトに変化を与え、Renderはゲームオブジェクトを描画する役割を担当します。

それぞれ、このような順番でよびだされます。

f:id:garicchi:20141208181837p:plain

つまりUpdateとRenderメソッドは1秒間に60回呼び出されます。

privateメンバにはTextureRenderer型のスマートポインタが宣言されています。これがゲームオブジェクトとなります。

TitleScreen.cppを見てみましょう。

コンストラクタではTextureRendererの初期化をおこなっています。

コンストラクタ引数には、DeviceResources、描画する画像のパス、描画する座標、描画サイズ、回転角を指定します。

TitleScreen::TitleScreen(const shared_ptr<DeviceResources>& deviceResources)
    :ScreenBase(deviceResources){
    
    texture1 =shared_ptr<TextureRenderer>(new TextureRenderer(m_deviceResources, "assets/star.png",Vector2F(500,300),Vector2F(100,100),0.0f));
}

CreateResourcesメソッドではAddObjectメソッドによってゲームオブジェクトをゲーム画面に登録します。

void TitleScreen::CreateResources(){
    ScreenBase::CreateResources();
    AddObject(texture1);
}

もしゲーム画面から登録解除したい場合はRemoveObjectメソッドを実行しましょう。

ReleaseResourcesメソッドでは確保したリソースの解放をおこないます。

今回はCreateResorcesで何もリソースを確保していないので何もおこないません。

C++にはガベージコレクションは存在しないので自分で確保したメモリはここで解放しましょう。

void TitleScreen::ReleaseResources(){
    ScreenBase::ReleaseResources();
}

Updateメソッドではゲームオブジェクトに対して操作をします。 引数にあるGameInputクラスはユーザー入力を保持するのでm_pointInputsコンテナの値を調べてゲームオブジェクトの回転角を増やします。

ScreenBase* TitleScreen::Update(const StepTimer& timer, const GameInput& input){
    ScreenBase::Update(timer,input);
    
    for (unsigned int i = 0; i < input.m_pointInputs.size(); i++){
        
        texture1->m_rotation += 0.05;
    }

    ScreenBase* nextScreen=this;

    return nextScreen;
}

Renderメソッドではゲームオブジェクトを描画します。

しかしAddObjectメソッドでゲームオブジェクトを登録しておけば勝手に描画してくれます。

void TitleScreen::Render(){
    ScreenBase::Render();
}

つまりゲームオブジェクトを登録するには、

をする流れになります。

2D画像を描画する

2D画像を描画するには、TextureRendererクラスを使います。

あらかじめ描画したいpngファイルをAssetsフォルダ内に入れておいて、以下のTexureRendererをゲームオブジェクトとしてAddObjectメソッドでスクリーンに追加します。

TextureRenderer.hをインクルードしましょう

#include "Content/TextureRenderer.h"

引数は、DeviceResources、画像のパス、描画する座標、描画するサイズ、回転角で指定します。

shared_ptr<TextureRenderer> texture =shared_ptr<TextureRenderer>(new TextureRenderer(m_deviceResources, "assets/star.png",Vector2F(500,300),Vector2F(100,100),0.0f));


AddObject(texture);

文字を描画する

文字を描画するにはTextRendererクラスを使います。

TextRenderer.hをインクルードしましょう。

#include "Content/TextRenderer.h"

引数は、DeviceResources、描画する文字、描画フォント、描画座標、描画色、描画サイズを指定します。

shared_ptr<TextRenderer> text=shared_ptr<TextRenderer>(new TextRenderer(m_deviceResources,"もんじゃ焼き","Segoe UI",Point2F(10,10),ColorF::White,34.0f));

AddObject(text);

サウンドを鳴らす

サウンドを鳴らすにはSoundObjectクラスを使います。

あらかじめ鳴らしたいwavサウンドファイルをAssetsフォルダ内に追加しておきます。

SoundObject.hをインクルードしておきます。

#include "Content/SoundObject.h"
shared_ptr<SoundObject> sound = shared_ptr<SoundObject>(new SoundObject(m_deviceResources, "assets/hoge.wav"));
AddObject(sound);

あとは鳴らしたいときにPlayメソッドを呼び出します。

sound->Play();

タッチ入力を検知する

ゲームに対する入力は、ScreenのUpdateメソッドに引数として渡されるGameInputクラスを使います。

ScreenBase* TitleScreen::Update(const StepTimer& timer, const GameInput& input){
    ScreenBase::Update(timer,input);
    
    for (unsigned int i = 0; i < input.m_pointInputs.size(); i++){
        
        //m_pointInputにはタッチされている数だけ情報が入っている
        int x=input.m_pointInputs.at(0).m_point.X;
        int y = input.m_pointInputs.at(0).m_point.Y;

    }

    ScreenBase* nextScreen=this;

    return nextScreen;
}

独自のゲームオブジェクトを作成する

私が用意したゲームオブジェクトはTextureRenderer、TextRenderer、SoundObjectの3つですが、それ以外にもGameObjectクラスを継承することで自分でゲームオブジェクトを作成することができます。

自分でゲームオブジェクトをつくると、AddObjectメソッドで登録することでUpdateメソッドとRenderメソッドが1秒間に60回自動で呼ばれるオブジェクトを作ることができます。

簡単なゲームオブジェクトとして、SimpleRenderer.hとSimpleRenderer.cppを用意しておきました。

SimpleRenderer.h

#pragma once

#include "Common/DeviceResources.h"
#include "Common/StepTimer.h"
#include "ToolkitHelper\GameInput.h"
#include "ToolkitHelper/GameObject.h"


using namespace std;
using namespace DX;
using namespace ToolkitHelper;

class SimpleRenderer:public GameObject{
public:
    SimpleRenderer(const shared_ptr<DeviceResources>& deviceResources);

    void CreateResources();
    void ReleaseResources();
    void Update(const StepTimer& timer, const GameInput& input);
    void Render();

};

SimpleRenderer.cpp

#include "pch.h"
#include "SimpleRenderer.h"

using namespace std;
using namespace DX;

//レンダラーの初期化処理を行う
SimpleRenderer::SimpleRenderer(const shared_ptr<DeviceResources>& deviceResources)
    :GameObject(deviceResources){


}

//レンダラーで使うリソースを確保する
void SimpleRenderer::CreateResources(){

}

//レンダラーで使ったリソースを解放する
void SimpleRenderer::ReleaseResources(){

}

//レンダラーの更新処理を行う
void SimpleRenderer::Update(const StepTimer& timer, const GameInput& input){

}

//レンダラーの描画処理を行う
void SimpleRenderer::Render(){

}

これを改良して新しくゲームオブジェクトを作成することができます。

新しくゲーム画面を作成する

現在はTitleScreenというクラスで定義されたゲーム画面にゲームオブジェクトを追加しています。

しかしゲーム画面は1つではないのでScreenBaseクラスを継承して新しくゲーム画面を作成することができます。

GameScreenクラスを見てみましょう。

#pragma once

#include "Common/DeviceResources.h"
#include "Common/StepTimer.h"
#include "ToolkitHelper/ScreenBase.h"
#include "ToolkitHelper\GameInput.h"

using namespace std;
using namespace DX;
using namespace ToolkitHelper;

class GameScreen :public ScreenBase{
public:
    GameScreen(const shared_ptr<DeviceResources>& deviceResources);

    void CreateResources();
    void ReleaseResources();
    ScreenBase* Update(const StepTimer& timer, const GameInput& input);
    void Render();


};
#include "pch.h"
#include "GameScreen.h"

#include "Content/TextureRenderer.h"
#include "Content\TextRenderer.h"
#include "Content\SoundObject.h"

using namespace std;
using namespace DX;
using namespace D2D1;

GameScreen::GameScreen(const shared_ptr<DeviceResources>& deviceResources)
    :ScreenBase(deviceResources){
}

void GameScreen::CreateResources(){
    ScreenBase::CreateResources();
    
}

void GameScreen::ReleaseResources(){
    ScreenBase::ReleaseResources();
}

ScreenBase* GameScreen::Update(const StepTimer& timer, const GameInput& input){
    ScreenBase::Update(timer,input);

    ScreenBase* nextScreen = this;



    return nextScreen;
}

void GameScreen::Render(){
    ScreenBase::Render();
}

これを改良して新しくゲーム画面を作成することができます。

ゲーム画面を遷移する

ScreenBaseを継承したゲーム画面は、UpdateメソッドでScreenBase型のポインタを返します。

今は

ScreenBase* GameScreen::Update(const StepTimer& timer, const GameInput& input){
    ScreenBase::Update(timer,input);

    ScreenBase* nextScreen = this;



    return nextScreen;
}

のようにthisポインタで自身を返すことでその画面を維持し続けているわけです。

次の画面に遷移するには、新しいゲーム画面をnewして、returnすればOKです。

ScreenBase* GameScreen::Update(const StepTimer& timer, const GameInput& input){
    ScreenBase::Update(timer,input);

    ScreenBase* nextScreen = new GameScreen(m_deviceResources);
        //こうすることで次の画面はGameScreenになる


    return nextScreen;
}

ゲーム画面の状態遷移はオートマトンの考え方に基づいています。次の画面は今の画面が決定するということですね。

横画面は?

Windowsストアアプリは横画面でいけるけどWindowsPhoneはなんか横画面にするとエラーがでました。ちょっと忙しくて解決しきれなかった...

ユニバーサルアプリは?

VisualStudioのテンプレートエクスポーターがユニバーサルアプリに対応してないからできませんでした。

どちらかのプロジェクトを作成してプロジェクトを右クリック→Windows8.1の追加とかで作成して、Shardプロジェクトにcppファイルとhファイルを移動させましょう。

3Dは?

DirectXToolkitが.cmoモデルファイルと.sdkmeshモデルファイルにしか対応してないです。

なのでXファイルとかMMDモデルとかはDXToolkitに頼ることなく自分でDirectXを叩いてインポートする必要がある。

私は勉強不足なのでいつか頑張りたい。

ちなみにスキンメッシュアニメーションもまだDirectXToolkitは対応していない。

もっといろんなゲームオブジェクトほしいし足りない機能たくさんあるんだけど

プロジェクトはGitHubオープンソースで公開しています。

もともとはDirectX全く知らない人がストア向けゲームをつくる敷居を下げるためのものなので最低限の機能しか用意できませんでした*1

とはいえ、DirectXToolkitがそのまま叩けるので自分でゲームオブジェクトとかいろいろ作ってみましょう。*2

まとめ

やっぱり、学生は特にWindowsツールを作りたいというよりも、Windowsでゲームを作りたいと思っている学生が非常に多いように思います。

Windowsストアアプリの市場はお世辞にもめちゃくちゃ盛り上がってるということはなく、WindowsPhoneは国内にでてすらくれません。

しかしゲームに関しては、趣味レベルで作って市場はちいさくても公開して、誰かに使ってもらえるとなると非常にうれしい市場であり、Windowsストアはそのための環境を用意しています。

僕はそのようなWindowsでゲームプログラミングがしたいんだ!!っていう人たちの最初の敷居をさげることが非常に重要だと思っていて、今回多少なりともゲームプログラミング入門者を助けれたんじゃないかなと思います。

まだまだWindowsPhoneアドベントカレンダーは続きます。次の投稿者は@miu_hiro_さんです。きっと面白いネタを出してくれると期待して、僕は留年がかかったテスト勉強をします...

*1:実力的に

*2:だれか一緒に開発してほしい