How to fix copy paste with multiple roads? - c#

We have a cycle from 11 to 36, that adds to a list objects. Objects we have - from 11 to 15, 21, 22, and 31 to 35. Then we use it to move that enemy to those objects out of the list. The problem with that - we need to have a different routes. So, how to solve that? What I tried is use a lists of the list and try to sort things in them, but how to get information out it, and how to make it affect to an integer number _road? Either way, it's not that optimised, even is it's somehow working. I tried even massives, but it have the same problem as lists. Having different lists = code copy paste and it ain't looking right. So we probably need only one list. So we need to take an information for each 5 object in the list, probably with the .Skip(i∙5-5) and .Keep(i∙5), and here when I stuck, because I have no clue how to create a cycle with that math. I mean, I'm trying to programm for a week, if the answer it simple, I'll get it.
Also, I know that LINQ may have problems with optimisation, if there's a way to do that without LINQ, I'm open to it.
And, I did think about fabric method for this one, even Unity told me one's to do that, but I didn't see how to implement that in here, so, if you have an idea, again, open to it, it might be easier to understand the code or less things to do, I don't know.
Here's two different lines of code one's mine, and one... How it was looking.
2 codes:
public class Vrag : MonoBehaviour
{
public int _road = 1;
public static List<Transform> _roads = new List<Transform>();
void Awake()
{
for (int i = 11; i < 36; i++)
{
_roads.Add(GameObject.Find($"{i}").transform); //Also, not every object in this cycle is existing, so it's better to add "if" statement for not existed number of "i".
}
}
int _pos = 0;
void Update()
{
//On each 5 object in the list we should add to _road 1, each _road number we need to .Skip(x∙5-5) and .Keep(x∙5) in a if statement? So it's like sorted or so, and I can call this public int and use it like a sort thingy? So it's kinda like "for" cycle in "if" statement with "if" statement, I dunno. And it's LINQ.
transform.position = Vector3.MoveTowards(transform.position, _roads[_pos].position, 0.1f);
if (transform.position == _roads[_pos].position)
{
_pos += 1;
transform.LookAt(_roads[_pos]);
}
}
}
public class Vrag : MonoBehaviour {
public int nomerDorogi;
public List<Transform> way1 = new List<Transform>();
public List<Transform> way2 = new List<Transform>();
public List<Transform> way3 = new List<Transform>();
int pos=0;
void Start () {
way1.Add(GameObject.Find("11").transform);
way1.Add(GameObject.Find("12").transform);
way1.Add(GameObject.Find("13").transform);
way1.Add(GameObject.Find("14").transform);
way1.Add(GameObject.Find("15").transform);
way2.Add(GameObject.Find("21").transform);
way2.Add(GameObject.Find("22").transform);
way3.Add(GameObject.Find("31").transform);
way3.Add(GameObject.Find("32").transform);
way3.Add(GameObject.Find("33").transform);
way3.Add(GameObject.Find("34").transform);
way3.Add(GameObject.Find("35").transform);
}
void Update () {
if (nomerDorogi==1) {
transform.position = Vector3.MoveTowards(transform.position,way1[pos].position,0.1f);
if (transform.position == way1[pos].position)
pos+=1;
transform.LookAt(way1[pos]);
}
if (nomerDorogi==2) {
transform.position = Vector3.MoveTowards(transform.position,way2[pos].position,0.1f);
if (transform.position == way2[pos].position)
pos+=1;transform.LookAt(way2[pos]);
}
if (nomerDorogi==3) {
transform.position = Vector3.MoveTowards(transform.position,way3[pos].position,0.1f);
if (transform.position == way3[pos].position)
pos+=1;transform.LookAt(way3[pos]);
}

Related

Performance issue looping through big tilemap in unity 2D C#

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

PUN 2 Getting and Setting Custom Properties

I'm still relatively new to Photon since the depreciation of UNet. I'm having trouble getting and setting local custom properties. I'm trying to have two different teams (players and angels) be chosen. Each player starts as a spectator. A certain percentage of players are chosen to be the angels, and the rest are assigned as players. I can manage to get and set the property of a randomly chosen player, but I can't seem to assign the values for the remaining. The snippet of code is below.
private IEnumerator TeamBalance()
{
angelCount = Mathf.Floor(PhotonNetwork.PlayerList.Length * angelPercent);
currentAngels = angelCount;
for (int i = 0; i < angelCount;)
{
int index = Random.Range(0, PhotonNetwork.PlayerList.Length);
if (PhotonNetwork.PlayerList[index].CustomProperties["team"].ToString() == "spectator")
{
PhotonNetwork.PlayerList[index].CustomProperties["team"] = "angel";
i++;
}
}
foreach (var player in PhotonNetwork.PlayerList)
{
if (player.CustomProperties["team"].ToString() == "spectator")
{
player.CustomProperties["team"] = "player";
}
}
yield return null;
}
The end result for 3 players ends up picking 1 angel, but with 2 spectators still remaining.
You need to use Player.SetCustomProperties function to set properties instead of assigning them directly. This allows PUN to track what’s been changed and update properly.
https://doc-api.photonengine.com/en/pun/v2/class_photon_1_1_realtime_1_1_player.html#a0c1010eda4f775ff56f8c86b026be41e

Why I'm getting null or index out of bound? [duplicate]

This question already has answers here:
What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?
(5 answers)
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 3 years ago.
In this case animators contains 3 items.
But let' say I don't know how many items there is. Could be 3 or 30.
I can get the "medea_m_arrebola" index fine but how do I get the rest indexs ?
I tried on this line also 2 and now 1 : But getting exception IndexOutOfRangeException: Index was outside the bounds of the array.
soldiers_indexs = new int[1];
And if not using this line I'm getting null on the line :
soldiers_indexs[i] = i;
How can I get the rest items indexs ?
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OpenningScene : MonoBehaviour
{
[Header("Animators")]
public Animator[] animators;
[Space(5)]
[Header("Movement Settings")]
public Transform target;
public float movingSpeed = 1f;
public bool slowDown = false;
[Space(5)]
[Header("Rotation Settings")]
public float rotationSpeed;
public DepthOfField dephOfField;
public float waitingAnimation;
public float startConversation;
private Vector3 targetCenter;
private bool startWaitingAnim = true;
private bool endRot = false;
private int medea_m_arrebola_index;
private int[] soldiers_indexs;
// Use this for initialization
void Start()
{
targetCenter = target.GetComponent<Renderer>().bounds.center;
soldiers_indexs = new int[1];
for (int i = 0; i < animators.Length; i++)
{
animators[i].SetFloat("Walking Speed", movingSpeed);
if(animators[i].name == "medea_m_arrebola")
{
medea_m_arrebola_index = i;
}
else
{
soldiers_indexs[i] = i;
}
}
}
Because C# is not Javascript.
Your code with soldiers_indexs[i] = i; would work perfectly in Javascript, even if the end result is hard to understand. However, an array in C-type languages is a defined data structure with lower and upper bounds. C# does not allow you to do things that appear to make no sense.
As it's not clear from your code what you are attempting to do, I can't advise a forward path. However, it is clear that using the index variable i to refer to a place on an array that you are not iterating is a mistake.
Generally, in C#, we use more general-purpose data structures such as a List<T>. In addition, I do not recommend for loops. A foreach loop nearly always makes more sense, and is always easier to read.

Call method when specific division of integer is reached

I am programming a video game, and in it, I would like to call a method that adds a player bonus life for every 2,000 points scored. I have no idea what operator(s) to use for such a thing.
If (score is divisible by 2000, each increment){
DoSomething();
}
I'm not even sure if I'm asking this question correctly. Basically when the player scores 2,000pts, 4,000pts, 6,000pts, etc, I want to give him/her a bonus life by calling a method. I already have the method created; I was just wondering how I can apply the conditions that call it.
I tried using this:
public int bonusTarget = 2000;
paddle = GameObject.Find("Paddle").GetComponent<Paddle>();
if(score >= bonusTarget){
paddle.Bonus();
bonusTarget += 2000;
}
but, it awarded more than one bonus life each increment. I need to award the bonus life only one time for each 2,000pts
"score is divisible by 2000"
if (score % 2000 == 0) DoSomething()
If You need to track score use property instead of varilable eg.:
private int _score;
public int Score
{
get
{
return _score;
}
set
{
var a = Math.Floor(_score / 2000)
var b = Math.Floor(value / 2000)
if (a < b) DoSomething();
_score = value;
}
}
Put the if(score is divisible by 2000) check inside the DoSomething() method.
There's no reason to keep this outside the method that increases the bonus lives, in the dozens of possible code paths that increase the score. You need ONE CENTRAL location that the score increases, and checks various conditions.
You need a centralized location to add to the score, like this:
public void AddToScore(int points)
{
score += points;
if (score >= bonusTarget)
{
bonusTarget += 2000;
paddle.Bonus();
}
}
I switched the two lines inside the conditional block in case paddle.Bonus() tries adding points itself, which could cause issues, possibly even infinite recursion.
If you make it so you always add to the score using this method, you can add any special handling you want. You might also want to write a ResetScore() and a GetScore() so you never access score directly elsewhere in your code.

C# XNA: Optimizing Collision Detection?

I'm working on a simple demo for collision detection, which contains only a bunch of objects bouncing around in the window. (The goal is to see how many objects the game can handle at once without dropping frames.)
There is gravity, so the objects are either moving or else colliding with a wall.
The naive solution was O(n^2):
foreach Collidable c1:
foreach Collidable c2:
checkCollision(c1, c2);
This is pretty bad. So I set up CollisionCell objects, which maintain information about a portion of the screen. The idea is that each Collidable only needs to check for the other objects in its cell. With 60 px by 60 px cells, this yields almost a 10x improvement, but I'd like to push it further.
A profiler has revealed that the the code spends 50% of its time in the function each cell uses to get its contents. Here it is:
// all the objects in this cell
public ICollection<GameObject> Containing
{
get
{
ICollection<GameObject> containing = new HashSet<GameObject>();
foreach (GameObject obj in engine.GameObjects) {
// 20% of processor time spent in this conditional
if (obj.Position.X >= bounds.X &&
obj.Position.X < bounds.X + bounds.Width &&
obj.Position.Y >= bounds.Y &&
obj.Position.Y < bounds.Y + bounds.Height) {
containing.Add(obj);
}
}
return containing;
}
}
Of that 20% of the program's time is spent in that conditional.
Here is where the above function gets called:
// Get a list of lists of cell contents
List<List<GameObject>> cellContentsSet = cellManager.getCellContents();
// foreach item, only check items in the same cell
foreach (List<GameObject> cellMembers in cellContentsSet) {
foreach (GameObject item in cellMembers) {
// process collisions
}
}
//...
// Gets a list of list of cell contents (each sub list = 1 cell)
internal List<List<GameObject>> getCellContents() {
List<List<GameObject>> result = new List<List<GameObject>>();
foreach (CollisionCell cell in cellSet) {
result.Add(new List<GameObject>(cell.Containing.ToArray()));
}
return result;
}
Right now, I have to iterate over every cell - even empty ones. Perhaps this could be improved on somehow, but I'm not sure how to verify that a cell is empty without looking at it somehow. (Maybe I could implement something like sleeping objects, in some physics engines, where if an object will be still for a while it goes to sleep and is not included in calculations for every frame.)
What can I do to optimize this? (Also, I'm new to C# - are there any other glaring stylistic errors?)
When the game starts lagging out, the objects tend to be packed fairly tightly, so there's not that much motion going on. Perhaps I can take advantage of this somehow, writing a function to see if, given an object's current velocity, it can possibly leave its current cell before the next call to Update()
UPDATE 1 I decided to maintain a list of the objects that were found to be in the cell at last update, and check those first to see if they were still in the cell. Also, I maintained an area of the CollisionCell variable, when when the cell was filled I could stop looking. Here is my implementation of that, and it made the whole demo much slower:
// all the objects in this cell
private ICollection<GameObject> prevContaining;
private ICollection<GameObject> containing;
internal ICollection<GameObject> Containing {
get {
return containing;
}
}
/**
* To ensure that `containing` and `prevContaining` are up to date, this MUST be called once per Update() loop in which it is used.
* What is a good way to enforce this?
*/
public void updateContaining()
{
ICollection<GameObject> result = new HashSet<GameObject>();
uint area = checked((uint) bounds.Width * (uint) bounds.Height); // the area of this cell
// first, try to fill up this cell with objects that were in it previously
ICollection<GameObject>[] toSearch = new ICollection<GameObject>[] { prevContaining, engine.GameObjects };
foreach (ICollection<GameObject> potentiallyContained in toSearch) {
if (area > 0) { // redundant, but faster?
foreach (GameObject obj in potentiallyContained) {
if (obj.Position.X >= bounds.X &&
obj.Position.X < bounds.X + bounds.Width &&
obj.Position.Y >= bounds.Y &&
obj.Position.Y < bounds.Y + bounds.Height) {
result.Add(obj);
area -= checked((uint) Math.Pow(obj.Radius, 2)); // assuming objects are square
if (area <= 0) {
break;
}
}
}
}
}
prevContaining = containing;
containing = result;
}
UPDATE 2 I abandoned that last approach. Now I'm trying to maintain a pool of collidables (orphans), and remove objects from them when I find a cell that contains them:
internal List<List<GameObject>> getCellContents() {
List<GameObject> orphans = new List<GameObject>(engine.GameObjects);
List<List<GameObject>> result = new List<List<GameObject>>();
foreach (CollisionCell cell in cellSet) {
cell.updateContaining(ref orphans); // this call will alter orphans!
result.Add(new List<GameObject>(cell.Containing));
if (orphans.Count == 0) {
break;
}
}
return result;
}
// `orphans` is a list of GameObjects that do not yet have a cell
public void updateContaining(ref List<GameObject> orphans) {
ICollection<GameObject> result = new HashSet<GameObject>();
for (int i = 0; i < orphans.Count; i++) {
// 20% of processor time spent in this conditional
if (orphans[i].Position.X >= bounds.X &&
orphans[i].Position.X < bounds.X + bounds.Width &&
orphans[i].Position.Y >= bounds.Y &&
orphans[i].Position.Y < bounds.Y + bounds.Height) {
result.Add(orphans[i]);
orphans.RemoveAt(i);
}
}
containing = result;
}
This only yields a marginal improvement, not the 2x or 3x I'm looking for.
UPDATE 3 Again I abandoned the above approaches, and decided to let each object maintain its current cell:
private CollisionCell currCell;
internal CollisionCell CurrCell {
get {
return currCell;
}
set {
currCell = value;
}
}
This value gets updated:
// Run 1 cycle of this object
public virtual void Run()
{
position += velocity;
parent.CellManager.updateContainingCell(this);
}
CellManager code:
private IDictionary<Vector2, CollisionCell> cellCoords = new Dictionary<Vector2, CollisionCell>();
internal void updateContainingCell(GameObject gameObject) {
CollisionCell currCell = findContainingCell(gameObject);
gameObject.CurrCell = currCell;
if (currCell != null) {
currCell.Containing.Add(gameObject);
}
}
// null if no such cell exists
private CollisionCell findContainingCell(GameObject gameObject) {
if (gameObject.Position.X > GameEngine.GameWidth
|| gameObject.Position.X < 0
|| gameObject.Position.Y > GameEngine.GameHeight
|| gameObject.Position.Y < 0) {
return null;
}
// we'll need to be able to access these outside of the loops
uint minWidth = 0;
uint minHeight = 0;
for (minWidth = 0; minWidth + cellWidth < gameObject.Position.X; minWidth += cellWidth) ;
for (minHeight = 0; minHeight + cellHeight < gameObject.Position.Y; minHeight += cellHeight) ;
CollisionCell currCell = cellCoords[new Vector2(minWidth, minHeight)];
// Make sure `currCell` actually contains gameObject
Debug.Assert(gameObject.Position.X >= currCell.Bounds.X && gameObject.Position.X <= currCell.Bounds.Width + currCell.Bounds.X,
String.Format("{0} should be between lower bound {1} and upper bound {2}", gameObject.Position.X, currCell.Bounds.X, currCell.Bounds.X + currCell.Bounds.Width));
Debug.Assert(gameObject.Position.Y >= currCell.Bounds.Y && gameObject.Position.Y <= currCell.Bounds.Height + currCell.Bounds.Y,
String.Format("{0} should be between lower bound {1} and upper bound {2}", gameObject.Position.Y, currCell.Bounds.Y, currCell.Bounds.Y + currCell.Bounds.Height));
return currCell;
}
I thought this would make it better - now I only have to iterate over collidables, not all collidables * cells. Instead, the game is now hideously slow, delivering only 1/10th of its performance with my above approaches.
The profiler indicates that a different method is now the main hot spot, and the time to get neighbors for an object is trivially short. That method didn't change from before, so perhaps I'm calling it WAY more than I used to...
It spends 50% of its time in that function because you call that function a lot. Optimizing that one function will only yield incremental improvements to performance.
Alternatively, just call the function less!
You've already started down that path by setting up a spatial partitioning scheme (lookup Quadtrees to see a more advanced form of your technique).
A second approach is to break your N*N loop into an incremental form and to use a CPU budget.
You can allocate a CPU budget for each of the modules that want action during frame times (during Updates). Collision is one of these modules, AI might be another.
Let's say you want to run your game at 60 fps. This means you have about 1/60 s = 0.0167 s of CPU time to burn between frames. No we can split those 0.0167 s between our modules. Let's give collision 30% of the budget: 0.005 s.
Now your collision algorithm knows that it can only spend 0.005 s working. So if it runs out of time, it will need to postpone some tasks for later - you will make the algorithm incremental. Code for achieving this can be as simple as:
const double CollisionBudget = 0.005;
Collision[] _allPossibleCollisions;
int _lastCheckedCollision;
void HandleCollisions() {
var startTime = HighPerformanceCounter.Now;
if (_allPossibleCollisions == null ||
_lastCheckedCollision >= _allPossibleCollisions.Length) {
// Start a new series
_allPossibleCollisions = GenerateAllPossibleCollisions();
_lastCheckedCollision = 0;
}
for (var i=_lastCheckedCollision; i<_allPossibleCollisions.Length; i++) {
// Don't go over the budget
if (HighPerformanceCount.Now - startTime > CollisionBudget) {
break;
}
_lastCheckedCollision = i;
if (CheckCollision(_allPossibleCollisions[i])) {
HandleCollision(_allPossibleCollisions[i]);
}
}
}
There, now it doesn't matter how fast the collision code is, it will be done as quickly as is possible without affecting the user's perceived performance.
Benefits include:
The algorithm is designed to run out of time, it just resumes on the next frame, so you don't have to worry about this particular edge case.
CPU budgeting becomes more and more important as the number of advanced/time consuming algorithms increases. Think AI. So it's a good idea to implement such a system early on.
Human response time is less than 30 Hz, your frame loop is running at 60 Hz. That gives the algorithm 30 frames to complete its work, so it's OK that it doesn't finish its work.
Doing it this way gives stable, data-independent frame rates.
It still benefits from performance optimizations to the collision algorithm itself.
Collision algorithms are designed to track down the "sub frame" in which collisions happened. That is, you will never be so lucky as to catch a collision just as it happens - thinking you're doing so is lying to yourself.
I can help here; i wrote my own collision detection as an experiment. I think i can tell you right now that you won't get the performance you need without changing algorithms. Sure, the naive way is nice, but only works for so many items before collapsing. What you need is Sweep and prune. The basic idea goes like this (from my collision detection library project):
using System.Collections.Generic;
using AtomPhysics.Interfaces;
namespace AtomPhysics.Collisions
{
public class SweepAndPruneBroadPhase : IBroadPhaseCollider
{
private INarrowPhaseCollider _narrowPhase;
private AtomPhysicsSim _sim;
private List<Extent> _xAxisExtents = new List<Extent>();
private List<Extent> _yAxisExtents = new List<Extent>();
private Extent e1;
public SweepAndPruneBroadPhase(INarrowPhaseCollider narrowPhase)
{
_narrowPhase = narrowPhase;
}
public AtomPhysicsSim Sim
{
get { return _sim; }
set { _sim = null; }
}
public INarrowPhaseCollider NarrowPhase
{
get { return _narrowPhase; }
set { _narrowPhase = value; }
}
public bool NeedsNotification { get { return true; } }
public void Add(Nucleus nucleus)
{
Extent xStartExtent = new Extent(nucleus, ExtentType.Start);
Extent xEndExtent = new Extent(nucleus, ExtentType.End);
_xAxisExtents.Add(xStartExtent);
_xAxisExtents.Add(xEndExtent);
Extent yStartExtent = new Extent(nucleus, ExtentType.Start);
Extent yEndExtent = new Extent(nucleus, ExtentType.End);
_yAxisExtents.Add(yStartExtent);
_yAxisExtents.Add(yEndExtent);
}
public void Remove(Nucleus nucleus)
{
foreach (Extent e in _xAxisExtents)
{
if (e.Nucleus == nucleus)
{
_xAxisExtents.Remove(e);
}
}
foreach (Extent e in _yAxisExtents)
{
if (e.Nucleus == nucleus)
{
_yAxisExtents.Remove(e);
}
}
}
public void Update()
{
_xAxisExtents.InsertionSort(comparisonMethodX);
_yAxisExtents.InsertionSort(comparisonMethodY);
for (int i = 0; i < _xAxisExtents.Count; i++)
{
e1 = _xAxisExtents[i];
if (e1.Type == ExtentType.Start)
{
HashSet<Extent> potentialCollisionsX = new HashSet<Extent>();
for (int j = i + 1; j < _xAxisExtents.Count && _xAxisExtents[j].Nucleus.ID != e1.Nucleus.ID; j++)
{
potentialCollisionsX.Add(_xAxisExtents[j]);
}
HashSet<Extent> potentialCollisionsY = new HashSet<Extent>();
for (int j = i + 1; j < _yAxisExtents.Count && _yAxisExtents[j].Nucleus.ID != e1.Nucleus.ID; j++)
{
potentialCollisionsY.Add(_yAxisExtents[j]);
}
List<Extent> probableCollisions = new List<Extent>();
foreach (Extent e in potentialCollisionsX)
{
if (potentialCollisionsY.Contains(e) && !probableCollisions.Contains(e) && e.Nucleus.ID != e1.Nucleus.ID)
{
probableCollisions.Add(e);
}
}
foreach (Extent e2 in probableCollisions)
{
if (e1.Nucleus.DNCList.Contains(e2.Nucleus) || e2.Nucleus.DNCList.Contains(e1.Nucleus))
continue;
NarrowPhase.DoCollision(e1.Nucleus, e2.Nucleus);
}
}
}
}
private bool comparisonMethodX(Extent e1, Extent e2)
{
float e1PositionX = e1.Nucleus.NonLinearSpace != null ? e1.Nucleus.NonLinearPosition.X : e1.Nucleus.Position.X;
float e2PositionX = e2.Nucleus.NonLinearSpace != null ? e2.Nucleus.NonLinearPosition.X : e2.Nucleus.Position.X;
e1PositionX += (e1.Type == ExtentType.Start) ? -e1.Nucleus.Radius : e1.Nucleus.Radius;
e2PositionX += (e2.Type == ExtentType.Start) ? -e2.Nucleus.Radius : e2.Nucleus.Radius;
return e1PositionX < e2PositionX;
}
private bool comparisonMethodY(Extent e1, Extent e2)
{
float e1PositionY = e1.Nucleus.NonLinearSpace != null ? e1.Nucleus.NonLinearPosition.Y : e1.Nucleus.Position.Y;
float e2PositionY = e2.Nucleus.NonLinearSpace != null ? e2.Nucleus.NonLinearPosition.Y : e2.Nucleus.Position.Y;
e1PositionY += (e1.Type == ExtentType.Start) ? -e1.Nucleus.Radius : e1.Nucleus.Radius;
e2PositionY += (e2.Type == ExtentType.Start) ? -e2.Nucleus.Radius : e2.Nucleus.Radius;
return e1PositionY < e2PositionY;
}
private enum ExtentType { Start, End }
private sealed class Extent
{
private ExtentType _type;
public ExtentType Type
{
get
{
return _type;
}
set
{
_type = value;
_hashcode = 23;
_hashcode *= 17 + Nucleus.GetHashCode();
}
}
private Nucleus _nucleus;
public Nucleus Nucleus
{
get
{
return _nucleus;
}
set
{
_nucleus = value;
_hashcode = 23;
_hashcode *= 17 + Nucleus.GetHashCode();
}
}
private int _hashcode;
public Extent(Nucleus nucleus, ExtentType type)
{
Nucleus = nucleus;
Type = type;
_hashcode = 23;
_hashcode *= 17 + Nucleus.GetHashCode();
}
public override bool Equals(object obj)
{
return Equals(obj as Extent);
}
public bool Equals(Extent extent)
{
if (this.Nucleus == extent.Nucleus)
{
return true;
}
return false;
}
public override int GetHashCode()
{
return _hashcode;
}
}
}
}
and here's the code that does the insertion sort (more-or-less a direct translation of the pseudocode here):
/// <summary>
/// Performs an insertion sort on the list.
/// </summary>
/// <typeparam name="T">The type of the list supplied.</typeparam>
/// <param name="list">the list to sort.</param>
/// <param name="comparison">the method for comparison of two elements.</param>
/// <returns></returns>
public static void InsertionSort<T>(this IList<T> list, Func<T, T, bool> comparison)
{
for (int i = 2; i < list.Count; i++)
{
for (int j = i; j > 1 && comparison(list[j], list[j - 1]); j--)
{
T tempItem = list[j];
list.RemoveAt(j);
list.Insert(j - 1, tempItem);
}
}
}
IIRC, i was able to get an extremely large performance increase with that, especially when dealing with large numbers of colliding bodies. You'll need to adapt it for your code, but that's the basic premise behind sweep and prune.
The other thing i want to remind you is that you should use a profiler, like the one made by Red Gate. There's a free trial which should last you long enough.
It looks like you are looping through all the game objects just to see what objects are contained in a cell. It seems like a better approach would be to store the list of game objects that are in a cell for each cell. If you do that and each object knows what cells it is in, then moving objects between cells should be easy. This seems like it will yield the biggest performance gain.
Here is another optimization tip for determing what cells an object is in:
If you have already determined what cell(s) an object is in and know that based on the objects velocity it will not change cells for the current frame, there is no need to rerun the logic that determines what cells the object is in. You can do a quick check by creating a bounding box that contains all the cells that the object is in. You can then create a bounding box that is the size of the object + the velocity of the object for the current frame. If the cell bounding box contains the object + velocity bounding box, no further checks need to be done. If the object isn't moving, it's even easier and you can just use the object bounding box.
Let me know if that makes sense, or google / bing search for "Quad Tree", or if you don't mind using open source code, check out this awesome physics library: http://www.codeplex.com/FarseerPhysics
I'm in the exact same boat as you. I'm trying to create an overhead shooter and need to push efficiency to the max so I can have tons of bullets and enemies on screen at once.
I'd get all of my collidable objects in an array with a numbered index. This affords the opportunity to take advantage of an observation: if you iterate over the list fully for each item you'll be duplicating efforts. That is (and note, I'm making up variables names just to make it easier to spit out some pseudo-code)
if (objs[49].Intersects(objs[51]))
is equivalent to:
if (objs[51].Intersects(objs[49]))
So if you use a numbered index you can save some time by not duplicating efforts. Do this instead:
for (int i1 = 0; i1 < collidables.Count; i1++)
{
//By setting i2 = i1 + 1 you ensure an obj isn't checking collision with itself, and that objects already checked against i1 aren't checked again. For instance, collidables[4] doesn't need to check against collidables[0] again since this was checked earlier.
for (int i2 = i1 + 1; i2 < collidables.Count; i2++)
{
//Check collisions here
}
}
Also, I'd have each cell either have a count or a flag to determine if you even need to check for collisions. If a certain flag is set, or if the count is less than 2, than no need to check for collisions.
Just a heads up: Some people suggest farseer; which is a great 2D physics library for use with XNA. If you're in the market for a 3D physics engine for XNA, I've used bulletx (a c# port of bullet) in XNA projects to great effect.
Note: I have no affiliation to the bullet or bulletx projects.
An idea might be to use a bounding circle. Basically, when a Collidable is created, keep track of it's centre point and calculate a radius/diameter that contains the whole object. You can then do a first pass elimination using something like;
int r = C1.BoundingRadius + C2.BoundingRadius;
if( Math.Abs(C1.X - C2.X) > r && Math.Abs(C1.Y - C2.Y) > r )
/// Skip further checks...
This drops the comparisons to two for most objects, but how much this will gain you I'm not sure...profile!
There are a couple of things that could be done to speed up the process... but as far as I can see your method of checking for simple rectangular collision is just fine.
But I'd replace the check
if (obj.Position.X ....)
With
if (obj.Bounds.IntersercsWith(this.Bounds))
And I'd also replace the line
result.Add(new List<GameObject>(cell.Containing.ToArray()));
For
result.Add(new List<GameObject>(cell.Containing));
As the Containing property returns an ICollection<T> and that inherits the IEnumerable<T> that is accepted by the List<T> constructor.
And the method ToArray() simply iterates to the list returning an array, and this process is done again when creating the new list.
I know this Thread is old but i would say that the marked answar was completly wrong...
his code contain a fatal error and don´t give performance improvent´s it will take performence!
At first a little notic...
His code is created so that you have to call this code in your Draw methode but this is the wrong place for collision-detection. In your draw methode you should only draw nothing else!
But you can´t call HandleCollisions() in Update, because Update get a lots of more calls than Draw´s.
If you want call HandleCollisions() your code have to look like this... This code will prevent that your collision detection run more then once per frame.
private bool check = false;
protected override Update(GameTime gameTime)
{
if(!check)
{
check = true;
HandleCollisions();
}
}
protected override Draw(GameTime gameTime)
{
check = false;
}
Now let us take a look what´s wrong with HandleCollisions().
Example: We have 500 objects and we would do a check for every possible Collision without optimizing our detection.
With 500 object we should have 249500 collision checks (499X500 because we don´t want to check if an object collide with it´s self)
But with Frank´s code below we will lose 99.998% of your collosions (only 500 collision-checks will done). << THIS WILL INCREASE THE PERFORMENCES!
Why? Because _lastCheckedCollision will never be the same or greater then allPossibleCollisions.Length... and because of that you would only check the last index 499
for (var i=_lastCheckedCollision; i<_allPossibleCollisions.Length; i++)
_lastCheckedCollision = i;
//<< This could not be the same as _allPossibleCollisions.Length,
//because i have to be lower as _allPossibleCollisions.Length
you have to replace This
if (_allPossibleCollisions == null ||
_lastCheckedCollision >= _allPossibleCollisions.Length)
with this
if (_allPossibleCollisions == null ||
_lastCheckedCollision >= _allPossibleCollisions.Length - 1) {
so your whole code can be replaced by this.
private bool check = false;
protected override Update(GameTime gameTime)
{
if(!check)
{
check = true;
_allPossibleCollisions = GenerateAllPossibleCollisions();
for(int i=0; i < _allPossibleCollisions.Length; i++)
{
if (CheckCollision(_allPossibleCollisions[i]))
{
//Collision!
}
}
}
}
protected override Draw(GameTime gameTime)
{
check = false;
}
... this should be a lot of faster than your code ... and it works :D ...
RCIX answer should marked as correct because Frank´s answar is wrong.

Categories

Resources