У меня есть список, заполненный классом спрайта, который я создал.Я использую это, чтобы вызвать обновление и нарисовать методы для каждого.Проблема в том, что когда кто-то уничтожен, мне нужно удалить его из этого списка.Когда я пытаюсь это сделать, следующий метод обновления или рисования выдает ошибку: «Коллекция была изменена; операция перечисления может не выполняться».Я не знаю, как это исправить ... любая помощь будет оценена.Спасибо!
РЕДАКТИРОВАТЬ: Хорошо, исходный код, но имейте в виду, он распределен по многим классам и не очень организован:
Хорошо, так вот класс 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);
}
}
}
И это мой класс Корабля:
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();
}
}
Это подкласс 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;
}
}
}