- Home /
Improving Performance of Map Loading
My project will load map chunks from the resources, and only load the map chunks nearby. This works perfectly. The issue is right now I'm adding pathfinding to my project and I'm using raycasting to determine if an area is blocked by an object and is walkable.
This cycles through the entire map and sends out raycasts to determine unwalkable areas, and the Y points of each height level's floor.
private ArrayList clipMap = new ArrayList ();
private ArrayList heightMap = new ArrayList ();
private int squareCount = (int)(Config.MAP_SIZE / Config.CLIP_MAP_SQUARE_SIZE);
private Color[] colors = new Color[] {Color.red, Color.blue, Color.green, Color.yellow, Color.cyan};
public void BuildClipMap (float baseX, float baseZ, int HighestFloor)
{
for (int i = 0; i < HighestFloor; i++) {
int layerMask = (1 << Config.OBJECT_MAP_LAYER + i);
for (int x2 = 0; x2 < squareCount; x2+= 1) {
for (int z2 = 0; z2 < squareCount; z2+= 1) {
float x = x2 * Config.CLIP_MAP_SQUARE_SIZE;
float z = z2 * Config.CLIP_MAP_SQUARE_SIZE;
RaycastHit hit = new RaycastHit ();
if (Physics.Linecast (new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), 0, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)),
new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), Config.MAP_HEIGHT + 100, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)), out hit, layerMask)) {
if (hit.collider != null) {
clipMap.Add (new ClipMapEntry (i, Mathf.FloorToInt (x / Config.CLIP_MAP_SQUARE_SIZE), Mathf.FloorToInt (z / Config.CLIP_MAP_SQUARE_SIZE)));
Debug.DrawLine (new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), hit.point.y, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)), new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), hit.collider.bounds.size.y + hit.point.y, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)), colors.Length > i ? colors [i] : Color.gray, 120.0f);
}
}
}
}
}
}
public void BuildHeightMap (float baseX, float baseZ, int HighestFloor)
{
for (int i = 0; i < HighestFloor; i++) {
int layerMask = (1 << Config.FLOOR_MAP_LAYER + i);
for (int x2 = 0; x2 < squareCount; x2+= 1) {
for (int z2 = 0; z2 < squareCount; z2+= 1) {
float x = x2 * Config.CLIP_MAP_SQUARE_SIZE;
float z = z2 * Config.CLIP_MAP_SQUARE_SIZE;
RaycastHit hit2 = new RaycastHit ();
if (Physics.Linecast (new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), Config.MAP_HEIGHT + 100, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)),
new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), 0, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)), out hit2, layerMask)) {
if (hit2.collider != null) {
heightMap.Add (new HeightMapEntry (i, Mathf.FloorToInt (x / Config.CLIP_MAP_SQUARE_SIZE), Mathf.FloorToInt (z / Config.CLIP_MAP_SQUARE_SIZE), hit2.point.y));
Debug.DrawLine (new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), hit2.point.y, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)), new Vector3 (x + baseX + (Config.CLIP_MAP_SQUARE_SIZE / 2), hit2.point.y + 0.15f, z + baseZ + (Config.CLIP_MAP_SQUARE_SIZE / 2)), Color.white, 120.0f);
}
}
}
}
}
}
I can set the CLIP_MAP_SQUARE_SIZE to be bigger or smaller (bigger = faster, but much less exact). I want it to be 0.5, but calling this method on each map chunk (250x250) takes about 2000MS at 0.5, and 500MS at 1.0.
I need to reduce that time by a lot, and have 0.5 load at around 200MS Max. Does anyone have any ideas? I'd really appreciate it.
This is what these methods do (visually with debug):
There is no way you will be able to make this much faster than it already is. You will ins$$anonymous$$d need to find other methods to do your pathfinding
I actually got it down to about 200$$anonymous$$S by reducing the CLIP_$$anonymous$$AP_SQUARE_SIZE to 2.0 ins$$anonymous$$d of 0.5. I was noticing how many nodes were there under my character and it was completely unecessary. Now ins$$anonymous$$d of firing 250,000 raycasts it sends out about 16,000. Still, I would love it faster :/
The more accurate, the slower it will be. You need a different method all together if you want proper performance improvements :)
If you're loading map chunks, that I'm guessing are not too small, you counld just pre-calculate the grid and save it alongside the chunk.
Answer by GambinoInd · Jul 24, 2013 at 06:59 AM
After optimizing this like crazy by using Lists instead of ArrayLists, and also pre-computing the data the speed was relatively the same.
But, due to precomputing the data, loading the data used none of Unity's functions and could be run using a seperate thread. I started using seperate threads per map chunk, and this caused my entire computer to lag as it was using my entire processor. To fix this I isolated it to just one seperate thread, and it's running flawlessly now at 9 Milliseconds per map chunk, instead of 500 Milliseconds per map chunk earlier.
Thanks to all who helped me!!
I wrote my own secondary thread handler if anybody wishes to use it:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
public class SecondaryThread {
public delegate void Action();
private static Thread secondThread;
private static bool stopThread;
private static List<Action> functionList = new List<Action>();
public static void addTask(Action method) {
functionList.Add (method);
if(secondThread == null) {
secondThread = new Thread(new ThreadStart(loop));
secondThread.Start ();
}
}
public static void close() {
if(secondThread == null)
return;
stopThread = true;
secondThread.Interrupt();
secondThread.Abort();
}
private static void loop() {
while (stopThread == false) {
for(int i = 0; i < functionList.Count; i++) {
Action entry = (Action) functionList[i];
entry();
functionList.RemoveAt(i);
}
}
}
}
Use it like this:
private void methodName() {
doStuff();
}
SecondaryThread.addTask(methodName);
I assume you mean you could do the pre-loading in 9ms then leave the heavy stuff going in the background to finish whenever and not that putting it in another thread made the actual algorithm faster?
I'm still pretty shocked how slow importing the data was though... Care to share the algorithm? $$anonymous$$aybe we can improve it
Yes, Pre-loading. It's a 9$$anonymous$$S freeze on the main thread.
I'm sure we can, and I really appreciate it. It's still a bit messy, but it's much better than before:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public class Clip$$anonymous$$ap
{
private List<string> clip$$anonymous$$ap = new List<string> ();
private List<string> height$$anonymous$$ap = new List<string> ();
private int regionX, regionY;
private void ThreadAction ()
{
string path1 = "Assets/Resources/" + Config.HEIGHT_$$anonymous$$AP_DIRECTORY;
string fileName1 = path1 + regionX + "." + regionY + ".txt";
string heightText = File.ReadAllText(fileName1);
if (heightText != null) {
string[] text = heightText.Split ('-');
for (int i = 0; i < text.Length; i++) {
string[] subText = text [i].Split (',');
if (subText.Length == 4) {
height$$anonymous$$ap.Add (int.Parse (subText [0]) + "," + int.Parse (subText [1]) + "," + int.Parse (subText [2]) + "," + float.Parse (subText [3]));
}
}
}
string path2 = "Assets/Resources/" + Config.CLIP_$$anonymous$$AP_DIRECTORY;
string fileName2 = path2 + regionX + "." + regionY + ".txt";
string clipText = File.ReadAllText(fileName2);
if (clipText != null) {
string[] text = clipText.Split ('-');
for (int i = 0; i < text.Length; i++) {
string[] subText = text [i].Split (',');
if (subText.Length == 3) {
clip$$anonymous$$ap.Add (int.Parse (subText [0]) + "," + int.Parse (subText [1]) + "," + int.Parse (subText [2]));
}
}
}
}
public Clip$$anonymous$$ap (int regionX_, int regionY_)
{
regionX = regionX_;
regionY = regionY_;
SecondaryThread.addTask (this.ThreadAction);
}
public List<string> getClip$$anonymous$$ap ()
{
return clip$$anonymous$$ap;
}
public List<string> getHeight$$anonymous$$ap ()
{
return height$$anonymous$$ap;
}
}
Is there a reason you don't use a binary format? It'd be far faster to parse. Do you need it in a human-readable format? Without considering the overheads from the splits, the difference in file size alone would result in about 75% less reading.
If you do have a strong reason for keeping it human readable, you could perhaps use \n as the separator between entries, and read the file with File.ReadAllLines or iterate over File.ReadLines. This would save you the huge Split() at least, although without profiling it I can't be sure just how much difference it would make...
I actually just updated it to Binary Format. And that's a great idea, splitting it with \n. Split is such a bother sometimes, and it looks messy as well. Thank you very much!
it might be better to use ThreadPool.queueuserworkitem ins$$anonymous$$d of your SecondaryThread.addTask;
ThreadPool.queueuserworkitem(() => DoSomething(5));
void DoSomething(int num){
Debug.log(num);
}
Your answer

Follow this Question
Related Questions
How do I get my game to run faster? 4 Answers
performance optimization 5 Answers
Mesh collider cost 1 Answer
Advice about overdraw 1 Answer
Memory Managmenet 1 Answer