First, sorry for my bad English, English is not my native language.
I have a problem when I draw a Texture2D in screen. When I move a Texture in screen,
colors in border of the texture flickers. This is my code:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SharpDX.Direct2D1.Effects;
using System;
using System.Collections.Generic;
namespace PixelTest
{
public class Drawable
{
public Texture2D Image;
public Point Position;
public Point Size;
public Rectangle Source;
}
public class MainGame : Game
{
//PRIVATE READ-ONLY VARIABLES
private readonly GraphicsDeviceManager _graphics;
private readonly List<Drawable> _sprites;
//VARIABLES
private Texture2D _screen;
private Texture2D _tiles;
private SpriteBatch _spriteBatch;
private Point _camera;
private Point _scale;
private bool _byRect;
private KeyboardState _previousKeyState;
private BlendState _state;
private RasterizerState _rState;
//CONSTRUCTOR
public MainGame()
{
_graphics = new GraphicsDeviceManager(this);
_sprites = new List<Drawable>();
_tiles = null;
_spriteBatch = null;
_camera = Point.Zero;
_scale = new Point(1, 1);
Content.RootDirectory = "Content";
IsMouseVisible = true;
IsFixedTimeStep = true;
//_graphics.PreferHalfPixelOffset = true;
_graphics.PreferMultiSampling= true;
//_graphics.SynchronizeWithVerticalRetrace = false;
_state = new BlendState()
{
AlphaBlendFunction = BlendFunction.Add,
AlphaSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.Zero,
AlphaDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.Zero,
ColorBlendFunction= BlendFunction.Add,
ColorDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.Zero,
ColorSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.One,
};
_rState = new RasterizerState()
{
MultiSampleAntiAlias = false
};
}
protected override void Initialize()
{
base.Initialize();
_sprites.Add(new Drawable
{
Image = _tiles,
Position = new Point(32, 64),
Size = new Point(8, 8),
Source = new Rectangle(0, 0, 8, 8)
});
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
_tiles = Content.Load<Texture2D>("i001_tiles");
_screen = new Texture2D(GraphicsDevice, _graphics.PreferredBackBufferWidth, _graphics.PreferredBackBufferHeight);
var colors = new Color[_graphics.PreferredBackBufferWidth * _graphics.PreferredBackBufferHeight];
for (var i = 0; i < _graphics.PreferredBackBufferWidth * _graphics.PreferredBackBufferHeight; i++)
colors[i] = Color.Black;
_screen.SetData(colors);
}
protected override void UnloadContent()
{
base.UnloadContent();
_tiles.Dispose();
_screen.Dispose();
}
protected override void Update(GameTime gameTime)
{
base.Update(gameTime);
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
var sKey = Keyboard.GetState();
var translateUnit = Point.Zero;
var scaleUnit = 0;
if (sKey.IsKeyDown(Keys.A))
translateUnit.X = -1;
else if (sKey.IsKeyDown(Keys.D))
translateUnit.X = 1;
else
translateUnit.X = 0;
if (sKey.IsKeyDown(Keys.W))
translateUnit.Y = -1;
else if (sKey.IsKeyDown(Keys.S))
translateUnit.Y = 1;
else
translateUnit.Y = 0;
if (sKey.IsKeyDown(Keys.F))
scaleUnit = -1;
else if (sKey.IsKeyDown(Keys.G))
scaleUnit = 1;
else
scaleUnit = 0;
if (sKey.IsKeyDown(Keys.U) && _previousKeyState.IsKeyUp(Keys.U))
_byRect = !_byRect;
var speed = translateUnit;// * 1;
_camera += speed;// RoundVec(speed);
_scale = new Point(4, 4);
_previousKeyState = sKey;
}
protected override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
GraphicsDevice.Clear(Color.Blue);
_spriteBatch.Begin(SpriteSortMode.Deferred, _state, SamplerState.PointClamp, null, _rState, null, null);
//_spriteBatch.Draw(_screen, Vector2.Zero, Color.White);
foreach (var item in _sprites)
{
if (true)
{
var pos = item.Position;
var size = item.Size;
pos += _camera;
pos *= _scale;
size *= _scale;
var rect = new Rectangle(
(int)pos.X,
(int)pos.Y,
(int)size.X,
(int)size.Y);
_spriteBatch.Draw(
item.Image,
rect,
item.Source,
Color.White,
0F,
Vector2.Zero,
SpriteEffects.None,
0F);
}
else
{
var pos = item.Position;
//pos = RoundVec(pos * _scale) / _scale;
pos += _camera;
pos *= _scale;
_spriteBatch.Draw(
item.Image,
pos.ToVector2(),
item.Source,
Color.White,
0F,
Vector2.Zero,
_scale.ToVector2(),
SpriteEffects.None,
0F);
}
}
_spriteBatch.End();
}
}
}
I dont use floats values for camera position in this test and the flickers continue to happens. I dont know what is happen.
I tried various things, but nothing work.
Resources:
i001_tiles.png (no alpha values)
PrintScreens:
Maybe is just a ottical illusion, or a monitor problem, but the green part of texture merge with the blue background on camera movement, i dont know why.
In Movement Up:
This only occour with the colors different of white, when i movement camera to right, only the one pixel in green in the right merge with blue background.
I'm writing a bit of code where I animate a hundred copies of the same sprite onto the screen by creating a sprite class. I want the sprites, an animation of four-ring interlocking rings spinning within each other, to be drawn at a certain height, drop down on the screen, and bounce like a ball, with each bounce being progressively less until it they stop completely. I have managed to get this part done; however, I can't seem to find a way to randomize the acceleration and animation speed of each different sprite. Can someone provide some suggestions to my code?
Game 1.cs
namespace lab_6
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Sprite rings;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferHeight = 600;
graphics.PreferredBackBufferWidth = 800;
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
Texture2D rings_texture = Content.Load<Texture2D>("Images/threerings");
//animation
Point frameSize = new Point(75, 75);
Point currentFrame = new Point(0, 0);
Point sheetSize = new Point(6, 8);
int millisecondsPerFrame = 50;
rings = new Sprite(rings_texture, ringsPos,
frameSize, 0, currentFrame, sheetSize, ringsSpeed, millisecondsPerFrame);
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
this.Exit();
// TODO: Add your update logic here
rings.Update(gameTime, Window.ClientBounds);
base.Update(gameTime);
}
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin();
rings.Draw(gameTime, spriteBatch);
spriteBatch.End();
}
}
}
Sprite.cs
namespace lab_6
{
public class Sprite
{
//basics
protected Texture2D rings;
protected Vector2 ringsPos = new Vector2(0,0);
protected Color tint = Color.White;
protected Vector2 ringsSpeed = new Vector2(0,0);
protected Vector2 ringsAccel = new Vector2(0, 1);
//animation
protected Point frameSize = new Point(75,75);
protected Point currentFrame = new Point(0, 0);
protected Point sheetSize = new Point(6,8);
//animation timing
protected int timeSinceLastFrame = 0;
protected int millisecondsPerFrame = 50;
const int defaultMillisecondsPerFrame = 16;
//bounding box offset
protected int collisionOffset;
Random r = new Random(DateTime.Now.Millisecond);
public Sprite(Texture2D rings, Vector2 ringsPos, Point frameSize,
int collisionOffset, Point currentFrame, Point sheetSize, Vector2 ringsSpeed,
int millisecondsPerFrame)
{
this.rings = rings;
this.ringsPos = ringsPos;
this.frameSize = frameSize;
this.collisionOffset = collisionOffset;
this.currentFrame = currentFrame;
this.sheetSize = sheetSize;
this.ringsSpeed = ringsSpeed;
this.millisecondsPerFrame = millisecondsPerFrame;
}
public virtual void Update(GameTime gameTime, Rectangle clientBounds)
{
int maxY = (600 - frameSize.Y );
ringsAccel.Y += (byte)r.Next((1 / 10), 1);
ringsSpeed.Y += ringsAccel.Y;
ringsPos.Y += ringsSpeed.Y;
if (ringsPos.Y > maxY)
{
ringsSpeed *= -0.8f;
ringsPos.Y = maxY;
}
//Update animation frame
millisecondsPerFrame = 50;
millisecondsPerFrame *= ((byte)r.Next(1, 10));
timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
if (timeSinceLastFrame > millisecondsPerFrame)
{
timeSinceLastFrame = 0;
++currentFrame.X;
if (currentFrame.X >= sheetSize.X)
{
currentFrame.X = 0;
++currentFrame.Y;
if (currentFrame.Y >= sheetSize.Y)
currentFrame.Y = 0;
}
}
}
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
for(int i = 0; i < 100; )
{
Vector2 newPos = ringsPos + new Vector2((10 * i), (1 * (byte)r.Next((1/10), 1)));
spriteBatch.Draw(rings, newPos,
new Rectangle(currentFrame.X * frameSize.X,
currentFrame.Y * frameSize.Y,
frameSize.X, frameSize.Y),
tint, 0, Vector2.Zero, 1f, SpriteEffects.None, 0);
i++;
r = new Random(DateTime.Now.Second);
}
}
}
}
Move the constructor for ringSpeed and ringAccel to the constructor and randomize them there.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I was developing a game using XNA frame work and here is my code
I just want to generate infinite number of enemies
here is my code please help
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;
namespace fight_in_the_sky
{
/// <summary>
/// This is the main type for your game
/// </summary>
///
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spritebatch;
Texture2D anim, background1, background2, enemy;
Rectangle rect0, rect2, rectdst, srcrect, rect3, rect4, srcrect2, enemy_rect;
float elapsed;
float delay = 40f;
int frames = 0;
Random random = new Random();
public Vector2 velocity;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 800;
graphics.PreferredBackBufferHeight = 600;
IsMouseVisible = true;
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
rectdst = new Rectangle(0, 250, 115, 69);
rect3 = new Rectangle(0,0,1280,720);
rect4 = new Rectangle(1280, 0, 1280, 720);
enemy_rect = new Rectangle(random.Next(800-94,800-47),random.Next(600-122,600-61),376/8,61);
velocity.X = 3f;
velocity.Y = 3f;
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spritebatch = new SpriteBatch(GraphicsDevice);
background1= Content.Load<Texture2D>("starfield006");
background2= Content.Load<Texture2D>("starfield005");
anim=Content.Load<Texture2D>("shipAnimation");
enemy =Content.Load<Texture2D>("mineAnimation");
rect0 = new Rectangle(0, 0, 800, 600);
rect2 = new Rectangle(0, 250,200, 100);
// TODO: use this.Content to load your game content here
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
// if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
// this.Exit();
// TODO: Add your update logic here
elapsed += (float)gameTime.ElapsedGameTime.Milliseconds;
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
rectdst.X -= 12;
if (rectdst.X < 0) { rectdst.X = 0; }
}
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
rectdst.X += 12;
if (rectdst.X > 700) { rectdst.X = 700; }
}
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
rectdst.Y -= 12;
if (rectdst.Y < 0) { rectdst.Y = 0; }
}
if (Keyboard.GetState().IsKeyDown(Keys.Down))
{ rectdst.Y += 10; if (rectdst.Y > 550) { rectdst.Y = 550; } }
base.Update(gameTime);
if (elapsed >= delay)
{
if (frames >= 7)
{ frames = 0; }
else { frames++; }
elapsed = 0;
}
srcrect = new Rectangle(115 * frames, 0, 115, 69);
srcrect2 = new Rectangle(47*frames,0,47,61);
if (rect3.X + background1.Width <= 0)
rect3.X = rect4.X + background2.Width;
if (rect4.X + background2.Width <= 0)
rect4.X = rect3.X + background1.Width;
rect3.X -= 5;
rect4.X -= 5;
enemy_rect.X +=(int) velocity.X;
enemy_rect.Y += (int)velocity.Y;
if (enemy_rect.Y >= 600 - 61 || enemy_rect.Y <= 0) velocity.Y = -velocity.Y;
if (enemy_rect.X >= 800 - 47) velocity.X = -velocity.X;
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spritebatch.Begin();
spritebatch.Draw(background1,rect3, Color.Azure);
spritebatch.Draw(background2,rect4, Color.Azure);
spritebatch.Draw(anim, rectdst, srcrect, Color.White);
spritebatch.Draw(enemy, enemy_rect, srcrect2, Color.White);
spritebatch.End();
base.Draw(gameTime);
}
}
}
and here is a shot of my current output
http://imgur.com/7y43S7O
Infinite is unpractical to build into a game, because sooner or later you would either run out of RAM or your screen would be too full to call it a game. Anyway, I can give you a basic idea to work with.
If you want your enemy count to keep increasing you can use a list and add an enemy every time you want one to appear
List<Enemy> enemies = new List<Enemy>;
In Update:
if(spawnTimer > spawnInterval && enemies.Count()<enemyLimit){
enemies.add(new Enemy([rectangle of spawn location and image size]);
spawnTimer = 0;
}
for(int i=0; i<enemies.Count();i++){
if(enemies[i].defeated){
enemies.Remove(enemies[i]);
}
spawnTimer+=gameTime.ElapsedGameTime.TotalSeconds;
in Draw:
for(int i=0; i<enemies.Count();i++){
spritebatch.Draw(enemy, enemies[i].Rect, srcrect2, Color.White);
}
class Enemy{
public Rectangle Rect{get;set;}
public bool defeated{get;set;}
public int Health{get;set;}
public Enemy(Rectangle rect){
Rect = rect;
Health = 100;
defeated = false;
}
}
The problem that I am having is wrapping my brain around how I could use a single png called Test Map.png:
This has a black border around the screen and smaller stepping blocks for the player to test the collision. I have the gravity working by using a player class and the main class Game1.cs to draw and update the game. I use this ball:
This is my sprite that I move around the screen.
Here is the player.cs:
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.Input;
namespace Gravity_Test_V2
{
class Player
{
public Texture2D Texture;
public Vector2 Velocity;
public Vector2 Position;
public float ground;
private float Speed;
private Rectangle screenBound;
public bool isJumping; //are we jumping or not
public bool goingUp; //if going up or not
public float initialVelocity; //initial velocity
private float jumpU; //How high the player can jump
private float g; //gravity
public float t; //time
private KeyboardState prevKB;
public Player(Texture2D Texture, Vector2 Position, float Speed, Rectangle screenBound)
{
this.Texture = Texture;
this.Position = Position;
ground = Position.Y;
this.Speed = Speed;
this.screenBound = screenBound;
Velocity = Vector2.Zero;
isJumping = goingUp = true;
jumpU = 2.5f;
g = -9.8f;
t = 0;
}
public void Update(GameTime gameTime)
{
Position.X += (Velocity.X * Speed);
//Set the Y position to be subtracted so that the upward movement would be done by decreasing the Y value
Position.Y -= (Velocity.Y * Speed);
goingUp = (Velocity.Y > 0);
// TODO: Add your update logic here
if (isJumping == true)
{
//motion equation using velocity: v = u + at
Velocity.Y = (float)(initialVelocity + (g * t));
//Increase the timer
t += (float)gameTime.ElapsedGameTime.TotalSeconds;
}
if (isJumping == true && Position.Y > screenBound.Height - Texture.Height)
{
Position.Y = ground = screenBound.Height - Texture.Height;
Velocity.Y = 0;
isJumping = false;
t = 0;
}
if (Position.X < 0)
{
//if Texture touches left side of the screen, set the position to zero and the velocity to zero.
Position.X = 0;
Velocity.X = 0;
}
else if (Position.X + Texture.Width > screenBound.Width)
{
//if Texture touches left side of the screen, set the position to zero and the velocity to zero.
Position.X = screenBound.Width - Texture.Width;
Velocity.X = 0;
}
if (Position.Y < 0)
{
//if the Texture touches the top of the screen, reset the timer and set the initial velocity to zero.
Position.Y = 0;
t = 0;
initialVelocity = 0;
}
}
public void Input(KeyboardState keyState)
{
if (keyState.IsKeyDown(Keys.Space) && (isJumping == false || Position.Y == ground))
{
isJumping = true;
initialVelocity = jumpU;
}
if (keyState.IsKeyDown(Keys.Left) && !keyState.IsKeyDown(Keys.Right))
{
if (Velocity.X > -1.0f)
{
Velocity.X -= (1.0f / 10);
}
else
{
Velocity.X = -1.0f;
}
}
else if (!keyState.IsKeyDown(Keys.Left) && keyState.IsKeyDown(Keys.Right))
{
if (Velocity.X < 1.0f)
{
Velocity.X += (1.0f / 10);
}
else
{
Velocity.X = 1.0f;
}
}
else
{
if (Velocity.X > 0.05 || Velocity.X < -0.05)
Velocity.X *= 0.70f;
else
Velocity.X = 0;
}
prevKB = keyState;
}
public void Fall()
{
t = 0;
initialVelocity = 0;
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height), Color.White);
}
}
}
Is there some simple way to make it so the player can collide with the Test Map.png while its inside the Test Map's texture?
EDIT: 1/21/2014 9 AM 'ish'
Level One:
EDIT: 1/21/2014 10:27
I have used a pixle based system to test to see if the player colides with an object but I try to sepreate the object from the play into classes and it will stop working. I mixed both my movment and collision projects together to try and make it work. I took player.cs (witch I have not changed) and added the pixel based collision into the Game1.cs I need to know how to make the player, witch is being controlled by the player.cs class, be seen by the Game1.cs class and used while being called upon by the player.cs class.
Note* I have also changed it so that the game would be using the falling triangles supplied by the pixel based system. I will add the test image when I am able to make this work.
At the moment The player can move and jump but is not reconsidered as colliding.
EDIT: 1/21/2014 10:34
I use 2 projects:
Collision:
http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel
Movment System:
http://gamepopper.co.uk/academic-projects/2012-2/jumping-platformer-example/
I have mixed them up and used pices of them to try and make my own platform.
Game1.cs:
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;
namespace Collision_Test
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
KeyboardState prevKB;
Player player;
SpriteFont font;
Texture2D personTexture;
Texture2D blockTexture;
// The color data for the images; used for per pixel collision
Color[] personTextureData;
Color[] blockTextureData;
Vector2 personPosition;
const int PersonMoveSpeed = 5;
public static int screenWidth = 800;
public static int screenHeight = 500;
// Blocks
List<Vector2> blockPositions = new List<Vector2>();
float BlockSpawnProbability = 0.01f;
const int BlockFallSpeed = 1;
Random random = new Random();
// For when a collision is detected
bool personHit = false;
// The sub-rectangle of the drawable area which should be visible on all TVs
Rectangle safeBounds;
// Percentage of the screen on every side is the safe area
const float SafeAreaPortion = 0.05f;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
this.graphics.PreferredBackBufferWidth = screenWidth;
this.graphics.PreferredBackBufferHeight = screenHeight;
this.graphics.ApplyChanges();
}
protected override void Initialize()
{
base.Initialize();
// Calculate safe bounds based on current resolution
Viewport viewport = graphics.GraphicsDevice.Viewport;
safeBounds = new Rectangle(
(int)(viewport.Width * SafeAreaPortion),
(int)(viewport.Height * SafeAreaPortion),
(int)(viewport.Width * (1 - 2 * SafeAreaPortion)),
(int)(viewport.Height * (1 - 2 * SafeAreaPortion)));
// Start the player in the center along the bottom of the screen
personPosition.X = (safeBounds.Width - personTexture.Width) / 2;
personPosition.Y = safeBounds.Height - personTexture.Height;
}
/// <summary>
/// Load your graphics content.
/// </summary>
protected override void LoadContent()
{
blockTexture = Content.Load<Texture2D>("Block");
personTexture = Content.Load<Texture2D>("Person");
font = Content.Load<SpriteFont>("Font");
player = new Player(personTexture, Vector2.Zero, 6.0f, new Rectangle(0, 0,
this.graphics.PreferredBackBufferWidth,
this.graphics.PreferredBackBufferHeight));
// Extract collision data
blockTextureData =
new Color[blockTexture.Width * blockTexture.Height];
blockTexture.GetData(blockTextureData);
personTextureData =
new Color[personTexture.Width * personTexture.Height];
personTexture.GetData(personTextureData);
// Create a sprite batch to draw those textures
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
}
void HandleInput(KeyboardState keyState)
{
player.Input(keyState);
if (prevKB.IsKeyUp(Keys.F) && keyState.IsKeyDown(Keys.F))
{
this.graphics.ToggleFullScreen();
this.graphics.ApplyChanges();
}
}
protected override void Update(GameTime gameTime)
{
// Get input
KeyboardState keyboard = Keyboard.GetState();
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
HandleInput(Keyboard.GetState());
player.Update(gameTime);
prevKB = Keyboard.GetState();
// Allows the game to exit
if (gamePad.Buttons.Back == ButtonState.Pressed ||
keyboard.IsKeyDown(Keys.Escape))
{
this.Exit();
}
// Spawn new falling blocks
if (random.NextDouble() < BlockSpawnProbability)
{
float x = (float)random.NextDouble() *
(Window.ClientBounds.Width - blockTexture.Width);
blockPositions.Add(new Vector2(x, -blockTexture.Height));
}
// Get the bounding rectangle of the person
Rectangle personRectangle =
new Rectangle((int)personPosition.X, (int)personPosition.Y,
personTexture.Width, personTexture.Height);
// Update each block
personHit = false;
for (int i = 0; i < blockPositions.Count; i++)
{
// Animate this block falling
blockPositions[i] =
new Vector2(blockPositions[i].X,
blockPositions[i].Y + BlockFallSpeed);
// Get the bounding rectangle of this block
Rectangle blockRectangle =
new Rectangle((int)blockPositions[i].X, (int)blockPositions[i].Y,
blockTexture.Width, blockTexture.Height);
// Check collision with person
if (IntersectPixels(personRectangle, personTextureData,
blockRectangle, blockTextureData))
{
personHit = true;
}
// Remove this block if it have fallen off the screen
if (blockPositions[i].Y > Window.ClientBounds.Height)
{
blockPositions.RemoveAt(i);
// When removing a block, the next block will have the same index
// as the current block. Decrement i to prevent skipping a block.
i--;
}
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice device = graphics.GraphicsDevice;
// Change the background to red when the person was hit by a block
if (personHit)
{
device.Clear(Color.Red);
}
else
{
device.Clear(Color.CornflowerBlue);
}
spriteBatch.Begin();
player.Draw(spriteBatch);
// Draw blocks
foreach (Vector2 blockPosition in blockPositions)
spriteBatch.Draw(blockTexture, blockPosition, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
/// <summary>
/// Determines if there is overlap of the non-transparent pixels
/// between two sprites.
/// </summary>
/// <param name="rectangleA">Bounding rectangle of the first sprite</param>
/// <param name="dataA">Pixel data of the first sprite</param>
/// <param name="rectangleB">Bouding rectangle of the second sprite</param>
/// <param name="dataB">Pixel data of the second sprite</param>
/// <returns>True if non-transparent pixels overlap; false otherwise</returns>
static bool IntersectPixels(Rectangle rectangleA, Color[] dataA,
Rectangle rectangleB, Color[] dataB)
{
// Find the bounds of the rectangle intersection
int top = Math.Max(rectangleA.Top, rectangleB.Top);
int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
int left = Math.Max(rectangleA.Left, rectangleB.Left);
int right = Math.Min(rectangleA.Right, rectangleB.Right);
// Check every point within the intersection bounds
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
// Get the color of both pixels at this point
Color colorA = dataA[(x - rectangleA.Left) +
(y - rectangleA.Top) * rectangleA.Width];
Color colorB = dataB[(x - rectangleB.Left) +
(y - rectangleB.Top) * rectangleB.Width];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
}
// No intersection found
return false;
}
}
}
Provided the background of the texture is transparent, you could use Per-Pixel Collision detection.
Essentially it checks the pixels rather than a rectangular box to determine if a collision has occurred. Given your "player" is a ball, it's probably a good idea to use this anyway.
Considering that your map is only black & White, you could process it before starting the game and obtain coords of every Rectangle of black areas, and then use it to use simple collision detection by just checking intersection between the ball bounding box and rectangles.
For example, in pseudocode:
You have a list:
List<Rectangle> rects = new List<Rectangle>();
void LoadMap()
{
Texture2D map = Content.Load<Texture2D>(#"TexturePath");
//Get a 1D array with image's pixels
Color[] map_pixels = new Color[map.Width * map.Height];
map.GetData(map_pixels);
//Convert it in a 2D array
Color[,] map_pixels_2D = new Color[map.Width, map.Height];
for (int x = 0; x < map.Width; x++)
for (int y = 0; y < map.Height; y++)
map_pixels_2D[x, y] = map_pixels[x + y * map.Width];
//**NOTE THAT**: From here it is just an example, probably not working good,
//I wrote it just to share the idea
//Here goes the code to trace rectangles in the map
Rectangle r = Rectangle.Empty;
bool NWvertex_done = false, NEvertex_done = false, SWvertex_done = false;
for (int x = 0; x < map.Width; x++)
{
if (!SWvertex_done)
{
if (map_pixels_2D[x, y+1] == Color.White); //last bottom vertex
{
r.Height = r.Y + y;
SWvertex_done = true;
rects.Add(r);
NWvertex_done = false;
NEvertex_done = false;
r = Rectangle.Empty;
}
}
for (int y = 0; y < map.Height; y++)
{
if (map_pixels_2D[x, y] != Color.White
{
if (!NWvertex_done)
{
SWvertex_done = false;
r.X = x;
r.Y = y;
NWvertex_done = true;
}
else if(!NEvertex_done)
{
if (map_pixels_2D[x, y+1] == Color.White); //last right vertex
{
r.Width = r.X + x;
NEvertex_done = true;
}
}
}
}
}
}
public override void Update(GameTime gametime)
{
//maybe other things
//
foreach (Rectangle rect in rects)
{
//Better with Distance of ball-center and rect
if (ballRect.Intersect(rect))
{
//there is a collision!
//Do something
}
break;
}
//maybe other things
//
}
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;
}
}
}
var list = new List<int> { 1, 2, 3, 4, 5, 6 };
foreach( var i in list )
{
if( i % 2 == 0 )
{
list.Remove(i); // crash
}
}
You have not posted any code (you should), so I am guessing you have something like this. Well, that's not gonna work. If you change the collection then the state of the enumerator may be trashed and it is letting you know.
You can either A) loop and create a collection of items to remove, and then remove them later, or B) use a fancy LINQ method like so:
var odds = list.Where( i => i % 2 != 0 );
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 standard for loop.
// 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);
}
}
In the example above, let's just say that someConditionIsMet evaluates to true every other loop. So here's the results of our loop:
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.
See the problem? When i was equal to 1, we removed something from myContainer, which changed its size, so when i was 3 we 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 using foreach.
There are a couple of solutions to this problem:
1.) Use a for loop, and iterate from the end of the container.
// 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.
Now you can see that we managed to properly delete two entries in which someConditionIsMet was 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 reverse for loop. 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.
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.) Use a for loop, 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:
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.
I think the simple solution would be something like this:
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--;
}
}
Well, XNA as far as i know runs Update and Draw in the same thread, but maybe something is different for you. Could you try adding some thread synchronization where you modify or access the Projectiles list in your Ship class?
Might solve it for you, and then again might not :)
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);
}
}
Also, could you specify where exactly do you get this exception? Is it in the Ship.cs Update/Draw? And which one: Update or Draw?
Also you say that Railgun round is causing the problem, does that mean that there are other types that don't have this problem?
Edit
Slight edit: you might want to add, in your Projectile class where you check to fire your hit event a condition to check if the event is registered.
if (enemyRect.Contains((int)Position.X, (int)Position.Y))
if(hit != null)
hit(this, new EventArgs());
Also where you fire the other railgun, you don't add any events: Is this on purpose? Those projectiles won't be removed, but I hardly think that is causing the problem...
Edit2
Did you consider writing some sort of a common repository for textures you access in all your classes?
You could make a static method of a helper class that gives you a reference to a texture based on a key, which it holds in a private dictionary. You populate this when you load your level, and then when you need individual textures you just call them up. This way you wont have to pass ContentManager all over the place, and won't have to load textures every time a bullet is fired.
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];
}
}
When you draw your projectile, presuming you've added the texture beforehand, you just call
Texture2DRepository.Get("RailgunSprite");
Edit3
When moving sprites, you don't have to calculate angles then recalculate X and Y positions, you could just use vectors to do that for you:
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());
}
Note that the way you wrote it, these are homing projectiles (if you give different values to pos argument, the projectiles are going to follow).