- SFML Game Development By Example
- Raimondas Pupius
- 426字
- 2021-07-23 14:55:08
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.
- Embedded Linux Projects Using Yocto Project Cookbook
- Implementing Modern DevOps
- LabVIEW Graphical Programming Cookbook
- Python時(shí)間序列預(yù)測
- 數(shù)據(jù)結(jié)構(gòu)案例教程(C/C++版)
- 焊接機(jī)器人系統(tǒng)操作、編程與維護(hù)
- 程序設(shè)計(jì)基礎(chǔ)教程:C語言
- Go語言精進(jìn)之路:從新手到高手的編程思想、方法和技巧(1)
- Visual Basic程序設(shè)計(jì)實(shí)驗(yàn)指導(dǎo)(第二版)
- Building Wireless Sensor Networks Using Arduino
- Hands-On Nuxt.js Web Development
- 深度探索Go語言:對(duì)象模型與runtime的原理特性及應(yīng)用
- Hack與HHVM權(quán)威指南
- WCF技術(shù)剖析(卷1)
- 3ds Max 2018從入門到精通