Created
April 10, 2015 00:14
-
-
Save twobob/f7b34a4b404a9cee44da to your computer and use it in GitHub Desktop.
Combined grid tracking, can handle particle list, terrain trees, GO colliders. Places collider / NavMeshObstacles. Uses trigger value to Invoke
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
public class GridValue | |
{ | |
public enum EntityType | |
{ | |
None, | |
Tree, | |
Rock | |
} | |
public string id; | |
public Vector3 position; | |
public EntityType entityType; | |
public GameObject go; | |
} | |
public class Grid | |
{ | |
public static Dictionary<string, Grid> grids = new Dictionary<string, Grid>(); | |
private Dictionary<int, Dictionary<string, GridValue>> cells = new Dictionary<int, Dictionary<string, GridValue>>(); | |
private Dictionary<string, GridValue> objectIndex = new Dictionary<string, GridValue>(); | |
private Dictionary<string, int> cellsIndex = new Dictionary<string, int>(); | |
public static float coordOffset = 5000f; | |
private int max; | |
private int cellSize = 0; | |
private float convFactor; | |
private int width; | |
private int cellCount; | |
private List<GridValue> result; | |
public Grid(int max, int cellSize) | |
{ | |
this.max = max; | |
this.cellSize = cellSize; | |
this.convFactor = 1.0f / this.cellSize; | |
this.width = (int)(this.max / this.cellSize); | |
this.cellCount = this.width * this.width; | |
} | |
public int Count() | |
{ | |
return objectIndex.Count; | |
} | |
public static Grid FindOrCreate(string id, int max, int cellSize) | |
{ | |
if (!grids.ContainsKey(id)) | |
{ | |
grids[id] = new Grid(max, cellSize); | |
} | |
return grids[id]; | |
} | |
private HashSet<int> __mcells; | |
public HashSet<int> CellsWithinBounds(float x, float z) | |
{ | |
x += coordOffset; | |
z += coordOffset; | |
//HashSet<int> | |
__mcells = new HashSet<int>(); | |
int offset = this.cellSize; | |
int startX = (int)(x - offset); | |
int startZ = (int)(z - offset); | |
int endX = (int)(x + offset); | |
int endZ = (int)(z + offset); | |
for (int rowNum = startX; rowNum <= endX; rowNum += offset) | |
{ | |
for (int colNum = startZ; colNum <= endZ; colNum += offset) | |
{ | |
if (rowNum >= 0 && colNum >= 0) | |
{ | |
__mcells.Add(Hash(rowNum, colNum)); | |
} | |
} | |
} | |
return __mcells; | |
} | |
private HashSet<int> __cells; | |
private ICollection<GridValue> __gridValues; | |
public List<GridValue> Neighbors(float x, float z, GridValue.EntityType entityType) | |
{ | |
result = new List<GridValue>(); | |
// ICollection<GridValue> gridValues; | |
__gridValues = null; | |
//HashSet<int> | |
__cells = CellsWithinBounds(x, z); | |
foreach (int cell in __cells) | |
{ | |
__gridValues = GridValuesInCell(cell); | |
if (__gridValues == null) | |
{ | |
continue; | |
} | |
foreach (GridValue gridValue in __gridValues) | |
{ | |
if (gridValue != null) | |
{ | |
if (entityType == GridValue.EntityType.None) | |
{ | |
result.Add(gridValue); | |
} | |
else if (gridValue.entityType == entityType) | |
{ | |
result.Add(gridValue); | |
} | |
} | |
} | |
} | |
return result; | |
} | |
public ICollection<GridValue> GridValuesInCell(int cell) | |
{ | |
if (cells.ContainsKey(cell)) | |
{ | |
return cells[cell].Values; | |
} | |
else | |
{ | |
return null; | |
} | |
} | |
public void Set(GridValue gridValue) | |
{ | |
bool hasExisting = false; | |
int oldCellValue = -1; | |
string id = gridValue.id; | |
if (objectIndex.ContainsKey(id)) | |
{ | |
hasExisting = true; | |
oldCellValue = cellsIndex[id]; | |
} | |
int cell = Hash(gridValue.position.x + coordOffset, gridValue.position.z + coordOffset); | |
Dictionary<string, GridValue> cellGridValues; | |
if (hasExisting && oldCellValue != cell) | |
{ | |
if (cells.ContainsKey(oldCellValue)) | |
{ | |
cellGridValues = cells[oldCellValue]; | |
if (cellGridValues.ContainsKey(id)) | |
{ | |
cellGridValues.Remove(id); | |
} | |
if (cellGridValues.Count == 0) | |
{ | |
cells.Remove(oldCellValue); | |
} | |
} | |
} | |
cellsIndex[id] = cell; | |
objectIndex[id] = gridValue; | |
if (cells.ContainsKey(cell)) | |
{ | |
cellGridValues = cells[cell]; | |
cellGridValues[id] = gridValue; | |
} | |
else | |
{ | |
cellGridValues = new Dictionary<string, GridValue>(); | |
cellGridValues[id] = gridValue; | |
cells[cell] = cellGridValues; | |
} | |
} | |
public int Hash(float x, float z) | |
{ | |
return (int)((x * this.convFactor)) + (int)((z * this.convFactor)) * this.width; | |
} | |
} | |
public sealed class Objectpool2<T> | |
where T : new() | |
{ | |
private readonly int size; | |
private readonly Queue<T> queue; | |
private int count = 0; | |
/// <summary> | |
/// Initializes a new instance of the ObjectPool class. | |
/// </summary> | |
/// <param name="size">The size of the object pool.</param> | |
public Objectpool2(int size) | |
{ | |
this.size = size; | |
queue = new Queue<T>(); | |
} | |
public int Count() | |
{ | |
return queue.Count; | |
} | |
/// <summary> | |
/// Retrieves an item from the pool. | |
/// </summary> | |
/// <returns>The item retrieved from the pool.</returns> | |
public T Get() | |
{ | |
if (queue.Count > 0) | |
{ | |
T item = queue.Dequeue(); | |
count--; | |
return item; | |
} | |
else | |
{ | |
Debug.Log("Queue is empty count=" + count + " size=" + size); | |
return default(T); | |
} | |
} | |
/// <summary> | |
/// Places an item in the pool. | |
/// </summary> | |
/// <param name="item">The item to place to the pool.</param> | |
public void Put(T item) | |
{ | |
queue.Enqueue(item); | |
count++; | |
} | |
} | |
public class GridTracking : MonoBehaviour | |
{ | |
public int MaxCapacity = 5000; | |
public int ObjectPoolSize = 200; | |
public int SearchDistance = 20; | |
public static int __MaxCapacity; | |
public static int __ObjectPoolSize = 200; | |
public static int __SearchDistance = 20; | |
private Grid grid; | |
private Dictionary<string, GridValue> objectIndex = new Dictionary<string, GridValue>(5000 * 4); | |
private Objectpool2<GameObject> objectPool = new Objectpool2<GameObject>(200); | |
private int removeCheckCount; | |
Transform player; | |
void Start() | |
{ | |
objectIndex = new Dictionary<string, GridValue>(MaxCapacity * 4); | |
objectPool = new Objectpool2<GameObject>(ObjectPoolSize); | |
__MaxCapacity = MaxCapacity; | |
__ObjectPoolSize = ObjectPoolSize; | |
__SearchDistance = SearchDistance; | |
if (GameManager.playerEntity == null) | |
{ | |
player = GameObject.Find("TestController").transform; // or w/e | |
} | |
else | |
{ | |
player = GameManager.playerEntity.transform; // I created a placeholder static GameManager Class | |
} | |
grid = Grid.FindOrCreate("terrain", MaxCapacity, SearchDistance); | |
StartRepeatingCheckForParticleCount(); | |
} | |
private void StartRepeatingCheckForParticleCount() | |
{ | |
InvokeRepeating("CreateGridsWhenParticlesPlaced", 0, 0.05F); // skim edges | |
} | |
private void CreateGridsWhenParticlesPlaced() | |
{ | |
// Now actually create some stuff. | |
// Once the value is indicated in the manager class that we are ready. | |
if (!GameManager.TreesPlaced) | |
{ | |
return; | |
} | |
CreateGrid(); | |
CreatePool(); | |
InvokeRepeating("UpdateGrid", 0.005f, 2f); | |
CancelInvoke("CreateGridsWhenParticlesPlaced"); | |
Debug.Log("Cancelled CreateGridsWhenParticlesPlaced as complete "); | |
} | |
public static void SetMeshColliders(GameObject go, bool enabled) | |
{ | |
MeshCollider col; | |
col = go.GetComponent<MeshCollider>() as MeshCollider; | |
if (col == null) | |
{ | |
foreach (Transform s in go.transform) | |
{ | |
col = s.GetComponent<MeshCollider>() as MeshCollider; | |
if (col != null) | |
{ | |
col.enabled = enabled; | |
} | |
} | |
} | |
else | |
{ | |
col.enabled = enabled; | |
} | |
} | |
public static void CreateGrid() | |
{ | |
Grid grid = Grid.FindOrCreate("terrain", __MaxCapacity, __SearchDistance); | |
// GAMEOBJECT IN A PARENT VERSION | |
foreach (Transform t in GameObject.Find("Items").transform) | |
{ | |
foreach (Transform s in t.gameObject.transform) | |
{ | |
GridValue gridValue = new GridValue(); | |
gridValue.position = s.position; | |
gridValue.id = "rock+" + s.name; | |
gridValue.go = s.gameObject; | |
gridValue.entityType = GridValue.EntityType.Rock; | |
grid.Set(gridValue); | |
} | |
} | |
// TERRAIN TREE VERSION | |
//int treecount = 0; | |
//foreach (Transform t in GameObject.Find("Terrains").transform) | |
//{ | |
// Terrain terrain = t.GetComponent<Terrain>() as Terrain; | |
// foreach (TreeInstance ti in terrain.terrainData.treeInstances) | |
// { | |
// Vector3 tpos = (Vector3.Scale(terrain.terrainData.size, ti.position) + t.position); | |
// GridValue gridValue = new GridValue(); | |
// gridValue.position = tpos; | |
// gridValue.id = "tree_" + treecount; | |
// gridValue.entityType = GridValue.EntityType.Tree; | |
// grid.Set(gridValue); | |
// treecount++; | |
// } | |
//} | |
// PARTICLE TREE VERSION | |
int treecount = 0; | |
ParticleSystem.Particle p; | |
for (int i = 0; i < PlaceParticles.ParticleList.Length; i++) | |
{ | |
p = PlaceParticles.ParticleList[i]; | |
Vector3 tpos = p.position; | |
GridValue gridValue = new GridValue(); | |
gridValue.position = tpos; | |
gridValue.id = "tree_" + treecount; | |
gridValue.entityType = GridValue.EntityType.Tree; | |
grid.Set(gridValue); | |
treecount++; | |
} | |
// Debug.Log("Grid size " + grid.Count()); | |
} | |
void CreatePool() | |
{ | |
GameObject trees = new GameObject(); | |
trees.name = "trees"; | |
for (int i = 0; i < __ObjectPoolSize; i++) | |
{ | |
GameObject go = new GameObject(); | |
go.transform.parent = trees.transform; | |
go.tag = "harvestable"; | |
go.name = "Tree"; | |
go.transform.position = Vector3.zero; | |
Rigidbody rigid = go.AddComponent<Rigidbody>(); | |
rigid.isKinematic = true; | |
CapsuleCollider col = go.AddComponent<CapsuleCollider>() as CapsuleCollider; | |
col.center = Vector3.zero; | |
col.height = 10f; | |
col.radius = 0.4f; | |
col.direction = 1; | |
col.enabled = false; | |
NavMeshObstacle nmo = go.AddComponent<NavMeshObstacle>() as NavMeshObstacle; | |
nmo.center = Vector3.zero; | |
nmo.shape = NavMeshObstacleShape.Capsule; | |
nmo.height = 10f; | |
nmo.radius = 0.4f; | |
nmo.enabled = false; | |
objectPool.Put(go); | |
} | |
} | |
// Update is called once per frame | |
void UpdateGrid() | |
{ | |
List<GridValue> gridValues = grid.Neighbors(player.position.x, player.position.z, 0); | |
if (gridValues.Count > 0) | |
{ | |
AddNew(gridValues); | |
if (removeCheckCount >= 1) | |
{ | |
removeCheckCount = 0; | |
RemoveOld(gridValues); | |
//Debug.Log ("Gridvalues count " + gridValues.Count + " indexcount " + objectIndex.Count + " queuecount " + objectPool.Count ()); | |
} | |
else | |
{ | |
removeCheckCount++; | |
} | |
} | |
} | |
void AddNew(List<GridValue> gridValues) | |
{ | |
int addCount = 0; | |
foreach (GridValue gridValue in gridValues) | |
{ | |
if (AddCollider(gridValue)) | |
{ | |
addCount++; | |
} | |
if (addCount > 10) | |
{ | |
return; | |
} | |
} | |
} | |
private GridValue _tempGridValueReference; | |
void RemoveOld(List<GridValue> gridValues) | |
{ | |
HashSet<GridValue> toCheck = new HashSet<GridValue>(gridValues); | |
List<string> idsToRemove = new List<string>(); | |
foreach (GridValue old in objectIndex.Values) | |
{ | |
if (!toCheck.Contains(old)) | |
{ | |
idsToRemove.Add(old.id); | |
} | |
} | |
int removeCount = 0; | |
for (int i = 0; i < idsToRemove.Count; i++) | |
{ | |
GridValue gridValue = objectIndex[idsToRemove[i]]; | |
RemoveCollider(gridValue); | |
objectIndex.Remove(idsToRemove[i]); | |
removeCount++; | |
if (removeCount > 20) | |
{ | |
break; | |
} | |
} | |
//if (removeCount > 0) | |
//{ | |
// Debug.Log(removeCount); // unelide to get a readout | |
//} | |
} | |
void RemoveCollider(GridValue gridValue) | |
{ | |
if (gridValue.entityType == GridValue.EntityType.Rock) | |
{ | |
SetMeshColliders(gridValue.go, false); | |
} | |
else if (gridValue.entityType == GridValue.EntityType.Tree) | |
{ | |
GameObject go = gridValue.go; | |
go.transform.position = Vector3.zero; | |
go.GetComponent<CapsuleCollider>().enabled = false; | |
go.GetComponent<NavMeshObstacle>().enabled = false; | |
objectPool.Put(go); | |
gridValue.go = null; | |
} | |
} | |
bool AddCollider(GridValue gridValue) | |
{ | |
if (objectIndex.ContainsKey(gridValue.id)) | |
{ | |
return false; | |
} | |
if (gridValue.entityType == GridValue.EntityType.Rock) | |
{ | |
SetMeshColliders(gridValue.go, true); | |
} | |
else if (gridValue.entityType == GridValue.EntityType.Tree) | |
{ | |
GameObject go = objectPool.Get(); | |
go.transform.position = gridValue.position; | |
go.GetComponent<CapsuleCollider>().enabled = true; | |
go.GetComponent<NavMeshObstacle>().enabled = true; | |
gridValue.go = go; | |
} | |
objectIndex[gridValue.id] = gridValue; | |
return true; | |
} | |
} |
Err. Looks okay.
I would be very happy to see a gist of it :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I've added a
Remove(id)
method to the Grid, and it seems to work, but I'm not sure if there are any side effects I'm not noticing with it:Do you think I should add anything to it?
Btw, I changed the ids to integers instead of strings.