List<T> Remove a Sprite - c#

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).

Related

Detect if player is hit by shadow unity2D

I'm making a 2D game that the player can't touch the light source, so I typed the light code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shadow2DLight : MonoBehaviour
{
public float range = 5.0f;
public float intensity = 1.0f;
public Color color = Color.white;
//Internal fields, cache V and P matrix for this frame.
internal Matrix4x4[] V = new Matrix4x4[4]; //Four matricies for right, down, left ,up
internal Matrix4x4 P;
public static List<Shadow2DLight> lights = new List<Shadow2DLight>();
// Start is called before the first frame update
private void OnEnable() {
lights.Add(this);
}
private void OnDisable() {
lights.Remove(this);
}
}
then the block code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
[RequireComponent(typeof(SpriteRenderer))]
public class Shadow2DCaster : MonoBehaviour
{
private new SpriteRenderer renderer;
private Mesh shadowMesh;
private void Awake() {
renderer = GetComponent<SpriteRenderer>();
shadowMesh = new Mesh();
shadowMesh.vertices = renderer.sprite.vertices.Select(t => new Vector3(t.x, t.y, 0.0f)).ToArray();
var originalTriangles = renderer.sprite.triangles;
var newTriangleIndicies = new List<int>();
for (int i = 0; i < originalTriangles.Length / 3; i++) {
newTriangleIndicies.Add(originalTriangles[3 * i]);
newTriangleIndicies.Add(originalTriangles[3 * i + 1]);
newTriangleIndicies.Add(originalTriangles[3 * i + 1]);
newTriangleIndicies.Add(originalTriangles[3 * i + 2]);
newTriangleIndicies.Add(originalTriangles[3 * i + 2]);
newTriangleIndicies.Add(originalTriangles[3 * i]);
}
shadowMesh.SetIndices(newTriangleIndicies.ToArray(), MeshTopology.Lines, 0);
shadowMesh.UploadMeshData(true);
}
public Mesh GetLineMesh() {
return shadowMesh;
}
public static List<Shadow2DCaster> casters = new List<Shadow2DCaster>();
private static Dictionary<Sprite, Mesh> shadowMeshCache;
private void OnEnable() {
casters.Add(this);
}
private void OnDisable() {
casters.Remove(this);
}
}
then the Shadow2DPipeline code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[RequireComponent(typeof(Camera))]
public class Shadow2DPipeline : MonoBehaviour {
private Material shadowMapMaterial;
private Material lightingMaterial;
private RenderTexture shadowMap;
private CommandBuffer shadowMapCmdBuffer;
private CommandBuffer lightingCmdBuffer;
private MaterialPropertyBlock propertyBlock;
private Mesh lightingQuad;
private const int MAX_LIGHTS_COUNT = 64;
private const int SHADOW_MAP_WIDTH = 256;
private int ShadowMapHeight {
get {
return MAX_LIGHTS_COUNT * 4;
}
}
private void Start() {
propertyBlock = new MaterialPropertyBlock();
shadowMap = new RenderTexture(SHADOW_MAP_WIDTH, ShadowMapHeight, 0, RenderTextureFormat.RFloat, RenderTextureReadWrite.Linear);
shadowMap.filterMode = FilterMode.Point;
shadowMap.antiAliasing = 1;
shadowMap.anisoLevel = 0;
shadowMapMaterial = new Material(Resources.Load<Shader>("2DShadowMap"));
lightingMaterial = new Material(Resources.Load<Shader>("2DAdditiveLight"));
{
lightingQuad = new Mesh();
lightingQuad.vertices = new Vector3[] {
new Vector3(-1.0f, -1.0f),
new Vector3(-1.0f, 1.0f),
new Vector3(1.0f, -1.0f),
new Vector3(1.0f, 1.0f),
};
lightingQuad.triangles = new int[] {
0, 1, 2, 2, 1, 3
};
lightingQuad.UploadMeshData(true);
}
}
private void OnPreRender() {
var cam = GetComponent<Camera>();
var shadowCasters = Shadow2DCaster.casters;
var lights = Shadow2DLight.lights;
{ //Update shadow map rendering command buffer.
if (shadowMapCmdBuffer == null) {
shadowMapCmdBuffer = new CommandBuffer();
cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, shadowMapCmdBuffer);
}
shadowMapCmdBuffer.Clear();
/*
* Shadow caster mesh is line-strip mesh.
*
* In shadow map, each 4 rows belong to one light, corresponding to depth in (0, 90), (90, 180)...(270, 360).
*
* To render shadow mesh at correct position, a naive MVP matrix is used.
* Then, after transforming to clip space, manually edit y component to the corresponding row.
*/
shadowMapCmdBuffer.SetRenderTarget(shadowMap);
shadowMapCmdBuffer.ClearRenderTarget(true, true, Color.black);
shadowMapCmdBuffer.SetGlobalVector(ShaderKeys.ShadowMap2DSize, new Vector4(1.0f / shadowMap.width, 1.0f / shadowMap.height, shadowMap.width, shadowMap.height));
//Calculate V and P for each light.
foreach (var light in lights) {
for (int i = 0; i < 4; i++) { //four directions.
//This TRS here calculates light->world matrix.
//The Quaternion.Euler(90.0f * i, 90.0f, 270.0f) aligns light to right, down, left, up.
//-1.0f in z-component in scale corrects camera forward direction.
var V = Matrix4x4.TRS(light.transform.position, Quaternion.Euler(90.0f * i, 90.0f, 270.0f), new Vector3(1.0f, 1.0f, -1.0f));
//Inverse it, so it's a world->light matrix.
V = V.inverse;
var P = Matrix4x4.Perspective(90.0f, 1.0f, 0.01f, 10.0f);
P = GL.GetGPUProjectionMatrix(P, true);
light.V[i] = V;
light.P = P;
}
}
foreach (var shadowCaster in shadowCasters) {
var M = shadowCaster.transform.localToWorldMatrix;
for (int iLight = 0; iLight < Mathf.Min(MAX_LIGHTS_COUNT, lights.Count); iLight++) {
//Render shadow map of each light, by drawing shadow line mesh onto the shadowmap.
for (int iDir = 0; iDir < 4; iDir++) { //Four directions, right, down, left, up.
var light = lights[iLight];
var MVP = light.P * light.V[iDir] * M;
shadowMapCmdBuffer.SetGlobalMatrix(ShaderKeys.ShadowMap2DMVP, MVP);
float writeClipSpaceY = (iLight * 4 + iDir + 0.5f) / (float)(ShadowMapHeight);
shadowMapCmdBuffer.SetGlobalFloat(ShaderKeys.ShadowMap2DWriteRow, (writeClipSpaceY - 0.5f) * 2.0f);
shadowMapCmdBuffer.DrawMesh(shadowCaster.GetLineMesh(), Matrix4x4.identity /*Use our own MVP matrix*/, shadowMapMaterial);
}
}
}
}
{ //Update lighting command buffer.
//Draw a "light quad" for each light.
//
//There's lots of ways to add light to 2d scene.
//Here we just use an additive "layer"
//Edit to what you like.
if (lightingCmdBuffer == null) {
lightingCmdBuffer = new CommandBuffer();
lightingCmdBuffer.name = "2D Lighting";
cam.AddCommandBuffer(CameraEvent.AfterForwardAlpha, lightingCmdBuffer);
}
lightingCmdBuffer.Clear();
lightingCmdBuffer.SetGlobalTexture(ShaderKeys.ShadowMap2D, shadowMap);
for (int iLight = 0; iLight < Mathf.Min(MAX_LIGHTS_COUNT, lights.Count); iLight++) {
var light = lights[iLight];
lightingCmdBuffer.SetGlobalVector(ShaderKeys.LightParams, new Vector4(light.range, light.intensity, light.transform.position.x, light.transform.position.y));
lightingCmdBuffer.SetGlobalVector(ShaderKeys.LightColor, light.color);
lightingCmdBuffer.SetGlobalFloat(ShaderKeys.ShadowMap2DLightIndex, (float)iLight);
for (int iVP = 0; iVP < ShaderKeys._ShadowMap2DVP.Length; iVP++) {
lightingCmdBuffer.SetGlobalMatrix(ShaderKeys._ShadowMap2DVP[iVP], light.P * light.V[iVP]);
}
var M = Matrix4x4.TRS(light.transform.position, Quaternion.identity, Vector3.one * light.range);
lightingCmdBuffer.SetGlobalMatrix(ShaderKeys.LightM, M);
lightingCmdBuffer.DrawMesh(lightingQuad, M, lightingMaterial);
}
}
}
public static class ShaderKeys {
public static int ShadowMap2DSize = Shader.PropertyToID("_ShadowMap2DSize");
public static int ShadowMap2DMVP = Shader.PropertyToID("_ShadowMap2DMVP");
public static int ShadowMap2DWriteRow = Shader.PropertyToID("_ShadowMap2DWriteRow");
public static int ShadowMap2D = Shader.PropertyToID("_ShadowMap2D");
public static int ShadowMap2DLightIndex = Shader.PropertyToID("_ShadowMap2DLightIndex");
public static int[] _ShadowMap2DVP = new int[] {
Shader.PropertyToID("_ShadowMap2DVP_Right"),
Shader.PropertyToID("_ShadowMap2DVP_Down"),
Shader.PropertyToID("_ShadowMap2DVP_Left"),
Shader.PropertyToID("_ShadowMap2DVP_Up"),
};
public static int LightM = Shader.PropertyToID("_LightM");
public static int LightParams = Shader.PropertyToID("_LightParams");
public static int LightColor = Shader.PropertyToID("_LightColor");
}
}
but how i can detect if the player touch the light??
you can get the source code from here : https://github.com/yangrc1234/Unity2DShadowMap
preview from here : https://i.stack.imgur.com/oGgr2.gif
So one solution would be not to directly detect any shadows, but leverage the essential properties of shadows to get the same effect.
An area that is in shadow is in shadow because there is an opaque object between it and the light source.
An area that is in light is in light because there are no objects between it and the light source.
Therefore you can simply detect if there is an object between your player and the light source using a raycast and use that information to determine if you are in shadow/light.
Very roughly:
Vector3 playerToLight = light.transform.position - player.transform.position;
float distance = playerToLight.magnitude;
vctor3 origin = player.transform.position;
vector3 direction = playerToLight.normalized;
if(Physics.Raycast(origin,direction,distance)){
// in shadow
} else {
// in light
}

How to debug where an unexpected null is coming from in my class?

In my Boss class, it says that my List "bulletlist" is null and give me a run time error but I see no errors can someone tell me what I am doing wrong? The games runs fine if I don't update my boss class. I put in my main class, player class, and bullet class, in as well.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 Space_Shooter
{
public class Boss
{
public Texture2D texture, bulletTexture , healthBarTexture;
public Vector2 position;
public float randomPositionX, randomPositionY , bulletDamage, shipDamage , health , MaxBullets;
public int speed, bulletSpeed , bulletDelayTime, bulletDelayTimeReset;
public bool isVisible;
public Rectangle boundingBox , bossHealthBar;
public List<Bullet> bulletList;
Random random = new Random();
Player p = new Player();
public Boss(Texture2D newTexture, Vector2 newPosition, Texture2D newBulletTexture)
{
texture = newTexture;
position = newPosition;
bulletTexture = newBulletTexture;
bulletList = new List<Bullet>();
bulletSpeed = 25;
bulletDamage = 25;
shipDamage = p.health / 2;
health = 2000;
bulletDelayTime = 30;
bulletDelayTimeReset = 30;
MaxBullets = 30;
isVisible = true;
}
public Boss()
{
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, position, Color.White);
spriteBatch.Draw(healthBarTexture, bossHealthBar, Color.White);
foreach (Bullet b in bulletList)
{
b.Draw(spriteBatch);
}
}
public void loadContent(ContentManager content)
{
texture = content.Load<Texture2D>("Ship4");
bulletTexture = content.Load<Texture2D>("EnemyBullet");
healthBarTexture = content.Load <Texture2D> ("healthbar");
}
public void update(GameTime gameTime)
{
updateBullets();
Shoot();
checkBossCollison();
// Making bosses health bar
bossHealthBar = new Rectangle((int)position.X, (int)position.Y + 30, (int)health, 25);
// Creating collision box for boss
boundingBox = new Rectangle((int)position.X, (int)position.Y, (int)texture.Width, (int)texture.Height);
randomPositionX = random.Next(50, 750);
randomPositionY = random.Next(50, 400);
// setting boss movement
if (position.Y > randomPositionY)
position.Y -= speed;
if (position.Y < randomPositionY)
position.Y += speed;
if (position.X > randomPositionX)
position.X -= speed;
if (position.X < randomPositionX)
position.X += speed;
}
public void updateBullets()
{
// for each bullet in the bulletList: update the movement and if the bullet hits the bottom of the screen, remove it from the list
foreach (Bullet b in bulletList)
{
//make a Bounding box for every bullet in the bulletList
b.boundingBox = new Rectangle((int)b.position.X, (int)b.position.Y, b.texture.Width, b.texture.Height);
// set movement for the bullets
// if a bullet hits the bottom of the screen, then make visable false
if (b.position.Y >= 850)
b.isVisible = false;
// go through the bulletList and see if any of the bullets are not visible, if they are not then remove that bullet from the bulletList
for (int i = 0; i < bulletList.Count; i++)
{
if (!bulletList[i].isVisible)
{
bulletList.RemoveAt(i);
i--;
}
}
}
}
public void checkBossCollison()
{
// if boss ship comes in bcontact with player ship player is damaged
if (boundingBox.Intersects(p.boundingBox))
p.health -= shipDamage;
// if player bullets hit boss then boss is damaged and player bullets go invisible
for(int i = 0; i < p.bulletList.Count; i++)
{
if (boundingBox.Intersects(p.bulletList[i].boundingBox))
{
health -= p.bulletDamage;
p.bulletList[i].isVisible = false;
if (health == 0)
isVisible = false;
}
}
// if boss shoots player player is damaged and boss bullets go invisible
for(int i = 0; i < bulletList.Count; i++)
{
if (p.boundingBox.Intersects(bulletList[i].boundingBox))
{
p.health -= bulletDamage;
bulletList[i].isVisible = false;
}
}
}
public void Shoot()
{
// shoot only if our bullet delay resets
if (bulletDelayTime >= 0)
bulletDelayTime--;
// If bulletDelay is at 0, create a new bullet at enemy position, make it visible on the screen, then add that bullet to bulletList
if (bulletDelayTime <= 0)
{
// create new bullet and center it front and center of enemy ship
Bullet newBullet = new Bullet(bulletTexture);
newBullet.position = new Vector2(position.X + texture.Width / 2 - newBullet.texture.Width / 2, position.Y + 30);
newBullet.isVisible = true;
if (bulletList.Count() < MaxBullets)
bulletList.Add(newBullet);
}
// Reset bullet delay
if (bulletDelayTime == 0)
bulletDelayTime = bulletDelayTimeReset;
}
}
}
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 Space_Shooter
{
// Main
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
// variabls
SpriteBatch spriteBatch;
Random random = new Random();
Random randomNumberOfAsteroids = new Random();
int maxNumberOfAsteroids;
int numbersOfInvisibleEnemies;
// Lists
List<Asteroid> asteroidList = new List<Asteroid>();
List<Enemy> enemyList = new List<Enemy>();
// making player and starfield objects
Player P = new Player();
StarField starfield = new StarField();
Enemy E = new Enemy();
Boss boss = new Boss();
// Construtor
public Game1()
{
graphics = new GraphicsDeviceManager(this);
// setting full scren to off and setting screen measurments
graphics.IsFullScreen = false;
graphics.PreferredBackBufferWidth = 800;
graphics.PreferredBackBufferHeight = 850;
maxNumberOfAsteroids = 5; // Set the max number of asteroids that will be on the screen at one time ( randomly 0 to Max number)
numbersOfInvisibleEnemies = 0;
// Setting Title of screen
this.Window.Title = "Star Voyage";
Content.RootDirectory = "Content";
}
// Initialize
protected override void Initialize()
{
base.Initialize();
}
// Load content
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// Call Load content function from Player.cs
P.LoadContent(Content);
starfield.LoadContent(Content);
boss.loadContent(Content);
}
// unload content
protected override void UnloadContent()
{
}
// update game screen
protected override void Update(GameTime gameTime)
{
// updating enemies and checking collision of thier ships to player ship
foreach (Enemy e in enemyList)
{
if (e.boundingBox.Intersects(P.boundingBox))
{
P.health -= e.enemyShipDamage;
e.isVisible = false;
}
// check enemy bullet collision with player ship
for (int i = 0; i < e.bulletList.Count; i++)
{
if (P.boundingBox.Intersects(e.bulletList[i].boundingBox))
{
P.health -= e.enemyBulletDamage;
e.bulletList[i].isVisible = false;
}
}
// check player bullet collision with enemy ships
for (int i = 0; i < P.bulletList.Count; i++)
{
if (e.boundingBox.Intersects(P.bulletList[i].boundingBox))
{
// when bullet hits enemy ship they disapear
P.bulletList[i].isVisible = false;
e.health -= P.bulletDamage;
if (e.health == 0)
e.isVisible = false;
}
}
e.Update(gameTime);
}
// for each asteroid in the asteroid list call the update function from Asteroid.cs
foreach (Asteroid a in asteroidList)
{
//Check to see if any of the arteroids are colliding with player ship
if (a.boundingBox.Intersects(P.boundingBox))
{
P.health -= a.asteroidDamage; // taking 20 off of player health bar each time the play shit is hit by an asteroid
a.isVisible = false;
}
// going through bullet list to see if any bullets come in contact with asteroids if they do set both the bullet and the asteroid to not visible
for (int i = 0; i < P.bulletList.Count; i++)
{
if (a.boundingBox.Intersects(P.bulletList[i].boundingBox))
{
a.isVisible = false;
P.bulletList.ElementAt(i).isVisible = false;
}
}
a.Update(gameTime);
}
loadAsteroids();
loadEnemyShips();
// Call fucntion Update from Player,cs
P.Update(gameTime);
// Call function update from StarField.cs
starfield.Update(gameTime);
boss.update(gameTime);
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
// Draw to game screen
protected override void Draw(GameTime gameTime)
{
spriteBatch.Begin();
starfield.Draw(spriteBatch);
foreach (Asteroid a in asteroidList)
{
a.Draw(spriteBatch);
}
foreach (Enemy e in enemyList)
{
e.Draw(spriteBatch);
}
// Backgound color
GraphicsDevice.Clear(Color.CornflowerBlue);
// Call Draw fuction from Player.cs
P.Draw(spriteBatch);
// boss.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
// Load Asteroids function
public void loadAsteroids()
{
// creating random variabls for X and Y positions of asteroids
int random_x = random.Next(0, 750), random_y = random.Next(-600, -50);
// if the number of Asteroids on the screen is less than the variable (numberOfAsteroids), create more untill it is not
if (asteroidList.Count() < randomNumberOfAsteroids.Next(0, maxNumberOfAsteroids))
{
asteroidList.Add(new Asteroid(Content.Load<Texture2D>("asteroid"), new Vector2(random_x, random_y)));
}
// if any of the asteroids from the list are destroyed (not visible) then remove them from the list
for (int i = 0; i < asteroidList.Count; i++)
{
if (!asteroidList[i].isVisible)
{
asteroidList.RemoveAt(i);
i--;
}
}
}
public void loadEnemyShips()
{
// creating random variabls for X and Y positions of asteroids
int random_x = random.Next(0, 750), random_y = random.Next(-600, -50);
// if the number of enemies on the screen is less than the variable (numberOfEnemies), create more untill it is not
if (enemyList.Count() < 3 && numbersOfInvisibleEnemies <= 9)
{
enemyList.Add(new Enemy(Content.Load<Texture2D>("enemyship"), new Vector2(random_x, random_y), Content.Load<Texture2D>("EnemyBullet")));
}
// keeping count of the number of destroied enemies
for (int i = 0; i < enemyList.Count; i++)
{
if (!enemyList[i].isVisible)
numbersOfInvisibleEnemies += 1;
// if any of the enemies from the list are destroyed (not visible) or if they go passed the bottom of the screen, then remove them from the list
if (!enemyList[i].isVisible || enemyList[i].position.Y >= 840)
{
enemyList.RemoveAt(i);
--i;
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 Space_Shooter
{
// main
public class Player
{
// Variables
public Texture2D texture , bulletTexture , healthTexture;
public Vector2 position , healthBarPosition;
public int speed;
public float health , bulletDamage , bulletDelay , bulletDelayReset;
double bulletCount;
// Lists
public List<Bullet> bulletList;
// Collision variables
public bool isColliding;
public Rectangle boundingBox , healthRectangle;
// Constructor
public Player()
{
bulletList = new List<Bullet>();
bulletDelay = 1; // for faster fire rate reduce number
bulletDelayReset = 5; // bullet delay reset has to be the same number as bullet delay
bulletCount = 1000; // amount of bullets alowed on the screen at a time
texture = null;
health = 200;
bulletDamage = 3;
healthBarPosition = new Vector2(50, 50);
position = new Vector2 (300, 300) ;
speed = 10;
isColliding = false;
}
// Load content
public void LoadContent(ContentManager Content)
{
texture = Content.Load<Texture2D>("player01");
bulletTexture = Content.Load<Texture2D>("playerbullet");
healthTexture = Content.Load<Texture2D>("healthbar");
}
// Draw
public void Draw(SpriteBatch spritebatch)
{
spritebatch.Draw(texture, position, Color.White);
spritebatch.Draw(healthTexture, healthRectangle, Color.White);
// For each Bullet b (bullet) in our bullet list, draw the bullet
foreach (Bullet b in bulletList)
{
b.Draw(spritebatch);
}
}
// Update
public void Update(GameTime gametime)
{
// Checking for keyboard input every frame
KeyboardState keystate = Keyboard.GetState();
// setting collision box for player ship
boundingBox = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
//set rectangle for healthbar (where to print on the screen and the height and width (25) is height (health is width
healthRectangle = new Rectangle((int) healthBarPosition.X, (int) healthBarPosition.Y, (int)health, 25);
if (keystate.IsKeyDown(Keys.Space))
{
shoot();
}
updateBullets();
// ship controls
if (keystate.IsKeyDown(Keys.W))
position.Y = position.Y - speed;
if (keystate.IsKeyDown(Keys.S))
position.Y = position.Y + speed;
if (keystate.IsKeyDown(Keys.A))
position.X = position.X - speed;
if (keystate.IsKeyDown(Keys.D))
position.X = position.X + speed;
// Keep ship in screen bounds
if (position.X <= 0)
position.X = 0;
if (position.X >= 800 - texture.Width)
position.X = 800 - texture.Width;
if (position.Y <= 0)
position.Y = 0;
if (position.Y >= 850 - texture.Height)
position.Y = 850 - texture.Height;
}
// Shoot Function, used to set the starting position of the players bullet
public void shoot()
{
// Shoot only if the bullet delay resets
if (bulletDelay >= 0)
bulletDelay--;
// If bulletDelay is at 0, create a new bullet at player position, make it visible on the scrren, then add that bullet to bulletList
if (bulletDelay <= 0)
{
Bullet newBullet1 = new Bullet(bulletTexture);
newBullet1.position = new Vector2(position.X - newBullet1.texture.Width / 2, position.Y + 30);
newBullet1.isVisible = true;
Bullet newBullet2 = new Bullet(bulletTexture);
newBullet2.position = new Vector2(position.X + 64 - newBullet2.texture.Width / 2, position.Y + 30);
newBullet2.isVisible = true;
// control the amount of bullets on the screen by increasing number
if (bulletList.Count() < bulletCount)
bulletList.Add(newBullet1);
bulletList.Add(newBullet2);
}
// Reset bullet dleay
if (bulletDelay == 0)
bulletDelay = bulletDelayReset;
}
public void updateBullets()
{
// for each bullet in the bulletList: update the movement and if the bullet hits the top of the screen, remove it from the list
foreach (Bullet b in bulletList)
{
// set movement for the bullets
b.position.Y = b.position.Y - b.speed;
//make a Bounding box for every bullet in the bullet list
b.boundingBox = new Rectangle((int)b.position.X, (int)b.position.Y, b.texture.Width, b.texture.Height);
// if a bullet hits the top of the screen, then make visable false
if (b.position.Y <= 0)
b.isVisible = false;
}
// go through the bulletList and see if any of the bullets are not visible, if they are not then remove that bullet from the bulletList
for (int i = 0; i < bulletList.Count; i++)
{
if (!bulletList[i].isVisible)
{
bulletList.RemoveAt(i);
i--;
}
}
}
}
}
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 Space_Shooter
{
public class Bullet
{
public Rectangle boundingBox;
public Texture2D texture;
public Vector2 position , origin;
public float speed;
public bool isVisible;
public Bullet(Texture2D newTexture)
{
speed = 25;
texture = newTexture;
isVisible = false;
}
public void Draw(SpriteBatch spritebatch)
{
spritebatch.Draw(texture, position, Color.White);
}
}
}
You initialize the list in the constructor:
public Boss(Texture2D newTexture, Vector2 newPosition, Texture2D newBulletTexture)
But, you don't call this constructor. You call Boss boss = new Boss();
Thus the constructor which initializes the list is not called.
You should initialize the list in all constructors:
public Boss()
{
bulletList = new List<Bullet>();
}
Better is too make the default constructor set all the default values, and if you need to set other values in another constructor, is to call the default constructor from the "second" constructor using : this():
public Boss()
{
bulletList = new List<Bullet>();
bulletSpeed = 25;
bulletDamage = 25;
shipDamage = p.health / 2;
health = 2000;
bulletDelayTime = 30;
bulletDelayTimeReset = 30;
MaxBullets = 30;
isVisible = true;
}
public Boss(Texture2D newTexture, Vector2 newPosition, Texture2D newBulletTexture)
: this() //Call default constructor
{
texture = newTexture;
position = newPosition;
bulletTexture = newBulletTexture;
}

Field is never assigned and will always have default value null

So I just finished up doing a class for my wandering & pursuing enemies yet once I've gone through and declared the class and done the necessary coding when I run the game I get a NullReferenceException pop up which then tells me that the field Virtual_Alien is never assigned and will always have the default value of null.
What am I doing wrong here?
main game code:
private Roaming_Aliens roamingAlien;
private Virtual_Aliens virtualAlien;
public Vector2 playerPosition;
public Player mainPlayer = new Player();
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
mainPlayer.position = playerPostion;
// now assign that image to each of the characters we have
roamingAlien.LoadContent(Content);
virtualAlien.LoadContent(Content);
mainPlayer.LoadContent(Content);
score.LoadContent(Content);
// mainBackground.LoadContent(Content);
}
protected override void Update(GameTime gameTime)
{
currentKey = Keyboard.GetState();
if (currentKey.IsKeyDown(Keys.Escape))
{
this.Exit();
}
mainPlayer.Update(gameTime);
score.Update(gameTime);
virtualAlien.Update(gameTime);
roamingAlien.Wander();
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
//mainBackground.Draw(spriteBatch);
mainPlayer.Draw(spriteBatch);
virtualAlien.Draw(spriteBatch);
roamingAlien.Draw(spriteBatch);
spriteBatch.End();
}
Virtual alien code:
public class Virtual_Aliens
{
private enum TankAiState
{
Chasing,
Wander
}
private float maxSpeed;
private float maxRotation;
private float chaseDistance;
private float hysteresis;
private Texture2D texture;
private Vector2 drawingOrigin;
private Vector2 position;
private TankAiState tankState = TankAiState.Wander;
private float orientation;
private Random random = new Random();
private Rectangle viewportbounds;
public Rectangle boundingBox;
public Vector2 playerPosition;
private Vector2 heading;
public Virtual_Aliens(Rectangle pos, Rectangle b)
{
position = new Vector2 (100, 100);
boundingBox = new Rectangle(pos.X, pos.Y, pos.Width, pos.Height);
viewportbounds = new Rectangle(b.X, b.Y, b.Width, b.Height);
orientation = 0.0f;
heading = new Vector2(0, 0);
maxSpeed = 2.0f;
maxRotation = 0.20f;
random = new Random();
hysteresis = 15.0f;
chaseDistance = 250.0f;
}
public void LoadContent(ContentManager Content)
{
texture = Content.Load<Texture2D>("images/asteroid");
}
private Vector2 OrientationAsVector(float orien)
{
Vector2 orienAsVect;
orienAsVect.X = (float)Math.Cos(orien);
orienAsVect.Y = (float)Math.Sin(orien);
return orienAsVect;
}
Vector2 wanderPosition = new Vector2();
public void Wander()
{
// The wander effect is accomplished by having the character aim in a random
// direction. Every frame, this random direction is slightly modified.
// the max +/- the agent will wander from its current position
float wanderLimits = 0.5f;
// this defines what proportion of its maxRotation speed the agent will turn
float turnFactor = 0.15f;
// randomly define a new position
wanderPosition.X += MathHelper.Lerp(-wanderLimits, wanderLimits, (float)random.NextDouble());
wanderPosition.Y += MathHelper.Lerp(-wanderLimits, wanderLimits, (float)random.NextDouble());
// normalize the wander position, ...
if (wanderPosition != Vector2.Zero)
wanderPosition.Normalize();
// now find the new orientation based on the wanderPosition
orientation = TurnToFace(wanderPosition, orientation, turnFactor * maxRotation);
// determine the heading vector based on orientation
heading = OrientationAsVector(orientation);
// finally update the agents position based upon the new heading and its speed
// assume a wandering agent only moves at 0.5 of maxSpeed
position += heading * 0.5f * maxSpeed;
WrapForViewport();
}
private void WrapForViewport()
{
if (position.X < 0)
{
position.X = viewportbounds.Width;
}
else if (position.X > viewportbounds.Width)
{
position.X = 0;
}
if (position.Y < 0)
{
position.Y = viewportbounds.Height;
}
else if (position.Y > viewportbounds.Height)
{
position.Y = 0;
}
}
private float WrapAngle(float radian)
{
while (radian < -MathHelper.Pi)
{
radian += MathHelper.TwoPi;
}
while (radian > MathHelper.Pi)
{
radian -= MathHelper.TwoPi;
}
return radian;
}
private float TurnToFace(Vector2 steering, float currentOrientation, float turnSpeed)
{
float newOrientation;
float desiredOrientation;
float orientationDifference;
float x = steering.X;
float y = steering.Y;
// the desiredOrientation is given by the steering vector
desiredOrientation = (float)Math.Atan2(y, x);
// find the difference between the orientation we need to be
// and our current Orientation
orientationDifference = desiredOrientation - currentOrientation;
// now using WrapAngle to get result from -Pi to Pi
// ( -180 degrees to 180 degrees )
orientationDifference = WrapAngle(orientationDifference);
// clamp that between -turnSpeed and turnSpeed.
orientationDifference = MathHelper.Clamp(orientationDifference, -turnSpeed, turnSpeed);
// the closest we can get to our target is currentAngle + orientationDifference.
// return that, using WrapAngle again.
newOrientation = WrapAngle(currentOrientation + orientationDifference);
return newOrientation;
}
public void Update(GameTime gameTime)
{
if (tankState == TankAiState.Wander)
{
chaseDistance -= hysteresis / 2;
}
else if (tankState == TankAiState.Chasing)
{
chaseDistance += hysteresis / 2;
}
// Second, now that we know what the thresholds are, we compare the tank's
// distance from the cat against the thresholds to decide what the tank's
// current state is.
float distanceFromPlayer = Vector2.Distance(position, playerPosition);
if (distanceFromPlayer > chaseDistance)
{
// just like the mouse, if the tank is far away from the cat, it should
// idle.
tankState = TankAiState.Wander;
}
else
{
tankState = TankAiState.Chasing;
}
// Third, once we know what state we're in, act on that state.
float currentTankSpeed;
if (tankState == TankAiState.Chasing)
{
// the tank wants to chase the cat, so it will just use the TurnToFace
// function to turn towards the cat's position. Then, when the tank
// moves forward, he will chase the cat.
orientation = TurnToFace(playerPosition, orientation, maxRotation);
currentTankSpeed = maxSpeed;
}
else if (tankState == TankAiState.Wander)
{
Wander();
}
}
public void Draw(SpriteBatch spriteBatch)
{
drawingOrigin = new Vector2(texture.Width / 2, texture.Height / 2);
spriteBatch.Draw(texture, position, null, Color.White, orientation, drawingOrigin, 1.0f, SpriteEffects.None, 0.0f);
}
public Vector2 PlayerPosition
{
set
{
playerPosition = value;
}
get
{
return playerPosition;
}
}
}
You declare (twice actually) a variable called virtualAlien (of type Virtual_Aliens). However, you never assign it anything (while using it all over the place.
You declaration needs to look like:
Virtual_Alien virtualAlien = new Virtual_Alien();
This is assigns it to a new instance of the Virtual_Alien class, allowing you to use it later on. If you need parameters that aren't available until later, put the instantiation at the point when all needed information is available, and add null checks around any use of the variable that could happen before the instantiating code is called.
The romaingAlien member appears to have a similar problem.

Player collision on a singe image map

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
//
}

Brickbreaker clone, ball brick collision and ball behavior on brick collision

I started learning c# monogame a few weeks ago and i wanted to start a project.
The project I choose is a brickbreaker clone and everything goes well. But I stumbled on something that I have spent hours looking on google for without a answer.
The problem I have is with collision detection between the ball and the bricks.
Between the paddle and the ball work I have a working solution.
Which is :
// Collision between the ball and player
public void Collision(Player player, Ball ball)
{
MinX = (int)player.PlayerPosition.X - ball.Width;
MaxX = (int)player.PlayerPosition.X + player.Width + ball.Width;
MinY = 0;
MaxY = (int)player.PlayerPosition.Y - ball.Height;
Rectangle BallRectangle = new Rectangle((int)ball.BallPosition.X, (int)ball.BallPosition.Y, ball.Width, ball.Height);
Rectangle PlayerRectangle = new Rectangle((int)player.PlayerPosition.X, (int)player.PlayerPosition.Y, player.Width, player.Height);
if (BallRectangle.Intersects(PlayerRectangle))
{
ball.BallPosition.Y = MathHelper.Clamp(ball.BallPosition.Y, MinY, MaxY);
ball.BallPosition.X = MathHelper.Clamp(ball.BallPosition.X, MinX, MaxX);
ball.BallSpeed.Y *= -1;
float BallMiddlePoint = ball.BallPosition.X + (ball.Width / 2);
float PlayerMiddlePoint = player.PlayerPosition.X + (player.Width / 2);
ball.BallSpeed.X = (BallMiddlePoint - PlayerMiddlePoint) / 10 + ball.ExtraSpeedOverTime;
}
}
Now back to my problem, I use Rectangle.Intersect(Rectangle) to check for collision.
The main problem is: How do I let the ball bounce correctly on the bricks?
I hope I give enough information.
My ball class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace BrickBreakerV2
{
class Ball
{
// The texture of the ball
Texture2D BallTexture;
// The position of the ball
public Vector2 BallPosition;
// The speed of the ball
public Vector2 BallSpeed;
// The speed that gets added over time
public float ExtraSpeedOverTime;
// Time values used in incrementing the speed over time
TimeSpan IncreaseSpeed;
TimeSpan PreviousSpeedIncrease;
// The "active" state of the ball
public bool Active;
// The state of movement of the ball
public bool Moveball;
// The Width of the ball
public int Width
{
get { return BallTexture.Width; }
}
// The Height of the ball
public int Height
{
get { return BallTexture.Height; }
}
// Construct a class for Boundaries
CollisionHandler Collision = new CollisionHandler();
public void Initialize(ContentManager Content, GraphicsDevice graphicsDevice, Player player)
{
BallTexture = Content.Load<Texture2D>("Graphics\\ball.png");
BallPosition = new Vector2(player.PlayerPosition.X + (player.Width / 2) - (Width / 2),
player.PlayerPosition.Y - Height - 5);
BallSpeed = new Vector2(5.0f, -5.0f);
ExtraSpeedOverTime = 1.0f;
IncreaseSpeed = TimeSpan.FromSeconds(12f);
PreviousSpeedIncrease = TimeSpan.Zero;
Active = true;
Moveball = false;
}
public void Update(GraphicsDevice graphicsDevice, GameTime gameTime, Player player)
{
if (Moveball)
{
BallPosition += BallSpeed;
}
else
{
BallPosition = new Vector2(player.PlayerPosition.X + (player.Width / 2) - (Width / 2),
player.PlayerPosition.Y - Height - 5);
}
if (gameTime.TotalGameTime - PreviousSpeedIncrease > IncreaseSpeed)
{
ExtraSpeedOverTime += 0.01f;
BallSpeed.X *= ExtraSpeedOverTime;
BallSpeed.Y *= ExtraSpeedOverTime;
PreviousSpeedIncrease = gameTime.TotalGameTime;
}
// Makes sure that the ball doesn't go to fast
if (BallSpeed.X > 10.50f)
BallSpeed.X = 10.50f;
if (BallSpeed.Y > 10.50f)
BallSpeed.Y = 10.50f;
if (BallSpeed.X < -10.50f)
BallSpeed.X = -10.50f;
if (BallSpeed.Y < -10.50f)
BallSpeed.Y = -10.50f;
// Keep the ball in the boundaries
Collision.GetBoundaries(graphicsDevice, this);
// Detect collision between ball and player
Collision.Collision(player, this);
}
public void Update(Brick brick)
{
// Detect collision between ball and brick
Collision.Collision(this, brick);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(BallTexture, BallPosition, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 1f);
}
}
}
My Brick class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace BrickBreakerV2
{
class Brick
{
// The texture of the brick
public Texture2D BrickTexture;
// The position of the bricks
public Vector2 BrickPosition;
// The color tint of the brick
public Color ColorTint;
// The "active" state of the brick
public bool Active;
// The width of the brick
public int Width
{
get { return BrickTexture.Width; }
}
// The height of the brick
public int Height
{
get { return BrickTexture.Height; }
}
// Construct a class for collisionhandler
CollisionHandler Collision;
public void Initialize(ContentManager Content)
{
BrickTexture = Content.Load<Texture2D>("Graphics\\brick.png");
BrickPosition = new Vector2(600, 500);
Active = true;
ColorTint = Color.White;
Collision = new CollisionHandler();
}
public void Update(Ball ball)
{
Collision.Collision(ball, this);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(BrickTexture, BrickPosition, null, ColorTint, 0f, Vector2.Zero, 1f, SpriteEffects.None, 1f);
}
}
}
And my code block that handles the collision between the ball and bricks in CollisionHandler.cs:
// Collision between ball and brick
public void Collision(Ball ball, Brick brick)
{
Rectangle BallRectangle = new Rectangle((int)ball.BallPosition.X, (int)ball.BallPosition.Y, ball.Width, ball.Height);
Rectangle BrickRectangle = new Rectangle((int)brick.BrickPosition.X, (int)brick.BrickPosition.Y, brick.Width, brick.Height);
if (BallRectangle.Intersects(BrickRectangle))
{
int XOverlap;
int YOverlap;
if (BallRectangle.Center.X < BrickRectangle.Center.X)
{
XOverlap = (BallRectangle.X + ball.Width) - BrickRectangle.X;
}
else
{
XOverlap = (BrickRectangle.X + brick.Width) - BallRectangle.X;
}
if (BallRectangle.Center.Y < BrickRectangle.Center.Y)
{
YOverlap = (BallRectangle.Y + ball.Height) - BrickRectangle.Y;
}
else
{
YOverlap = (BrickRectangle.Y + brick.Height) - BallRectangle.Y;
}
if (XOverlap == YOverlap)
{
ball.BallSpeed.X *= -1;
ball.BallSpeed.Y *= -1;
}
else if (XOverlap < YOverlap)
{
ball.BallSpeed.X *= -1;
}
else
{
ball.BallSpeed.Y *= -1;
}
brick.Active = false;
}
}
I want to help you, but I'm not going to debug your code. Fortunately basic 2D collision detection and brick breaker clones have been done many many times before.
A quick google search turned up the following tutorial. I haven't done it myself but if I where you I would start here:
http://xnagpa.net/xna4beginner.php
Good luck.

Categories

Resources