テンプレート関数について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
はね

テンプレート関数について

#1

投稿記事 by はね » 17年前

現在、DirectXにて画像を読み込む関数を汎用的にしようと思い、テンプレート関数を作ったのですが、

main.obj : error LNK2019: 未解決の外部シンボル "long __cdecl Init2D<struct PLAYER>(struct PLAYER *,char *,struct D3DXVECTOR2 *)" (??$Init2D@UPLAYER@@@@YAJPAUPLAYER@@PADPAUD3DXVECTOR2@@@Z) が関数 "long __cdecl InitD3d(struct HWND__ *)" (?InitD3d@@YAJPAUHWND__@@@Z) で参照されました。

のエラーの解決法が分からずに困っております。
大変恐縮ですが、どなたかご教授お願いします。


一応参考までにエラーに関係ありそうな部分を抜粋し、下に載せさせていただきました。

==================================Gloval.h内======================================================

//プロトタイプ宣言
template<class T>
HRESULT Init2D(T *,LPSTR Filename,D3DXVECTOR2 *vec2);

================================================================================================

==================================LoadBMP.cpp内===================================================

template<class T>
HRESULT Init2D(T* pg2d,LPSTR Filename,D3DXVECTOR2 *vec2){
if(FAILED(D3DXCreateTextureFromFileEx(pDevice,Filename,vec2->x,vec2->y,0,0,D3DFMT_UNKNOWN,D3DPOOL_DEFAULT,
D3DX_FILTER_NONE,D3DX_DEFAULT,0xffff00ff,NULL,NULL,&pg2d->pTexture))){
MessageBox(0,"テクスチャ失敗","",MB_OK);
return E_FAIL;
}
if(FAILED(D3DXCreateSprite(pDevice,&pg2d->pSprite))){
MessageBox(0,"スプライト失敗","",MB_OK);
return E_FAIL;
}
return S_OK;
}

==================================================================================================

===========================================main.cpp内=============================================

Init2D(&Player[0],"sh/char1.bmp",&D3DXVECTOR2(240,48));
//Init2D(構造体アドレス,画像名,読み込む画像の縦横幅);

==================================================================================================

全てのcppファイルでGloval.hをインクルードしています。
よろしくお願いいたします。

Justy

Re:テンプレート関数について

#2

投稿記事 by Justy » 17年前

 テンプレートの定義はヘッダで、が基本なので、
とりあえず、LoadBMP.cppの Init2D()を Gloval.hへ持っていけばいいはずです。

はね

Re:テンプレート関数について

#3

投稿記事 by はね » 17年前

回答ありがとうございます。
Justy様のおっしゃるようにヘッダーに書いたらうまくいきました。

1つ質問なのですが、ヘッダーでは宣言のみで定義はcppファイルでやりましょう。
のような事を見たのですが、テンプレートは例外なのでしょうか?

Justy

Re:テンプレート関数について

#4

投稿記事 by Justy » 17年前

>1つ質問なのですが、ヘッダーでは宣言のみで定義はcppファイルでやりましょう。
>のような事を見たのですが、テンプレートは例外なのでしょうか?

 そうなります。

C++編(言語解説) 第27章 テンプレートクラス
http://www.geocities.jp/ky_webid/cpp/language/027.html
 の中段参照。


 ただ、言語的には exportというキーワードがあり、これを使えば
cppの方に書くこともできるはずです。
(問題はコンパイラがこの機能をサポートしているかどうか、ですが)

テンプレートをヘッダファイル外で定義する - YAMAGUCHI::weblog
http://d.hatena.ne.jp/ymotongpoo/20080115/1200373530



#ところで、このテンプレート関数 Init2D()ですが、
T型の pSpriteのメンバをスプライトと見なして初期化をするようですが、
1つのクラス(T)の中で2つ以上スプライトが必要になったら、
どうするんでしょう?? Init2D2()とか作るのでしょうか。

GPGA

Re:テンプレート関数について

#5

投稿記事 by GPGA » 17年前

pSpriteは、描画元のイメージではなく
スプライトを描画するための関数のインターフェイスになります。

このpSpriteはグローバルな領域に一つだけ作成して、スプライト描画関数を呼ぶときに
使いまわすのが一般的だと思います。

はね

Re:テンプレート関数について

#6

投稿記事 by はね » 17年前

>>Justy様
>#ところで、このテンプレート関数 Init2D()ですが、
>T型の pSpriteのメンバをスプライトと見なして初期化をするようですが、
>1つのクラス(T)の中で2つ以上スプライトが必要になったら、
>どうするんでしょう?? Init2D2()とか作るのでしょうか。
現在は1つのオブジェクトにつき、1枚の画像しか使用しないのでpSpriteは1つで何の問題もありませんが、
もし、2枚以上の画像が必要なった場合には・・・どうなんでしょ?^^;
考えたこともなかったので憶測ですがpSpriteを配列にしてみたり、でしょうか。
すみません勉強不足です。

>>GPGA
現在は、プレイヤー等の画像を表示させる必要がある構造体にはメンバとして構造体にはそれぞれpSpriteを持たせております。
と言うのも、画像を回転させる等の処理ではPlayer.pSprite->SetTransform(&mat);のような関数を使わないとできないのでこのような形しか思いつきませんでした。

GPGA

Re:テンプレート関数について

#7

投稿記事 by GPGA » 17年前

SetTransformに入れるマトリクスの値をあらかじめプレイヤーのメンバで持っておき
処理の部分で、このマトリクスに回転行列をいれ、描画のときにそのメンバを使用すれば
よいと思います。
LPD3DXSPRITE g_pSprite; // グローバルで宣言

// 描画
void Draw(){
{
    g_pSprite->Begin(0);
    for (int i = 0; i < MAX_PLAYER; ++i) {
        g_pSprite->SetTransform(Player.mat);
        g_pSprite->Draw(Player.pTexture, NULL, NULL, NULL, 0xFFFFFFFF);
    }
    g_pSprite->End();
}
 

DirectX9.0cからID3DXSprite::Beginの仕様が変更されました。
現在のデバイス状態を保存して内容を書き換え
ID3DXSprite::Endを呼び出したときに元に戻します。
このデバイスの設定の書き換えというのは、処理が軽いものではないので
グローバル領域にひとつだけ作ることで、この負荷を抑えることができます。
もっとも、これに関してはID3DXSprite::BeginのD3DXSPRITE_DONOTSAVESTATEを
渡すことで、保存および復元を行わないようにすることができますが・・・。

ほかにも、デバイスロストやデバイスリセットが起こった場合に
すべてのスプライトに対して、ID3DXSprite::OnLostDeviceや
ID3DXSprite::OnResetDeviceを呼び出す手間も、一度で済みます。

Justy

Re:テンプレート関数について

#8

投稿記事 by Justy » 17年前

 あー、GPGAさんの仰るとおり、ID3DXSpriteが幾つも出てくることはそう無いですね。
 むしろ、テクスチャ(pTexture)の方ですね、問題があるとすれば。


>もし、2枚以上の画像が必要なった場合には・・・どうなんでしょ?^^;
>考えたこともなかったので憶測ですがpSpriteを配列にしてみたり、でしょうか。

 配列にしてもこのInit2D()では処理できないですね。

 pTextureの型が 生の IDirect3DTexture9*なのか、IDirect3DTexture8*なのか、
或いは CComPtrなそれなのかはわかりませんが、ちゃんとテクスチャクラスを
作って、そのメンバ関数でロードとかさせた方がいいかと思います。

 そうしておけば、プレイヤーが何枚もテクスチャを持つ必要があっても
テクスチャクラスをメンバに持たせるだけで、環境が許す限り幾つでも
持てますから。

はね

Re:テンプレート関数について

#9

投稿記事 by はね » 17年前

>>GPGA様
なるほど!確かに個々のオブジェクトに行列を持たせればスプライト一個で済みますね。
貴重な回答ありがとうございます。
早速実装してみます。

>>Justy様
>配列にしてもこのInit2D()では処理できないですね。
はい、おっしゃるようにD3DXCreateSprite()の時点でおかしな事になっていました。
テクスチャの型はLPDIRECT3DTEXTURE9です。
テクスチャクラスですか。今はパッと思いつきませんが今後実装してみようと思います。
ありがとうございました。

GPGA

Re:テンプレート関数について

#10

投稿記事 by GPGA » 17年前

今から書くことは私の考え方ですので、参考にしてもらってもかまいませんし無視してもらってもかまいません。
私は昔PSの実習機を使っていたことがあり、その作りをベースにしています。

DirectXで描画を行う際に必要な項目を三つのクラスに分けています。
・テクスチャークラス
・スプライトクラス
・描画クラス

大雑把に、各クラスのインターフェイスを記述します。
// テクスチャークラス
class CTexture {
public :
    // 読み込み関数
    bool CreatFromFile(const char* szFileName);                 // ファイルから読み込み
    bool CreatFromMemory(const char* pcBuffer, DWORD size);     // メモリから読み込み

    // サイズ関数
    DWORD GetWidth() const;     // 幅を取得
    DWORD GetHeight() const;    // 高さを取得

privaete :
    // テクスチャーに必要な変数
    ・・・
};


// スプライトクラス
class CSprite {
public :
    int dstx;           // 描画先のX座標
    int dsty;           // 描画先のY座標
    int srcx;           // テクスチャーのX座標
    int srcy;           // テクスチャーのY座標
    int width;          // 幅
    int height;         // 高さ
    int rotate;         // 回転角度
    int alpha;          // アルファ値
    float scalex;       // X拡縮値
    float scaley;       // Y拡縮値
    CTexture* texture;  // 描画するテクスチャー
};


// 描画クラス
class CDraw {
public :
    // 通常描画
    static void DrawSprite(CSprite& sprite);    // スプライトを裏画面に描画する

    // 画面反映
    static void Present();                      // 裏画面に描かれた画像を表画面に描画する
};
 
まずテクスチャークラスはその名のとおり、テクスチャーのみを扱います。
テクスチャークラス一つにつき画像一枚と考えてもらって問題ありません。

スプライトクラスは画面に描画する画像の設定を扱います。
メンバのtextureに、テクスチャークラスのポインタを指定することで、描画する画像を選択します。
また、表示するX座標やY座標、回転角度、拡縮、半透明などの値を設定することができます。


描画クラスはスプライトクラスを受け取り、設定にそって描画を行います。

以上の分け方をすることにより、必要最低限の情報で描画を行うことができます。




これらを実際のゲームで使用する場合、テクスチャークラスはグローバル配列で持ちます。
スプライトクラスはプレイヤークラスのメンバ変数として持ちます。
以下に例を示します。
プレイヤークラス
class Player {
public :
    CSprite sprite;
};


// グローバル領域にテクスチャー配列とプレイヤー配列を定義
CTexture g_Texture[5];
Player g_Player[2];

// テクスチャー読み込み関数
void LoadTexture() {
    // 0.png~4.pngという画像を読み込む
    for (int i = 0; i < 5; ++i) {
        char fname[20];
        sprintf(fname, "%d.png", i);
        g_Texture.CreateFromFile(fname);
    }
}

// プレイヤーの初期化を行う
void InitPlayer() {
    for (int i = 0; i < 2; ++i) {
        g_Player.sprite.texture = &g_Texture;     // テクスチャーを登録する
        g_Player.sprite.dstx = 10;                   // 描画座標を設定する
        ・・・
    }
}


// プレイヤーの描画を行う
void DrawPlayer() {
    for (int i = 0; i < 2; ++i) {
        CDraw::DrawSprite(g_Player.sprite);      // スプライトの描画を行う
    }

    // 表画面に反映する
    CDraw::Present();
}
 


使用例が非常に大雑把かつ、C風になってしまいましたが、使い方がわかりましたでしょうか?
(C++風に書くと記述が長くなるのでC風で書かせていただきました)
このやり方ですと、プレイヤーが2つの画像を描画したい場合、プレイヤークラスの
メンバに、スプライトクラスをもう一つ追加して設定することで、描画できます。
長文、失礼しました。

閉鎖

“C言語何でも質問掲示板” へ戻る