Collision with a picturebox
Hello,
Situation:
I am creating a 2D game for college which is a simple dodging game in C#, the player can move left and right to dodge falling pictureboxes as well as collecting them.
problem:
i have added collision to the pictureboxes which works absolutely fine except one problem; when the players avatar
picturebox collides with the falling picturebox, the falling picturebox needs to be deleted otherwise it would keep taking chunks of health every tick of the timer which the detection if statement is in. but when i delete the picturebox using the remove control, the player still collides with where the picturebox was.
question: hw do i fully delete a picturebox that doesnt leave anything behind.
code for removing picturebox:
Code:
if (pictureBox1.Bounds.IntersectsWith(pictureBox5.Bounds)) {
this.Controls.Remove(pictureBox5);
pictureBox5.Dispose();
hp = hp - 10;
}
thanks in advance.
this is my first post on this forum, if there is anything i could do better in my post, please let me know <3
Arrokai
Re: Collision with a picturebox
Hi arrokai,
I'm having the same problem with the collision detection between the two pictureboxes.
I have +- the same code for removing the picturebox, but -as you said- the player still collides with the place where the picturebox was.
I'm wondering if you have found the solution for this problem because I can't find it ??
Greetz
Re: Collision with a picturebox
Create a List<Control> member variable, and add all the controls that are to be checked for collision to it (if it's only the one picture box, then just add that; this way, if you want to introduce more colliders later, you won't have to change the collision handling code). Then instead of doing:
if (pictureBox1.Bounds.IntersectsWith(pictureBox5.Bounds)) // this always checks against the pictureBox5 - the object is still in memory even if you remove it from the form
{ /* (omitted) */}
do this:
Code:
// Suppose you declared your list like this (at some other place in the code):
// List<Control> colliders = new List<Control>();
for(int i = colliders.Count - 1; i >= 0 ; --i) // looping backwards
{
if (pictureBox1.Bounds.IntersectsWith(colliders[i].Bounds))
{
this.Controls.Remove(colliders[i]); // (1) remove from the form
colliders[i].Dispose(); // (2) dispose
hp -=10; // (3) do game-related logic
colliders.RemoveAt(i); // (4) remove from the colliders collection
// Important! Don't try to access the element at the index i anymore - it was deleted
// so it's either not the same element as before (deletion from the middle),
// or it doesn't even exist (deletion from the end)!
}
}
Why is the collection iterated backwards? Because objects are being removed, and the size of the list changes; going from the end to the start ensures that the changes are only localized to the elements that have already been visited, and the index remains valid, as well as the overall logic. For this reason, you cannot use the foreach loop - it's illegal to delete elements inside it.
Re: Collision with a picturebox
Thank you for your reply,
I see how that works and it fixed the problem :)
Re: Collision with a picturebox
hey,
TheGreatCthulhu has posted a response right after you commented if you saw :3
arrokai
Re: Collision with a picturebox
Thank you for your quick responses !!
In my case the for loop wouldn't work.
I was really desperate but now I've found a solution for my problem. I just have to add before each if statement (for collision) an if statement if the picturebox is visible or not.
Code:
Code:
if (picBox1.Visible)
{
if (picBoxBal.Bounds.IntersectsWith(picBox1.Bounds))
{
BalSpeedY = -BalSpeedY;
score++;
LblScore.Text = "Score: " + score.ToString();
this.Controls.Remove(picBox1);
picBox1.Dispose();
}
}
Re: Collision with a picturebox
It would work - and while your current solution works fine for your specific purpose, what if you later on decide you want to test against a different picture box, or that you want to add more than one collider (for example, you could create a simple game where the player must avoid several obstacles)?
The difference is that, in your case, since you're relying on the Visible property, you don't have to remove the picture box from the Control collection - you can just set Visible to false; this way it's easier to switch it on and off at will.
That said, there is a slight problem in your code above - you're lucky it works, because, it just so happens that one of the calls you make on the picBox1 has a convenient side effect of setting the Visible property to false. Note that, just by reading your code you can't tell why would the if condition fail on the next test. The method call that makes it all work is the call to the Dispose() method. However, once a control is disposed, it cannot be used normally anymore - some method and property calls will fail, others will not, and once again, you were lucky that the Visible property is still works.
As all this behavior is not documented (you can't find anything on MSDN that explicitly states that calling Dispose sets Visible to false, or that Visible is guaranteed not to throw an ObjectDisposedException), in theory it could change from one implementation of .NET Library to another, so you shouldn't rely on it.
It's better that you add one more line of code where you explicitly set the Visible property to false.
Code:
if (picBox1.Visible)
{
if (picBoxBal.Bounds.IntersectsWith(picBox1.Bounds))
{
BalSpeedY = -BalSpeedY;
score++;
LblScore.Text = "Score: " + score.ToString();
picBox1.Visible = false;
// Now you can safely ditch these, which leaves you with the option to turn it back on in a simple way:
// this.Controls.Remove(picBox1);
// picBox1.Dispose();
}
}
This way, everything is much more clear, and it will work for sure.
Some other things you can do; you can extract this code into a method, and pass the collider to test against as a parameter:
Code:
private bool TestForCollisionWith(Control target)
{
bool result = false;
if (target.Visible)
{
if (picBoxBal.Bounds.IntersectsWith(target.Bounds))
{
BalSpeedY = -BalSpeedY;
score++;
LblScore.Text = "Score: " + score.ToString();
target.Visible = false;
result = true;
}
}
return result; // false if no collision, true if a collision was detected
}
// And then you can call this method from where the original code used to be:
TestForCollisionWith(picBox1); // BENEFIT: you can now pass something other than picBox1
The method returns a bool indicating if a collision was detected or not, for convenience.
Finally, you can use a List<Control> and a loop in the same way as arrokai, except now the only thing you'd need to do inside the loop is to call the TestForCollisionWith() method. You can also remove the picture box from the list (but you don't have to, as now the code tests the Visible property; not removing it introduces some redundancy though, but for a small number of stored objects this is not a big problem).
Re: Collision with a picturebox
Thank you !!
It was really helpful!
I have now changed my code to your last piece of code because I use 7 pictureboxes.
Thanks!!