I am trying to make my simple game and I came across small issue. So I have 2 vectors of pointers.
One is for bullets and one is for enemy units.
First I tried to iterate through all of my bullets and then in second loop iterate through my enemies and when I find collision I erase enemy unit and bullet, but erasing bullets is crashing my game, so I figured out that I shouldnt erase from vector while its still iterating in first loop.
My second ide was something like this:
std::vector<Bullet*>::iterator remove = std::remove_if(bullets.begin(), bullets.end(),
[&](Bullet* x)
{
for (std::vector<EnemyUnit*>::iterator it = enemyUnits.begin(); it != enemyUnits.end(); ++it)
{
if (x->collision->CheckCollision((*it)->collision))
{
enemyUnits.erase(it);
return true;
}
else
{
return false;
}
}
});
bullets.erase(remove, bullets.end());
So it seems to work in a way, but my bullets can only collide with one enemy at a time and it seems like its not checking for all enemies. For example I shoot 5 bullets and firstly they can only collide with one enemy and after one bullet kills enemy other bullets will be able to collide with second enemy and so on . It seems like
for (std::vector<EnemyUnit*>::iterator it = enemyUnits.begin(); it != enemyUnits.end(); ++it)
Doesnt take place and it only gets one enemy? Is it wrong way to do it?
I need to use pointers and vectors, because its for my project.
The predicate for std::remove_if returns after the first check, no matter what.
To fix this, the predicate can return on a collision, or at the end (after the loop) on no collision, e.g.
for (auto it = enemyUnits.begin(); it != enemyUnits.end(); ++it)
{
if (x->collision->CheckCollision((*it)->collision))
{
enemyUnits.erase(it);
return true;
}
}
return false;
This way, if a collision is detected, it will exit early and return true.
If there's no collision with any enemy, it will stop after the loop and return false.
Answer advising to remove the return statements is correct. The return statements are exiting the inside loop early, but that isn't the only problem. Removing those returns will reveal the next problem. You need:
it = enemyUnits.erase(it);
to correctly set the iterator after an erase to prepare for the next pass through the loop.
Related
I'm iterating through the vector std::vector<Bullet*> bullets, and I'm looking for collisions with an enemy. It works great in every case, except for the case when: the last fired bullet (there has to be more than one) collides with the enemy.
Code-
for(std::vector<Bullet*>::iterator it = bullets.begin(); it != bullets.end(); ++it)
{
if ((*it)->getSprite()->getGlobalBounds().intersects(enemy->getSprite()->getGlobalBounds()))
{
delete *it;
bullets.erase(it);
enemy->destroy();
if (bullets.size() == 0)
break;
}
}
I commented particular elements in the for loop, and found out that the bullet.erase(it) call crashes the program.
When that crash happens, I receive a return code: 134 (0x86). What's the issue for that code?
(*it)->getSprite() returns a pointer to a sprite from Bullet class.
What about using remove_if and erase combo:
auto is_hit = [&enemy](Bullet *bullet)
{
if (bullet->getSprite()->getGlobalBounds().intersects(enemy->getSprite()->getGlobalBounds()))
{
delete bullet;
enemy->destroy();
return true;
}
return false;
};
bullets.erase(std::remove_if(bullets.begin(), bullets.end(), is_hit), bullets.end());
For your consideration:
The following code snippet shows how I clean a vector from its tail (the complementary action to adding element to the tail with push_back())
while(!gBoard.empty())
{
Cell_t* cell = gBoard.back(); // fetch last element (a ptr)
gBoard.pop_back(); // remove last element
delete cell; // remove cell from heap - raw pointer
}
Perhaps you could do this style of clean and use multiple vectors ... it still might be faster than alternatives.
In your problem, each bullet appears to have at least two destinations ... hit or miss.
while ( ! Bullets.empty() ) // spin through bullet list
{
Bullet* aBullet = Bullets.back(); // fetch copy of last element
Bullets.pop_back(); // remove last element
if (*aBullet)-> getSprite()->getGlobalBounds().
intersects(enemy->getSprite()->getGlobalBounds()))
{
// HIT!
Hit.push_back(aBullet); // capture the element to Hit bucket
enemy->destroy(); // tbd - a decision? or always final?
// no delete
if (bullets.size() == 0) // no more to compute, redundant to while
break;
}
else
{
// MISS
Missed.push_back(aBullet); // capture element to Missed bucket
}
} // while
assert(bullets.empty()); // bullets have been consumed
// clean up spent bullets that intersected
while (! Hit.empty() )
{
Bullet* aBullet = Hit.back(); // copy last element from Hit
Hit.pop_back(); // remove last element from Hit
delete aBullet; // tbr - delete the dynamic memory
}
// clean up spent bullets that missed
// move the bullet from Missed vec back into Bullets vec
// for tbd - furthur evaluation ... did the bullet hit any other obj
// the following also happens to 'undo' the seq reversal
while (! Missed.empty() )
{
Bullets.push_back (Missed.back()); // copy last element from Missed
Missed.pop_back(); // remove last element from Missed
// tbd - also delete the missed bullet?
// or do you check for these bullets to collide with other objects
}
// possibly a copy can do this last loop, but this is simple and
// undoes the reversal.
and found out that the bullet.erase(it) call [for that last element]
crashes the program
In some sense, you are probably performing the erase prematurely.
Consider the following:
It is possible that the test parameters of range, target, and weapon-type might combine to achieve, for example, a 10% hit ratio. Thus, in a collection of 1000 shots, (1000 == bullets.size()), there would be (~) 100 bullets that have hit the target.
Your code finds each of the elements, and creates 100 'holes' in the vector by using bullets.erase(). Because the vector data is maintained contiguous, the erase method also moves other elements to fill the holes created by erase. (The details of how might differ between implementations.)
Generally, 100 erases causes 100 shuffles of less than (at-most) 1000 elements each time ... this one-at-a-time-approach would probably be a comparatively 'slow' process.
As an alternative to the current design, instead of find-and-erase, defer the erase until your code has identified and marked all 'intersects'.
you can find the intersects (hits) in the same way, but 'mark' them, don't erase them yet. Options include adding a bool to the Bullet class, or maintaining a matching bool vector to hold this flag for each bullet.
Using two indexes,
-- i1 initialized to 0 (first (left most) vector element) and
-- i2 initialized to (bullets.size() - 1) [last (right most) vector element]
-- Spin increment i1 to find the first hit,
-- Spin decrement i2 to find the last miss,
-- then std::swap (bullets[i1], bullets[i2])
Repeat until i1 >= i2
Now that all the hits are contiguous AND at the tail of the vector, perform a single erase of the 100 hits
This should eliminate any shuffling.
Also, there should be no use of an erased element ... because the erase occurs at the end of the process.
I think I read somewhere that distance() when returning the iterator position can be finicky. And sometimes it doesn't return the right position. I want to know if this is true or if I'm not using it right.
I'm trying to find when a particle in a vector of 21 is hovered. The idea is to switch the state of the others once one gets hovered.
I'm using find() to know when the particle is hovered, hence true.
vector<bool>::iterator it;
it = find(_tmp->isParticleHovered.begin(), _tmp->isParticleHovered.end(), true);
if (it != _tmp->isParticleHovered.end()){// we look if a particle is being hovered.
isHovered = true;// we use this to check internally the first boid
}else{
isHovered = false;
}
Now I also wanted to know not only when it was hovered but which one was hovered so I added this:
vector<bool>::iterator it;
it = find(_tmp->isParticleHovered.begin(), _tmp->isParticleHovered.end(), true);
if (it != _tmp->isParticleHovered.end()){// we look if a particle is being hovered.
l = distance(_tmp->isParticleHovered.begin(), it);
isHovered = true;// we use this to check internally the first boid
}else{
isHovered = false;
l = -1;
}
So knowing the index, I wanted to switch the states of the others so I came up with the following:
if ( l == -1){
if ( boidState5){
resetFamilyBoidState(_tmp);// makes all the particles go back to the same state
boidState2 = true;
boidState5 = false;
}
}else if ( l != -1){
if ( boidState2 ){
makeBoidStateless(_tmp, l);// I pass L, to this function, tell the function to switch all the particles to a different state except the one that is being hovered.
boidState5 = true;
boidState2 = false;
}
}
It will work for a couple of times but when I will hover from particle to particle rapidly it will get confused, and sometimes l will return 21 which will make it crash since the particle vector size is 21 - being 20 the latest container.
I came up with a solution without using neither find() nor distance():
int FamiliesController::returnInfoBoxState(){
for ( int i = 0; i < boidList.size(); i++){
if ( boidList[i]->boidState == 2){
return i;
}
}
return -1;
}
In the controller class I created a function that would return me the index number when that specific state was called, otherwise it would return -1. Using the same if statement it worked fine.
I'm curious to find about find() and distance(). Any clarification is much appreciated.
std::distance is exact. There is no room for doubt.
You are most likely misunderstanding its function, though. You said that it may return the "wrong position". It never returns positions anyway. An iterator is a position.
Also, you may want to check std::bitset<21>. It's more appropriate when the number of bits is fixed, and it has extra helper functions such as .reset()
I am making a simple game in SFML 2 and it came smoothly so far. I created two sf::Sprite lists, one for enemies and one for lasers. The enemies spawn randomly off-screen and the lasers are created whenever input is given. I created a collision loop for both the lists and executed my code. There are no compile time and run time errors. The laser-enemy collision works fine for the first 3 to 4 enemies but after that, the collision does not occur. What might be causing this problem? Please help me on this. Thanks. Here's my code.
std::list<sf::Sprite>::iterator enemyit = enemy.begin(), next;
std::list<sf::Sprite>::iterator greenlaserit = greenlaser.begin(), reload;
while(enemyit != enemy.end())
{
next = enemyit;
next++;
while(greenlaserit != greenlaser.end())
{
reload = greenlaserit;
reload++;
if(enemyit->getGlobalBounds().intersects(greenlaserit->getGlobalBounds()))
{
enemy.erase(enemyit);
greenlaser.erase(greenlaserit);
++erased;
}
greenlaserit = reload;
}
enemyit = next;
}
It seems to be that you are doing a lot of iterator manipulation and that is likely to be where the problem is occurring.
If you can use c++11, I would suggest looking into the for each loop (http://www.cprogramming.com/c++11/c++11-ranged-for-loop.html), to keep things really simple to read and understand (and thus, easier to debug).
You could do something like this:
std::list<sf::Sprite> enemies;
std::list<sf::Sprite> lasers;
for (sf::Sprite enemy: enemies) {
for (sf::Sprite laser : lasers) {
if (enemy.getGlobalBounds().intersects(laser.getGlobalBounds())) {
enemies.remove(enemy);
lasers.remove(laser);
}
}
}
Edit: otherwise, one method I have found for figuring out an iterator problem is stepping through it by hand. I draw two rectangles with cells for each location, and keep track of the iterators and run through the logic step by step. Before each iteration of your logic, write down your expected results. Then go through it by hand and see if your results match your expectations.
I know that in order to kill invaders in C++, I need to make a collider.
However, nothing will ever kill the invaders in that game.
Here's the code in the header:
bool DoCollision(float Xbpos, float Ybpos, int BulWidth, int BulHeight, float Xipos, float Yipos, int InvWidth, int InvHeight);
This is the function I'm initializing:
bool Game::DoCollision(float Xbpos, float Ybpos, int BulWidth, int BulHeight, float Xipos, float Yipos, int InvWidth, int InvHeight) {
if (Xbpos+BulWidth < Xipos || Xbpos > Xipos+InvWidth) return false;
if (Ybpos+BulHeight < Yipos || Ybpos > Yipos+InvHeight) return false;
return true;
}
And this is what happens if somebody presses the space key:
if (code == 57) { //Space
myKeyInvader.MeBullet.Active = true;
myKeyInvader.MeBullet.Xpos = myKeyInvader.Xpos + 10;
myKeyInvader.MeBullet.Ypos = myKeyInvader.Ypos - 10;
myKeyInvader.MeBullet.yvuel = 0.2;
myKeyInvader.MeBullet.BulletP->CopyTo(m_Screen,myKeyInvader.Xpos,myKeyInvader.Ypos);
if (DoCollision(Invaders[counter].MyBullet.Xbpos,Invaders[counter].MyBullet.Ybpos,Invaders[counter].MyBullet.BulWidth,
Invaders[counter].MyBullet.BulHeight,Invaders[counter].Xipos,Invaders[counter].Yipos,Invaders[counter].InvWidth,Invaders[counter].InvHeight)) {
//myKeyInvader.Ypos = 100;
Invaders[counter].Active = false;
printf("Collide!\n");
}
}
Does anybody know what's going wrong?
The problem isn't C++. The problem is how you are using it. The only way you'll get a kill with your code as written is if the invader is right on top of you. But that's too late. The alien invader has already killed you.
What you need to do is make those bullets into objects that you propagate over time, just like your invaders are objects that you propagate over time. The response to the user pressing a space key should be to add a new instance of a bullet to the set of active bullets. Each of those active bullets has a position that changes with time. On each time step, you should advance the states of the active invaders per the rules that dictate how invaders move and advance the states of the active bullets per the rules that dictate how bullets move. Remove bullets when they reach the top of the screen, and if an alien invader reaches the bottom of the screen, game over.
After propagating, removing off-screen bullets, and checking for game over, you want to check for collisions between each of the N bullets with each of the M invaders. When a collision is detected, remove the bullet from the set of active bullets and delete the alien invader from the set of active invaders. And of course you'll want some nifty graphics to show the user that another alien bit the dust.
Aside: Being an NxM problem, this check might be the biggest drain on CPU usage. You can speed this up with some simple heuristics.
You could manage the collections of alien invaders and bullets yourself, carefully using new and delete so as to prevent your invaders and bullets from killing your program with a memory leak. You don't have to do this. C++ gives you some nifty tools to manage these collections. Use one of the C++ standard library collections instead of rolling your own collection. For example, std::vector<AlienInvader> invaders; or std::list<AlienInvader> invaders, and the same for bullets. You'll be deleting from the middle a lot, which suggests that std::list or std::deque might be more appropriate than std::vector here.
You test the collision for the fired item just when they are created
Shouldn't be the test collision done in the main loop for each existing item at each frame ?
Don't worry, C++ has got all you need to kill invaders :)))
It's not easy to give advice based on so little code, but here the only logical error seems to be you test for collision only when space is pressed; you should test for it in an outside loop probably:
if (code == 57) { //Space
myKeyInvader.MeBullet.Active = true;
myKeyInvader.MeBullet.Xpos = myKeyInvader.Xpos + 10;
myKeyInvader.MeBullet.Ypos = myKeyInvader.Ypos - 10;
myKeyInvader.MeBullet.yvuel = 0.2;
myKeyInvader.MeBullet.BulletP->CopyTo(m_Screen,myKeyInvader.Xpos,myKeyInvader.Ypos);
}
From a logical point of view, pressing Space should fire a bullet: the starting position for the bullet is set, and so is its speed on the Y axis (so that it goes up).
The code that check for collision should go outside of this if block. In fact, this block of code is executed only if you're still pressing space -that is: still firing-. Should collision be checked only if you're "still firing"? Do the fact that you fired a bullet and started waiting for it to destroy the invader interfere in some way with the fact that this bullet can reach the invader and, indeed, destroy it? Of course not!
if (DoCollision(Invaders[counter].MyBullet.Xbpos,Invaders[counter].MyBullet.Ybpos,Invaders[counter].MyBullet.BulWidth,
Invaders[counter].MyBullet.BulHeight,Invaders[counter].Xipos,Invaders[counter].Yipos,Invaders[counter].InvWidth,Invaders[counter].InvHeight)) {
//myKeyInvader.Ypos = 100;
Invaders[counter].Active = false;
printf("Collide!\n");
}
You want collision to be checked in an outside loop, the same that probably also contains the checks for key presses. In this way, even if you're just looking at the screen and waiting, the program keeps testing the condition and, when it's fulfilled, code associated with the event of collision is executed (that is: an invader is "inactivated").
You say //Space , is that what it is or should it be 32 (if ASCII) instead of 57? Does the program flow into the if==57 block?
Your code looks fine, but you need two loops around the collision checker: one for checking all invaders (not just one of them) and another one to check at every bullet position along its trajectory, not just the moment when it leaves the gun.
I will assume we have an auxiliary function that moves the bullet and returns whether it is still inside the screen:
bool BulletIsInScreen();
Then we can write the loops:
if (code == 57) { // Space
while (BulletIsInScreen()) {
for (size_t i = 0; i < counter; ++i) { // counter is the number of invaders,
// according to your comment to your own answer
myKeyInvader.MeBullet.Active = true;
myKeyInvader.MeBullet.Xpos = myKeyInvader.Xpos + 10;
myKeyInvader.MeBullet.Ypos = myKeyInvader.Ypos - 10;
myKeyInvader.MeBullet.yvuel = 0.2;
myKeyInvader.MeBullet.BulletP->CopyTo(m_Screen,myKeyInvader.Xpos,myKeyInvader.Ypos);
if (DoCollision(Invaders[i].MyBullet.Xbpos, Invaders[i].MyBullet.Ybpos,
Invaders[i].MyBullet.BulWidth, Invaders[i].MyBullet.BulHeight,
Invaders[i].Xipos, Invaders[i].Yipos,
Invaders[i].InvWidth, Invaders[i].InvHeight)) {
//myKeyInvader.Ypos = 100;
Invaders[i].Active = false;
printf("Collide!\n");
}
}
}
}
Now this should work as expected.
Here is my code for checking if future move is legal, I have assumed its legal and copied move into mySquares array. I then call this method in the game cycle set in the form and in the timer handler which is:
canvas->drawGrid();
testBlock->drawBlock();
testBlock->moveDown();//this method has checkBounds for when hit sides, top & bottom
if(newBlock->canMoveDown()==false)
{
newBlock->addMySelfToGameBoard();
mainGameBoard->updateGrid();
}
//timer1 handler finish
bool TTetrisBlock::canMoveDown()
{
array<Point>^ temporaryCopy = gcnew array<Point>(4);
bool canGoDown = true;
for(int i=0;i<mySquares->Length;i++)
{
//Set future move
temporaryCopy[i].X = mySquares[i].X;
temporaryCopy[i].Y = mySquares[i].Y+1;
}
//Check if future move cells are full, if not assign values to mySquares
//Check if future move is legal
for(int j=0;j<temporaryCopy->Length;j++)
{
if(gameBoard->isCellOccupied(temporaryCopy[j].X,temporaryCopy[j].Y) == true)
{
mySquares[j].X = temporaryCopy[j].X;
mySquares[j].Y = temporaryCopy[j].Y;
}
}
return canGoDown;
}
//end of moveDown
in my gameboard class i have the method which checks if TCell is occupied or not. TGameBoar holds an array of TCells which has a color and bool isOccupied = false;
bool TGameBoard::isCellOccupied(int c,int r)
{
//Checks if TCell is occupied
return myGrid[c,r]->getIsOccupied();
}
It Crashes and indicates here was the problem, Im currently learning C++ at school. I would appreciate some help. I am also struggling with the Keydown for moving left and right using e->KeyData == Keys::Left) etc. and creating a newblock when gone through loop.
I have my project rar if you want to check it out. I have all the classes done, its just putting it together is the hard bit.
Project Tetris
I see three problems.
First you should only move mySquares when isCellOccupied returns false (not true as you currently have it). I suspect this is the cause of your crash as it looks like you will be moving a block into a cell that is already occupied.
Second, when isCellOccupied returns true you should set canGoDown to false and break out of your for loop (or better yet, make canGoDown (==true) an additional condition of your for loop i.e. j < temporaryCopy->Length && canGoDown). As it is, your function always return true because it is never set to false and that can't be right.
Just making an assumption here, but don't all mySquares consist of 4 elements? You are initializing temporaryCopy with 4 elements but it isn't clear whether mySquares has 4 elements. If not, this could be dangerous as in your first loop you are looping on mySquares->Length and addressing temporaryCopy with that index value, which could be out of range. And then later doing the opposite. It might be better to use a constant (4) in all all loops or better yet, always use mySquares->Length (especially when creating the temporaryCopy array) to ensure that both arrays contain the same number of elements.