Removing list items in C# / XNA - c#

I am a beginner with XNA Game Studios and want to learn some basics in game programming.
I am currently creating a small space shooter, something like a variation of Space Invaders.
To make my rockets look better I've created some smoke trails to follow them, but would like to remove them after some time (400 milliseconds) so the screen is not blocked by that smoke.
To achieve this I have created the following code, which seems pretty logical to me.
for(int i=0; i < rocketPosition.Count; i++)
{
rocketPosition[i] = new Vector2(rocketPosition[i].X, rocketPosition[i].Y - rocketSpeed);
Vector2 smokePosition = rocketPosition[i];
smokePosition.X += Rocket.Width / 2 + smokeTexture.Width / 2 + randomizer.Next(10) - 5;
smokePosition.Y += Rocket.Height + randomizer.Next(10) - 5;
smokeList.Add(new Particle(smokePosition, gameTime.TotalGameTime.Milliseconds));
if (rocketPosition[i].Y < 0 - Rocket.Height)
{
rocketPosition.RemoveAt(i);
}
}
for(int i = 0; i < smokeList.Count; i++)
{
if (smokeList[i].Time < gameTime.TotalGameTime.Milliseconds - smokeDuration)
{
smokeList.RemoveAt(i);
}
}
Particle is a class I created in order to have both the creation-time of the list item (which represents the smoke particle), as well as its position Vector2.
However instead of deleting the smoke trail from bottom to top it stops in between and looks like the following picture:
I hope someone can help me with my code.

Removing an item from the iterating list decrement the count therefore:
for(int i=rocketPosition.Count; i > 0 ; i--)
{
rocketPosition[i] = new Vector2(rocketPosition[i].X, rocketPosition[i].Y - rocketSpeed);
Vector2 smokePosition = rocketPosition[i];
smokePosition.X += Rocket.Width / 2 + smokeTexture.Width / 2 + randomizer.Next(10) - 5;
smokePosition.Y += Rocket.Height + randomizer.Next(10) - 5;
smokeList.Add(new Particle(smokePosition, gameTime.TotalGameTime.Milliseconds));
if (rocketPosition[i].Y < 0 - Rocket.Height)
{
rocketPosition.RemoveAt(i);
}
}
for(int i = smokeList.Count; i > 0 ; i--)
{
if (smokeList[i].Time < gameTime.TotalGameTime.Milliseconds - smokeDuration)
{
smokeList.RemoveAt(i);
}
}

Related

Expensive GetComponent<CanvasGroup>() call, unsure how to cache

I am getting a performance warning from my IDE that the GetComponent() call is expensive, especially since I am calling MoveCards() in Update(). I understand why it's expensive and usually know how to fix with caching in Awake() but since it's prefixed with cards[i], I am unsure how to go about it.
private void MoveCards()
{
// This loop moves the cards.
for (var i = 0; i < cards.Length; i++)
{
cards[i].localPosition = Vector3.Lerp(cards[i].localPosition, cardPositions[i + cardArrayOffset],
Time.deltaTime * cardMoveSpeed);
if (!(Mathf.Abs(cards[i].localPosition.x - cardPositions[i + cardArrayOffset].x) < 0.01f)) continue;
cards[i].localPosition = cardPositions[i + cardArrayOffset];
// This disables interaction with cards that are not on top of the stack.
cards[i].gameObject.GetComponent<CanvasGroup>().interactable = cards[i].localPosition.x == 0;
}
}
What I would do is make the card GameObject have it’s own MonoBehaviour to store a reference to those components. Then, change the cards collection to store a reference to the card MonoBehaviour.
You would store cards[] as a CanvasGroup instead of a GameObject.
You would declare it like this in Awake():
GameObject[] cardObjects = new GameObject[10];
CanvasGroup[] cards = new CanvasGroup[cardObjects.Length];
for (int i = 0; i < cardObjects.Length; i++)
{
cards[i] = cardObjects[i].GetComponent<CanvasGroup>();
}
We loop through all of the card objects, and assign that component to a certain object in an array.
In your function you showed in the question, you would change it to this:
private void MoveCards()
{
for (int i = 0; i < cardObjects.Length; i++)
{
cardObjects[i].transform.localPosition = Vector3.Lerp(cardObjects[i].transform.localPosition, cardPositions[i + cardArrayOffset], Time.deltaTime * cardMoveSpeed);
if (!(Mathf.Abs(cardObjects[i].transform.localPosition.x - cardPositions[i + cardArrayOffset].x) < 0.01f)) continue;
cardObjects[i].localPosition = cardPositions[i + cardArrayOffset]
cards[i].interactable = cardObjects[i].transform.localPosition.x == 0;
}
}
We change all of the cards variables to cardObjects. On the GetComponent<>() line, we keep cards and remove the .gameObject.GetComponent<CanvasGroup>().
Important: You didn’t show us where some of the variables were created. Like cardPositions. If this is created using cards, make sure to use the cardObjects variable.

HackerRank Climbing the Leaderboard

This question has to do with this challenge on HackerRank. It seems to be failing some cases, but I'm not clear what's wrong with the algorithm (many people seem to have problem with timeouts, that's not an issue here, everything runs plenty fast, and all the cases that are visible to me pass, so I don't have a specific case that's failing).
The essential outline of how the algorithm works is as follows:
First be sure that Alice isn't already winning over the existing highest score (degenerate case), if she is just tell the world she's #1 from start to finish. Otherwise, at least one score on the leaderboard beats Alice's first try.
Start by walking down the scores list from the highest until we find a place where Alice fits in and record the scores that beat Alice's initial score along the way.
If we reach the end of the scores list before finding a place for Alice's bottom score, pretend there is a score at the bottom of the list which matches Alice's first score (this is just convenient for the main loop and reduces the problem to one where Alice's first score is on the list somewhere)
At this point we have a (sorted) array of scores with their associated ranks, rankAry[r - 1] is the minimum score needed for Alice to attain rank r as of the end of the if clause following the first while loop.
From there, the main algorithm takes over where we walk through Alice's scores and note her rank as we go by comparing against the benchmarks from the scores array that we setup as rankAry earlier. curRank is our candidate rank at each stage which we've definitely achieved by the time this loop starts (by construction).
If we're at rank 1 we will be forever more, so just populate the current rank as 1 and move on.
If we're currently tied with or beating the current benchmark and that's not the end of the line, keep peeking at the next one and if we're also beating that next one, decrease the current benchmark location and iterate
Once this terminates, we've found the one we're going to supplant and we cannot supplant anything further, so assign this rank to this score and repeat until done
As far as I can tell this handles all cases correctly, even if Alice has repeated values or increases between the benchmarks from scores, we should stay at the same rank until we hit the new benchmarks, but the site feedback indicates there must be a bug somewhere.
All the other approaches I've been able to find seem to be some variation on doing a binary search to find the score each time, but I prefer not having to constantly search each time and just use the auxiliary space, so I'm a little stumped on what could be off.
static int[] climbingLeaderboard(int[] scores, int[] alice) {
int[] res = new int[alice.Length];
if (scores.Length == 0 || alice[0] >= scores[0]) { //degenerate cases
for (int i = 0; i < alice.Length; ++i) {
res[i] = 1;
}
return res;
}
int[] rankAry = new int[scores.Length + 1];
rankAry[0] = scores[0]; //top score rank
int curPos = 1; //start at the front and move down
int curRank = 1; //initialize
//initialize from the front. This way we can figure out ranks as we go
while (curPos < scores.Length && scores[curPos] > alice[0]) {
if (scores[curPos] < scores[curPos-1]) {
rankAry[curRank] = scores[curPos]; //update the rank break point
curRank++; //moved down in rank
}
curPos++; //move down the array
}
if (curPos == scores.Length) { //smallest score still bigger than Alice's first
rankAry[curRank] = alice[0]; //pretend there was a virtual value at the end
curRank++; //give rank Alice will have for first score when we get there
}
for (int i = 0; i < alice.Length; ++i) {
if (curRank == 1) { //if we're at the top, we're going to stay there
res[i] = 1;
continue;
}
//Non-degenerate cases
while (alice[i] >= rankAry[curRank - 1]) {
if (curRank == 1 || alice[i] < rankAry[curRank - 2]) {
break;
}
curRank--;
}
res[i] = curRank;
}
return res;
}
You have a couple of bugs in your algorithm.
Wrong mapping
Your rankAry must map a rank (your index) to a score. However, with this line rankAry[0] = scores[0];, the highest score is mapped to 0, but the highest possible rank is 1 and not 0. So, change that to:
rankAry[1] = scores[0];
Wrong initial rank
For some reason, your curRank is set to 1 as below:
int curRank = 1; //initialize
However, it's wrong since your alice[0] is less than scores[0] because of the following block running at the beginning of your method:
if (scores.Length == 0 || alice[0] >= scores[0]) { //degenerate cases
for (int i = 0; i < alice.Length; ++i) {
res[i] = 1;
}
return res;
}
So, at best your curRank is 2. Hence, change it to:
int curRank = 2;
Then, you can also remove curRank++ as your curRank has a correct initial value from:
if (curPos == scores.Length) { //smallest score still bigger than Alice's first
rankAry[curRank] = alice[0]; //pretend there was a virtual value at the end
curRank++; // it's not longer needed so remove it
}
Improve "Non-degenerate cases" handling
Your break condition should consider rankAry at curRank - 1 and not curRank - 2 as it's enough to check the adjacent rank value. Also, a value at curRank - 2 will produce wrong results for some input but I won't explain for which cases specifically - I'll leave it up to you to find out.
Fixed Code
So, I fixed your method according to my comment above and it passed it all the tests. Here it is.
static int[] climbingLeaderboard(int[] scores, int[] alice) {
int[] res = new int[alice.Length];
if (scores.Length == 0 || alice[0] >= scores[0]) { //degenerate cases
for (int i = 0; i < alice.Length; ++i) {
res[i] = 1;
}
return res;
}
int[] rankAry = new int[scores.Length + 1];
rankAry[1] = scores[0]; //top score rank
int curPos = 1; //start at the front and move down
int curRank = 2; //initialize
//initialize from the front. This way we can figure out ranks as we go
while (curPos < scores.Length && scores[curPos] > alice[0]) {
if (scores[curPos] < scores[curPos-1]) {
rankAry[curRank] = scores[curPos]; //update the rank break point
curRank++; //moved down in rank
}
curPos++; //move down the array
}
if (curPos == scores.Length) { //smallest score still bigger than Alice's first
rankAry[curRank] = alice[0]; //pretend there was a virtual value at the end
}
for (int i = 0; i < alice.Length; ++i) {
if (curRank == 1) { //if we're at the top, we're going to stay there
res[i] = 1;
continue;
}
//Non-degenerate cases
while (alice[i] >= rankAry[curRank - 1]) {
if (curRank == 1 || alice[i] < rankAry[curRank - 1]) {
break;
}
curRank--;
}
res[i] = curRank;
}
return res;
}

Why doesn't Debug.Log work in this for loop?

Here is what I have. This DrawPoints() method is called in an Update() method, meaning it is called repeatedly if it doesn't have extra conditions if I understand correctly. So basically I'm trying to make the for loop only run once by putting the outside if (iTrack == 0) check on it.
What is weird is that the Debug.Log commands will not run at all (the text "Here 1" or "Here2" never show up in Console), BUT the other code inside like var lerpedPosition and whatToSpawnClone run.
Why don't the Debug Log commands work? Is there something obviously wrong I'm doing with this code? This is very confusing.
int iTrack = 0;
int matricesNumber = 2;
public void DrawPoints()
{
int startIndex = 0;
int endIndex = mesh.vertexCount;
float t = Mathf.Clamp((Time.time % 2f) / 2f, 0f, 1f);
if (iTrack == 0) {
if (matricesNumber == 2)
{
for (int i = startIndex; i < endIndex; i++)
{
Debug.Log("Here 1");
var lerpedPosition = Vector3.Lerp(matrices1[i].position, matrices2[i].position, t);
whatToSpawnClone = Instantiate(whatToSpawnPrefab, lerpedPosition, matrices2[i].rotation) as GameObject;
if (i == (endIndex - 1))
{
Debug.Log("Here 2");
iTrack = 1;
}
}
}
}
}
Based on the description of your question I will assume that you are creating a script for Unity from here on out. Take a look at this. It offers two solutions that would be worth a try, mainly the second one as it is as simple as a check to make sure you have Debug output enabled for the console.

Chart in winform displaying wrong Point

I have the following code.
I have hardcoded the x and y values to test.
And for some reason for the point (0,-0.5) it plots (1,-0.5)
For the life of me I do not know what is going on, because if you try other values then the graph displays correctly.
foreach (var grp in q)
{
point = new DataPoint();
Sum1 = grp.Sum1 > 2 ? 2 : grp.Sum1;
Sum1 = Sum1 < -2 ? -2 : Sum1;
Sum2 = grp.Sum2 > 2 ? 2 : grp.Sum2;
Sum2 = Sum2 < -2 ? -2 : Sum2;
point.XValue = 0;
point.YValues = new double[] { -0.5 };
chart1.Series.Add(grp.Id.ToString());
chart1.Series[grp.Id.ToString()].ChartType = SeriesChartType.Point;
chart1.Series[grp.Id.ToString()].Label = grp.Id.ToString();
chart1.Series[grp.Id.ToString()].Points.Add(point);
chart1.Series[grp.Id.ToString()].ToolTip = "THEMES = " + Sum1 + "\n PRICES = " + Sum2;
chart1.Series[grp.Id.ToString()].LabelToolTip = "THEMES = " + Sum1 + "\n PRICES = " + Sum2;
chart1.Series[grp.Id.ToString()].MarkerSize = 11;
chart1.Update();
if (grp.Id.ToString() == "WW" || grp.Id.ToString() == "PB"
|| grp.Id.ToString() == "AJ" || grp.Id.ToString() == "AK")
{
avgTheme += (float)Sum1;
avgPrice += (float)Sum2;
count++;
}
}
UPDATE:
this line needed to be added, works only with .NET 4.5
chart1.Series["ABC"].CustomProperties = "IsXAxisQuantitative=True";
This is really weird! Looks like a very hard to believe bug. I played around but can only confirm that there seems to be no way to set a single Point to position 0 in a Series.
Here is a silly workaround:
S1.ChartType = SeriesChartType.Point;
for (int i=0; i < 2; i++)
{
DataPoint point = new DataPoint();
point.SetValueXY(i, -0.5);
if (i > 0) point.Color = Color.Transparent;
S1.Points.Add(point);
}
I wish I knew what this is about - Chart is so ill-documented there might still be some system to the madness..
Update: When you add a Timer and let its Tick remove the transparent 2nd Point, you can see how the 1st Point jumps from 0 to 1. So weird..
Ah ha....
This is not a bug. This is the correct behavior for a SERIESCHARTTYPE.POINT of chart.
The purpose of the chart is to show a [b]series [/b]of values {from left to right}, not a set of X,Y points.
Series value 1 is 4
Series value 2 is 1
Series value 3 is 6
and so on.
So the bug is not in the chart, but in understanding what the chart type is designed for and meant to be used for.
You can see here how each element in the array only uses the y value for the point.
Could it be that you want to graph some points? Maybe you are confusing a chart with a graph?
If you are trying to graph points this might help:
https://www.daniweb.com/software-development/csharp/code/217204/function-plotting-in-c

XNA performance

I'm writing some sort of Geometry Wars inspired game except with added 2d rigid body physics Ai pathfinding some waypoint analysis line of sight checks load balancing etc. It seems that even though with around 80-100 enemies on screen it can work reasonably fast with all that stuff enabled the performance completely breaks down once you get to a total of 250 (150 enemies) objects or so. I've searched for any O(n^2) parts in the code but there don't seem to be any left. I'm also using spatial grids.
Even if I disable pretty much everything from the supposedly expensive Ai related processing it doesn't seem to matter, it like still breaks down at 150 enemies.
Now I implemened all the code from scratch, currently even the matrix multiplication code, and I'm almost completely relying on the GC as well as using C# closures for some things, so I expect this to be seriously far from being optimized, but still it doesn't make sense to me that with like 1/15 of the processing work but double the objects the game suddenly starts to slow down to crawl? Is this normal, how is the XNA platform normally supposed to scale as far as the amount of objects being processed is concerned?
I remember Some slerp spinning cube thing I did at first could handle more than 1000 at once so I think I'm doing something wrong?
edit:
Here's the grid structure's class
public abstract class GridBase{
public const int WORLDHEIGHT = (int)AIGridInfo.height;
public const int WORLDWIDTH = (int)AIGridInfo.width;
protected float cellwidth;
protected float cellheight;
int no_of_col_types;
// a dictionary of lists that gets cleared every frame
// 3 (=no_of_col_types) groups of objects (enemy side, players side, neutral)
// 4000 initial Dictionary hash positions for each group
// I have also tried using an array of lists of 100*100 cells
//with pretty much identical results
protected Dictionary<CoordsInt, List<Collidable>>[] grid;
public GridBase(float cellwidth, float cellheight, int no_of_col_types)
{
this.no_of_col_types = no_of_col_types;
this.cellheight=cellheight;
this.cellwidth=cellwidth;
grid = new Dictionary<CoordsInt, List<Collidable>>[no_of_col_types];
for (int u = 0; u < no_of_col_types; u++)
grid[u] = new Dictionary<CoordsInt, List<Collidable>>(4000);
}
public abstract void InsertCollidable(Collidable c);
public abstract void InsertCollidable(Grid_AI_Placeable aic);
//gets called in the update loop
public void Clear()
{
for (int u = 0; u < no_of_col_types; u++)
grid[u].Clear();
}
//gets the grid cell of the left down corner
protected void BaseCell(Vector3 v, out int gx, out int gy)
{
gx = (int)((v.X + (WORLDWIDTH / 2)) / cellwidth);
gy = (int)((v.Y + (WORLDHEIGHT / 2)) / cellheight);
}
//gets all cells covered by the AABB
protected void Extent(Vector3 pos, float aabb_width, float aabb_height, out int totalx, out int totaly)
{
var xpos = pos.X + (WORLDWIDTH / 2);
var ypos = pos.Y + (WORLDHEIGHT / 2);
totalx = -(int)((xpos / cellwidth)) + (int)((xpos + aabb_width) / cellwidth) + 1;
totaly = -(int)((ypos / cellheight)) + (int)((ypos + aabb_height) / cellheight) + 1;
}
}
public class GridBaseImpl1 : GridBase{
public GridBaseImpl1(float widthx, float widthy)
: base(widthx, widthy, 3)
{
}
//adds a collidable to the grid /
//caches for intersection test
//checks if it should be tested to prevent penetration /
//tests penetration
//updates close, intersecting, touching lists
//Collidable is an interface for all objects that can be tested geometrically
//the dictionary is indexed by some simple struct that wraps the row and column number in the grid
public override void InsertCollidable(Collidable c)
{
//some tag so that objects don't get checked more than once
Grid_Query_Counter.current++;
//the AABB is allocated in the heap
var aabb = c.CollisionAABB;
if (aabb == null) return;
int gx, gy, totalxcells, totalycells;
BaseCell(aabb.Position, out gx, out gy);
Extent(aabb.Position, aabb.widthx, aabb.widthy, out totalxcells, out totalycells);
//gets which groups to test this object with in an IEnumerable (from a statically created array)
var groupstestedagainst = CollidableCalls.GetListPrevent(c.CollisionType).Select(u => CollidableCalls.group[u]);
var groups_tested_against = groupstestedagainst.Distinct();
var own_group = CollidableCalls.group[c.CollisionType];
foreach (var list in groups_tested_against)
for (int i = -1; i < totalxcells + 1; i++)
for (int j = -1; j < totalycells + 1; j++)
{
var index = new CoordsInt((short)(gx + i), (short)(gy + j));
if (grid[list].ContainsKey(index))
foreach (var other in grid[list][index])
{
if (Grid_Query_Counter.Check(other.Tag))
{
//marks the pair as close, I've tried only keeping the 20 closest but it's still slow
other.Close.Add(c);
c.Close.Add(other);
//caches the pair it so that checking if the pair intersects doesn't go through the grid //structure loop again
c.CachedIntersections.Add(other);
var collision_function_table_id = c.CollisionType * CollidableCalls.size + other.CollisionType;
//gets the function to use on the pair for testing penetration
//the function is in a delegate array statically created to simulate multiple dispatch
//the function decides what coarse test to use until descending to some complete //geometric query
var prevent_delegate = CollidableCalls.preventfunctions[collision_function_table_id];
if (prevent_delegate == null) { Grid_Query_Counter.Put(other.Tag); continue; }
var a = CollidableCalls.preventfunctions[collision_function_table_id](c, other);
//if the query returns true mark as touching
if (a) { c.Contacted.Add(other); other.Contacted.Add(c); }
//marks it as tested in this query
Grid_Query_Counter.Put(other.Tag);
}
}
}
//adds it to the grid if the key doesn't exist it creates the list first
for (int i = -1; i < totalxcells + 1; i++)
for (int j = -1; j < totalycells + 1; j++)
{
var index = new CoordsInt((short)(gx + i), (short)(gy + j));
if (!grid[own_group].ContainsKey(index)) grid[own_group][index] = new List<Collidable>();
grid[own_group][index].Add(c);
}
}
[...]
}
First. Profile your code. Even if you just use manually inserted time stamps to surround blocks you're interested in. I prefer to use the profiler that comes built into Visual Studio Pro.
However, based in your description, I would assume your problems are due to too many draw calls. Once you exceed 200-400 draw calls per frame your performance can drop dramatically. Try batching your rendering and see if this improves performance.
You can use a profiler such as ANTS Profiler to see what may be the problem.
Without any code theres not much I can do.

Categories

Resources