I just started programming with SFML, I'm trying to make something shoot a bullet. Currently I have made the bullet move one way when I press space. But I cannot shoot multiple bullets at the same time, it just uses the same image. I can't figure out what I should do, if I should give each bullet it's own name?
Also should I delete it when it's out from the window? If I should how can I do it?
Here's the relevant code:
void Game::handleWeaponInput(sf::Keyboard::Key key, bool isPressed)
{
if (key == sf::Keyboard::Space)
{
mIsFired = isPressed;
if (!mBulletTexture.loadFromFile("Media/Textures/Bullet.png"))
{
}
mBullet.setTexture(mBulletTexture);
mBullet.setPosition(100.f, 100.f);
mBullet.setRotation(90.f);
}
}
And:
void Game::update(sf::Time elapsedTime)
{
sf::Vector2f bulletMovement(0.f, 0.f);
if (mIsFired)
bulletMovement.x += 300.f;
mBullet.move(bulletMovement * elapsedTime.asSeconds());
}
One thing you can do is create a std::vector of bullets so you can track each one individually.
class Game
{
sf::Texture mBulletTexture;
// declare a std::vector of bullets
std::vector<sf::Sprite> mBullets;
public:
void handleWeaponInput(sf::Keyboard::Key key, bool isPressed);
void update(sf::Time elapsedTime);
};
void Game::handleWeaponInput(sf::Keyboard::Key key, bool isPressed)
{
if (key == sf::Keyboard::Space)
{
mIsFired = isPressed;
if (!mBulletTexture.loadFromFile("Media/Textures/Bullet.png"))
{
}
mBullets.emplace_back(); // add another sf::Sprite to vector
mBullets.back().setTexture(mBulletTexture);
mBullets.back().setPosition(100.f, 100.f);
mBullets.back().setRotation(90.f);
}
}
void Game::update(sf::Time elapsedTime)
{
sf::Vector2f bulletMovement(0.f, 0.f);
if (mIsFired)
bulletMovement.x += 300.f;
// move each bullet in the vector
for(auto& bullet: mBullets)
bullet.move(bulletMovement * elapsedTime.asSeconds());
}
Obviously this exact code won't work properly as you will also have to manage how each bullet moves (what direction etc....). But hopefully it will give you an idea how you could scale from one bullet to many bullets.
It may be worth defining a class Bullet that contains the sf::Sprite along with information about the bullet's speed and direction?
Then you could make your vector with those:
class Bullet
{
sf::Sprite sprite;
sf::Texture texture;
sf::Vector2f direction;
public:
// ...
};
class Game
{
// declare a std::vector of bullets
std::vector<Bullet> mBullets;
// ...
};
first of all, I want to tackle your update method: You should update each existing bullet each frame independently of whether you fired one or not:
void Game::update(sf::Time elapsedTime) {
sf::Vector2f bulletMovement(300.f, 0.f);
for (auto& bullet : mBullets) // range-based-for and auto require C++11
bullet.move(bulletMovement * elapsedTime.asSeconds());
}
As others have already suggested, for a simple use case, a vector of bullets works nicely. If you need more complex data structures to handle your game entities, try researching on things like "scene graph" or "quad tree".
std::vector<sf::Sprite> mBullets;
to add a bullet, simply use
// possibly construct sprite seperatly and add some transforms beforehand
mBullets.push_back(sf::Sprite(/* however you store your texture */));
And yes, it is a good idea to destroy bullets once they are outside of your screen, since you would be wasting draw calls and other operations on entities which you will never see again. Using the tools SFML already provides, one could do it like this:
void Game::destroyBulletsOutsideView() {
sf::FloatRect viewBounds(0, 0, mWindow.getSize().x, mWindow.getSize().y);
for (auto iter = mBullets.begin(); iter != mBullets.end(); ++iter) {
if (!viewBounds.intersects(iter->getGlobalBounds())
mBullets.erase(iter);
}
}
Side note: You will likely want to add something like a reload or cooldown time to your Bullets, so you can't shoot tons of bullets per second.
Related
It's easier for me to explain this kind of stuff in videogame terms. I'll try to be as clear as possible please bear with me...
I have a Bullet class that's made of different components, such as a Sprite component and a Transform Component... It would look something like this:
class Bullet
{
public:
Bullet( texture2D texture, Rectangle sourceRectangle, Rectangle destinationRectangle )
Bullet();
~Bullet();
// Returns a pointer to its sprite component so that I can use it wherever I want in the scene:
Sprite* const Sprite() const{ return this->sprite; }
// Setters:
void SetAngle( float new_angle ){ this->angle = new_angle; }
void SetSpeed( float new_speed ){ this->speed = new_speed; }
void SetRadius( float new_radius ){ this->radius = new_radius; }
private:
// Sprite component:
Sprite* sprite;
private:
// Regular member data:
float angle;
float speed;
float radius;
};
The sprite component is simply another class that holds a texture, source rectangle, destination rectangle, and some member functions to manipulate it (Move, Rotate, SetTexture, etc ). Nothing really special. It gets initialized within the constructor
What I need to do is to be able to copy one pointer to a bullet object entirely to another new pointer to a bullet, including the sprite component. Something like this:
// These arguments will be "fed" into the sprite component
Bullet* bullet1 = new Bullet( texture, sourceRect, destRect );
Bullet* bullet2 = new Bullet(); // Using default constructor.
*bullet2 = *bullet1;
Technically, doing it this way works. However, only the regular member data gets copied I think, but not the sprite component. It only points to the first bullet's sprite component. At least that's what I think is happening.
Hopefully I was clear explaining this issue, Thanks for taking the time.
I'm making a Breakout game in C++ using the SFML library, I have a "paddle" class as well as a "ball" class, both being updated and rendered in a "game" class, which uses a render window from a "window" class.
My issue is that in order to determine whether the Ball object has collided with the Paddle object, I obviously need access to the Paddle object's x/y position data members. I'm using an update (or tick) function for each object, I'd rather not just pass a reference to the Paddle object into the Ball's update function.
What is the general accepted way to do achieve my desired functionality?
Here is my Ball header:
class Ball
{
public:
Ball(float x, float y, int size, sf::Vector2f moveSpeed);
~Ball();
void Tick();
void Move();
void Render(sf::RenderWindow& window);
void Reset();
sf::Vector2f GetMoveSpeed() { return m_moveSpeed; }
private:
void CheckCollision();
int m_size;
sf::Vector2f m_moveSpeed;
sf::Vector2f m_position;
sf::CircleShape m_ballCircle;
};
Here is my Game update function:
void Game::Update()
{
m_window.Update();
m_ball.Tick();
m_paddle.Tick();
}
Use separate object which implements algorithm for updating ball. This object knows about all your data: ball and paddle, and calculates new position.
You can inject algorithm into your game class, which would hold pointer to interface. This allows you to change algorithm without changing Game, Ball or Paddle classes.
Let your Game check collision and handle them appropriately:
void Game::Update()
{
m_window.Update();
m_ball.Tick();
m_paddle.Tick();
if(areColliding(m_ball, m_paddle))
{
resolveCollision(m_ball, m_paddle);
}
}
In this case, areColliding and resolveCollision could be private member functions of Game.
Every object in my game world has a vector of sprites that visually represent that object. My issue is that i cant seem to draw them properly on the screen:
This is the drawable object that every drawable inherits from:
class Drawable {
private:
static vector<Drawable*> sprites;
protected:
vector<sf::Texture> myTextures;
vector<sf::Sprite> mySprites;
public:
Drawable();
static vector<Drawable*> getSprites();
void draw(sf::RenderWindow&) const;
};
And its .cpp:
vector<Drawable*> Drawable::drawables;
Drawable::Drawable() {
drawables.push_back(this);
}
vector<Drawable*> Drawable::getDrawables() {
return drawables;
}
void Drawable::draw(sf::RenderWindow& window) const {
for (auto sprite : mySprites) {
window.draw(sprite);
}
}
Example of a object that inherits from drawable:
class Terrain : public Drawable {
private:
void loadSprite(string);
public:
Terrain(string);
};
and its .cpp:
Terrain::Terrain(string fileName) {
loadSprite(fileName);
}
void Terrain::loadSprite(string fileName) {
sf::Texture texture;
texture.loadFromFile(fileName);
myTextures.push_back(texture);
sf::Sprite sprite;
sprite.setTexture(texture);
mySprites.push_back(sprite);
}
In this case the terrain sprite is only a white-box during run-time. I think this is because the "texture" and "sprite" var in loadSprite gets destroyed after the method goes out of scope.
I know i could probably solve the issue by saving "texture" and "sprite" in the terrain-class (not creating them locally in a method like now). But this seems unnecessary to me, cant i store them in the vectors mySprites and myTextures instead?
I think this is because the "texture" and "sprite" var in loadSprite gets destroyed after the method goes out of scope.
You're right. sf::Sprite stores a reference to sf::Texture. loadSprite would work one-time only if you'd do sprite.setTexture(myTextures.back());. But std::vector's elements will be reallocated as you push_back. I recommend std::vector<std::shared_ptr<sf::Texture>> for simplicity.
Better yet, load all the textures at once, so that you don't have duplicate ones and use IDs to refer to them.
I have a texture and sprite in a base class that is being extended by another class, however when drawn, the sprite displays as a white box. I know this is something to do with the sprite losing it's link to the texture object, but I'm kind of new to C++, so I'm not really sure how it happened.
Here is the code (I've removed some of the irrelevant parts to cut down the size):
Pickup.h:
#ifndef PICKUPS_PICKUP_H
#define PICKUPS_PICKUP_H
#include <SFML\Graphics.hpp>
#include "..\Player.h"
namespace Pickups
{
class Pickup
{
private:
sf::Vector2f position;
sf::Texture texture;
sf::Sprite sprite;
public:
Pickup();
bool drop(float dt);
void draw(sf::RenderWindow* window);
void setTexture(sf::String name);
void setPos(sf::Vector2f position);
sf::Vector2f getPos();
void isColliding(Player* player);
virtual void onCollect(Player* player) = 0;
};
}
#endif
pickup.cpp:
#include "Pickup.h"
namespace Pickups
{
Pickup::Pickup()
{
}
void Pickup::draw(sf::RenderWindow* window)
{
window->draw(sprite);
}
void Pickup::setTexture(sf::String name)
{
if (!texture.loadFromFile("images/pickups/" + name + ".png"))
std::cout << "Error loading image: images/pickups/" + name.toAnsiString() + ".png" << std::endl;
else
sprite.setTexture(texture);
}
}
Health.h:
#ifndef PICKUPS_HEALTH_H
#define PICKUPS_HEALTH_H
#include "Pickup.h"
namespace Pickups
{
class Health : public Pickup
{
private:
int worth;
public:
Health(sf::Vector2f position, int worth);
void onCollect(Player* player);
};
}
#endif
health.cpp:
#include "Health.h"
namespace Pickups
{
Health::Health(sf::Vector2f position, int worth)
{
setTexture("health");
setPos(position);
this->worth = worth;
}
void Health::onCollect(Player* player)
{
player->addLives(worth);
}
}
(I don't know if this is part of the problem, but I might as well post it too)
I store the pickups in a vector like so:
std::vector<Pickups::Health> pickups;
A std::vector copies or moves the inserted elements, so as long as you have the default copy constructor or as long as you do not change this dirty a texture per element-style, (the elements just need to have one common texture object to actually point to, so you waste much much memory) the pointer that the sf::Sprite object holds to the texture gets invalid. To see why we need to think whats happens on insertion:
You setup a nice Pickupish object and add it to the vector which calls the copy-constructor. Lets say your nice object that you wanted to add is object A and the now added/copied object is B. Both have a sprite S and a texture T. Both textures are valid, but the problem is this: A's S points to A's T, so after copying it to B B's S points also to A's T! As I assume A is just temporary so it gets destructed, together with its texture, and there you have it, a nice white box.
You can solve this in some other dirty ways like making your own copy-constructor in Pickup like this:
Pickup::Pickup(const Pickup& other)
: position(other.position), texture(other.texture), sprite(other.sprite)
{ sprite.setTexture(texture); }
or by storing std::unique_ptr<Pickups::Health>'s and not just Pickups::Health's.
However a much better way you should use is some kind of Resourcemanager, which just stores all relevant textures, ideally one, a big tileset, because loading once but big is faster than loading multiple but small textures. You can write your own very simple manager or use some other e.g. the one from the great Thor library. To set a specific tile as texture for a Sprite just call sf::Sprite::setTextureRect.
I want to mention some additional improvements to your design. Let Pickup derive from sf::Drawable and define its pure virtual draw function, which you can make private. Thus your from Pickup deriving object doesn't need to know from any sf::RenderTarget and you can just do target.draw(myPickupObject).
There is no need to store the position, just let Pickup derive from sf::Transformable, too. You don't have to implement any functions, the only thing you need to do is applying the matrix to the sf::RenderStates object thats passed to draw.
Overall your draw function might look like this:
void Pickup::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
//'applying' transformation-matrix
states.transform *= getTransform();
target.draw(sprite, states);
}
So your Pickup has now only sf::Sprite as member and overall your header just needs to include SFML/Graphics/Sprite.hpp.
For avoid this type of problem I always declare my Texture as a pointer and deleting it in the destructor of the class. Like this your Texture will always exist whenever your object is not destroyed.
And it's always good to verify the loading of the image :
if (!texture.loadFromFile("images/pickups/health.png")){
//some error code
}
But it's not the problem here.
I am fairly new to SFML and am trying to make a scrolling plane game that shoots out a bullet whenever I press the spacebar. I've gotten all of the movement working, but I'm trying to figure out how to put the bullets into another class file so that I can have a whole set of them later on (I.E. create.bullet(), create.missle(), etc.). Here is the code that I have now.
void create::bullet(int xPos, int yPos)
{
sf::Texture bulletTexture;
if(!bulletTexture.loadFromFile("bullet.png"))
cout << "There was an error loading the bullet texture file." << endl;
sf::Sprite bulletSprite;
bulletSprite.setTexture(bulletTexture);
bulletSprite.setPosition(xPos, yPos);
window.draw(bulletSprite);
}
I have the sf::RenderWindow instance called window in the main class, but I apparently can't reference it directly from another class. I haven't been able to implement velocity yet either, but I should be able to do that. Another thing that I need help with however, is getting it so that there is no limit on the number of bullets that can be fired. It seems like if I just have this function run whenever spacebar is pressed, it will just reset the sprite to the new position and get rid of the old one. Thanks for the help!
First of all, loading textures from files is slow. You should do it once when the program or level starts, and store the texture somewhere.
Instead of a Create class, make a Bullet class. Then, you can have a vector of bullets (or pointers/smart pointers to them). Each time you want a new bullet, you just push_back() it to the vector. If a bullet needs to be destroyed you erase() it. Then, for the game itself, you need to call Move() for every bullet and then Draw() for every bullet in the vector. Once you have that done, you can add the collision detection and so on.
You also have a choice - each bullet can have its own sf::sprite and modify it, or you can have one sf::sprite for each game sprite and reposition it for every bullet.
Personally, I'm using the second approach. My Bullet class looks like this:
Bullet::Bullet(std::string ntype, double nx, double ny, double nvx, double nvy):
type(ntype), x(nx), y(ny), vx(nvx), vy(nvy)
{
angle=atan2(vy, vx)*(180/M_PI);
}
void Bullet::Move()
{
x+=vx;
y+=vy;
};
void Bullet::Draw()
{
DrawSprite(type, x, y, angle+90);
};
In the separate .cpp file, I have a string ordered map of sf::sprites. My draw function looks like this:
void DrawSprite(std::string type, float x, float y, float angle)
{
sf::Sprite temp=sprites[type];
temp.setRotation(angle);
temp.setPosition(x, y);
window.draw(temp);
}