Hello all,
for a vertical shoot 'em up game I created a scrolling background with stars. I wanted it to have a parallax effect with differently sized stars, scrolling in different speed. I also wanted it to be created procedurally and completely random since I intend to expand it to be more beautiful in my game in the end (with procedurally created galaxies and stuff). It uses C++11 stuff from <random> and Lambdas, so you will have to enable it for your compiler. Asides from this the project does not use any files so you can just paste and compile the code.
I post this for anyone to use and, more importantly, to get comments on how it is done. This is my first try with sfml, so please tell me if I use something wrong or in a way that causes bad performance.
Thanks for any comments and best regards
Draugr
Here's a video preview - thanks to eXpl0it3r!
Here comes the code:
#include <SFML/Graphics.hpp>
#include "Starfield.hpp"
int main() {
sf::Vector2u screenDimensions(800, 600);
sf::RenderWindow window(sf::VideoMode(screenDimensions.x, screenDimensions.y, 32), "Game main window", sf::Style::Titlebar | sf::Style::Close);
window.setSize(screenDimensions);
window.setFramerateLimit(60);
//create an empty black image onto which the starfield will be painted every frame
sf::Image starsImage;
starsImage.create(screenDimensions.x, screenDimensions.y, sf::Color::Black);
sf::Texture starsTexture;
starsTexture.loadFromImage(starsImage);
starsTexture.setSmooth(false);
sf::Sprite starsSprite;
starsSprite.setTexture(starsTexture);
starsSprite.setPosition(0, 0);
Starfield backgroundStars(screenDimensions.x, screenDimensions.y);
//Game loop
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
//Keypress related events
case sf::Event::KeyPressed:
if(event.key.code == sf::Keyboard::Escape || event.key.code == sf::Keyboard::Return){
window.close();
}
break;
default:
break;
}
}
starsTexture.loadFromImage(starsImage);
backgroundStars.drawStarfield(starsTexture);
window.clear(sf::Color(0, 0, 0));
window.draw(starsSprite);
window.display();
backgroundStars.updateStarfield();
}
return 0;
}
#ifndef STARFIELD_H
#define STARFIELD_H
#include "Star.hpp"
#include <SFML/Graphics.hpp>
#include <random>
#include <vector>
#include <ctime>
using std::vector;
class Starfield
{
public:
Starfield();
Starfield(int, int);
~Starfield() {}
void updateStarfield();
void drawStarfield(sf::Texture&);
protected:
int maxSmallStars;
int maxMediumStars;
int maxLargeStars;
sf::Uint16 x_Size;
sf::Uint16 y_Size;
vector<Star> smallStars;
vector<Star> mediumStars;
vector<Star> largeStars;
std::default_random_engine re_x;
std::default_random_engine re_y;
std::uniform_int_distribution<int> my_distribution_x;
std::uniform_int_distribution<int> my_distribution_y;
sf::Image smallStarImage;
sf::Image mediumStarImage;
sf::Image largeStarImage;
};
//starfield won't work without proper data
Starfield::Starfield(): maxSmallStars(0), maxMediumStars(0), maxLargeStars(0), x_Size(800), y_Size(600)
{
}
Starfield::Starfield(int xResolution, int yResolution)
{
x_Size = xResolution;
y_Size = yResolution;
//size of the different star sizes in pixels
sf::Uint16 smallSize = 1;
sf::Uint16 mediumSize = 2;
sf::Uint16 largeSize = 4;
//create the images that will be used to update the background texture
smallStarImage.create(smallSize, smallSize, sf::Color::White);
mediumStarImage.create(mediumSize, mediumSize, sf::Color::White);
largeStarImage.create(largeSize, largeSize, sf::Color::White);
//init random generator
my_distribution_x = std::uniform_int_distribution<int>(0, xResolution);
my_distribution_y = std::uniform_int_distribution<int>(0, yResolution);
re_x.seed(std::time(0));
re_y.seed(std::time(0)+24);
//The higher reduceStars the fewer stars; classDifference sets the proportionality between small, medium and large stars. The higher the number, the fewer stars in each larger class.
int reduceStars = 8;
int classDifference = 3;
maxSmallStars = (xResolution / (reduceStars * 10)) * (yResolution / reduceStars);
maxMediumStars = (xResolution / (reduceStars * 10 * classDifference)) * (yResolution / (reduceStars * classDifference));
maxLargeStars = (xResolution / (reduceStars * 10 * classDifference * classDifference)) * (yResolution / (reduceStars * classDifference * classDifference));
//generate a start set of stars
while((int)smallStars.size() <= maxSmallStars){
smallStars.push_back(Star(my_distribution_x(re_x), my_distribution_y(re_y)));
}
while((int)mediumStars.size() <= maxMediumStars){
mediumStars.push_back(Star(my_distribution_x(re_x), my_distribution_y(re_y)));
}
while((int)largeStars.size() <= maxLargeStars){
largeStars.push_back(Star(my_distribution_x(re_x), my_distribution_y(re_y)));
}
}
void Starfield::updateStarfield()
{
//remove all stars that have exceeded the lower screen border
smallStars.erase(remove_if(smallStars.begin(), smallStars.end(), [&](Star& p_Star){
return (p_Star.getYPos() > y_Size);
}
), smallStars.end());
mediumStars.erase(remove_if(mediumStars.begin(), mediumStars.end(), [&](Star& p_Star){
return (p_Star.getYPos() > y_Size);
}
), mediumStars.end());
largeStars.erase(remove_if(largeStars.begin(), largeStars.end(), [&](Star& p_Star){
return (p_Star.getYPos() > y_Size);
}
), largeStars.end());
//move every star, according to its size to create a parallax effect
for_each(smallStars.begin(), smallStars.end(), [&](Star& p_Star){
p_Star.addYPos(1);
}
);
for_each(mediumStars.begin(), mediumStars.end(), [&](Star& p_Star){
p_Star.addYPos(2);
}
);
for_each(largeStars.begin(), largeStars.end(), [&](Star& p_Star){
p_Star.addYPos(4);
}
);
//create new stars until the set limit is reached
while((int)smallStars.size() <= maxSmallStars){
smallStars.push_back(Star(my_distribution_x(re_x), 0));
}
while((int)mediumStars.size() <= maxMediumStars){
mediumStars.push_back(Star(my_distribution_x(re_x), 0));
}
while((int)largeStars.size() <= maxLargeStars){
largeStars.push_back(Star(my_distribution_x(re_x), 0));
}
}
//update a target texture with all stars contained in this starfield
void Starfield::drawStarfield(sf::Texture& p_Texture)
{
for(vector<Star>::iterator it = smallStars.begin(); it != smallStars.end(); ++it){
p_Texture.update(smallStarImage, it->getXPos(), it->getYPos());
}
for(vector<Star>::iterator it = mediumStars.begin(); it != mediumStars.end(); ++it){
p_Texture.update(mediumStarImage, it->getXPos(), it->getYPos());
}
system("clear");
for(vector<Star>::iterator it = largeStars.begin(); it != largeStars.end(); ++it){
p_Texture.update(largeStarImage, it->getXPos(), it->getYPos());
}
}
#endif // STARFIELD_H
#ifndef STAR_H
#define STAR_H
#include <SFML/Graphics.hpp>
class Star
{
public:
Star() {}
Star(sf::Uint16, sf::Uint16);
~Star() {}
sf::Uint16 getXPos();
sf::Uint16 getYPos() const;
void setXPos(sf::Uint16);
void setYPos(sf::Uint16);
void addYPos(sf::Uint16);
private:
sf::Uint16 xPos;
sf::Uint16 yPos;
};
Star::Star(sf::Uint16 p_X_Pos, sf::Uint16 p_Y_Pos)
{
xPos = p_X_Pos;
yPos = p_Y_Pos;
}
sf::Uint16 Star::getXPos()
{
return xPos;
}
sf::Uint16 Star::getYPos() const
{
return yPos;
}
void Star::setXPos(sf::Uint16 x)
{
xPos = x;
return;
}
void Star::setYPos(sf::Uint16 y)
{
yPos = y;
return;
}
void Star::addYPos(sf::Uint16 y)
{
yPos += y;
return;
}
#endif // STAR_H