Список <T>Удалить спрайт - PullRequest
       38

Список <T>Удалить спрайт

0 голосов
/ 06 марта 2012

У меня есть список, заполненный классом спрайта, который я создал.Я использую это, чтобы вызвать обновление и нарисовать методы для каждого.Проблема в том, что когда кто-то уничтожен, мне нужно удалить его из этого списка.Когда я пытаюсь это сделать, следующий метод обновления или рисования выдает ошибку: «Коллекция была изменена; операция перечисления может не выполняться».Я не знаю, как это исправить ... любая помощь будет оценена.Спасибо!

РЕДАКТИРОВАТЬ: Хорошо, исходный код, но имейте в виду, он распределен по многим классам и не очень организован:

Хорошо, так вот класс 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; 
        } 
    } 
} 

Ответы [ 4 ]

3 голосов
/ 06 марта 2012
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 );
2 голосов
/ 06 марта 2012

Проблема в том, что вы используете 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.
1 голос
/ 06 марта 2012

Я думаю, что простое решение будет примерно таким:

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--;
    }
}
0 голосов
/ 06 марта 2012

Ну, насколько мне известно, XNA запускает Update и Draw в одной и той же ветке, но, возможно, что-то для вас не так.Не могли бы вы попробовать добавить какую-нибудь синхронизацию потоков, где вы изменяете или получаете доступ к списку Снарядов в вашем классе Кораблей?

Может решить это за вас, а затем снова может и не:)

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?И какой из них: Обновление или Ничья?

Кроме того, вы говорите, что раунд оружейного оружия вызывает проблему, означает ли это, что есть другие типы, у которых нет этой проблемы?

Редактировать

Небольшое редактирование: возможно, вы захотите добавить в свой класс Projectile, где вы проверяете, чтобы выстрелить ваше событие попадания, условие, чтобы проверить, зарегистрировано ли событие.

       if (enemyRect.Contains((int)Position.X, (int)Position.Y)) 
           if(hit != null)
                hit(this, new EventArgs()); 

Также там, где вы запускаете другоеРейлган, вы не добавляете никаких событий: это специально?Эти снаряды не будут удалены, но я вряд ли думаю, что это является причиной проблемы ...

Edit2

Вы думали написать какой-то общий репозиторий для текстур, к которым у вас есть доступ во всех вашихклассы?

Вы можете создать статический метод вспомогательного класса, который дает вам ссылку на текстуру, основанную на ключе, который он хранит в частном словаре.Вы заполняете это, когда загружаете свой уровень, а затем, когда вам нужны отдельные текстуры, вы просто вызываете их.Таким образом, вам не придется проходить 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");

Edit3

При перемещении спрайтов вам не нужно вычислять углы, а затем пересчитывать позиции 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, снаряды последуют).

...