我有一个填充了我创建的精灵类的列表。我用它来调用更新并在每个上绘制方法。问题是,当一个被销毁时,我需要将其从该列表中删除。当我尝试这样做时,下一个更新或绘制方法会出错:“集合已被修改;枚举操作可能无法执行。”我不知道如何解决这个问题...任何帮助将不胜感激。谢谢!
编辑:好的,源代码是,但要注意,它分散在许多类中,并且它不是很有条理:
好的,所以这里的GameComponent类都来自:
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);
}
}
}
这是我的Ship类:
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();
}
}
这是产生Projectile类的子类F-302:这是实际发生错误的地方:
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]--;
}
}
}
这是导致问题的抛射物类:
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;
}
}
}
最后,这是RailgunRounds,一个射弹的子类,是我目前唯一产生的弹丸,但它导致了这个问题:
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;
}
}
}
问题是你正在使用foreach
,并且在循环通过你的精灵时,你正在移除一个改变容器的东西,这使得无法正确地向前循环。为了更好地说明导致问题的原因,让我们看一个使用标准for
循环的简单示例。
// For this example let's say myContainer has 4 elements in it.
for ( int i = 0; i < myContainer.size(); ++i )
{
if ( someConditionIsMet )
{
myContainer.Remove(i);
}
}
在上面的例子中,我们只是说someConditionIsMet
每隔一个循环计算为true。所以这是循环的结果:
1.) i = 0, someConditionIsMet is false, so we do nothing.
2.) i = 1, someConditionIsMet is true, so we remove myContainer at index 1. myContainer.size() is now 3.
3.) i = 2, someConditionIsMet is false, so we do nothing.
4.) i = 3, so we do not loop again because 3 is not less than myContainer.size(), which is 3.
看到问题了吗?当i
等于1
时,我们从myContainer中移除了一些东西,这改变了它的大小,所以当i
是3
时,我们没有机会从myContainer中移除另一个元素。如果您在使用foreach
时能够更改容器,则会发生这种情况。
这个问题有几个解决方案:
1.)使用for
循环,并从容器的末尾迭代。
// For this example let's say myContainer has 4 elements in it.
for ( int i = myContainer.size() - 1; i >= 0; --i )
{
if ( someConditionIsMet )
{
myContainer.Remove(i);
}
}
1.) i = 3, someConditionIsMet is true, so we remove myContainer at index 3. myContainer.size() is now 3.
2.) i = 2, someConditionIsMet is false, so we do nothing.
3.) i = 1, someConditionIsMet is true, so we remove myContainer at index 1. myContainer.size() is now 2.
4.) i = 0, someConditionIsMet is false, so we do nothing.
现在你可以看到我们设法正确删除了someConditionIsMet
为真的两个条目,而在第一个例子中我们只能删除一个条目。该解决方案提供比下一个示例(下面)更好的性能。
2.)您可以在使用foreach
进行迭代时保留需要删除的所有内容的列表,然后使用反向for
循环将其删除。这样效率较低,但在某些情况下,您会发现需要在正向迭代循环,因为顺序可能很重要。
List<entries> entriesToRemove = new List<entries>();
foreach(entry in myContainer)
{
if ( someConditionIsMet )
{
entriesToRemove.Add(entry);
}
}
for ( int i = entriesToRemove.size() - 1; i > 0; --i )
{
myContainer.Remove(entriesToRemove[i]);
}
entriesToRemove.clear();
3.)使用for
循环,向前迭代,任何时候从容器中删除元素,然后减少迭代器。这已经在本页的另一个答案中已经提到(由Acidic发布),所以我不赞成这个:
for ( int i = 0; i < myContainer.size(); ++i )
{
if ( someConditionIsMet )
{
myContainer.Remove(i);
--i;
}
}
1.) i = 0, someConditionIsMet is false, so we do nothing.
2.) i = 1, someConditionIsMet is true, so we remove myContainer at index 1. myContainer.size() is now 3. We decrement i by 1, so it's now 0 again but will be 1 on the next iteration because of the ++i in the for loop.
3.) i = 1, someConditionIsMet is false, so we do nothing.
4.) i = 2, someConditionIsMet is true, so we remove myContainer at index 2. myContainer.size() is now 2. We decrement i by 1, so it's now 1 again but will be 2 on the next iteration because of the ++i in the for loop so we will not loop again because 2 is not less than myContainer.size(), which is now 2.
var list = new List<int> { 1, 2, 3, 4, 5, 6 };
foreach( var i in list )
{
if( i % 2 == 0 )
{
list.Remove(i); // crash
}
}
你没有发布任何代码(你应该),所以我猜你有类似的东西。好吧,那不行。如果您更改了集合,那么枚举器的状态可能会被删除,并且它会让您知道。
你可以A)循环并创建一个要删除的项集合,然后在以后删除它们,或者B)使用一个花哨的LINQ方法,如下所示:
var odds = list.Where( i => i % 2 != 0 );
我认为简单的解决方案是这样的:
for (int i = 0; i < list.Count; i++)
{
list[i].Update(); // If the update is made at the same place
if (list[i].Removed) // A flag that indicates the sprite should be removed
{
list.Remove(i);
i--;
}
}
好吧,据我所知,XNA在同一个线程中运行Update和Draw,但也许对你来说有些不同。您是否可以尝试在Ship类中修改或访问Projectiles列表时添加一些线程同步?
可能会为你解决,然后可能不会:)
public void Update(List<Ship> ships)
{
...
lock(Projectiles)
{
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)
{
...
lock(Projectiles)
{
foreach (Projectile p in Projectiles)
p.Draw(spriteBatch);
}
}
另外,你能说明你在哪里得到这个例外吗?它在Ship.cs Update / Draw中吗?哪一个:更新还是绘制?
另外你说Railgun轮导致问题,这是否意味着还有其他类型没有这个问题?
轻微编辑:您可能希望在您的Projectile类中添加一个条件来检查事件是否已注册,您可以在其中检查以触发命中事件。
if (enemyRect.Contains((int)Position.X, (int)Position.Y))
if(hit != null)
hit(this, new EventArgs());
另外,在你发射其他轨道炮的地方,你不添加任何事件:这是故意的吗?那些射弹不会被移除,但我几乎认为这不会导致问题......
您是否考虑为您在所有类中访问的纹理编写某种公共存储库?
您可以创建一个辅助类的静态方法,该方法为您提供基于键的纹理引用,它在私有字典中保存。在加载关卡时填充这个,然后当你需要单独的纹理时,你只需要调用它们。这样你就不必在整个地方传递ContentManager,并且每次发射子弹时都不必加载纹理。
class Texture2DRepository
{
static Dictionary<String, Texture2D> _textures = new Dictionary<String, Texture2D>();
public static void Add(String key, Texture2D texture)
{
//maybe add logic to replace entries with same key
Dictionary.Add(key, texture);
}
public static Texture2D Get(String key)
{
return Dictionary[key];
}
}
当你绘制你的射弹时,假设你事先添加了纹理,你只需要打电话
Texture2DRepository.Get("RailgunSprite");
移动精灵时,您不必计算角度,然后重新计算X和Y位置,您可以使用向量为您执行此操作:
public void Update(Vector2 pos)
{
//you get a vector pointing from position to enemy ship position
Vector2 direction = pos - Position;
if (speed < maxSpeed)
speed += (acceleration * time);
//You add to your position the direction vector modified to the length of speed.
Position += direction.Normalize() * speed;
if (enemyRect.Contains((int)Position.X, (int)Position.Y))
if(hit != null)
hit(this, new EventArgs());
}
请注意,你写它的方式,这些是归位射弹(如果你给pos
论证赋予不同的值,射弹将会跟随)。