I have a list populated with a sprite class I created. I use this to call the update and draw methods on each. The problem is, when one is destroyed, I need to remove it from that list. When I try to do so, the next update or draw method gets an error: “Collection was modified; enumeration operation may not execute.” I don’t know how to fix this…any help would be appreciated. Thank you!
EDIT: Okay, source code it is, but be warned, it’s spread out across many classes and it’s not very organized:
Okay, so here’s the GameComponent class this is all originating from:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using ShipBattle.Classes;
using ShipBattle.Classes.Ships.Fighters;
namespace ShipBattle
{
/// <summary>
/// This is a game component that implements IUpdateable.
/// </summary>
public class BuildMenu : DrawableGameComponent
{
enum RaceSelected { Tauri, Goauld }
RaceSelected raceSelected;
Button f302Button, engageButton;
ListButton tauriButton, goauldButton;
Sprite deathGlider, menu, f302Stats;
SpriteBatch spriteBatch;
SpriteFont font;
/*
* [0] F302s
* [1] Death Gliders
* [2] Al'kesh's
* [3] Promethei
* [4] Ha'tak's
* [5] Daedaluses
*/
List<int> buildList;
List<int> enemyBuildList;
public BuildMenu(Game game)
: base(game)
{
spriteBatch = new SpriteBatch(Game.GraphicsDevice);
buildList = new List<int>();
enemyBuildList = new List<int>();
raceSelected = RaceSelected.Tauri;
}
/// <summary>
/// Allows the game component to perform any initialization it needs to before starting
/// to run. This is where it can query for any required services and load content.
/// </summary>
public override void Initialize()
{
buildList.Add(0);
base.Initialize();
enemyBuildList.Add(10);
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
//Fonts needed for info
font = Game.Content.Load<SpriteFont>(@"Fonts/MenuFont");
//The little enemy ship icons at the top
deathGlider = new Sprite(Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Enemy Ships/Death Glider"), new Vector2(30, 85), Color.White,
0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f);
//The actual build buttons
f302Button = new Button(new Texture2D[] { Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Player Ships/F-302/F-302") },
new Texture2D[] { Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Player Ships/F-302/F-302 On Over") },
new Texture2D[] { Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Player Ships/F-302/F-302 On Click") },
new Vector2(50, 175), Color.White, 0.0f, Vector2.Zero, 0.5f, SpriteEffects.None, 0.5f, 1, false, false, false, true);
f302Button.onClick += new OnClick(f302_onClick);
f302Button.onRightClick +=new OnClick(f302Button_onRightClick);
f302Stats = new Sprite(Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Player Ships/F-302/Stats"),
new Vector2(f302Button.Position.X + (f302Button.Textures[0].Width / 2), f302Button.Position.Y), Color.White, 0.0f, Vector2.Zero, 0.5f, SpriteEffects.None, 1.0f);
//The button to change the race
tauriButton = new ListButton(Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Tauri/Tauri"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Tauri/Tauri On Over"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Tauri/Tauri On Click"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Tauri/Tauri Selected"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Tauri/Tauri Selected On Over"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Tauri/Tauri Selected On Click"),
new Vector2(512, 710), true);
tauriButton.onClick +=new OnClick(tauriButton_onClick);
goauldButton = new ListButton(Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Goauld/Goauld"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Goauld/Goauld On Over"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Goauld/Goauld On Click"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Goauld/Goauld Selected"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Goauld/Goauld Selected On Over"),
Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Race Buttons/Goauld/Goauld Selected On Click"),
new Vector2(562, 710), true);
goauldButton.onClick += new OnClick(goauldButton_onClick);
//The menu background
menu = new Sprite(Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Build Menu"), Vector2.Zero, Color.White, 0.0f, Vector2.Zero, 1.0f,
SpriteEffects.None, 0.0f);
//The little button that says, "Engage" at the buttom
engageButton = new Button(new Texture2D[] { Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Engage Button/Engage Button") },
new Texture2D[] { Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Engage Button/Engage Button On Over") },
new Texture2D[] { Game.Content.Load<Texture2D>(@"Textures/Menus/Build Menu/Engage Button/Engage Button On Click") },
new Vector2 (1024 - 125, 768 - 125), Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f, 1, false, false, false, false);
engageButton.onClick += new OnClick(engageButton_onClick);
base.LoadContent();
}
/// <summary>
/// Allows the game component to update itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime)
{
switch (((Game1)Game).Level)
{
case 1:
Level1Update();
break;
}
switch (raceSelected)
{
case RaceSelected.Tauri:
tauriButton.IsSelected = true;
goauldButton.IsSelected = false;
break;
case RaceSelected.Goauld:
tauriButton.IsSelected = false;
goauldButton.IsSelected = true;
break;
}
tauriButton.Update();
goauldButton.Update();
engageButton.Update();
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Draw(GameTime gameTime)
{
spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);
switch (((Game1)Game).Level)
{
case 1:
Level1Draw();
break;
}
engageButton.Draw(spriteBatch);
spriteBatch.DrawString(font, "$" + ((Game1)Game).player.Money.ToString(), new Vector2(800, 160), Color.White, 0.0f, Vector2.Zero, 1.0f,
SpriteEffects.None, 1.0f);
menu.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
/*
* Event Handlers
*/
//Click the button!
private void f302_onClick(object sender, EventArgs e)
{
if (((Game1)Game).player.Money > 0)
{
buildList[0]++;
((Game1)Game).player.Money -= 200;
}
}
private void f302Button_onRightClick (object sender, EventArgs e)
{
if (buildList[0] > 0)
{
buildList[0]--;
((Game1)Game).player.Money += 200;
}
}
//Click the go button!
private void engageButton_onClick (object sender, EventArgs e)
{
((Game1)Game).fightScreen.GetShips(enemyBuildList, buildList);
((Game1)Game).gameState = GameState.InGame;
}
//Change the race
private void tauriButton_onClick(object sender, EventArgs e)
{
raceSelected = RaceSelected.Tauri;
}
private void goauldButton_onClick(object sender, EventArgs e)
{
raceSelected = RaceSelected.Goauld;
}
//Level-specific Update and Draw
//functions
private void Level1Update()
{
switch (raceSelected)
{
case RaceSelected.Tauri:
f302Button.Update();
break;
case RaceSelected.Goauld:
break;
}
}
private void Level1Draw()
{
switch (raceSelected)
{
case RaceSelected.Tauri:
//F-302 Stuff
f302Button.Draw(spriteBatch);
f302Stats.Draw(spriteBatch);
spriteBatch.DrawString(font, buildList[0].ToString(),
new Vector2(f302Button.Position.X, (f302Button.Position.Y + (f302Button.Textures[0].Height / 2)) - 35),
Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f);
break;
case RaceSelected.Goauld:
break;
}
tauriButton.Draw(spriteBatch);
goauldButton.Draw(spriteBatch);
deathGlider.Draw(spriteBatch);
}
}
}
And this is my Ship class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ShipBattle.Classes.Ships.Projectiles;
using ShipBattle.Classes.Ships.Projectiles.Tauri;
namespace ShipBattle.Classes.Ships
{
public abstract class Ship
{
public Texture2D Texture { get; set; }
public Vector2 Position
{
get
{
return _position;
}
set
{
_position = value;
}
}
public float Rotation { get; set; }
private Vector2 _position;
#region Health & Shielding
protected float hullIntegrity;
protected float shieldStrength;
#endregion
#region Guns
protected List<Vector2> WeaponsEnplacements = new List<Vector2>();
protected List<Vector2> WeaponEmplacementOffsets = new List<Vector2>();
/// <summary>
/// The rates of fire for all weapons, represented in terms of the delay between frames
/// </summary>
protected List<float> WeaponRatesOfFire = new List<float>();
protected abstract List<float> CooldownLeft { get; set; }
protected List<Projectile> Projectiles = new List<Projectile>();
protected int[] Ammo;
protected int[] Damage;
#endregion
#region Targeting Logic
bool hasTarget = false;
int targetHashCode;
Vector2 targetShipPosition;
Ship target;
#endregion
#region Physics Stuff
float angleA, b, a, speed = 0;
double turningRadius = 10 * (Math.PI / 180);
//Acceleration
protected int mass; // kg
protected float force; // kN, thruster power
protected float acceleration; // m/s^2
//Velocity
protected float maxSpeed; // m/s, calculated using 30-second burn
protected float initialSpeed = 0;
protected float finalSpeed = 0;
protected float time = 0.016666f;
#endregion
public void Update(List<Ship> ships)
{
if (!hasTarget)
{
targetHashCode = GetTarget(ships).GetHashCode();
hasTarget = true;
}
else
foreach (Ship ship in ships)
if (targetHashCode == ship.GetHashCode())
{
CheckTarget(ship);
targetShipPosition = ship.Position;
target = ship;
}
//Rotate the sprite using the turning radius
Rotation = Utilities.TurnToFace(Position, new Vector2(targetShipPosition.X, targetShipPosition.Y), (float)Rotation, (float)turningRadius);
//Move the sprite, using KINEMATIC PHYSICS, ***!!!
Move();
//Recalculate the List<Vector2> weapons enplacements based on the current rotation angle
RecalculateWeaponsPositions();
//Cooldown the guns
Cooldown();
for (int i = 0; i < Projectiles.Count; i++)
if (Projectiles[i].Remove)
Projectiles.RemoveAt(i);
foreach (Projectile p in Projectiles)
p.Update(targetShipPosition);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, Position, null, Color.White, Rotation, new Vector2(Texture.Width / 2, Texture.Height / 2), 0.5f,
SpriteEffects.None, 0.0f);
foreach (Projectile p in Projectiles)
p.Draw(spriteBatch);
}
/// <summary>
/// Uses trig and the thruster power to move the ship. b is the y distance to move, a is the x distance to move
/// </summary>
private void Move()
{
if (finalSpeed < maxSpeed)
finalSpeed = speed + (acceleration * time);
angleA = Rotation;
b = (float)Math.Cos(angleA) * finalSpeed;
a = (float)Math.Sin(angleA) * finalSpeed;
_position.Y -= b;
_position.X += a;
speed = finalSpeed;
}
/// <summary>
/// Acquires the closes enemy ship
/// </summary>
/// <param name="ships">The ships to search through</param>
/// <returns></returns>
private Ship GetTarget(List<Ship> ships)
{
Ship rVal = null;
int distance = int.MaxValue;
float c;
foreach (Ship ship in ships)
{
c = Vector2.Distance(Position, ship.Position);
if (c < distance)
rVal = ship;
}
return rVal;
}
/// <summary>
/// Reorients the positions of all the weapon positions on this ship
/// </summary>
private void RecalculateWeaponsPositions()
{
for (int i = 0; i < WeaponsEnplacements.Count; i++)
{
WeaponsEnplacements[i] = RotateWeapons(WeaponEmplacementOffsets[i]);
}
}
/// <summary>
/// Recalculates the positions of the weapons on this ship based off their default offset and the current angle
/// </summary>
/// <param name="weapon">The weapon position to recalculate</param>
/// <param name="offset">The default offset of that weapon</param>
private Vector2 RotateWeapons(Vector2 offset)
{
Quaternion rotation = Quaternion.CreateFromAxisAngle(Vector3.Backward, (float)Rotation);
return Vector2.Transform(offset, rotation) + Position;
}
protected void Projectile_Hit(Projectile sender, EventArgs e)
{
sender.Remove = true;
if (sender is RailgunRound)
if (target.shieldStrength <= 0)
target.hullIntegrity -= sender.Damage;
else
target.shieldStrength -= sender.Damage / 4;
}
/// <summary>
/// Checks to see if the target ship is within weapons range
/// </summary>
/// <param name="target"></param>
protected abstract void CheckTarget(Ship target);
/// <summary>
/// Decrements the cooldown of all weapons
/// </summary>
protected abstract void Cooldown();
}
}
This is the subclassed F-302 that is producing the Projectile class: this is where the error actually occurs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using ShipBattle.Classes.Ships.Projectiles;
using ShipBattle.Classes.Ships.Projectiles.Tauri;
namespace ShipBattle.Classes.Ships.Fighters
{
public class F302 : Ship
{
const double missileMinimumFiringArc = Utilities.NINETY_DEGREES / 2;
const double missileMaximumFiringArc = Utilities.NINETY_DEGREES + (Utilities.NINETY_DEGREES / 2);
const double railgunMinimumFiringArc = 1.5;
const double railgunMaximumFiringArc = 1.6;
protected override List<float> CooldownLeft { get; set; }
private ContentManager content;
public F302(Vector2 position, ContentManager Content)
{
content = Content;
Texture = content.Load<Texture2D>(@"Textures\Ships\Tauri\Fighters\F302");
Position = position;
Rotation = 0;
#region Physics Stuff
mass = 19200;
force = 76.3f;
acceleration = (force * 1000) / mass;
maxSpeed = Utilities.GetVelocity(acceleration);
#endregion
#region Hull & Shielding
hullIntegrity = 10;
shieldStrength = 0;
#endregion
#region Weapons!!!
/*
* [0] = Port Missile
* [1] = Starboard Missile
* [2] = Port Railgun
* [3] = Starboard Railgun
*/
Ammo = new int[4];
Damage = new int[4];
//Port Missile
WeaponsEnplacements.Add(new Vector2(14, 5));
WeaponEmplacementOffsets.Add(new Vector2(14, 5));
Ammo[0] = 2;
Damage[0] = 50;
WeaponRatesOfFire.Add(0);
//Starboard Missile
WeaponsEnplacements.Add(new Vector2(35, 5));
WeaponEmplacementOffsets.Add(new Vector2(35, 5));
Ammo[1] = 2;
Damage[1] = 50;
WeaponRatesOfFire.Add(0);
//Cooldowns = 7.2f
//Port Railgun
WeaponsEnplacements.Add(new Vector2(24, 0));
WeaponEmplacementOffsets.Add(new Vector2(24, 0));
Ammo[2] = 10000;
Damage[2] = 2;
WeaponRatesOfFire.Add(30.0f);
//Starboard Railgun
WeaponsEnplacements.Add(new Vector2(26, 0));
WeaponEmplacementOffsets.Add(new Vector2(26, 0));
Ammo[3] = 10000;
Damage[3] = 2;
WeaponRatesOfFire.Add(30.0f);
CooldownLeft = WeaponRatesOfFire;
Projectiles = new List<Projectile>();
#endregion
}
protected override void CheckTarget(Ship target)
{
double distance = Vector2.Distance(Position, target.Position);
Vector2 vector1 = Vector2.Normalize(Position - target.Position);
Vector2 vector2 = new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation));
double angle = Math.Acos(Vector2.Dot(vector1, vector2));
if (angle > missileMinimumFiringArc && angle < missileMaximumFiringArc)
{
if (distance < 250)
FireMissiles();
}
if (angle > railgunMinimumFiringArc && angle < railgunMaximumFiringArc)
FireRailguns();
}
protected void FireMissiles()
{
//Add code so you don't waste all your missiles on one death glider
}
protected void FireRailguns()
{
//If the port railgun is ready to fire
if (CooldownLeft[2] <= 0)
{
RailgunRound rr = new RailgunRound(WeaponsEnplacements[0], Rotation, content);
rr.hit += new ProjectileHit(Projectile_Hit);
Projectiles.Add(rr);
//Reset the cooldown
CooldownLeft[2] = WeaponRatesOfFire[2];
}
//If the port railgun is ready to fire
if (CooldownLeft[3] <= 0)
{
Projectiles.Add(new RailgunRound(WeaponsEnplacements[1], Rotation, content));
//Reset the cooldown
CooldownLeft[3] = WeaponRatesOfFire[3];
}
}
protected override void Cooldown()
{
for (int f = 0; f < CooldownLeft.Count; f++)
CooldownLeft[f]--;
}
}
}
This is the projectile class that’s causing the issue:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using ShipBattle.Classes;
namespace ShipBattle.Classes.Ships.Projectiles
{
public abstract class Projectile
{
protected Texture2D Texture { get; set; }
public Vector2 Position
{
get
{
return _position;
}
set
{
_position = value;
}
}
protected Vector2 _position;
protected float Rotation { get; set; }
public event ProjectileHit hit;
protected Rectangle enemyRect;
public float Damage { get; set; }
public bool Remove { get; set; }
#region Physics Stuff
protected float angleA, b, a, speed = 0;
protected double turningRadius = MathHelper.ToRadians(360);
//Acceleration
protected float mass; // kg
protected float force; // kN, thruster power
protected float acceleration; // m/s^2
//Velocity
protected float maxSpeed; // m/s, calculated using 30-second burn
protected float initialSpeed = 0;
protected float finalSpeed = 0.0f;
protected float time = 0.016666f;
#endregion
/// <summary>
/// Updates the projectile sprite
/// </summary>
/// <param name="Pos">The position of the enemy</param>
public void Update(Vector2 pos)
{
enemyRect.X = (int)pos.X;
enemyRect.Y = (int)pos.Y;
Rotation = Utilities.TurnToFace(Position, new Vector2(pos.X, pos.Y), (float)Rotation, (float)turningRadius);
//Is things up?
Move();
if (enemyRect.Contains((int)Position.X, (int)Position.Y))
hit(this, new EventArgs());
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, Position, null, Color.White, Rotation, new Vector2(Texture.Width / 2, Texture.Height / 2), 1.0f, SpriteEffects.None, 0.1f);
}
protected void Move()
{
if (finalSpeed < maxSpeed)
finalSpeed = speed + (acceleration * time);
angleA = Rotation;
b = (float)Math.Cos(angleA) * finalSpeed;
a = (float)Math.Sin(angleA) * finalSpeed;
_position.Y -= b;
_position.X += a;
speed = finalSpeed;
}
}
}
And finally, this is the RailgunRounds, a subclass of projectile, the only one I’m currently spawning, but it’s causing the issue:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
namespace ShipBattle.Classes.Ships.Projectiles.Tauri
{
public class RailgunRound : Projectile
{
public RailgunRound(Vector2 Pos, float Rot, ContentManager content)
{
this.Texture = content.Load<Texture2D>(@"Textures\Ships\Tauri\Weapons\Railgun Round");
this.Position = Pos;
this.Rotation = Rot;
mass = 0.5f;
force = 5;
acceleration = (force * 1000) / mass;
maxSpeed = 28.3575f;
enemyRect = new Rectangle();
enemyRect.Width = 50;
enemyRect.Height = 50;
Damage = 2;
Remove = false;
}
}
}
The problem is that you’re using a
foreach, and while looping through your sprites you’re removing one which alters the container, which makes it impossible to properly loop forward through. To better illustrate why this is causing a problem, let’s look at a simple example using a standardforloop.In the example above, let’s just say that
someConditionIsMetevaluates to true every other loop. So here’s the results of our loop:See the problem? When
iwas equal to1, we removed something from myContainer, which changed its size, so wheniwas3we did not get a chance to remove another element from myContainer. This is what could happen if you were able to alter a container when you were usingforeach.There are a couple of solutions to this problem:
1.) Use a
forloop, and iterate from the end of the container.Now you can see that we managed to properly delete two entries in which
someConditionIsMetwas true, whereas in the first example we were only able to delete one entry. This solution provides better performance than the next example (below).2.) You can maintain a list of everything that needs to be removed while iterating with your
foreach, and then remove them later on with a reverseforloop. This is less efficient, but in some cases you’ll find that you need to iterate through a loop in the forward direction because order might matter.3.) Use a
forloop, iterate in a the forward direction, and anytime you remove an element from the container just decrement your iterator afterward. This was already mentioned in another answer on this page already (posted by Acidic), so I don’t take credit for this: