官术网_书友最值得收藏!

The World class

Our snake can now move and collide with itself. While functional, this doesn't make a really exciting game. Let's give it some boundaries and something to munch on to increase the score by introducing the World class.

While it's possible to just make separate objects for everything we talk about in here, this project is simple enough to allow certain aspects of itself to be nicely contained within a single class that can manage them without too much trouble. This class takes care of everything to do with keeping the game boundaries, as well as maintaining the apple the player will be trying to grab.

Let's take a look at the class header:

class World{
public:
    World(sf::Vector2u l_windSize);
    ~World();

    int GetBlockSize();

    void RespawnApple();

    void Update(Snake& l_player);
    void Render(sf::RenderWindow& l_window);
private:
    sf::Vector2u m_windowSize;
    sf::Vector2i m_item;
    int m_blockSize;

    sf::CircleShape m_appleShape;
    sf::RectangleShape m_bounds[4];
};

As you can see from the preceding code, this class also keeps track of how big the objects in the game are. Aside from that, it simply retains four rectangles for the boundary graphics, a circle for drawing the apple, and an integer vector to keep track of the apple's coordinates, which is named m_item. Let's start implementing the constructor:

World::World(sf::Vector2u l_windSize){
  m_blockSize = 16;

  m_windowSize = l_windSize;
  RespawnApple();
  m_appleShape.setFillColor(sf::Color::Red);
  m_appleShape.setRadius(m_blockSize / 2);

  for(int i = 0; i < 4; ++i){
    m_bounds[i].setFillColor(sf::Color(150,0,0));
    if(!((i + 1) % 2)){
      m_bounds[i].setSize(sf::Vector2f(m_windowSize.x,
        m_blockSize));
    } else {
      m_bounds[i].setSize(sf::Vector2f(m_blockSize,
        m_windowSize.y));
    }
    if(i < 2){
      m_bounds[i].setPosition(0,0);
    } else {
      m_bounds[i].setOrigin(m_bounds[i].getSize());
      m_bounds[i].setPosition(sf::Vector2f(m_windowSize));
    }
  }
}

World::~World(){}

Up until the complex looking for loops, we simply initialize some member values from the local constructor variables, set the color and radius of the apple circle, and call the RespawnApple() method in order to place it somewhere on the grid.

The first for loop just iterates four times for each of the four sides of the game screen in order to set up a red rectangle wall on each side. It sets a dark red color for the rectangle fill and proceeds with checking the index value. First, we determine if the index is an even or an odd value by checking it with the following expression: if(!((i + 1) % 2)){.... This is done in order to know how big each wall has to be on a specific axis. Because it has to be as large as one of the screen dimensions, we simply make the other one as large as all the other graphics on the screen, which is represented by the m_blockSize value.

The last if statement checks if the index is below two. If it is, we're working with the top-left corner, so we simply set the position of the rectangle to (0,0). Since the origin of all the rectangle-based drawables in SFML is always the top-left corner, we don't need to worry about that in this case. However, if the index is 2 or higher, we set the origin to the size of the rectangle, which effectively makes it the bottom right corner. Afterwards, we set the position of the rectangle to be the same as the size of the screen, which puts the shape all the way down to the bottom right corner. You can simply set all the coordinates and origins by hand, but this approach makes the initialization of the basic features more automated. It may be hard to see the use for it now, but in more complicated projects this kind of thinking will come in handy, so why not start now?

Since we have our walls, let's take a look at how one might go about re-spawning the apple:

void World::RespawnApple(){
    int maxX = (m_windowSize.x / m_blockSize) - 2;
    int maxY = (m_windowSize.y / m_blockSize) - 2;
    m_item = sf::Vector2i(
        rand() % maxX + 1, rand() % maxY + 1);
    m_appleShape.setPosition(
        m_item.x * m_blockSize,
        m_item.y * m_blockSize);
}

The first thing we must do is determine the boundaries within which the apple can be spawned. We do so by defining two values: maxX and maxY. These are set to the window size divided by the block size, which gives us the number of spaces in the grid, from which we must then subtract 2. This is due to the fact that the grid indices begin with 0, not 1, and because we don't want to spawn the apple within the right or bottom walls.

The next step is to actually generate the random values for the apple coordinates. We use our pre-calculated values here and set the lowest possible random value to 1, because we don't want anything spawning in the top wall or the left wall. Since the coordinates of the apple are now available, we can set the m_appleShape graphic's position in pixel coordinates by multiplying the grid coordinates by the size of all our graphics.

Let's actually make all these features come to life by implementing the update method:

void World::Update(Snake& l_player){
    if(l_player.GetPosition() == m_item){
        l_player.Extend();
        l_player.IncreaseScore();
        RespawnApple();
    }

    int gridSize_x = m_windowSize.x / m_blockSize;
    int gridSize_y = m_windowSize.y / m_blockSize;

    if(l_player.GetPosition().x <= 0 ||
        l_player.GetPosition().y <= 0 ||
        l_player.GetPosition().x >= gridSize_x – 1 ||
        l_player.GetPosition().y >= gridSize_y - 1)
    {
            l_player.Lose();
    }
}

First, we check if the player's position is the same as that of the apple. If it is, we have a collision and the snake gets extended, the score increases, and the apple gets re-spawned. Next, we determine our grid size and check if the player coordinates are anywhere outside of the designated boundaries. If that's the case, we call the Lose() method to illustrate the collision with the wall and give the player a "game over".

In order to not keep the player blind, we must display the boundaries of the game, as well as the main point of interest - the apple. Let's draw everything on screen:

void World::Render(sf::RenderWindow& l_window){
    for(int i = 0; i < 4; ++i){
        l_window.draw(m_bounds[i]);
    }
    l_window.draw(m_appleShape);
}

All we have to do is iterate four times and draw each of the four respective boundaries. Then we draw the apple, which concludes our interest in this method.

One more thing to point out is that the other classes might need to know how big the graphics need to be, and for this reason, let's implement a simple method for obtaining that value:

int World::GetBlockSize(){ return m_blockSize; }

This concludes the World class.

主站蜘蛛池模板: 奉新县| 苗栗市| 阿尔山市| 汨罗市| 孝义市| 汶川县| 古蔺县| 师宗县| 阿尔山市| 潜山县| 长海县| 灵山县| 米林县| 普定县| 台山市| 临泽县| 富宁县| 佛冈县| 瑞安市| 临安市| 彩票| 瓮安县| 邹城市| 宽城| 勐海县| 丰顺县| 娱乐| 安福县| 望奎县| 昂仁县| 南乐县| 宁城县| 南召县| 阿尔山市| 元江| 凤城市| 凌源市| 涡阳县| 渭源县| 乌拉特后旗| 阿拉善盟|