LoginSignup
3
1

More than 5 years have passed since last update.

C++Siv3D入門講座 Vol.08 GameManagerクラス・前半課題

Last updated at Posted at 2017-01-23

C++Siv3D入門講座

GameManagerクラス

実際ゲームを作る時、PlayerやEnemyの配列、Bulletの配列、Effectの配列等をメンバに持ち、それらを管理するManagerクラスを作ると便利である。

今回は、PlayerとEnemyを持つManagerクラスの実体をグローバル変数にすることで、EnemyがPlayerが相互にアクセスする例を示す。
(前回は、EnemyクラスがPlayerへのポインタを持つことで、Playerクラスにアクセスすることができたが、今回はEnemyクラスがPlayerクラスへのポインタを持つ必要はない)

PlayerがEnemyの情報に、EnemyがPlayerの情報に相互にアクセス出来るようにするため、上手くファイルをインクルードしなければならない。

以下の例では、PlayerはshowEnemyX関数でEnemyのxにアクセスし、EnemyはshowPlayerX関数でPlayerのxにアクセスしている。

Player.h

#pragma once
#include <iostream>
#include "Manager.h"

class Player {
public:
    int x;
    Player(int _x);
    void update();
    void showEnemyX();
};

Player.cpp

#include "Manager.h"

Player::Player(int _x) :
    x(_x)
{
}

void Player::update() {
    std::cout << "Player内のupdateが呼ばれました。xは:" << x << std::endl;
}

void Player::showEnemyX() {
    std::cout << "Player内のshowEnemyXが呼ばれました。" << std::endl;
    std::cout << "Enemyのxは:" << manager.enemy.x << std::endl;
}

Enemy.h

#pragma once
#include <iostream>
#include "Manager.h"

class Enemy {
public:
    int x;
    Enemy(int _x);
    void update();
    void showPlayerX();
};

Enemy.cpp

#include "Manager.h"

Enemy::Enemy(int _x) :
    x(_x)
{
}

void Enemy::update() {
    std::cout << "Enemy内のupdateが呼ばれました。xは:" << x << std::endl;
}

void Enemy::showPlayerX() {
    std::cout << "Enemy内のshowPlayerXが呼ばれました。" << std::endl;
    std::cout << "Playerのxは:" << manager.player.x << std::endl;
}

Manager.h

#pragma once
#include "Player.h"
#include "Enemy.h"

class Manager {
public:
    Player player;
    Enemy enemy;

    Manager();
};

extern Manager manager;

Manager.cpp

#include "Manager.h"

Manager::Manager() :
    player(100),
    enemy(200)
{
};

Manager manager;

main.cpp

#include <iostream>
#include "Manager.h"

int main() {

    manager.player.update();
    manager.enemy.update();

    manager.player.showEnemyX();
    manager.enemy.showPlayerX();

    return 0;
}

Tips : range-based-for

これまでvectorに対して、for文で用いる時、毎回始めのイテレータと終わりのイテレータを書いていたが、これは面倒である。C++にはrange-based-forがありこれを用いると、配列のそれぞれの要素に対して行う処理が簡単に書ける。以下に書き方を示す。

for(配列の要素の型 受け取る変数 : 配列名){

}

以下にサンプルコードを用意した。今回は、自分で用意したMyClass型のvectorのそれぞれの要素のaを表示している。配列の要素の型はautoで推論している。constを使うことで値を間違って変更することを防いでおり、参照を用いて、無駄なコピーをが発生しないようにしている。

#include <iostream>
#include <vector>

class MyClass {
public:
    int a;
    MyClass(int _a) :
        a(_a) 
    {
    }
};

int main() {

    std::vector<MyClass> vec;
    for (int i = 0; i < 5; i++) {
        vec.emplace_back(MyClass(i));
    }

    for (const auto& i : vec) {
        std::cout << i.a << std::endl;

        // コメントを外すと値を書き換えることになるのでコンパイルエラー
        // i.a = 99;
    }

    return 0;
}

以下はMyClassのaを全て2倍にする例。参照を用いて、vectorの中身を書き換えている。auto&&と、&が1つではなく2つあるがタイプミスではない。今回の場合auto&でも問題ないが、auto&&を用いると右辺値にも対応できるようになり、より汎用的な書き方になるので、range-based-forを用いる際は基本的にこちらを推奨する。(ちなみに、VisualStudio上でマウスカーソルをiの上に持っていくとわかるが、iはちゃんとMyClass&と推論されていることがわかる)詳しくは、range-based for loopsの要素の型について参照。

#include <iostream>
#include <vector>

class MyClass {
public:
    int a;
    MyClass(int _a) :
        a(_a) 
    {
    }
};

int main() {

    std::vector<MyClass> vec;
    for (int i = 0; i < 5; i++) {
        vec.emplace_back(MyClass(i));
    }

    // 参照を用いてそれぞれのMyClassのaを2倍に
    for (auto&& i : vec) {
        i.a *= 2;
    }

    // 中身を表示
    for (const auto& i : vec) {
        std::cout << i.a << std::endl;
    }

    return 0;
}

演習問題(Siv3D)

  1. GameManagerクラスを作れ。

    GameManagerクラスは、PlayerやEnemyManager(Enemyのvectorを管理するクラス)をメンバに持ち、それらを管理するクラスである。

    GameManagerのインスタンスをグローバル変数にすることで、GameManagerを介してPlayerやEnemyの情報にアクセスできるようにせよ。

    GameManagerクラスのupdateを呼べば、PlayerとすべてのEnemyのupdateが呼ばれるようにせよ。

    EnemyクラスがPlayerクラスへのポインタを持っていた場合、それを消して、Playerクラスへのアクセスはグローバル空間に存在するGameManagerクラスのインスタンスを介して行うようにせよ。

  2. Enemyが自機を追うようにせよ。

  3. Enemyクラス内にint型のkind変数を作り、kind変数によって敵が多様な動きをするようにせよ。

    (kind==1なら自機に直進、kind==2ならsin軌道、kind==3なら出た位置で円運動…など)

  4. kindによって敵の色が変わるようにせよ。

  5. kindをenum型にせよ。クラス内で定義されたenumは、クラス外部からは(クラス名)::(要素の名前)で enumを指定できる。

ヒント:enum型

クラス内でenumを定義しコンストラクタで指定する例。

#include <iostream>

class Hoge {
public:
    enum Kind {
        A,
        B,
        C,
    };

    Kind kind;

    //コンストラクタ kindを指定
    Hoge(Kind _kind) :
    kind(_kind)
    {
    }
};

int main() {
    // コンストラクタでkindを指定
    Hoge ins(Hoge::B);

    // 出力。整数が出力される(0から始まる)
    std::cout << ins.kind << std::endl;

    return 0;
}

前半課題

これまでやったことを用いて、添付資料のサンプルゲームと似たようなものを作れ。今回は練習として、GameManagerクラスが他のクラスを持つ形にすること。

ヒント

解答例は以下のようなクラス構成にした。

GameManagerクラス(他のクラスを所持し、管理する)
|- Playerクラス
|- PlayerBulletManagerクラス(PlayerBulletクラスのvectorを所持)
|- EnemyManagerクラス(Enemyクラスのvectorを所持)
|- EnemyBulletManagerクラス(EnemyBulletクラスのvectorを所持)
|- EffectManagerクラス(Effectクラスのvectorを所持)
|- ScoreManagerクラス(スコアを管理するクラス)

リンク

前の回-Vol.07 remove_if・ラムダ式
次の回-Vol.09 new・delete・基底・派生クラス

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1