In my game I always have about 500 objects that are active at the same time.
I have also a GameObject[]-Array that stores positions for the active objects. All other positions in the Array are set to null.
Here' an example:
I have a cube at position 5,5,0.
My GameObject Array then looks like: cubes[5,5,0] = cube;
The game runs fine when i set the maximum of the Array to something like [100,10,100]. But if i increase this to something like [200,10,200] it starts to lag.
Is there a way to improve the array so that i can save more positions (with still a maximum of 500 objects active at the same time!)
Code:
GameObject[] cubePrefab;
GameObject[,,] cubes;
bool[,,] world;
int worldX = 100;
int worldY = 5;
int worldZ = 100;
void Start()
{
world = new bool[worldX, worldY, worldZ];
cubes = new GameObject[worldX, worldY, worldZ];
}
void Update()
{
for (var i = playerXMin; i < playerXMax; i++)
for (var i2 = playerYMin; i2 < playerYMax; i2++)
for (var i3 = playerZMin; i3 < playerZMax; i3++)
if (world[i, i2, i3] && cubes[i, i2, i3] == null)
cubes[i, i2, i3] = Instantiate(cubePrefab);
}
At the start, about 500 random positions of world get set to true.
playerXMin = player.transform.position.x - 10;
playerXMax = player.transform.position.x + 10;
and so on.
So, based off the comments, you have a very sparse array. A 100 * 10 * 100 array can store 100000 objects in it, and if you are iterating it, you still have to go over all the other 99500 nulls in it even if there are just 500 items actually in it.
I would recommend you using a more appropriate data type which allows for sparse allocations, for example, a Dictionary. Since Dictionarys are generic, and you are evitendtly trying to store some objects based off a 3D point (not necessarily in space, but still, 3 coordinates) you're looking for something like Dictionary<Vector3Int, GameObject>. I recommend Vector3Int as a key since it only deals in integers, so you'll able to avoid all the shenanigans related to floating points.
With a sparse structure, you can avoid looping through non-existent positions, which will greatly reduce the time it takes to iterate it in such an extreme case. Do note that sparse structures are usually technically slower, if you were to fill all 100000 positions. But since you aren't nearly even touching that amount, the reduction in iteration time will help you a lot.
Related
(solution is at the end)
I have a tilemap with tiles that can spread to the adjacent tiles based on data I store in a dictionary by position.
While the check for that is done in no time at all it does take considerable resources resulting in a lag everytime I call the function.
Currently I'm making sure the code doesn't get executed too fast by actually waiting a little each time we finished a loop-iteration. While I'm okay with my code not being executed as fast as it could, this just isn't the right way to go about it.
So basically: Is there a way to limit the execution of a single script/function in unity/c# to not use more than a certain percentage of the games resources (or something to that effect)? Or maybe there's a way to increase the functions performance significantly I just can't find?
Thanks in advance!
Solution <-----------
Thanks to the great advice from Michael Urvan and akaBase I was able to vastly increase performance by using chunks. I wasn't able to implement all the suggested improvements, so check out (and upvote) their respective answers for even more performance.
How: I reset a bool every couple of seconds. When that bool is set, I loop through part of the necessary tiles (chunk) in the Update()-method and remember the last index for the next Update()-call. When I reach the end of the tile-list I'm done and can set the bool to false and reset the index.
Here's a simplified version of my finished code:
{
[SerializeField] private Tilemap tilemap;
[SerializeField] private List<TileType> tileTypes;
[SerializeField] private List<TileType> growthTileTypes;
private Dictionary<Vector3Int, TileData> tileDataByPosition;
private List<TileData> spreadingTileDatas;
private Dictionary<TileBase, TileType> tileTypeByTile;
private bool spreading;
private spreadIndex = 0;
private int chunkSize = 10;
private void Awake()
{
// set up stuff here
InvokeRepeating("ResetSpreading", 10, 10);
}
private void Update()
{
if (spreading == true)
{
Spread();
}
}
private void Spread()
{
for (
int index = spreadIndex;
index < spreadingTileDatas.Count && index < spreadIndex + chunkSize;
index++
)
{
// do stuff here
}
spreadIndex += chunkSize;
if (spreadIndex >= spreadingTileDatas.Count)
{
spreadIndex = 0;
spreading = false;
}
}
}
To answer your: "Is there a way to limit the execution of a single script/function in unity/c# to not use more than a certain percentage of the games resources (or something to that effect)?" - What you want to do is have this loop only perform a fraction of the total items per loop and call it more often. You can process it in chunks or you can use time measurement as I noted at the end of this post.
I didn't examine your code specifically to see exactly what is being done, but this is a common way of splitting up the processing over time rather than how you were adding a tiny WaitForSeconds() within a Coroutine. The coroutine with such a tiny WaitForSeconds is probably terrible for performance and simply using Update() (which is called every frame) and splitting processing the way I describe will yield much better performance. If you google Coroutine performance you can find information and benchmarks on that.
You will create a variable to keep track of the current position of your index, and a variable that is the maximum number of indexes to process per frame - you will break out of your loop when the total # of items processed hits the max. You also will not do the loop from 0 to X - it will be from currentIndex to the end and then loop back around by setting it to 0. You will just break out when you hit max each time.
To summarize:
Remove IEnumerator from Spread() and make it a regular function, and instead of foreach(position in tilePosition) use for(int i=0;) etc
Add a variable like MaxToProcessPerFrame
Add another variable like currentIndex
Add a third variable like totalProcessedThisFrame that is set to zero each time you enter Spread()
for instance if (totalProcessedThisFrame++ >= MaxToProcessPerFrame) break; in your loop
add if (currentIndex >= tilePositions.Count) currentIndex = 0 so that it loops back around to the beginning
I also noticed that Spread() is calling UpdateTiles() at the end, which then does another StartCoroutine(Spread()) which will cause it to run twice and i'm sure is inefficient in some way since it will kind of double process possibly per iteration.
A likely good place to call UpdateTiles() would be on the if (currentIndex >= tilePositions.Count) since that would be at the end of one iteration of updating the whole list.
Once you have that setup, you can test different values for the MaxToProcessPerFrame, start with a high number and then lower it until it seems to run smoothly and it will break up the processing over multiple frames.
A second method in addition to breaking it up by a total # of items to be processed is to use a System.Diagnostics Stopwatch() and break out of your loop when it exceeds a total amount of milliseconds of processing.
These two methods will help it process smoothly once you play with different amounts.
And lastly, your method of calling StartCoroutine() constantly is also not performant. You should start a coroutine once or the least number of times needed, and then loop within it. It's common to use a while (Application.isRunning) {} loop as a basis for most Coroutines in Unity.
also for reference:
Unity 2017 Game Optimization: Optimize all aspects of Unity performance
By Chris Dickinson
I suggest keeping the data in a Multidimensional Array and call the specific area of the tilemap you need.
Example pseudo code
TileData[,] tileMapDatas;
void Start()
{
int rows = 5;
int columns = 5;
tileMapDatas = new TileData[rows, columns];
for(int r = 0; r < rows, r++)
{
for(int c = 0; c < columns, c++)
{
tileMapDatas[r, c] = new TileData();//Change to how you create them
}
}
}
void Spread(Tile tile)
{
if(tile.CanSpread)
{
//Check the mapdata for the tile above, (add check for array bounds)
if(tileMapDatas[tile.Row + 1, tile.Column].CanChange)
{
//Change
}
//Repeat for other directions
}
}
Using this method is a lot more efficient as you only need to check the neighbours of the targeted tile.
I've been trying to come up with an algorithm which deals, as the title states,
X amount of Cards, per Y amount of Cards, over Z amount of Players of a normal (52 piece) Deck of Cards which is sorted or unsorted. I've been walking into a wall for the past few hours to come up with a working solution, while also Googling to find similar problems. Unfortunately without success, hence this question.
An example would be: dealing 2 Cards, per 1, over 2 Players would result in
Player 1 receiving 1 card
Player 2 receiving 1 card
Player 1 receiving 1 card
Player 2 receiving 1 card
Until now I have a solution with which I'm able to run my application, although the actual dealing algorithm isn't keeping the 'per' parameter into account. It will deal the right amount of cards to the Y amount of players, although each player will receive the total amount to be dealed in 1 go..
I was wondering if anyone here had to handle a similar problem in the past? Or could guide me into the right direction? :/
public List<Card>[] Deel(int per, int players, int cards)
{
_currentCard= 0;
List<Card>[] output = new List<Card>[players];
if (_cardsDistributed < _deck.Count)
{
for (int i = 0; i < players; i++)
{
List<Card> hand = new List<Card>();
for (int j = 0; j < cards; j++)
{
_currentCard= 0;
hand.Add(_deck[_currentCard]);
_deck.Remove(_deck[_currentCard]);
_cardsDistributed++;
}
output[i] = hand;
}
return output;
}
else
return null;
}
One way to think about this is to get a deck of cards and do it yourself, by hand, and write down the steps. For example, you have three players and you want to deal each player four cards, two at a time. So what do you do?
You take the deck in hand, hold it over the first player's pile, and deal two cards. The code for that is pretty simple:
player = 1
for i = 1 to 2
deal next card to player
Then you move to the second player's pile and deal two cards, and you do the same thing for the third player. So you need a loop to go through the players:
for player = 1 to 3
for i = 1 to 2
deal next card to player
At this point you've dealt two cards to each of the three players.
If you want to deal X cards Y at a time, and Y is smaller than X, then you need to go around to each player multiple times. How many? Well, how many times does Y go into X? The answer is X/Y.
If you were doing this by hand, you would start over at player 1, deal him two cards, move on to player 2, etc. Adding that in code is simple:
numRounds = 4/2
for round = 1 to numRounds
for player = 1 to 3
for card = 1 to 2
deal next card to player
Now, replace the constant values with X, Y, and Z, and try it with some other combination by following those exact steps. Deal six cards to each player, three at a time. Did it work? Try a few other combinations to verify that the steps you wrote down always work.
Once you've determined that the algorithm you've developed works, then writing the code to implement it on the computer is easy. There are, of course, some minor details like how to deal a card, but those are easy compared to figuring out the overall approach to the problem.
I was fortunate that I discovered this approach to problem solving early in my education. Casting an algorithmic problem into physical terms lets me build a model that I can play with, and write down the steps I took to solve the problem. After that, writing the program is a simple matter of duplicating those steps in code. It doesn't work for all problems, but it's very effective for a large number of different problems that you will encounter.
If I understood your question correctly you need something like this:
public List<Card>[] Deel(int per, int players, int cards)
{
List<Card>[] output = new List<Card>[players];
// init hand for each player
for (int i = 0; i < players; i++)
{
output[i] = new List<Card>();
}
// assume the number of cards is divided by 'per' without a remainder
// otherwise you need one more round to deal rest (cards % per) cards
int rounds = cards / per;
for (int round = 0; round < rounds; round++)
{
for (int i = 0; i < players; i++)
{
for (int j = 0; j < per; j++)
{
if (_deck.Count > 0)
{
output[i].Add(_deck[0]);
_deck.Remove(_deck[0]);
_cardsDistributed++;
}
else
{
// should throw an exception because the deck contains no more cards
// or maybe you need to check it before dealing
}
}
}
}
return output;
}
I am making a game in Unity with c# code and I want a character to have something like aggro table.
It will be something like (float, int) table where int is other character id and float is actual aggro value.
Basically, I have few things in mind:
The table should be sorted by float keys so I can quickly get top
entries
I need to be able to frequently lookup entries by its int
value because of (3)
I want to change float key frequently and have
the table sorted up all the time
I dont care about memory usage
as there wont be that many entries
I do care about performance of
operations like inserting/removing/changing entries and re-sorting
the list
I am not very experienced in C# saw few things like sortedlist/dictionary, but it doesnt seem to be optimalized for all the things I want.
Do you have any advice?
EDIT: Not sure if I did a good job at explaining what I want to achieve. It might be very similar to a table of football player names and number of goals they scored during the season. I will frequently ask for "most productive players" and also I will frequently update their scores by looking up their names and changing their stats, needing the table to be sorted all the time.
You could use a List<Character>, or an array excluding the player's character. You keep the List<Character> sorted with the highest aggro value at the front. To keep everything sorted every frame you run quicksort first. Once a Character has a lower aggro value than the player's aggro threshhold you escape out of the method.
If aggro is above the threshold just run the aggro check.
You could extend this to work for multiplayer by having a List<Player>.
Something like:
void quicksort(List<Enemy> enemies, int first, int last)
{
int left = first;
int right = last;
int pivot = first;
first++;
while (last >= first)
{
if(enemies[first].Aggro >= enemies[pivot].Aggro &&
enemies[last].Aggro < enemies[pivot].Aggro)
swapp(enemies, first, last)
else if(enemies[first].Aggro >= enemies[pivot].Aggro)
last--;
else if(enemies[last].Aggro < colliders[pivot].Aggro)
first++;
else
{
last--;
first++;
}
}
swap(enemies, pivot, last);
pivot = last;
if(pivot > left)
quicksort(enemies, left, pivot);
if(right > pivot + 1)
quicksort(enemies, pivot + 1, right);
}
void swap(List<Enemy> enemies, int left, right)
{
var temp = enemies[right];
enemies[right] = enemies[left];
enemies[left] = temp;
}
void CheckAggro()
{
quicksort(enemies, 0, enemies.Count - 1);
for(int = 0; i < players.Count; i++)
{
for(int j = 0 < enemies.Count; j++)
{
if(players[i].AggroThreshhold < enemies[j].Aggro)
{
break;
}
// Perform what happens when enemy is high on aggro threshold.
}
}
}
If players have different aggro thresholds you could save all of the enemies who have aggro above the minimum to a separate List, and then do a check against that from the player with the lowest to highest threshold. Just keep the list of players sorted with the lowest aggro threshold first.
I think the best solution here in the SortedList. From What i could gather:
SortedList <TKey, TValue> has faster insertion and removal operations when it comes to sorted date.
There is a question that i think will help: When to use a SortedList<TKey, TValue> over a SortedDictionary<TKey, TValue>?
hope I helped.
for(int i = 0; i < gos.Length; i++)
{
float randomspeed = (float)Math.Round (UnityEngine.Random.Range (1.0f, 15.0f));
floats.Add (randomspeed);
_animator [i].SetFloat ("Speed", randomspeed);
}
Now what i get is only round numbers between 1 and 15. I mean i'm not getting numbers like 1.0 or 5.4 or 9.8 or 14.5 is it logical to have speed values like this ? If so how can i make that the random numbers will include also floats ?
Second how can i make sure that there will be no the same numbers ?
gos Length is 15
As noted in the other answer, you aren't getting fractional values, because you call Math.Round(), which has the express purpose of rounding to the nearest whole number (when called the way you do).
As for preventing duplicates, I question the need to ensure against duplicates. First, the number of possible values within the range you're selecting is large enough that the chances of getting duplicates is very small. Second, it appears you are selecting random speeds for some game object, and it seems to me that in that scenario, it's entirely plausible that once in a while you would find a pair of game objects with the same speed.
That said, if you still want to do that, I would advise against the linear searches recommended by the other answers. Game logic should be reasonably efficient, and in this scenario that would mean using a hash set. For example:
HashSet<float> values = new HashSet<float>();
while (values.Count < gos.Length)
{
float randomSpeed = UnityEngine.Random.Range(1.0f, 15.0f);
// The Add() method returns "true" if the value _wasn't_ already in the set
if (values.Add(randomSpeed))
{
_animator[values.Count - 1].SetFloat("Speed, randomSpeed);
}
}
// it's not clear from your question whether you really need the list of
// floats at the end, but if you do, this is a way to convert the hash set
// to a list
floats = values.ToList();
The reason you're not getting any decimals is because you're using Math.Round, this will either raise the float to the next whole number or lower it.
As for if it's logical, it depends.As for your case animation speed is usually done by floats because it can smoothly speed up and down.
Also to answer your question on how to avoid duplicates of the same float.. which in itself is already very unlikely, try doing this instead :
for(int i = 0; i < gos.Length; i++)
{
float randomspeed = 0f;
// Keep repeating this until we find an unique randomspeed.
while(randomspeed == 0f || floats.Contains(randomspeed))
{
// Use this is you want round numbers
//randomspeed = Mathf.Round(Random.Range(1.0f, 15.0f));
randomspeed = Random.Range(1.0f, 15.0f);
}
floats.Add (randomspeed);
_animator [i].SetFloat ("Speed", randomspeed);
}
Your first problem: if you use Math.Round(), you'll never get numbers like 5.4...
Second question: you can check for existance of the number before you add the number:
private float GenerateRandomSpeed()
{ return (float)UnityEngine.Random.Range (1.0f, 15.0f);}
for(int i = 0; i < gos.Length; i++)
{
float randomspeed= GenerateRandomSpeed();
while (floats.any(x=>x==randomspeed))
randomspeed=GenerateRandomSpeed();
floats.Add (randomspeed);
_animator [i].SetFloat ("Speed", randomspeed);
}
I didn't test it but i hope it can direct you to the answer.
Here is my code to create a grid with randomly generate mines. Problem is the mines is so diffuse, so when I count the mines for non-mine cells, it mainly have value 1, 2 and nearly don't have value 4, 5 , 6, 7. How to improve this algorithm?
Assume that number of columns, rows and mines are constant.
var r = new Random();
int columns, rows, TotalMine;
int[,] grid = new int[columns, rows];
int MineCount = 0;
int X = 0;
int Y = 0;
// Add Mines (This is so simple, it cause the problem)
while (MineCount++ < TotalMine)
{
do
{
X = r.Next(columns);
Y = r.Next(rows);
}
while (grid[X, Y] == -1);
grid[X, Y] = -1; // -1 = have mine
}
Your algorithm is perfectly fine and will create randomly spread mines (assuming the RNG is good enough).
One way improving I could imagine would be using a Game of Life algorithm to remove extremes, for examle cluttered fields where one field is surrounded by 7 or 8 mines.
Just iterate over all fields and count the surrounding mines (i.e. calculate the fields' numbers). If it's 7 or 8, remove one random mine next to it.
As an alternative, you could use perlin noise to create "clouds" and then only place mines randomly in areas where you've got at least a given "density". This way you can rather easily create bigger areas with nothing in between them.
You could also mix both ideas a bit:
Create a table or board as big as your playing field, with values being randomly distributed (true or false).
Do several iterations with a Game of Life algorithm to create some pattern (you should end up with "islands" or random structures).
Place your mines only within areas that are set to false (or true - whatever you choose).