★プレイヤーキャラクター 継承★


プレイヤーキャラクターと弾のクラスを作りましたが、2つのクラスを比べてみると同じようなデータがある事が分かります。

<プレイヤークラス>

class Player {
public:

    enum {
        UP,
        RIGHT,
        DOWN,
        LEFT,
    };

    Player();

    void Initialize();

    void Update(Engine *pEngine, Shot &refShot);

    void Draw(Engine *pEngine);

private:

    const int WIDTH;
    const int HEIGHT;

    const int SPEED_NORMAL;
    const int SPEED_HIGH;

    int m_x;
    int m_y;

    int m_speed;

    int m_direction;

    IntervalManage m_imAnimation;

    int m_animation;

    RECT m_sour;
    RECT m_dest;

    void Move(Engine *pEngine);

    void SpeedUp(Engine *pEngine);

    void ChangeAnimation();

    void Shoot(Engine *pEngine, Shot &refShot);
};

<弾クラス>

class Shot {
public:

    Shot();

    void Initialize();

    void Shoot(const int x, const int y, const int direction);

    void Update();

    void Draw(Engine *pEngine);

    int GetWidth() const;

    int GetHeight() const;

private:

    const int WIDTH;
    const int HEIGHT;

    const int SPEED;

    bool m_bShooting;

    int m_x;
    int m_y;

    int m_direction;

    RECT m_sour;
    RECT m_dest;

    void Move();
};

確認したところ赤字の部分が全く同じデータでした。

※関数にも同じものがありますが、今回は無視します。

列挙体は青く色づけしましたが、弾クラスでも使うデータですからこれも共用していると言う事です。


基底クラスの作成


この共通したデータ部分を「基底クラス」として分け、そこから継承を使って形を変えてみたいと思います。

まず、オブジェクトとしてプロジェクトに「Base.h」と「Base.cpp」を追加しましょう。

「Base.h」を開き、「Baseクラス」を作成し、共通したデータを宣言します。

class Base {
public:

protected:

    enum {
        UP,
        RIGHT,
        DOWN,
        LEFT,
    };

    const int WIDTH;
    const int HEIGHT;

    int m_x;
    int m_y;

    int m_direction;

    RECT m_sour;
    RECT m_dest;
};

継承するための「基底クラス」ですから、継承先で使えるようにprotectedメンバにします。


メンバ定数はコンストラクタ関数で初期値を設定しなければなりません。

コンストラクタ関数を追加したいのですが↓ではダメです。

class Base {
public:

    Base();

protected:

    enum {
        UP,
        RIGHT,
        DOWN,
        LEFT,
    };

    const int WIDTH;
    const int HEIGHT;

    int m_x;
    int m_y;

    int m_direction;

    RECT m_sour;
    RECT m_dest;
};

引数の無いコンストラクタ関数を設定した場合、設定する定数の値は変える事が出来ません。

このまま本体を作って初期値を設定した場合、

Base::Base()
    : WIDTH(32)
    , HEIGHT(32)
{

}

のように、プレイヤーのサイズは設定出来ても、弾のサイズは異なりますので上手くいきません。

ですから、コンストラクタ関数には引数を渡せるようにします。

class Base {
public:

    Base(const int width, const int height);

protected:

    enum {
        UP,
        RIGHT,
        DOWN,
        LEFT,
    };

    const int WIDTH;
    const int HEIGHT;

    int m_x;
    int m_y;

    int m_direction;

    RECT m_sour;
    RECT m_dest;
};

こうすれば、派生したクラスごとに初期値を設定出来るようになります。


「Base.cpp」を開き、コンストラクタ関数の本体を追加します。

Base::Base(const int width, const int height)
    : WIDTH(width)
    , HEIGHT(height)
{

}

プレイヤークラスへの継承


これで「基底クラス」は出来ましたので、プレイヤークラスに継承しましょう。

「Player.h」を開き、「基底クラス」のヘッダファイルをインクルードします。

//-----------------------------------------------------------------------------
// クラスの宣言を書きます。
//-----------------------------------------------------------------------------

#include "..\\Base\\Base.h"

class Back;

class Shot;

class Player {

「基底クラス」から継承するコードを追加します。

class Back;

class Shot;

class Player : public Base {
public:

    enum {
        UP,
        RIGHT,
        DOWN,
        LEFT,
    };

「基底クラス」で用意されている、列挙体、定数、変数を削除します。

class Player : public Base {
public:

    enum {
        UP,
        RIGHT,
        DOWN,
        LEFT,
    };

    Player();

    void Initialize();

    void Update(Engine *pEngine, Shot &refShot);

    void Draw(Engine *pEngine);

private:

    const int WIDTH;
    const int HEIGHT;

    const int SPEED_NORMAL;
    const int SPEED_HIGH;

    int m_x;
    int m_y;

    int m_speed;

    int m_direction;

    IntervalManage m_imAnimation;

    int m_animation;

    RECT m_sour;
    RECT m_dest;

    void Move(Engine *pEngine);

    void SpeedUp(Engine *pEngine);

    void ChangeAnimation();

    void Shoot(Engine *pEngine, Shot &refShot);
};

「Player.cpp」を開き、コンストラクタ関数を変更しましょう。

定数の「WIDTH」「HEIGHT」は「基底クラス」のコンストラクタ関数に引数を渡す事で設定します。

「基底クラス」のコンストラクタ関数に引数を渡すには、次のようにします。

Player::Player()
    : SPEED_NORMAL(2)
    , SPEED_HIGH(4)
    , Base(32, 32)
{

}

先にメンバ定数の初期化を行い、その後「基底クラス」のコンストラクタ関数に引数を渡しています。

コンストラクタ関数の初期化の順番についてはこちらに書いてあります。


弾クラスへの継承


次に弾クラスへの継承を行います。

「Shot.h」を開き、「基底クラス」のヘッダファイルをインクルードします。

//-----------------------------------------------------------------------------
// クラスの宣言を書きます。
//-----------------------------------------------------------------------------

#include "..\\Base\\Base.h"

class Shot {
public:

「基底クラス」から継承するコードを追加します。

class Shot : public Base {
public:

    Shot();

「基底クラス」で用意されている、定数、変数を削除します。

class Shot : public Base {
public:

    Shot();

    void Initialize();

    void Shoot(const int x, const int y, const int direction);

    void Update();

    void Draw(Engine *pEngine);

    int GetWidth() const;

    int GetHeight() const;

private:

    const int WIDTH;
    const int HEIGHT;

    const int SPEED;

    bool m_bShooting;

    int m_x;
    int m_y;

    int m_direction;

    RECT m_sour;
    RECT m_dest;

    void Move();
};

「Shot.cpp」を開き、コンストラクタ関数を変更しましょう。

Shot::Shot()
    : SPEED(10)
    , Base(16, 16)
{

}

次に「Move関数」の列挙体部分を変更します。

void Shot::Move()
{
    if (m_bShooting) {

        switch (m_direction) {

        case Base::UP:
            m_y -= SPEED;
            break;

        case Base::RIGHT:
            m_x += SPEED;
            break;

        case Base::DOWN:
            m_y += SPEED;
            break;

        case Base::LEFT:
            m_x -= SPEED;
            break;
        }        

        if (m_x >= WindowSetting::WINDOW_WIDTH || m_x <= -WIDTH ||
            m_y >= WindowSetting::WINDOW_HEIGHT || m_y <= -HEIGHT) {

            m_bShooting = false;
        }
    }
}

では、ビルドしてみましょう。



エラーが出ますよね。

  Base.cpp
    error C2504: 'Base': 定義されていない基底クラスが宣言されています。
    error C2504: 'Base': 定義されていない基底クラスが宣言されています。

「Base.cpp」において「Baseクラスが無い」と言うエラーが出ています。

このエラーの原因は、ヘッダファイルのインクルードの順番が関係しています。


「Base.cpp」をコンパイルしようとした場合、「Base.h」をインクルードします。

「Base.h」を見ると、「Baseクラス」の宣言より先に「GameBase.h」をインクルードしています。

「GameBase.h」を見ると↓のようにオブジェクト用ファイルがインクルードされています。

#include "Object\\Player\\Player.h"
#include "Object\\Back\\Back.h"
#include "Object\\Shot\\Shot.h"

最初にインクルードされる「Player.h」を見ると、

#include "..\\..\\GameBase.h"

//-----------------------------------------------------------------------------
// クラスの宣言を書きます。
//-----------------------------------------------------------------------------

#include "..\\Base\\Base.h"

class Back;

class Shot;

class Player : public Base { //※ここがエラー
public:

「GameBase.h」と「Base.h」をインクルードしていますが、すでに1回インクルードしていますのでインクルードガードが働き「無視」されます。

そのまま「Playerクラス」の宣言に入り、↑の※マークの部分でエラーになります。

この時点では「Baseクラス」の宣言はされていませんからね・・・

これは「前方参照」でも解決できません。


解決策


根本原因としては、プログラムを楽にするためオブジェクトファイルには「GameBase.h」を最初からインクルードするように作ってある事にあります。

「Baseクラス」では「GameBase.h」で宣言されている「プレイヤークラス」「背景クラス」「弾クラス」は使いません。

また、「TEXTURE_CHARACTER」「TEXTURE_BACK」「TEXTURE_SHOT」などのグラフィックも使いません。

ほぼ「GameBase.h」をインクルードする必要は無いのです・・・

「Base.h」を開き、「GameBase.h」をインクルードするのを止めましょう。

#pragma once

//-----------------------------------------------------------------------------
// クラスの宣言を書きます。
//-----------------------------------------------------------------------------

class Base {
public:

該当する行を削除しました。


「GameBase.h」をインクルードしない事で発生するエラーに対処します。

1つは「Base.h」のRECT構造体のところでエラーが出ているはずです。

RECT構造体を使うには「Windows.h」と言うヘッダファイルをインクルードしなければなりません。

#pragma once

#include <Windows.h>

//-----------------------------------------------------------------------------
// クラスの宣言を書きます。
//-----------------------------------------------------------------------------

class Base {
public:

「Windows.h」はウインドウズの機能を使うためにマイクロソフトが用意しているヘッダファイルなので「< >」でくくります。


もう1つは「Base.cpp」にある「名前空間」のusing指定の箇所です。

#include "Base.h"

using namespace KeyString;
using namespace InputKey;

これも使いませんので削除しましょう。


これで問題無ければエラーが無くなり、元通りに動作するはずです。


なぜ、ここで継承する形に変更したかと言うと、敵キャラクターの追加を考えたからです。

次回から敵キャラクターを作っていきましょう。


次へ

戻る

目次へ