Справка по проектированию инкапсуляции C ++ / SDL - PullRequest
2 голосов
/ 13 сентября 2011

Итак, я полу новичок в C ++ и совершенно новичок в SDL.Большая часть моих концептуальных знаний ООП приходит из Java и PHP.Так что терпите меня.

Я пытаюсь выработать простую логику проектирования с моей программой / скоро буду боковым скроллером.Моя проблема заключается в попытке сделать мой «экранный» слой (screen = SDL_SetVideoMode(...)) доступным для всех моих других классов;Класс героя, фоновый слой, враги и т. Д. Я неукоснительно следил за некоторыми процедурными уроками и пытался адаптировать их к более объектно-ориентированному подходу.Вот немного из того, что у меня есть:

main.cpp

#include "Game.h"
#include "Hero.h"

int main(int argc, char* argv[])
{
    //Init Game
    Game Game;

    //Load hero
    Hero Hero(Game.screen);

    //While game is running
    while(Game.runningState())
    {
        //Handle Window and Hero inputs
        Game.Input();
        Hero.userInput();

        //Draw
        Game.DrawBackground();
        Hero.drawHero();

        //Update
        Game.Update();
    }

    //Clean up
    Game.Clean();

    return 0;
}

Как видите, у меня есть класс Game и геройучебный класс.Класс Game отвечает за настройку начального окна и размещение фона.Он также обновляет экран, как вы можете видеть.

Теперь, так как мой класс Game содержит свойство 'screen', которое является дескриптором для SDL_SetVideoMode, я застрял, передавая это в любой другой класс (ex: Hero Hero(Game.screen);) который нужно обновить до этого экрана ... скажем через SDL_BlitSurface.

Теперь, это работает, однако я понимаю, что есть ПОЛНОСТЬЮ более элегантный подход.Как, возможно, оставить этот обработчик screen в глобальной области видимости (если это возможно)?

TLDR / Простая версия : Как лучше всего сделать мой обработчик окон / экранов доступным для всех моих последующих классов?

Ответы [ 5 ]

2 голосов
/ 13 сентября 2011

Мне нравится, как ты это делаешь.

Хотя вместо того, чтобы передавать ссылку на экран, я бы передавал ссылку на саму игру. Таким образом, каждый объект героя также знает, к какой игре он принадлежит, и затем может запросить у игрового объекта экран, как требуется.

Причина, по которой я бы сделал это, заключается в том, что через несколько лет, когда ваша игра станет широким и успешным продуктом, и вы конвертируете ее для онлайн-игры, вам действительно не нужно будет работать. Игровой сервер сможет легко поддерживать несколько игровых объектов, каждый из которых содержит несколько объектов-героев. Поскольку каждый объект героя хочет нарисовать, он запрашивает игру об экране и обновляет экран (теперь экран может очень сильно переходить от игрового объекта к игровому объекту и по-прежнему работать идеально (при условии, что они имеют одинаковый интерфейс).

class Game
{
   public:
       Game(Screen& screen)
          : screen(screen)
       {}
       virtual ~Game()           {}
       virtual Screen&  screen() { return theGameScreen;}

       void update() { /* Draw Screen. Then draw all the heros */ }

   private:
       friend Hero::Hero(Game&);
       friend Hero::~Hero();
       void addHero(Hero& newHero) {herosInGame.push_back(&newHero);}
       void delHero(Hero& newHeor) {/* Delete Hero from herosInGame */}

       // Implementation detail about how a game stores a screen
       // I do not have enough context only that a Game should have one
       // So theoretically:

       Screen&              theGameScreen;
       std::vector<Hero*>   herosInGame;


};

class Hero
{
    public:
        Hero(Game& game)
            : game(game)
         {game.addHero(*this);}
        virtual ~Hero()
         {game.delHero(*this);}


         virtual void Draw(Screen& screen)  {/* Draw a hero on the screen */}
     private:
         Game& game;
};

Main.

#include "Game.h"
#include "Hero.h"

int main(int argc, char* argv[])
{
    //Init Game
    Screen   aScreenObject
    Game     game(aScreenObject);

    //Load hero
    Hero     hero(game);             // or create one hero object for each player


    //While game is running
    while(game.runningState())
    {
        //Handle Window and Hero inputs
        Game.Input();
        Hero.userInput();

        //Update
        Game.update();
    }

    //Clean up
    // Game.Clean(); Don't do this
    //               This is what the destructor is for.
}
1 голос
/ 13 сентября 2011

Похоже, вы ищете способ реализовать шаблон проектирования Singleton , в котором у вас будет один Screen объект. Если вы знаете, что у вас когда-нибудь будет только один Screen объект, он должен работать нормально.

В этом случае вы реализуете статический метод для класса Game:


class Game
{
    public:
        static Game *GetTheSceenObject();
    private:
        static Screen *theScreen;    // details of initialisation ommitted
}

, который возвращает указатель на один Screen объект.


Если есть вероятность, что вы в конечном итоге будете использовать несколько экранов SDL, возможно, стоит создать метод Draw() в вашем классе Hero, который отвечает за рисование героя на каждом из Screen управляется классом Game путем перебора списка, предоставленного классом Game.

Эта функциональность может содержаться в методах общего класса DrawableThingy, из которого Hero и Enemy являются производными.

1 голос
/ 13 сентября 2011

Я не знаю, элегантно ли это, но то, что я делаю для игры с боковой прокруткой, я делаю для каждого класса класс show(), который рисует на экране и передает дескриптор экрана как параметр. Затем, когда я хочу что-то нарисовать на экране, я просто делаю foo.show(screen). Дескриптор экрана в main().

1 голос
/ 13 сентября 2011

Первое и, честно говоря, самое простое решение - это использование глобальной переменной.Да, да, да, все говорят, что глобальные переменные ужасны, но в этой ситуации это совершенно нормально.

Другое решение, которое немного труднее, но может привести к несколько более переносимому коду, заключается винкапсулируйте ваши функции рисования в один статический класс.Таким образом, вы можете рисовать прямо на экране, не передавая переменную, или не спать ночью, думая, что полиция проверки кода поймает вас, потому что вы использовали глобальную переменную.Плюс, это может потенциально облегчить, если вы когда-нибудь решите портировать свою игру на новую библиотеку.Какой-то быстрый и грязный псевдокод:

class Drawing
  public:
    static void Draw(x, y, sdl_surface graphic, sdl_rect & clip=null);
    static void init(sdl_surface & screen);
  private:
    sdl_surface screen;

void Drawing::Draw(x, y, sdl_surface graphic, sdl_rect & clip=null)
{
  sdl_blit(x, y, graphic, clip);
}

void Drawing::init(sdl_surface & screen)
{
  this.screen=screen;
}
0 голосов
/ 13 сентября 2011

Передача Game.screen вокруг - это больше ОО (хотя может быть лучше иметь функцию-получатель), чем наличие одного ее экземпляра, к которому можно получить доступ из любого класса, потому что, если у вас есть одна глобальная версия, вы не можете иметьболее одного Game.screen одновременно.

Однако, если вы знаете, что вам когда-либо понадобится только один, за все время существования программы, вы можете рассмотреть возможность сделать Game::Screen() общедоступной статической функцией в Game класс, который возвращает закрытый статический член screen.Таким образом, любой может позвонить Game::Screen() и получить пример screen.

(при условии, что ScreenType - это тип screen и вы сохраняете указатель на него):

class Game {
public:
    static ScreenType* Screen() {
        if (!screen)
            screen = GetScreenType(args);
        return screen; 
    }
}

private:
    // if you don't already know:
    // static means only one of this variable exists, *not* one per instance
    // so there is only one no matter how many instances you make
    // the same applies to static functions, which you don't need an instance to call
    static ScreenType* screen;
};

// and somewhere in a .cpp file
ScreenType* Game::screen = NULL;

// and you use it like this
ScreenType* scr = Game::Screen();
// use scr
...