【问题标题】:How to make my own classes with C++ and SFML如何使用 C++ 和 SFML 创建自己的类
【发布时间】:2013-10-09 05:15:58
【问题描述】:

在学习了 C++ 的基础知识后,我目前开始使用 SFML。我已经了解了数组、引用和之前的所有内容,但很难掌握使用类的概念。

在 SFML 中,我创建了一个简单的精灵移动程序,但是,我想将此信息移动到一个类中(假设它将被称为“玩家”)。我搞砸了很多,但我无法让它工作。

我尝试在一个类中创建一个函数来检查玩家输入,但我无法访问我在 main.js 中创建的精灵。我想将与播放器相关的所有内容移到 Player 类中,但需要一些建议。

这样做的正确方法是什么? (请不要说回去学习课程,这是我想学习的地方!)

ma​​in.cpp

#include <SFML/Graphics.hpp>
#include <string>
#include <iostream>

int main()
{
    //character position
    enum Direction{ Down, Left, Right, Up };
    sf::Vector2i source(1, Down);

    //window
    sf::RenderWindow window(sf::VideoMode(1200, 700), "Testing");
    window.setKeyRepeatEnabled(false);

    //player character
    sf::Texture pTexture;
    sf::Sprite pSprite;
    if(!pTexture.loadFromFile("image/playerSprite.png"))
        std::cout << "Texture Error" << std::endl;
    pSprite.setTexture(pTexture);
    pSprite.setScale(1.5f, 1.5f);

    //game loop
    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) //move up
        {

            source.y = Up;
            pSprite.move(0, -0.2);

            //animation
            source.x++;
            if(source.x * 32 >= pTexture.getSize().x)
            {
                source.x = 0;
            }
        }
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) //move down
        {
            source.y = Down;
            pSprite.move(0, 0.2);

            //animation
            source.x++;
            if(source.x * 32 >= pTexture.getSize().x)
            {
                source.x = 0;
            }
        }
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) //move right
        {
            source.y = Right;
            pSprite.move(0.2, 0);

            //animation
            source.x++;
            if(source.x * 32 >= pTexture.getSize().x)
            {
                source.x = 0;
            }
        }
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) //move left
        {
            source.y = Left;
            pSprite.move(-0.2, 0);

            //animation
            source.x++;
            if(source.x * 32 >= pTexture.getSize().x)
            {
                source.x = 0;
            }
        }

        pSprite.setTextureRect(sf::IntRect(source.x * 32, source.y * 32, 32, 32));
        window.draw(pSprite);
        window.display();

    }

    return 0;
}

【问题讨论】:

    标签: c++ class oop refactoring sfml


    【解决方案1】:

    免责声明:你不应该期待这样的答案,你真的应该阅读更多关于 OOP 的内容,这与 SFML 无关,这只是基本的重构。 p>

    如何用 OOP 思考

    首先,在编写功能之前,您应该设计真正适合这种情况的 OOP 结构。将每个班级视为一个整体的一部分,这就是你的课程。一个类实际上只是具有有用方法的数据的聚合,它只会以有意义的方式影响类内部的数据(或通过方法参数提供的数据)。

    请参阅basics of C++(更多面向您的 OOP 部分)以了解如何使其在 C++ 中工作。其他编程语言中的概念类似。

    使用您提供的代码

    您要求的是一个 Player 类,将播放器代码从程序主逻辑中取出是个好主意。你需要问自己:“我的播放器代码需要什么才能工作?”

    播放器类

    基本上,您的玩家只是一个精灵和一个位置。因此,您将这些数据作为私有成员封装到您的 Player 类中。这样可以防止其他代码弄乱玩家数据。要使用播放器数据,您需要在类中提供每个只影响播放器的方法。

    纹理和精灵

    我故意将纹理保留在播放器之外。纹理是重对象,这就是为什么 Sprite 对象只保留一个指向它的指针。精灵是轻量级的,可以轻松更改和复制。纹理对象和其他资产的管理是另一个主题,尽管这是我自己的resource manager code

    可选

    我没有花太多时间更改您的代码,但您可以更改处理移动的方式,只让一个“移动”方法采用Player::Direction 有一个参数。

    为了对您提供更多帮助并为您提供有关该主题的更多指导,我使用了“forward declaration”并将您的 Direction 枚举移动到班级内。这可能不是实现您想要的最佳方式,但我只是更改了您自己的代码以避免让您迷失方向。

    代码

    无论如何,这就是我的目标。

    播放器.h

    #ifndef PLAYER_H_
    #define PLAYER_H_
    
    #include <SFML/Graphics/Drawable.hpp>
    #include <SFML/Graphics/Sprite.hpp>
    
    // Forward Declaration
    namespace sf {
    class Texture;
    }
    
    // provide your namespace to avoid collision/ambiguities
    namespace test {
    /*
     *
     */
    class Player: public sf::Drawable {
    public:
    
        enum Direction {
            Down, Left, Right, Up
        };
    
        Player(const sf::Texture& playerTexture);
        virtual ~Player();
    
        virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
    
        void moveUp();
        void moveDown();
        void moveLeft();
        void moveRight();
    
    private:
    
        sf::Sprite mSprite;
        sf::Vector2i mSource;
    
    };
    
    } /* end namespace test */
    #endif /* PLAYER_H_ */
    

    播放器.cpp

    #include "Player.h"
    
    // you need this because of forward declaration
    #include <SFML/Graphics/Texture.hpp>
    #include <SFML/Graphics/Rect.hpp>
    #include <SFML/Graphics/RenderTarget.hpp>
    
    namespace test {
    Player::Player(const sf::Texture& imagePath) :
                    mSprite(imagePath),
                    mSource(1, Player::Down) {
    
        // do not need that line anymore, thanks to initialiser list
        //pSprite.setTexture(pTexture);
    
        mSprite.setScale(1.5f, 1.5f);
    
    }
    
    Player::~Player() {
        // TODO Auto-generated destructor stub
    }
    
    void Player::draw(sf::RenderTarget& target, sf::RenderStates states) const {
        target.draw(mSprite, states);
    }
    
    void Player::moveUp() {
        mSource.y = Up;
        mSprite.move(0, -0.2);
    
        //animation
        mSource.x++;
        if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
            mSource.x = 0;
        }
    
        mSprite.setTextureRect(sf::IntRect(mSource.x * 32, mSource.y * 32, 32, 32));
    }
    
    void Player::moveDown() {
        mSource.y = Down;
        mSprite.move(0, 0.2);
    
        //animation
        mSource.x++;
        if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
            mSource.x = 0;
        }
    }
    
    void Player::moveLeft() {
        mSource.y = Left;
        mSprite.move(-0.2, 0);
    
        //animation
        mSource.x++;
        if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
            mSource.x = 0;
        }
    }
    
    void Player::moveRight() {
        mSource.y = Right;
        mSprite.move(0.2, 0);
    
        //animation
        mSource.x++;
        if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
            mSource.x = 0;
        }
    }
    
    } /* end namespace test */
    

    main.cpp

    #include <SFML/Graphics.hpp>
    //#include <string> // not used for now
    #include <iostream>
    
    // don't forget to include your own header
    #include "Player.h"
    
    int main() {
    
        // just to save typing the "std::"
        using std::cout;
        using std::endl;
        using std::cerr;
    
        //window
        sf::RenderWindow window(sf::VideoMode(1200, 700), "Testing");
        window.setKeyRepeatEnabled(false);
    
        //player texture
        sf::Texture pTexture;
        if (!pTexture.loadFromFile("image/playerSprite.png")) {
            cerr << "Texture Error" << endl;
        }
    
        test::Player thePlayer(pTexture);
    
        //game loop
        while (window.isOpen()) {
            sf::Event event;
            while (window.pollEvent(event)) {
                if (event.type == sf::Event::Closed) {
                    window.close();
                }
            }
    
            window.clear();
    
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) //move up
                    {
                thePlayer.moveUp();
            } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) //move down
                    {
                thePlayer.moveDown();
            } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) //move right
                    {
                thePlayer.moveRight();
            } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) //move left
                    {
                thePlayer.moveLeft();
            }
    
            window.draw(thePlayer);
            window.display();
    
        }
    
        return 0;
    }
    

    其他良好做法

    Accessors, or Getters/Setters 是成员函数,可让您访问类的私有成员。

    在您的代码中,您可以执行以下操作:

     class Player {
        public: 
            Player(const sf::Texture& playerTexture);
            virtual ~Player();
    
           // to give access to a const reference of the sprite
           // One could call it like: sf::Sprite mySprite = myPlayerObject.getSprite();
           // notice also that the method itself is const, which assure you that
           // myPlayerObject won't change by calling getSprite()
           const sf::Sprite& getSprite() const{
               return mSprite;
           }
    
           // setSprite is not a const method, so it will change the data
           // inside myPlayerObject
           void setSprite(const sf::Sprite& newSprite){
               mSprite = newSprite;
           }
    
        private:
            sf::Sprite mSprite;
            sf::Vector2i mSource;
        };
    

    【讨论】:

    • 感谢您的回复,我已经通读了代码,到目前为止,我了解发生了什么,我研究了一些我没有的东西,例如创建自己的命名空间,现在我可以看到为什么它会有用,我想我的大脑还没有到我知道如何解决更复杂的事情的阶段!如果你解释我需要做什么而不是仅仅给我代码会更好,因为我会更容易理解它。做我想做的事似乎很复杂,就我一个人吗?
    • 另外,在我继续之前,您有什么建议我看或阅读的吗?我实际上不知道为什么我没有考虑使用参考,但我想我只需要将所有这些付诸实践,以达到我不需要帮助的水平!
    • 是的,你是对的,如果我稍微解释一下发生了什么,你会受益更多。让我通过编辑我的答案来试试这个。
    • 我试图澄清,但我可以分享的信息太多了...也许您可以通过 cmets 向我提问,以便我可以添加缺少的信息。
    • 另外,有没有更简单的方法来检查玩家输入? (对不起,如果这是一个坏问题)。我唯一不能做的就是访问我的精灵。我可以不加载纹理和精灵并在类的构造函数中初始化Vector2和枚举吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-28
    • 1970-01-01
    相关资源
    最近更新 更多