I'm writing the Contra Game by Directx9 and c++
please help me about list of bullets
i'm trying below code but it's error: vector intertor incompatible
std::vector<Bullet*> bullets
if (mKeyboard->IsKeyPress(DIK_X))
{
Bullet* toShoot = new Bullet(noneType, _position.x, _position.y, RIGHT);
toShoot->Init();
bullets.push_back(toShoot);
}
Update Funtion:
std::vector<Bullet*>::iterator it = bullets.begin();
while ((it) != bullets.end())
{
(*it)->Update(gameTime, c);
if ((*it)->IsLive() == false)
{
bullets.erase(it++);
}
}
Render funtion
std::vector<Bullet*>::iterator it = bullets.begin();
while (it != bullets.end())
{
if ((*it)->IsLive())
{
(*it++)->Render(gr, cx, cy);
}
}
You can't just increment an iterator passed to erase(…). Do this instead:
if (!(*it)->IsLive()) {
it = bullets.erase(it);
} else {
++it;
}
Your Render function has a different bug. It gets stuck on the first non-live bullet, since the increment is inside the if-block. This is one reason for(…) is usually preferable to while(…):
for (auto it = bullets.begin(); it != bullets.end(); ++it) {
if (…) {
…
}
}
In fact, the Update function should be likewise changed, but omit the ++it.
In your Update function, you are calling the erase while iterating over a vector. The problem is that your it iterator can get invalidated if it is being used in the loop at the same time you're erasing from the vector.
The alternative is to use the erase/remove_if idiom:
#include <algorithm>
//...
bullets.erase(std::remove_if(bullets.begin(), bullets.end(),
[&](Bullet& b) { b.Update(gameTime, c); return !b.IsLive(); }),
bullets.end());
The algorithm function remove_if() is called to determine which bullets will be removed by the erase() function. Note that the lambda includes the logic you used to determine if a bullet should be removed.
Related
I have a game where I check collision between bullets and enemies which I store as 2 vector containers. People say if you're gonna erase an element in the for loop you better use iterators and so I did. But I have a problem now with passing the iterator to a function. The thing is I don't necessarily need to erase the element so it has to be a bit more complex.
This is the way I check collision. "CircularCollision" works fine, no mistakes there.
void ResolveColision(Weapon &weap, Map &map)
{
std::vector<Bullet> bullets = weap.GetBullets();
if (!bullets.empty())
{
for (std::vector<Bullet>::iterator i = bullets.begin(); i != bullets.end(); ++i)
{
std::vector<Enemy> enemies = map.GetEnemies();
if (!enemies.empty())
{
for (std::vector<Enemy>::iterator j = enemies.begin(); j != enemies.end(); ++j)
{
if (CircularCollision((*i), (*j)))
{
weap.DeleteByIndex(i);
map.TakeDamageByIndex(j, weap.GetDamage());
std::cout << "HIT!\n";
}
}
}
}
}
}
Here's the method which is supposed to decrease the health of an enemy:
void Map::TakeDamageByIndex(std::vector<Enemy>::iterator &itr, int damage)
{
(*itr).SetHealth((*itr).GetHealth() - damage);
}
Here's the method which deletes the bullet:
void Weapon::DeleteByIndex(std::vector<Bullet>::iterator &itr)
{
destroySprite((*itr).GetSprite());
bullets.erase(itr);
}
I'm sure it looks horrible and it shouldn't work but I have no idea how to do it properly. Please help!
Also, both methods work properly when the for loops operate with indexes (e.g. bullets[i]), in that case the problem is with "Vector subscript out of range" error.
In DeleteByIndex(), change this:
bullets.erase(itr);
To this:
itr = bullets.erase(itr);
std::vector::erase() returns an iterator to the next remaining element after the element that was erased. That next element is where your outer loop needs to continue from on its next iteration.
As such, you need to change your outer loop from a for to a while instead, or else you will skip elements (in fact, your original code suffers from that problem when you were still using indexes):
void ResolveColision(Weapon &weap, Map &map)
{
std::vector<Bullet> bullets = weap.GetBullets();
std::vector<Bullet>::iterator bullerItr = bullets.begin();
while (bullerItr != bullets.end())
{
std::vector<Enemy> enemies = map.GetEnemies();
bool wasAnyHit = false;
for (std::vector<Enemy>::iterator enemyItr = enemies.begin(); enemyItr != enemies.end(); ++enemyItr)
{
if (CircularCollision(*bulletItr, *enemyItr))
{
wasAnyHit = true;
weap.DeleteByIndex(bulletItr);
map.TakeDamageByIndex(enemyItr, weap.GetDamage());
std::cout << "HIT!\n";
break;
}
}
if (!wasAnyHit)
++bulletItr;
}
}
That being said, I would suggest replacing the inner loop with std::find_if() instead. And renaming DeleteByIndex() and TakeDamageByIndex() since they don't take an index anymore. In fact, I would not pass an iterator to TakeDamage...() at all, pass the actual Enemy object instead. Or better, move TakeDamage() into Enemy itself.
Try something more like this:
void ResolveColision(Weapon &weap, Map &map)
{
auto bullets = weap.GetBullets();
auto bulletItr = bullets.begin();
while (bulletItr != bullets.end())
{
auto enemies = map.GetEnemies();
auto &bullet = *bulletItr;
auto enemyHit = std::find_if(enemies.begin(), enemies.end(),
[&](Enemy &enemy){ return CircularCollision(bullet, enemy); }
);
if (enemyHit != enemies.end())
{
weap.DeleteBulletByIterator(bulletItr);
enemyHit->TakeDamage(weap.GetDamage());
std::cout << "HIT!\n";
}
else
++bulletItr;
}
}
void Enemy::TakeDamage(int damage)
{
SetHealth(GetHealth() - damage);
}
void Weapon::DeleteBulletByIterator(std::vector<Bullet>::iterator &itr)
{
destroySprite(itr->GetSprite());
itr = bullets.erase(itr);
}
A few other comments in addition to Remy Lebeau’s answer.
It’s as efficient to pass a STL iterator by value as by reference, so the only reason you would need to pass one by reference is: when you intend to change the index and you want that change to be visible in the caller’s scope. (For example, a UTF-8 parser needs to consume anywhere from one to four bytes.) Since this code doesn’t need to do that, you’re better off just passing the iterator by value.
In general, if you aren’t modifying the variable you pass by reference, you should pass by const reference instead. In the case of Enemy::TakeDamage(), the only thing you do with the iterator is dereference it, so you might as well just pass in an Enemy& and call it with *i as the parameter.
The algorithm is not very efficient: if you delete a lot of items near the start of the list, you would need to move all remaining items of the array multiple times. This runs in O(N²) time. A std::list, although it has a high overhead compared to std::vector, can delete elements in constant time, and might be more efficient if you have a lot of insertions and deletions that are not at the end. You might also consider moving only the objects that survive to a new list and then destroying the old one. At least this way, you only need to copy once, and your pass runs in O(N) time.
If your containers store smart pointers to the objects, you only have to move the pointers to a new location, not the entire object. This will not make up for the overhead of lots of heap allocations if your objects are small, but could save you a lot of bandwidth if they are large. The objects will still be automatically deleted when the last reference to them is cleared.
You could do something like this:
void delByIndex(vector<int>::iterator &i, vector<int>& a)
{
a.erase(i);
}
int main()
{
vector<int> a {1,5,6,2,7,8,3};
vector<int> b {1,2,3,1};
for(auto i=a.begin();i!=a.end();)
{
bool flag = false;
for(auto j=b.begin();j!=b.end();j++)
{
if(*i==*j)
{
flag = true;
}
}
if(flag)
{
delByIndex(i, a);
}
else
i++;
}
for(auto i:a)
cout << i << " ";
return 0;
}
Be careful when using erase as it will change the size of the vector and also invalidates the vector iterator.
I am trying to create a pacman game watching tutorials. So when the ghost is in weak state and the pacman collides with the ghost i am getting the error Vector iterator not incrementable. Can anyone please solve this. Thanks in advance.
for (Ghost*ghost : m_ghosts)
{
if (ghost->getCollisionBox().intersects(m_pcMan->getCollisionBox()))
{
if (ghost->isWeak())
{
m_ghosts.erase(std::find(m_ghosts.begin(), m_ghosts.end(),ghost));
m_score += 100;
}
else
m_pcMan->die();
}
}
NOTE: You used a range-based for-loop, but inside the for-loop, you still use std::find to find an iterator to the current element so that you delete, that's inefficient.
Your problem stems from the fact that, after you delete an element pointed to by an iterator, all iterators and references to that element and after becomes invalid. The range-based for-loop tries to eventually increment an invalid iterator which invokes Undefined Behavior. Luckily, your STL implementation has checked iterators, so you got some nice error message.
There are a variety of ways to go about this. Though you may want to consider your Data Structure... Game Devs have more advanced tricks for this sort of thing. Nonetheless, here is a trick, swap the current ghost with the last on the vector, then pop the last element.
auto iter = m_ghosts.begin();
auto End = m_ghosts.end();
while(iter != End)
{
if (iter->getCollisionBox().intersects(m_pcMan->getCollisionBox()))
{
if (iter->isWeak())
{
if(iter != std::prev(End)){ //We want to prevent `std::swap(x,x)`
std::iter_swap(iter, std::prev(End));
End = m_ghost.erase(std::prev(End));
}
else{
iter = End = m_ghost.erase(std::prev(End));
}
m_score += 100;
}
else{
++iter;
m_pcMan->die();
}
}
}
Thank you for the help guys. This worked for me :)
for (int i=0;i<m_ghosts.size();i++)
{
Ghost* ghost = m_ghosts[i];
if (ghost->getCollisionBox().intersects(m_pcMan->getCollisionBox()))
{
if (ghost->isWeak())
{
m_ghosts.erase(std::find(m_ghosts.begin(), m_ghosts.end(), ghost));
m_score += 100;
}
else
m_pcMan->die();
}
}
I am quite green regarding vectors, and this is my first time actually using them for collision checking. This is for my project, and I am stumped on how to implement the collision. The current Collision check and response codes I have seem to be ... bad design.
This is my code:
for(auto it = ArrayofEntities.begin(); it != ArrayofEntities.end(); it++)
{
CEntity * go = (*it);
for(auto i = ArrayofEntities.begin(); i != ArrayofEntities.end();)
{
//Collision for entities. Collision Event returns the iterator after an element is erased.
CEntity * other = (*i);
if (go != other)
{
if (!theCollision.CheckCollision(go, other, false, false, false, false)) //Checks if it has collided go with other
{
i = go->CollisionEvent(*other, ArrayofEntities); //Run collision code, setting i to the iterator which is returned.
//break;
}
else
{
i++;
}
}
else
{
i++;
}
}
}
CEntity is the base class for all the entities.
My CheckCollision just returns a true or false on the collision, and my collision event runs the collision and returns an iterator (because I might have to destroy things in the vector).
My collision event is below
vector<CEntity*>::iterator bullet::CollisionEvent(CEntity &other, vector<CEntity*> & theArray)
{
case ZOMBIE:
{
other.hp -= power * 0.01;//Effect
int Counter, index, bulletindex;
auto it = theArray.begin();
//Find the bullet and the other in the array.
for (it = theArray.begin(), Counter = 0; it != theArray.end();it++, Counter++)
{
CEntity *go = NULL;
go = (*it);
if (go == &other)
{
index = Counter;
}
if(go->ID == BULLET && go->GetX() == GetX() && go->GetY() == GetY())
{
bulletindex = Counter;
}
}
this->~bullet();//Delete the bullet
theArray.erase(theArray.begin() + bulletindex);
if(other.hp <= 0)
{
other.~CEntity();
it = theArray.erase(theArray.begin() + index); //delete from array.
return it;
}
it = theArray.begin() + index;
return it;
}
}
I have basically done this like how I would do an array. Just check it against itself. The error it gives is "Vector Iterator not Incrementable", on the first for loop after the collision event has been run.
So my question: 1) What am I doing wrong?
2) Is my thinking to do this like checking arrays wrong?
This is my school project, so I have full control of the codes.
I would prefer to have a quick fix over a complete rewriting of all the collision codes, but if it really comes down to it, I will rewrite my codes.
If you look at the implementation of std::remove_if, you'll see that they've solved the issue of iterator invalidation in another way. instead of erasing elements, they move them to the end of the array.
This may be the easiest solution for you as well. Keep an iterator which points after the last "live" entirty. It starts out at .end but as bullets hit things, you swap the entities to the back of your range and decrement that last-live iterator.
Then, when you're done looping over your array, you clean up with a single call to .erase.
And yes, you should use either std::unique_ptr<CEntity> or std::shared_ptr<CEntity> in the collection. In that way, .erase won't just erase the pointer but also the object pointed to.
I'm trying to remove 'dead' bullets from my vector of bullets. Every frame, I'm calling the Bullet::update() function which looks like this:
void Bullet::update()
{
for(int i = 0; i != mAmmo.size(); i++)
{
if(mAmmo[i].sprite.getPosition().x > 700)
mAmmo[i].mAlive = false;
if(mAmmo[i].mAlive == false)
{
// I get a Debug Assertion Failed at runtime from this piece of code
mAmmo.erase(mAmmo.begin()+i);
}
if(mAmmo[i].mAlive == true)
{
mAmmo[i].sprite.move(mMovement);
}
}
}
Am I doing this completely incorrectly? This is the first time I've really used vectors more than just following through a tutorial. If I need to post any more code, just tell me. I've been working on this for the past few hours, so I'm a wee bit desperate to get this to work.
Thanks in advance!
You're easily walking into undefined behavior as soon as the ith element is the last element in your list. Use iterators, and pay special attention to the return value of erase(), as it automatically advances the iterator for you so your loop doesn't have to.
void Bullet::update()
{
for (auto it = mAmmo.begin(); it != mAmmo.end();)
{
if(it->sprite.getPosition().x > 700)
it->mAlive = false;
if (!it->mAlive)
{
// erase and get next iterator
it = mAmmo.erase(it);
}
else
{ // move and increment
it->sprite.move(mMovement);
++it;
}
}
}
I have a list of Star structs. These structs are in a std::list
I am double looping this list and compairing there locations to detect a collision. When A collision is found I will delete Star with the lowest mass. But how can I delete the Star when I am in the double Loop, and keep the loop going to check for more collisions?
It's worth mentioning that the second loop is a reverse loop.
Here is some code
void UniverseManager::CheckCollisions()
{
std::list<Star>::iterator iStar1;
std::list<Star>::reverse_iterator iStar2;
bool totalbreak = false;
for (iStar1 = mStars.begin(); iStar1 != mStars.end(); iStar1++)
{
for (iStar2 = mStars.rbegin(); iStar2 != mStars.rend(); iStar2++)
{
if (*iStar1 == *iStar2)
break;
Star &star1 = *iStar1;
Star &star2 = *iStar2;
if (CalculateDistance(star1.mLocation, star2.mLocation) < 10)
{
// collision
// get heaviest star
if (star1.mMass > star2.mMass)
{
star1.mMass += star2.mMass;
// I need to delete the star2 and keep looping;
}
else
{
star2.mMass += star1.mMass;
// I need to delete the star1 and keep looping;
}
}
}
}
}
You need to utilize the return value of the erase method like so.
iStar1 = mStars.erase(iStar1);
erase = true;
if (iStar1 == mStars.end())
break; //or handle the end condition
//continue to bottom of loop
if (!erase)
iStar1++; //you will need to move the incrementation of the iterator out of the loop declaration, because you need to make it not increment when an element is erased.
if you don't increment the iterator if an item is erased and check if you deleted the last element then you should be fine.
Since modifying the list invalidates the iterators (so that you cannot increment them), you have to keep safe the iterators before the list is changed.
In the most of the implementation std::list is a dual-linked list, hence a iteration like
for(auto i=list.begin(), ii; i!=list.end(); i=ii)
{
ii = i; ++ii; //ii now is next-of-i
// do stuff with i
// call list.erasee(i).
// i is now invalid, but ii is already the "next of i"
}
The safest way, is to create a list containing all the "collided", then iterate on the "collided" calling list.remove(*iterator_on_collided)
(but inefficient, since has O2 complexity)
You want to use the result of erase() to get the next iterator and advance the loop differently:
If you erase using the outer iterator you clearly can abondon checking this Star against others and break out of the inner loop. Only if the inner loop was complete you'd want to advance the outer iterator because otherwise it would be advanced by the erase().
If you erase using the inner loop you already advanced the iteration, otherwise, i.e. if no star was erased, you need to advance.
Sample code would look somethimg like this:
for (auto oit(s.begin()), end(s.end()); oit != end; )
{
auto iit(s.begin());
while (iit != end)
{
if (need_to_delete_outer)
{
oit = s.erase(oit);
break;
}
else if (need_to_delete_inner)
{
iit = s.erase(iit);
}
else
{
++iit;
}
}
if (iit == end)
{
++oit;
}
}