Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: When a bullet in bulletArray is erased, other sprites turn white.  (Read 7206 times)

0 Members and 2 Guests are viewing this topic.

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Hi guys.

I am having some trouble with sprites turning white (not starting out as white).

I have an array of bullets (an array of BulletPistol()) and they are initialized just fine with a texture. Problem occurs when my collision detecting erases an array entry, the other bullets turn white - so it seems that they lose reference to my texture. Why does this occur, when every single bullet should (well, they should atleast) have their own reference to the texture?

The bullets are erased using the erase() command and are added to the array like so:
BulletPistol newBullet(Player.getHandPosition(), Player.getHandRotation());
_projectileArray.push_back(newBullet);

I worry that I have messed up in my class structures.

Here are some of the classes I use:
class PhysicalObject
{
protected:
    sf::Texture _image;
    sf::Sprite _sprite;

public:
    float xVel;
    float yVel;
    float speed;

    PhysicalObject();
    PhysicalObject(const PhysicalObject& other) : _image(other._image), _sprite(other._sprite), xVel(other.xVel), yVel(other.yVel)
    {
        _sprite.setTexture(_image);

    };
    virtual ~PhysicalObject();

    virtual void update();
    virtual void draw(sf::RenderWindow &window);
    sf::Vector2f getPosition();
    float getRotation();
};

//-----------------------------------------------------------------------------
class Projectile : public PhysicalObject
{
public:
    Projectile();
    Projectile(const Projectile& other) : PhysicalObject(other) {};
    virtual ~Projectile();

    bool isDead;
    int pushBack;
    float angle;
    void die();
};

//-----------------------------------------------------------------------------

class BulletPistol : public Projectile
{
public:
    BulletPistol(sf::Vector2f position, float angle);
    BulletPistol(const BulletPistol& other) : Projectile(other) {};
    ~BulletPistol();

private:
};

PhysicalObject::PhysicalObject()
{
    speed = 0.f;
    xVel = 0.f;
    yVel = 0.f;
}
PhysicalObject::~PhysicalObject()
{

}
void PhysicalObject::update()
{
    _sprite.move(xVel, yVel);
}
void PhysicalObject::draw(sf::RenderWindow &window)
{
    window.draw(_sprite);
}
sf::Vector2f PhysicalObject::getPosition()
{
    return _sprite.getPosition();
}
float PhysicalObject::getRotation()
{
    return _sprite.getRotation();
}

//-----------------------------------------------------------------------------
Projectile::Projectile()
{
    pushBack = 2;
    isDead = false;
}
Projectile::~Projectile()
{
}
void Projectile::die()
{
    isDead = true;
}

//-----------------------------------------------------------------------------
BulletPistol::BulletPistol(sf::Vector2f position, float angle)
{
    // general
    _image.loadFromFile("resources/bullet_rpg.png");
    _image.setSmooth(true);
    _sprite.setTexture(_image);
    _sprite.setOrigin(_image.getSize().x/2, _image.getSize().y/2);
    _sprite.setPosition(position);
    _sprite.setRotation(angle + 180);

    // specifications
    speed = 20.0;

    // start moving
    angle -= 180;
    xVel = cos(angle*PI/180) * speed;
    yVel = sin(angle*PI/180) * speed;
}
BulletPistol::~BulletPistol()
{
   
}
 

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #1 on: October 04, 2013, 03:01:07 pm »
You have a couple things going.

_projectileArray.push_back(newBullet);

When you store your class in the array like that you are actually copying the BulletPistol class into the array (and thus when the array/vector is resized you are copying your BulletPistol class again). Use a std::unique_ptr<BulletPistol> instead with your array to avoid copying.

class PhysicalObject
{
protected:
    sf::Texture _image;
    sf::Sprite _sprite;
....

Once again you need to use a reference/pointer here to avoid copying.

BulletPistol::BulletPistol(sf::Vector2f position, float angle)
{
    // general
    _image.loadFromFile("resources/bullet_rpg.png");
....

And you should reread the tutorial on textures and pay close attention to the end about minimizing your textures. You are loading a new texture for every BulletPistol instance when you should already have it loaded and be reusing the same texture.
« Last Edit: October 04, 2013, 03:12:00 pm by zsbzsb »
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #2 on: October 04, 2013, 03:10:03 pm »
Quote
You have a couple things going.

_projectileArray.push_back(newBullet);

When you store your class in the array like that you are actually copying the BulletPistol class into the array (and thus when the array/vector is resized your copying your BulletPistol class again). Use a std::unique_ptr<BulletPistol> instead with your array to avoid copying.
Just to be sure, you suggest to use a std::unique_ptr rather than std::vector?

Quote
And you should reread the tutorial on textures and pay close attention to the end about minimizing your textures. You are loading a new texture for every BulletPistol instance when you should already have it loaded and be reusing the same texture.

That is most certainly on my todo-list. Just wanted to get something up on the screen ASAP.

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #3 on: October 04, 2013, 03:14:08 pm »
Just to be sure, you suggest to use a std::unique_ptr rather than std::vector?

No, use a std::unique_ptr<BulletPistol> with a std::vector<T>. So in the end you have std::vector<std::unique_ptr<BulletPistol>>.
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #4 on: October 04, 2013, 03:54:12 pm »
Just to be sure, you suggest to use a std::unique_ptr rather than std::vector?

No, use a std::unique_ptr<BulletPistol> with a std::vector<T>. So in the end you have std::vector<std::unique_ptr<BulletPistol>>.

In my engine I have changed
std::vector<Projectile> _projectileArray
to now
std::vector<std::unique_ptr<Projectile>> _projectileArray;
.

I assume that this now means that the array, _projectileArray, now holds pointers to the BulletPistol class rather than a BulletPistol class itself. Is this correct?

If so, how would the push_back look?

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #5 on: October 04, 2013, 04:06:35 pm »
I assume that this now means that the array, _projectileArray, now holds pointers to the BulletPistol class rather than a BulletPistol class itself. Is this correct?

Yes, and since you are using a smart pointer you do not need to worry about memory leaks, the smart pointer will automatically delete the BulletPistol class when it goes out of memory.

Quote
If so, how would the push_back look?

_projectileArray.push_back(std::unique_ptr<Projectile>(new BulletPistol(...)));
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #6 on: October 04, 2013, 04:20:04 pm »
I assume that this now means that the array, _projectileArray, now holds pointers to the BulletPistol class rather than a BulletPistol class itself. Is this correct?

Yes, and since you are using a smart pointer you do not need to worry about memory leaks, the smart pointer will automatically delete the BulletPistol class when it goes out of memory.

Quote
If so, how would the push_back look?

_projectileArray.push_back(std::unique_ptr<Projectile>(new BulletPistol(...)));

Thanks for all your help and guidance!

I am having some trouble in my CheckBulletCollision function, which deletes the bullets if they collide.
Is my iteration over the std::vector array wrong now?

Source:
...
void Engine::Update()
{
    sf::Vector2i mousePos = SYS_MousePos(window);
    Player.update(window.mapPixelToCoords(mousePos));

    // update bullets
    for (int i = _projectileArray.size() - 1; i >= 0; --i)
    {
        _projectileArray[i]->update();
        CheckBulletCollision(_projectileArray[i], i);
    }

    // update camera position
    UpdateCamera();

}
...
void Engine::CheckBulletCollision(std::unique_ptr<Projectile> projectile, int i)
{
    // left
    if (projectile->getPosition().x < window.mapPixelToCoords(sf::Vector2i(0, 0)).x)
    {
        _projectileArray.erase(_projectileArray.begin() + i);
        return;
    }
    // right
    if (projectile->getPosition().x > window.mapPixelToCoords(sf::Vector2i(SYS_WIDTH, 0)).x)
    {
        _projectileArray.erase(_projectileArray.begin() + i);
        return;
    }
    // up
    if (projectile->getPosition().y < window.mapPixelToCoords(sf::Vector2i(0, 0)).y)
    {
        _projectileArray.erase(_projectileArray.begin() + i);
        return;
    }
    // down
    if (projectile->getPosition().y > window.mapPixelToCoords(sf::Vector2i(0, SYS_HEIGHT)).y)
    {
        _projectileArray.erase(_projectileArray.begin() + i);
        return;
    }
}
 

Here's the error, which I just can't seem to figure out:
Code: [Select]
$ make all && ./main.x64
g++ -std=c++11 -Wno-switch -Iinclude -c cpp/game.cpp
cpp/game.cpp: In member function ‘void Engine::Update()’:
cpp/game.cpp:121:52: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Projectile; _Dp = std::default_delete<Projectile>]’
         CheckBulletCollision(_projectileArray[i], i);
                                                    ^
In file included from /usr/include/c++/4.8.1/memory:81:0,
                 from cpp/stdafx.h:17,
                 from cpp/game.cpp:2:
/usr/include/c++/4.8.1/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^
In file included from cpp/game.cpp:6:0:
cpp/game.h:22:10: error:   initializing argument 1 of ‘void Engine::CheckBulletCollision(std::unique_ptr<Projectile>, int)’
     void CheckBulletCollision(std::unique_ptr<Projectile> projectile, int i);
          ^
make: *** [game.o] Error 1

zsbzsb

  • Hero Member
  • *****
  • Posts: 1409
  • Active Maintainer of CSFML/SFML.NET
    • View Profile
    • My little corner...
    • Email
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #7 on: October 04, 2013, 04:27:53 pm »
You don't need to pass the smart pointer around, just pass around a reference to the smart pointer's object.

Change

void Engine::CheckBulletCollision(std::unique_ptr<Projectile> projectile, int i)
...
CheckBulletCollision(_projectileArray[i], i)
 

To

void Engine::CheckBulletCollision(Projectile& projectile, int i)
...
CheckBulletCollision(*_projectileArray[i], i)
« Last Edit: October 04, 2013, 04:33:34 pm by zsbzsb »
Motion / MotionNET - Complete video / audio playback for SFML / SFML.NET

NetEXT - An SFML.NET Extension Library based on Thor

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #8 on: October 04, 2013, 04:42:42 pm »
You don't need to pass the smart pointer around, just pass around a reference to the smart pointer's object.

Change

void Engine::CheckBulletCollision(std::unique_ptr<Projectile> projectile, int i)
...
CheckBulletCollision(_projectileArray[i], i)
 

To

void Engine::CheckBulletCollision(Projectile& projectile, int i)
...
CheckBulletCollision(*_projectileArray[i], i)

The textures are now working perfectly. Thanks for your help!

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #9 on: October 04, 2013, 05:44:26 pm »
You don't need to pass the smart pointer around, just pass around a reference to the smart pointer's object.

Change

void Engine::CheckBulletCollision(std::unique_ptr<Projectile> projectile, int i)
...
CheckBulletCollision(_projectileArray[i], i)
 

To

void Engine::CheckBulletCollision(Projectile& projectile, int i)
...
CheckBulletCollision(*_projectileArray[i], i)
Sorry for the double-posting, but just to be sure that I understood what happened. Could you explain to me why the other sprites suddenly turned white when erasing a BulletPistol() from the std::vector array?

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #10 on: October 04, 2013, 06:54:04 pm »
A std::vector is an STL container that wraps a dynamic array, a data structure using contiguous storage. When you call push_back() and its capacity (= maximal number of elements that fit into the vector) is exhausted, the container will trigger a reallocation, i.e. allocate a bigger array. In this step, elements are copied or moved from the old container to the new, bigger one, which invalidates all pointers, references and iterators to the old elements.

Instead of std::vector<std::unique_ptr<T>>, it would be easier to use a different container such as std::list<T> or std::deque<T>.

And don't use indices to iterate through the container, that's the purpose of iterators. Random access is not supported by all container types. By the way, the correct type for indices and sizes would be std::size_t, not int. An iterator loop looks as follows:
for (std::vector<...>::iterator itr = _projectileArray.begin();
     itr != _projectileArray.end(); ++itr)
{
    itr->update();
    CheckBulletCollision(*itr);
}

With C++11, you can deduce the type by replacing std::vector<...>::iterator with auto, or even better, use the range-based for loop:
for (Projectile& p : _projectileArray)
{
    p.update();
    CheckBulletCollision(p);
}

Some general tips: Make your member variables private, don't duplicate textures for each object, use the constructor initializer list, don't define unnecessary destructors, consider the rule of three, and use std:: before standard library functions such as cos().
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #11 on: October 10, 2013, 05:25:35 pm »
Instead of std::vector<std::unique_ptr<T>>, it would be easier to use a different container such as std::list<T> or std::deque<T>.

With C++11, you can deduce the type by replacing std::vector<...>::iterator with auto, or even better, use the range-based for loop:
for (Projectile& p : _projectileArray)
{
    p.update();
    CheckBulletCollision(p);
}

I've messed around with using a std::list rather than std::vector<std::unique_ptr...>>, and it works like a charm except in the cases when I need to remove items from the list.
I read you can't use list.erase() while iterating using a C++11 range loop, but how would I go about doing it then? My game segfaults when using erase while iterating.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #12 on: October 10, 2013, 07:21:02 pm »
I read you can't use list.erase() while iterating using a C++11 range loop, but how would I go about doing it then?
Use a conventional iterator loop like in good old times. You can even deduce the type with auto.

My game segfaults when using erase while iterating.
Assign the return value of erase() to the iterator, and make sure you don't increment the iterator in the cases where you erase.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

MarcusM

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: When a bullet in bulletArray is erased, other sprites turn white.
« Reply #13 on: October 11, 2013, 12:15:46 am »
I read you can't use list.erase() while iterating using a C++11 range loop, but how would I go about doing it then?
Use a conventional iterator loop like in good old times. You can even deduce the type with auto.

My game segfaults when using erase while iterating.
Assign the return value of erase() to the iterator, and make sure you don't increment the iterator in the cases where you erase.
Cheers once again! Works beautifully.