SFML2.6 图形模块--用顶点数组设计实体

介绍

SFML提供了简单的类来表示最常见的2D实体。虽然更复杂的实体可以轻松地从这些基本组件创建,但这并不总是最有效的解决方案。例如,如果绘制大量的精灵,则很快会达到图形卡的限制。原因是性能在很大程度上取决于调用绘制函数的次数。确实,每次调用都涉及设置一组OpenGL状态,重置矩阵,更改纹理等等。即使仅绘制两个三角形(精灵),所有这些都是必需的。这对于您的图形卡来说远非最佳:今天的GPU设计用于处理大量的三角形,通常是几千到数百万个。

为了填补这一差距,SFML提供了一种更低级别的机制来绘制东西:顶点数组。实际上,顶点数组在所有其他SFML类中都被内部使用。它们允许更灵活地定义2D实体,包含您需要的任意数量的三角形。它们甚至允许绘制点或线条。

什么是顶点,为什么它们总是在数组中?

顶点是您可以操作的最小图形实体。简而言之,它是一个图形点:它自然具有2D位置(x,y),但也具有颜色和一对纹理坐标。稍后我们将讨论这些属性的作用。

单独的顶点并不能做太多事情。它们总是被组合成图元:点(1个顶点),线(2个顶点),三角形(3个顶点)或四边形(4个顶点)。然后,您可以将多个图元组合在一起,创建实体的最终几何形状。

现在您理解了为什么我们总是谈论顶点数组,而不是单独的顶点。

一个简单的顶点数组

现在让我们看看sf :: Vertex类。它只是一个容器,包含三个公共成员和除构造函数之外没有任何函数。这些构造函数允许您从您关心的属性集中构造顶点 - 您并不总是需要为实体着色或打上纹理。

// create a new vertex
sf::Vertex vertex;

// set its position
vertex.position = sf::Vector2f(10.f, 50.f);

// set its color
vertex.color = sf::Color::Red;

// set its texture coordinates
vertex.texCoords = sf::Vector2f(100.f, 100.f);

…或者,使用正确的构造函数:

sf::Vertex vertex(sf::Vector2f(10.f, 50.f), sf::Color::Red, sf::Vector2f(100.f, 100.f));

现在,让我们定义一个图元。请记住,一个图元由多个顶点组成,因此我们需要一个顶点数组。SFML为此提供了一个简单的包装器:sf :: VertexArray。它提供了类似于std :: vector的数组语义,并且还存储其顶点定义的图元类型。

// create an array of 3 vertices that define a triangle primitive
sf::VertexArray triangle(sf::Triangles, 3);

// define the position of the triangle's points
triangle[0].position = sf::Vector2f(10.f, 10.f);
triangle[1].position = sf::Vector2f(100.f, 10.f);
triangle[2].position = sf::Vector2f(100.f, 100.f);

// define the color of the triangle's points
triangle[0].color = sf::Color::Red;
triangle[1].color = sf::Color::Blue;
triangle[2].color = sf::Color::Green;

// no texture coordinates here, we'll see that later

你的三角形已经准备好,现在可以开始绘制它了。绘制顶点数组与绘制任何其他SFML实体类似,可以使用draw函数:

window.draw(triangle);

在这里插入图片描述
可以看到,顶点的颜色被插值以填充图元。这是创建渐变的不错方式。

请注意,你不一定要使用sf::VertexArray类,它只是为方便而定义的,它本质上只是一个带有sf::PrimitiveType的std::vector< sf::Vertex >。如果你需要更多的灵活性或者静态数组,你可以使用自己的存储方式。然后,你必须使用draw函数的重载形式,该函数接受指向顶点、顶点数和图元类型的指针。

std::vector<sf::Vertex> vertices;
vertices.push_back(sf::Vertex(...));
...

window.draw(&vertices[0], vertices.size(), sf::Triangles);
sf::Vertex vertices[2] =
{
    sf::Vertex(...),
    sf::Vertex(...)
};

window.draw(vertices, 2, sf::Lines);

图元类型

让我们暂停一下,看看你可以创建哪些类型的基本几何图元。如上所述,你可以定义最基本的2D图元:点、线、三角形和四边形(四边形只是为了方便起见,内部图形卡会将其分成两个三角形)。还有“链式”变体,它们允许在两个连续的图元之间共享顶点。这样做的好处是,连续的图元通常在某种程度上是相互连接的。

让我们来看看完整的列表:

图元类型描述例子
sf::Points一组不相连的点。这些点没有厚度:无论当前的变换和视图如何,它们始终只占用一个像素。在这里插入图片描述
sf::Lines一组不相连的线。这些线没有厚度:无论当前的变换和视图如何,它们始终只有一个像素宽。在这里插入图片描述
sf::LineStrip一组相连的线。一个线段的结束顶点被用作下一个线段的起始顶点。在这里插入图片描述
sf::Triangles一组不相连的三角形。在这里插入图片描述
sf::TriangleStrip一组相连的三角形。每个三角形与下一个三角形共享其最后两个顶点。在这里插入图片描述
sf::TriangleFan一组连接到一个中心点的三角形。第一个顶点是中心,然后每个新顶点定义一个新的三角形,使用中心和前一个顶点。在这里插入图片描述
sf::Quads (已弃用)一组未连接的四边形。每个四边形的4个点必须一致地定义,可以顺时针或逆时针顺序。在这里插入图片描述

纹理

与其他SFML实体一样,顶点数组也可以进行纹理贴图。为此,您需要操作顶点的texCoords属性。该属性定义了哪个纹理像素被映射到顶点。

// create a triangle strip
sf::VertexArray triangleStrip(sf::TriangleStrip, 4);

// define it as a rectangle, located at (10, 10) and with size 100x100
triangleStrip[0].position = sf::Vector2f(10.f, 10.f);
triangleStrip[1].position = sf::Vector2f(10.f, 110.f);
triangleStrip[2].position = sf::Vector2f(110.f, 10.f);
triangleStrip[3].position = sf::Vector2f(110.f, 110.f);

// define its texture area to be a 25x50 rectangle starting at (0, 0)
triangleStrip[0].texCoords = sf::Vector2f(0.f, 0.f);
triangleStrip[1].texCoords = sf::Vector2f(0.f, 50.f);
triangleStrip[2].texCoords = sf::Vector2f(25.f, 0.f);
triangleStrip[3].texCoords = sf::Vector2f(25.f, 50.f);

纹理坐标是以像素为单位定义的(就像精灵和形状的textureRect一样)。它们没有规范化(在0到1之间),这可能会让习惯于OpenGL编程的人感到惊讶。

顶点数组是低级实体,只处理几何数据,不存储像纹理这样的其他属性。要使用纹理绘制一个顶点数组,必须将其直接传递给绘制函数:

sf::VertexArray vertices;
sf::Texture texture;

...

window.draw(vertices, &texture);

这是简短的版本,如果您需要传递其他渲染状态(例如混合模式或变换),则可以使用显式版本,该版本接受一个sf::RenderStates对象:

sf::VertexArray vertices;
sf::Texture texture;

...

sf::RenderStates states;
states.texture = &texture;

window.draw(vertices, states);

变换顶点数组

Transforming(变换)与纹理类似,变换信息并不储存在顶点数组中,你必须将其传递给绘制函数。在使用 sf::Transform 类进行变换时,它所作用的是整个顶点数组,不会修改原始顶点数据。因此,在绘制时,你需要将变换信息通过 sf::RenderStates 对象传递给绘制函数,以实现顶点数组的变换。

sf::VertexArray vertices;
sf::Transform transform;

...

window.draw(vertices, transform);

或者,如果你需要传递其它的渲染状态:

sf::VertexArray vertices;
sf::Transform transform;

...

sf::RenderStates states;
states.transform = transform;

window.draw(vertices, states);

如果想了解更多有关变换和sf::Transform类的内容,你可以阅读有关变换实体的教程。

创建一个类似SFML的实体

现在您知道如何定义自己的纹理/着色/变换实体,那么将其包装在类中不是很好吗?幸运的是,SFML为您提供了sf::Drawable和sf::Transformable基类,使这一点变得容易。这两个类是内置SFML实体sf::Sprite、sf::Text和sf::Shape的基础。

sf::Drawable是一个接口:它声明一个纯虚函数,没有成员或具体函数。继承sf::Drawable允许您以与SFML类相同的方式绘制类的实例:

class MyEntity : public sf::Drawable
{
private:

    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
};

MyEntity entity;
window.draw(entity); // internally calls entity.draw

请注意,这样做并不是强制性的,您也可以在您的类中拥有类似的draw函数,然后简单地使用entity.draw(window)进行调用。但是,使用sf::Drawable作为基类的方式更好、更一致。这也意味着,如果您计划存储一组可绘制的对象,您可以这样做而不需要任何额外的努力,因为所有可绘制的对象(SFML的和您的)都是从相同的类派生的。

另一个基类sf::Transformable没有虚函数。从它继承会自动将与其他SFML类相同的变换函数添加到您的类中(setPosition、setRotation、move、scale等)。您可以在转换实体的教程中了解更多关于这个类的知识。

使用这两个基类和一个顶点数组(在这个例子中,我们也会添加一个纹理),这是一个典型的类似SFML的图形类应该是这样的:

class MyEntity : public sf::Drawable, public sf::Transformable
{
public:

    // add functions to play with the entity's geometry / colors / texturing...

private:

    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
    {
        // apply the entity's transform -- combine it with the one that was passed by the caller
        states.transform *= getTransform(); // getTransform() is defined by sf::Transformable

        // apply the texture
        states.texture = &m_texture;

        // you may also override states.shader or states.blendMode if you want

        // draw the vertex array
        target.draw(m_vertices, states);
    }

    sf::VertexArray m_vertices;
    sf::Texture m_texture;
};

然后,您可以像使用内置的SFML类一样使用这个类:

MyEntity entity;

// you can transform it
entity.setPosition(10.f, 50.f);
entity.setRotation(45.f);

// you can draw it
window.draw(entity);

示例:瓷砖地图

有了以上所学的知识,我们可以创建一个封装瓷砖地图的类。整个地图将包含在一个顶点数组中,因此它的绘制速度将非常快。请注意,我们只有在整个瓷砖集合可以适合一个纹理时,才能应用这种策略。否则,我们至少需要使用一个纹理对应一个顶点数组。

以下是瓷砖地图类的声明:

class TileMap : public sf::Drawable, public sf::Transformable
{
public:

    bool load(const std::string& tileset, sf::Vector2u tileSize, const int* tiles, unsigned int width, unsigned int height)
    {
        // load the tileset texture
        if (!m_tileset.loadFromFile(tileset))
            return false;

        // resize the vertex array to fit the level size
        m_vertices.setPrimitiveType(sf::Triangles);
        m_vertices.resize(width * height * 6);

        // populate the vertex array, with two triangles per tile
        for (unsigned int i = 0; i < width; ++i)
            for (unsigned int j = 0; j < height; ++j)
            {
                // get the current tile number
                int tileNumber = tiles[i + j * width];

                // find its position in the tileset texture
                int tu = tileNumber % (m_tileset.getSize().x / tileSize.x);
                int tv = tileNumber / (m_tileset.getSize().x / tileSize.x);

                // get a pointer to the triangles' vertices of the current tile
                sf::Vertex* triangles = &m_vertices[(i + j * width) * 6];

                // define the 6 corners of the two triangles
                triangles[0].position = sf::Vector2f(i * tileSize.x, j * tileSize.y);
                triangles[1].position = sf::Vector2f((i + 1) * tileSize.x, j * tileSize.y);
                triangles[2].position = sf::Vector2f(i * tileSize.x, (j + 1) * tileSize.y);
                triangles[3].position = sf::Vector2f(i * tileSize.x, (j + 1) * tileSize.y);
                triangles[4].position = sf::Vector2f((i + 1) * tileSize.x, j * tileSize.y);
                triangles[5].position = sf::Vector2f((i + 1) * tileSize.x, (j + 1) * tileSize.y);

                // define the 6 matching texture coordinates
                triangles[0].texCoords = sf::Vector2f(tu * tileSize.x, tv * tileSize.y);
                triangles[1].texCoords = sf::Vector2f((tu + 1) * tileSize.x, tv * tileSize.y);
                triangles[2].texCoords = sf::Vector2f(tu * tileSize.x, (tv + 1) * tileSize.y);
                triangles[3].texCoords = sf::Vector2f(tu * tileSize.x, (tv + 1) * tileSize.y);
                triangles[4].texCoords = sf::Vector2f((tu + 1) * tileSize.x, tv * tileSize.y);
                triangles[5].texCoords = sf::Vector2f((tu + 1) * tileSize.x, (tv + 1) * tileSize.y);
            }

        return true;
    }

private:

    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
    {
        // apply the transform
        states.transform *= getTransform();

        // apply the tileset texture
        states.texture = &m_tileset;

        // draw the vertex array
        target.draw(m_vertices, states);
    }

    sf::VertexArray m_vertices;
    sf::Texture m_tileset;
};

现在我们来看使用这个类的应用程序:

int main()
{
    // create the window
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tilemap");

    // define the level with an array of tile indices
    const int level[] =
    {
        0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
        1, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,
        0, 1, 0, 0, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 0, 0,
        0, 1, 1, 0, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2, 0, 0,
        0, 0, 1, 0, 3, 0, 2, 2, 0, 0, 1, 1, 1, 1, 2, 0,
        2, 0, 1, 0, 3, 0, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1,
        0, 0, 1, 0, 3, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1,
    };

    // create the tilemap from the level definition
    TileMap map;
    if (!map.load("tileset.png", sf::Vector2u(32, 32), level, 16, 8))
        return -1;

    // run the main loop
    while (window.isOpen())
    {
        // handle events
        sf::Event event;
        while (window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                window.close();
        }

        // draw the map
        window.clear();
        window.draw(map);
        window.display();
    }

    return 0;
}

在这里插入图片描述
你可以在这里下载用于这个tilemap示例的瓷砖集。

例子:粒子系统

这个第二个示例实现了另一个常见实体:粒子系统。这个非常简单,没有纹理,尽可能少的参数。它演示了使用sf::Points原语类型与动态顶点数组,在每个帧中都会发生变化。

class ParticleSystem : public sf::Drawable, public sf::Transformable
{
public:

    ParticleSystem(unsigned int count) :
    m_particles(count),
    m_vertices(sf::Points, count),
    m_lifetime(sf::seconds(3.f)),
    m_emitter(0.f, 0.f)
    {
    }

    void setEmitter(sf::Vector2f position)
    {
        m_emitter = position;
    }

    void update(sf::Time elapsed)
    {
        for (std::size_t i = 0; i < m_particles.size(); ++i)
        {
            // update the particle lifetime
            Particle& p = m_particles[i];
            p.lifetime -= elapsed;

            // if the particle is dead, respawn it
            if (p.lifetime <= sf::Time::Zero)
                resetParticle(i);

            // update the position of the corresponding vertex
            m_vertices[i].position += p.velocity * elapsed.asSeconds();

            // update the alpha (transparency) of the particle according to its lifetime
            float ratio = p.lifetime.asSeconds() / m_lifetime.asSeconds();
            m_vertices[i].color.a = static_cast<sf::Uint8>(ratio * 255);
        }
    }

private:

    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
    {
        // apply the transform
        states.transform *= getTransform();

        // our particles don't use a texture
        states.texture = NULL;

        // draw the vertex array
        target.draw(m_vertices, states);
    }

private:

    struct Particle
    {
        sf::Vector2f velocity;
        sf::Time lifetime;
    };

    void resetParticle(std::size_t index)
    {
        // give a random velocity and lifetime to the particle
        float angle = (std::rand() % 360) * 3.14f / 180.f;
        float speed = (std::rand() % 50) + 50.f;
        m_particles[index].velocity = sf::Vector2f(std::cos(angle) * speed, std::sin(angle) * speed);
        m_particles[index].lifetime = sf::milliseconds((std::rand() % 2000) + 1000);

        // reset the position of the corresponding vertex
        m_vertices[index].position = m_emitter;
    }

    std::vector<Particle> m_particles;
    sf::VertexArray m_vertices;
    sf::Time m_lifetime;
    sf::Vector2f m_emitter;
};

还有一个使用它的小演示:

int main()
{
    // create the window
    sf::RenderWindow window(sf::VideoMode(512, 256), "Particles");

    // create the particle system
    ParticleSystem particles(1000);

    // create a clock to track the elapsed time
    sf::Clock clock;

    // run the main loop
    while (window.isOpen())
    {
        // handle events
        sf::Event event;
        while (window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                window.close();
        }

        // make the particle system emitter follow the mouse
        sf::Vector2i mouse = sf::Mouse::getPosition(window);
        particles.setEmitter(window.mapPixelToCoords(mouse));

        // update it
        sf::Time elapsed = clock.restart();
        particles.update(elapsed);

        // draw it
        window.clear();
        window.draw(particles);
        window.display();
    }

    return 0;
}

在这里插入图片描述

Create and develop exciting games from start to finish using SFML About This Book Familiarize yourself with the SFML library and explore additional game development techniques Craft, shape, and improve your games with SFML and common game design elements A practical guide that will teach you how to use utilize the SFML library to build your own, fully functional applications Who This Book Is For This book is intended for game development enthusiasts with at least decent knowledge of the C++ programming language and an optional background in game design. What You Will Learn Create and open a window by using SFML Utilize, manage, and apply all of the features and properties of the SFML library Employ some basic game development techniques to make your game tick Build your own code base to make your game more robust and flexible Apply common game development and programming patterns to solve design problems Handle your visual and auditory resources properly Construct a robust system for user input and interfacing Develop and provide networking capabilities to your game In Detail Simple and Fast Multimedia Library (SFML) is a simple interface comprising five modules, namely, the audio, graphics, network, system, and window modules, which help to develop cross-platform media applications. By utilizing the SFML library, you are provided with the ability to craft games quickly and easily, without going through an extensive learning curve. This effectively serves as a confidence booster, as well as a way to delve into the game development process itself, before having to worry about more advanced topics such as “rendering pipelines” or “shaders.” With just an investment of moderate C++ knowledge, this book will guide you all the way through the journey of game development. The book starts by building a clone of the classical snake game where you will learn how to open a window and render a basic sprite, write well-structured code to implement the design of the game, and use the AABB bounding box collision concept. The next game is a simple platformer with enemies, obstacles and a few different stages. Here, we will be creating states that will provide custom application flow and explore the most common yet often overlooked design patterns used in game development. Last but not the least, we will create a small RPG game where we will be using common game design patterns, multiple GUI. elements, advanced graphical features, and sounds and music features. We will also be implementing networking features that will allow other players to join and play together. By the end of the book, you will be an expert in using the SFML library to its full potential. Style and approach An elaborate take on the game development process in a way that compliments the reader's existing knowledge, this book provides plenty of examples and is kind to the uninitiated. Each chapter builds upon the knowledge gained from the previous one and offers clarifications on common issues while still remaining within the scope of its own subject and retaining clarity. Table of Contents Chapter 1: It's Alive! It's Alive! – Setup and First Program Chapter 2: Give It Some Structure – Building the Game Framework Chapter 3: Get Your Hands Dirty – What You Need to Know Chapter 4: Grab That Joystick – Input and Event Management Chapter 5: Can I Pause This? – Application States Chapter 6: Set It in Motion! – Animating and Moving around Your World Chapter 7: Rediscovering Fire – Common Game Design Elements Chapter 8: The More You Know – Common Game Programming Patterns Chapter 9: A Breath of Fresh Air – Entity Component System Continued Chapter 10: Can I Click This? – GUI Fundamentals Chapter 11: Don't Touch the Red Button! – Implementing the GUI Chapter 12: Can You Hear Me Now? – Sound and Music Chapter 13: We Have Contact! – Networking Basics Chapter 14: Come Play with Us! – Multiplayer Subtleties
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值