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

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.

Related

Unity want to get gameobjects from another array

I have an array that gets all the gameobjects with the enemyMelee tag and i want to put the ones that have the idEnemy equal to the idDestination in another array.
How can i do this?
The maybeBlobsEnemy is the array that has all of them and blobsEnemy is the array that i want with only the condition above
Here is my code
void Update()
{
maybeBlobsEnemy = GameObject.FindGameObjectsWithTag("blobMelee");
for (int i = 0; i < maybeBlobsEnemy.Length; i++)
{
if (maybeBlobsEnemy[i].GetComponent<BlobBehavior>().idBlob == idDestination)
{
blobsEnemy[i] = maybeBlobsEnemy[i];
}
}
Thank you for you time

Strange behavior adding objects to Array Unity3d C#

I am seeing some odd behavior, when adding objects to an array of GameObjects. Here is my sudo code:
public class GameController : MonoBehaviour {
public GameObject[] MyArray;
void Awake() {
Application.runInBackground = true;
MyArray= new GameObject[144];
}
//some code that calls myFunction
public IEnumerator myFunction(float time)
{
int counter = 0;
GameObject card= AnotherArrayOfGameObjects[0];
for (int j = 0; j < 144; j++)
{
card.name = "BLANK_" + (j+1);
Instantiate(card, cardVector, Quaternion.Euler(0, rotateAdujust, 180));
MyArray[counter] = card;
yield return new WaitForSeconds(time);
counter++;
}
}
}
This function kind of works ok. It creates my object and instantiates it. But, when it adds the object "card" to the array MyArray, it does not add it correctly. I.e, as this is in a loop, 0-143, I would expect the array to look like this:
MyArray[0] = "BLANK_1"
MyArray[1] = "BLANK_2"
....
MyArray[143] = "BLANK_144"
Instead, it looks like this:
MyArray[0] = "BLANK_144"
MyArray[1] = "BLANK_144"
....
MyArray[143] = "BLANK_144"
I can see all this, by the way, because MyArray is a public variable in my code, so I can see it in Unity's Inspector. If I debug, I can see each iteration overwrite the previous. So, in the first iteration of the loop, i get:
MyArray[0] = "BLANK_1"
Then in the second iteration of the loop, I get:
MyArray[0] = "BLANK_2"
MyArray[1] = "BLANK_2"
In the Third:
MyArray[0] = "BLANK_3"
MyArray[1] = "BLANK_3"
MyArray[2] = "BLANK_3"
Until it completes the full loop and all items in the array say "BLANK_144"
Can anyone explain why this is happening?
You are adding same card object to array on each iteration. And you are changing name of this object on each iteration. Thus you end up with array, all items of which are pointing to same card instance. And that card instance whill have latest name which you have assigned (on last iteration):
MyArray[counter] = card;
Remember - Instantiate method returns cloned object, but it does not change the object which you are cloning. So you instantiate new clone on each iteration, but you don't save it anywhere.
You should add to array card clones which you are instantiating:
GameObject card = AnotherArrayOfGameObjects[0];
for (int j = 0; j < 144; j++)
{
var cardClone = Instantiate(card, cardVector, Quaternion.Euler(0, rotateAdujust, 180));
cardClone.name = "BLANK_" + (j+1);
MyArray[j] = cardClone;
yield return new WaitForSeconds(time);
}

The unity console claims that the variable temp is declared but never used

using UnityEngine;
using System.Collections;
public class GameBoardScript : MonoBehaviour {
public int m_size;
public GameObject m_PuzzlePiece;
void Start() {
GameObject temp;
for (int i = 0; i < m_size; i++) {
for (int j = 0; j<m_size; j++) {
temp = (GameObject)Instantiate(m_PuzzlePiece, new Vector2(i*400/m_size, j*400/m_size), Quaternion.identity);
}
}
}
}
The reason you get this warning is because you assign the variable. But you don't use the variable of temp. Do anything with the variable after it's deceleration and the warning will go away! For example:
for (int i = 0; i < m_size; i++)
{
for (int j = 0; j<m_size; j++)
{
temp = (GameObject)Instantiate(m_PuzzlePiece, new Vector2(i*400/m_size, j*400/m_size), Quaternion.identity);
temp.SetActive(false); // this is an arbitrary use of temp. don't actually do this
}
}
Joe Blow is right that you should just call instantiate without assigning it's return to a variable if you don't intend to do anything to the instantiated GameObject afterwards. However just declaring the variable local to the inner for loop will still not remove the warning if you still fail to use it.
Please Note The compiler doesn't work out that the m_size variable could be 0 and might not reach the inner for loop. The warning occurrs because the line
temp = (GameObject)Instantiate(m_PuzzlePiece, new Vector2(i*400/m_size, j*400/m_size), Quaternion.identity);
is an assignment not a use of the variable temp. You could assign it as many times as you want and you will still get the same warning. You need to either use it or not assign the returned GameObject from Instantiate to a variable at all.

Get object data outside for loop in C#

I'm really new to C#, I started with Javascript so it's a bit of a struggle to understand the behaviour of C#.
I am trying to create a simple object inside my for loop and then display the assigned value afterwards but there is a scope issue with this, and am unclear on how I can then access it outside of the for loop?
This is my code setup:
public class Player{
public int identity;
public int score;
public Player(int id){
identity = id;
score = 0;
}
}
for (int i = 0; i < max; i++){
Player player = new Player(i);
}
//here i want to access a player and print the information for said player
I don't know how i can access the newly created players outside of the loop, how is this done?
I can understand how this could be confusing coming from JavaScript, in JavaScript all your variables are hoisted to the function (or global) scope. That's not true in C#, in C# variables are generally scoped within the nearest set of {}s. But are available for use within child scopes.
So you could write your code like this:
List<Player> players = new List<Player>();
Player player = null;
for (int i = 0; i < max; i++){
player = new Player(i);
players.Add(player);
}
Console.WriteLine(players[x]);
In this example you would have access to each individual player outside of the foreach because it's declared at the higher scope. But this would be more appropriately written:
List<Player> players = new List<Player>();
for (int i = 0; i < max; i++){
Player player = new Player(i);
players.Add(player);
}
Console.WriteLine(players[x]);
It's generally considered good practice to declare your variables as close to their usage as you can.
A final optimisation, that you yourself suggested, would be to eliminate the player declaration completely (as it's unnecessary) and inline it. That leaves us with the final version:
List<Player> players = new List<Player>();
for (int i = 0; i < max; i++){
players.Add(new Player(i));
}
Console.WriteLine(players[x]);
you can try something like this
Player player = new Player();
List<Player> playerlist = new List<Player>();
for (int i = 0; i < max; i++){
player = new Player(i);
playerlist.Add(player);
}

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