├── Classes ├── Heatmap.cs └── StringUtility.cs ├── Demo ├── Sidescroller │ ├── Art │ │ ├── Art Credits.txt │ │ ├── Click.png │ │ ├── block.png │ │ ├── blue.png │ │ ├── bonus.png │ │ ├── bonus_used.png │ │ ├── bush.png │ │ ├── character │ │ │ ├── front.png │ │ │ ├── jump.png │ │ │ ├── side.png │ │ │ ├── walk_animation.fla │ │ │ └── walk_animation.swf │ │ ├── cloud_1.png │ │ ├── cloud_2.png │ │ ├── cloud_3.png │ │ ├── coin.png │ │ ├── crate.png │ │ ├── enemies │ │ │ ├── fly_dead.png │ │ │ ├── fly_fly.png │ │ │ ├── fly_normal.png │ │ │ ├── slime_dead.png │ │ │ ├── slime_normal.png │ │ │ └── slime_walk.png │ │ ├── fence.png │ │ ├── fence_broken.png │ │ ├── grass.png │ │ ├── ground.png │ │ ├── ground_cave.png │ │ ├── hill_long.png │ │ ├── hill_short.png │ │ ├── shroom.png │ │ ├── spikes.png │ │ └── water.png │ ├── Sidescroller.unity │ └── SidescrollerDebugPoints.txt └── Top Down │ ├── Art │ ├── Materials │ │ ├── dirt.mat │ │ └── grass.mat │ ├── dirt.png │ └── grass.png │ ├── TopDownScene.unity │ └── TopDownSceneDebugPoints.txt ├── Editor ├── EditorExtensions.cs └── HeatmapEditor.cs ├── LICENSE ├── README.md ├── Readme.txt ├── Resources └── UnlitMaterial.mat └── Scripts ├── MouseClickDemo.cs └── Tracker.cs /Classes/Heatmap.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | #if UNITY_EDITOR 3 | using UnityEditor; 4 | #endif 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | /*! \The Heatmap class is responsible for creating the "heat" and textures. 8 | * 9 | * Contains all methods necessary for creating a Heatmap. 10 | */ 11 | public class Heatmap : ScriptableObject { 12 | 13 | #if UNITY_EDITOR 14 | [MenuItem("Window/Heatmap Documentation")] 15 | public static void OpenDocs() 16 | { 17 | Application.OpenURL("http://paraboxstudios.com/heatmap/docs/annotated.html"); 18 | } 19 | #endif 20 | 21 | public static GameObject projectionPlane; //!< A static reference to the plane which is used to display a heatmap. 22 | 23 | public static int RED_THRESHOLD = 235; //!< Red threshold. 24 | /// Minimum alpha a point must have to be red. 25 | public static int GREEN_THRESHOLD = 200; //!< Green threshold. 26 | /// Minimum alpha a point must have to be green. 27 | public static int BLUE_THRESHOLD = 150; //!< Blue threshold. 28 | /// Minimum alpha a point must have to be Blue. 29 | public static int MINIMUM_THRESHOLD = 100; //!< Minimum threshold. 30 | /// Minimum alpha a point must have to be rendered at all. 31 | 32 | /*! 33 | * Creates a Heatmap image given a set of world points. 34 | * 35 | * 36 | * This method accepts a series of world points, and returns a transparent overlay of the heatmap. Usually you will want to pair this call with CreateRenderPlane() to actually view the heatmap. 37 | * 38 | * An array of Vector3 points to be translated into heatmap points. 39 | * The camera to render from. If passed a null value, CreateHeatmap() attempts to use Camera.main. 40 | * Raidus in pixels that each point should create. Larger radii produce denser heatmaps, and vice-versa. 41 | * Returns a new Texture2D containing a transparent overlay of the heatmap. 42 | */ 43 | public static Texture2D CreateHeatmap(Vector3[] worldPoints, Camera cam, int radius) { 44 | 45 | if(cam == null) { 46 | if(Camera.main == null) { 47 | Debug.LogWarning("No camera found. Returning an empty texture."); 48 | return new Texture2D(0, 0); 49 | } 50 | else 51 | cam = Camera.main; 52 | } 53 | 54 | // Create new texture 55 | // Texture2D map = new Texture2D(Screen.width, Screen.height, TextureFormat.ARGB32, false); 56 | Texture2D map = new Texture2D( (int)cam.pixelWidth, (int)cam.pixelHeight, TextureFormat.ARGB32, false); 57 | 58 | // Set texture to alpha-fied state 59 | map.SetPixels(Heatmap.ColorArray(new Color(1f, 1f, 1f, 0f), map.width*map.height), 0); 60 | 61 | // Convert world to screen points 62 | Vector2[] points = new Vector2[worldPoints.Length]; 63 | for(int i = 0; i < worldPoints.Length; i++) { 64 | points[i] = cam.WorldToScreenPoint(worldPoints[i]); 65 | } 66 | 67 | /*** Generate Grayscale Values ***/ 68 | { 69 | int x2; // the offset x val in img coordinates 70 | int y2; // the offset y val in img coordinates (0,0) - (maxX, maxY) 71 | float pointAlpha = .9f; // The alpha that the darkest pixel will be in a poitn. 72 | Color color = new Color(1f, 1f, 1f, pointAlpha); 73 | int lineWidth = 1;//(int)(radius * .05f); 74 | Dictionary pixelAlpha = new Dictionary(); 75 | 76 | for(int i = 0; i < points.Length; i++) // generate alpha add for each point and a specified circumference 77 | { 78 | pixelAlpha.Clear(); 79 | for(int r = 0; r < radius; r+=lineWidth) // draw and fill them circles 80 | { 81 | for(int angle=0; angle<360; angle++) 82 | { 83 | x2 = (int)(r * Mathf.Cos(angle))+(int)points[i].x; 84 | y2 = (int)(r * Mathf.Sin(angle))+(int)points[i].y; 85 | 86 | // This could be sped up 87 | for(int y = y2; y > y2-lineWidth; y--) { 88 | for(int x = x2; x < x2+lineWidth; x++) { 89 | Vector2 coord = new Vector2(x, y); 90 | 91 | if(pixelAlpha.ContainsKey(coord)) 92 | pixelAlpha[coord] = color; 93 | else 94 | pixelAlpha.Add(new Vector2(x, y), color); 95 | } 96 | } 97 | } 98 | color = new Color(color.r, color.g, color.b, color.a - (pointAlpha/( (float)radius/lineWidth)) ); 99 | } 100 | 101 | // Since the radial fill code overwrites it's own pixels, make sure to only add finalized alpha to 102 | // old values. 103 | foreach (KeyValuePair keyval in pixelAlpha) 104 | { 105 | Vector2 coord = keyval.Key; 106 | Color previousColor = map.GetPixel((int)coord.x, (int)coord.y); 107 | Color newColor = keyval.Value; 108 | map.SetPixel((int)coord.x, (int)coord.y, new Color(newColor.r, newColor.b, newColor.g, newColor.a + previousColor.a)); 109 | } 110 | 111 | // Reset color for next point 112 | color = new Color(color.r, color.g, color.b, pointAlpha); 113 | } 114 | } 115 | 116 | map.Apply(); 117 | 118 | map.SetPixels( Colorize(map.GetPixels(0)), 0); 119 | 120 | map.Apply(); 121 | 122 | return map; 123 | } 124 | 125 | /*! 126 | * Creates a gameObject in front of the camera, and applies the supplied texture. 127 | * 128 | * 129 | * Works best with an orthographic camera. It builds the mesh using camera dimensions translated into world space. Use this in conjunction with CreateHeatmap() to create a heatmap and capture a screenshot with the heatmap texture overlaying the world. 130 | * 131 | * The heatmap image. 132 | */ 133 | public static void CreateRenderPlane(Texture2D map) 134 | { 135 | CreateRenderPlane(map, null); 136 | } 137 | 138 | /*! 139 | * Creates a gameObject in front of the camera, and applies the supplied texture. 140 | * 141 | * 142 | * Works best with an orthographic camera. It builds the mesh using camera dimensions translated into world space. Use this in conjunction with CreateHeatmap() to create a heatmap and capture a screenshot with the heatmap texture overlaying the world. 143 | * 144 | * The heatmap image. 145 | * The camera to render from. If passed a null value, CreateRenderPlane() attempts to use Camera.main. 146 | */ 147 | public static void CreateRenderPlane(Texture2D map, Camera cam) 148 | { 149 | if(cam == null) { 150 | if(Camera.main == null) { 151 | Debug.LogWarning("No camera found. Plane not created."); 152 | return; 153 | } 154 | else 155 | cam = Camera.main; 156 | } 157 | 158 | // Create Plane to project Heatmap 159 | Mesh m = new Mesh(); 160 | Vector3[] vertices = new Vector3[4]; 161 | int[] triangles = new int[6] { 162 | 2, 1, 0, 163 | 2, 3, 1 164 | }; 165 | 166 | vertices[0] = cam.ScreenToWorldPoint(new Vector2(0f, 0f)); 167 | vertices[1] = cam.ScreenToWorldPoint(new Vector2(cam.pixelWidth, 0f)); 168 | vertices[2] = cam.ScreenToWorldPoint(new Vector2(0f, cam.pixelHeight)); 169 | vertices[3] = cam.ScreenToWorldPoint(new Vector2(cam.pixelWidth, cam.pixelHeight)); 170 | 171 | Vector2[] uvs = new Vector2[4]; 172 | 173 | uvs[0] = new Vector2(0f, 0f); 174 | uvs[1] = new Vector2(1f, 0f); 175 | uvs[2] = new Vector2(0f, 1f); 176 | uvs[3] = new Vector2(1f, 1f); 177 | 178 | m.vertices = vertices; 179 | m.triangles = triangles; 180 | m.uv = uvs; 181 | m.RecalculateNormals(); 182 | m.Optimize(); 183 | 184 | // Hook it all up 185 | if(projectionPlane == null) { 186 | projectionPlane = new GameObject(); 187 | projectionPlane.AddComponent(); 188 | projectionPlane.AddComponent(); 189 | } else { 190 | DestroyImmediate(projectionPlane.GetComponent().sharedMesh); 191 | DestroyImmediate(projectionPlane.GetComponent().sharedMaterial.mainTexture); 192 | } 193 | 194 | projectionPlane.GetComponent().sharedMesh = m; 195 | MeshRenderer mr = projectionPlane.GetComponent(); 196 | Material mat = (Material)Resources.Load("UnlitMaterial"); 197 | mat.mainTexture = map; 198 | mr.sharedMaterial = mat; 199 | 200 | projectionPlane.name = "Heatmap Render Plane"; 201 | // Move the heatmap gameobject in front of the camera 202 | projectionPlane.transform.position = new Vector3(0f, 0f, 0f); 203 | projectionPlane.transform.Translate(Vector3.forward, cam.transform); 204 | } 205 | 206 | /*! 207 | * Creates and saves a screenshot. 208 | * 209 | * 210 | * Call this to take a screenshot with the current camera. Will not overwrite images if path already exists. 211 | * 212 | * The path to save screenshot image to. Path is relative to Unity project. Ex: "Assets/MyScreenshot.png" 213 | * 214 | * Returns a string containing the actual path image was saved to. This may be different than passed string if the path previously existed. 215 | * 216 | */ 217 | public static string Screenshot(string path) 218 | { 219 | return Heatmap.Screenshot(path, (Camera)null); 220 | } 221 | 222 | /*! 223 | * Creates and saves a screenshot. 224 | * 225 | * 226 | * Call this to take a screenshot with the current camera. Will not overwrite images if path already exists. 227 | * 228 | * The path to save screenshot image to. Path is relative to Unity project. Ex: "Assets/MyScreenshot.png" 229 | * The camera to render from. If passed a null value, CreateRenderPlane() attempts to use Camera.main. 230 | * 231 | * Returns a string containing the actual path image was saved to. This may be different than passed string if the path previously existed. 232 | * 233 | */ 234 | public static string Screenshot(string path, Camera cam) 235 | { 236 | if(cam == null) { 237 | if(Camera.main == null) 238 | return "Error! No camera found."; 239 | else 240 | cam = Camera.main; 241 | } 242 | 243 | foreach(Camera c in Camera.allCameras) 244 | c.enabled = false; 245 | 246 | cam.enabled = true; 247 | int i = 0; 248 | 249 | while(System.IO.File.Exists(path)) 250 | path = path.Replace(".png", ++i + ".png"); 251 | 252 | Application.CaptureScreenshot(path, 4); 253 | 254 | #if UNITY_EDITOR 255 | AssetDatabase.Refresh(); 256 | #endif 257 | 258 | return path; 259 | } 260 | 261 | /*! \brief Destroy any temporary objects created by the Heatmap class. 262 | * 263 | * By default, CreateHeatmap() creates a plane situated in front of the 264 | * camera to display the resulting heatmap. Call Release() to destroy 265 | * the heatmap image and mesh. 266 | */ 267 | public static void DestroyHeatmapObjects() 268 | { 269 | if(projectionPlane) { 270 | if(projectionPlane.GetComponent().sharedMaterial.mainTexture != null) 271 | DestroyImmediate(projectionPlane.GetComponent().sharedMaterial.mainTexture); 272 | DestroyImmediate(projectionPlane); 273 | } 274 | } 275 | 276 | public static Color[] ColorArray(Color col, int arraySize) 277 | { 278 | Color[] colArr = new Color[arraySize]; 279 | for(int i = 0; i < colArr.Length; i++) 280 | { 281 | colArr[i] = col; 282 | } 283 | return colArr; 284 | } 285 | 286 | /* 287 | * !!! 288 | * The Colorize() function is a modified version of the Colorize() method found 289 | * in the Mapex library. This is the license associated with it. This license 290 | * does not apply to the rest of the codebase included in this project, as it is 291 | * covered by the Unity Asset Store EULA. 292 | * 293 | * Copyright (C) 2011 by Vinicius Carvalho (vinnie@androidnatic.com) 294 | * 295 | * Permission is hereby granted, free of charge, to any person obtaining a copy 296 | * of this software and associated documentation files (the "Software"), to deal 297 | * in the Software without restriction, including without limitation the rights 298 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 299 | * copies of the Software, and to permit persons to whom the Software is 300 | * furnished to do so, subject to the following conditions: 301 | * 302 | * The above copyright notice and this permission notice shall be included in 303 | * all copies or substantial portions of the Software. 304 | * 305 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 306 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 307 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 308 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 309 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 310 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 311 | * THE SOFTWARE. 312 | */ 313 | public static Color[] Colorize(Color[] pixels) { 314 | for (int i = 0; i < pixels.Length; i++) { 315 | 316 | float r = 0, g = 0, b = 0, tmp = 0; 317 | pixels[i] *= 255f; 318 | 319 | float alpha = pixels[i].a; 320 | 321 | if (alpha == 0) { 322 | continue; 323 | } 324 | 325 | if (alpha <= 255 && alpha >= RED_THRESHOLD) { 326 | tmp = 255 - alpha; 327 | r = 255 - tmp; 328 | g = tmp * 12f; 329 | } else if (alpha <= (RED_THRESHOLD - 1) && alpha >= GREEN_THRESHOLD) { 330 | tmp = (RED_THRESHOLD - 1) - alpha; 331 | r = 255 - (tmp * 8f); 332 | g = 255; 333 | } else if (alpha <= (GREEN_THRESHOLD - 1) && alpha >= BLUE_THRESHOLD) { 334 | tmp = (GREEN_THRESHOLD - 1) - alpha; 335 | g = 255; 336 | b = tmp * 5; 337 | } else if (alpha <= (BLUE_THRESHOLD - 1) && alpha >= MINIMUM_THRESHOLD) { 338 | tmp = (BLUE_THRESHOLD - 1) - alpha; 339 | g = 255 - (tmp * 5f); 340 | b = 255; 341 | } else 342 | b = 255; 343 | pixels[i] = new Color(r, g, b, alpha / 2f); 344 | pixels[i] = NormalizeColor(pixels[i]); 345 | } 346 | 347 | return pixels; 348 | } 349 | 350 | public static Color NormalizeColor(Color col) 351 | { 352 | return new Color( col.r / 255f, col.g / 255f, col.b / 255f, col.a / 255f); 353 | } 354 | 355 | public static Bounds WorldBounds() 356 | { 357 | Object[] allGameObjects = GameObject.FindSceneObjectsOfType(typeof(GameObject)); 358 | 359 | GameObject p = new GameObject(); 360 | foreach(GameObject g in allGameObjects) 361 | g.transform.parent = p.transform; 362 | 363 | Component[] meshFilters = p.GetComponentsInChildren(typeof(MeshFilter)); 364 | 365 | Vector3 min, max; 366 | if(meshFilters.Length > 0) 367 | { 368 | min = ((MeshFilter)meshFilters[0]).gameObject.transform.TransformPoint(((MeshFilter)meshFilters[0]).sharedMesh.vertices[0]); 369 | max = min; 370 | } 371 | else 372 | { 373 | return new Bounds(); 374 | } 375 | 376 | foreach(MeshFilter mf in meshFilters) { 377 | Vector3[] v = mf.sharedMesh.vertices; 378 | 379 | for(int i = 0; i < v.Length; i++) 380 | { 381 | Vector3 w = mf.gameObject.transform.TransformPoint(v[i]); 382 | if(w.x > max.x) max.x = w.x; 383 | if(w.x < min.x) min.x = w.x; 384 | 385 | if(w.y > max.y) max.y = w.y; 386 | if(w.y < min.y) min.y = w.y; 387 | 388 | if(w.z > max.z) max.z = w.z; 389 | if(w.z < min.z) min.z = w.z; 390 | } 391 | } 392 | 393 | p.transform.DetachChildren(); 394 | DestroyImmediate(p); 395 | 396 | Vector3 size = new Vector3( (max.x - min.x), (max.y - min.y), (max.z - min.z) ); 397 | 398 | return new Bounds(size/2f + min, size); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /Classes/StringUtility.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | /*! \A utility class for manipulating strings. 6 | * 7 | * Contains a number of methods that make creating and reading Heatmap compatible text files very easy. 8 | */ 9 | public class StringUtility : MonoBehaviour { 10 | 11 | /*! 12 | * Takes an array of GameObjects and returns a text asset containing all of their world positions. 13 | * 14 | * 15 | * This text asset may be read using Vector3ArrayWithFile(). 16 | * 17 | * An array of gameObjects to extract Vector3 data from. 18 | * The path to save the resulting text asset to. Project relative. 19 | */ 20 | public static void GameObjectArrayToTextAsset(GameObject[] gos, string path) 21 | { 22 | #if !UNITY_WEBPLAYER 23 | string str = StringUtility.StringWithGameObjects(gos); 24 | System.IO.File.WriteAllText(path, str); 25 | #endif 26 | } 27 | 28 | /*! 29 | * Takes a Vector3 array and returns a text asset containing all the data in string format. 30 | * 31 | * 32 | * This text asset may be read using Vector3ArrayWithFile(). 33 | * 34 | * An Vector3 array that will be used to populate the text. 35 | * The path to save the resulting text asset to. Project relative. 36 | */ 37 | public static void Vector3ArrayToTextAsset(Vector3[] pos, string path) 38 | { 39 | #if !UNITY_WEBPLAYER 40 | string str = StringUtility.StringWithVector3Array(pos); 41 | System.IO.File.WriteAllText(path, str); 42 | #endif 43 | } 44 | 45 | /*! 46 | * Translates an array of gameObjects into a string of Vector3s. 47 | * 48 | * 49 | * The gameObject array. 50 | * 51 | */ 52 | public static string StringWithGameObjects(GameObject[] gos) 53 | { 54 | string str = ""; 55 | for(int i = 0; i < gos.Length; i++) 56 | { 57 | str += gos[i].transform.position + "\n"; 58 | } 59 | return str; 60 | } 61 | 62 | /*! 63 | * Translates an array of Vector3 points into a string of Vector3s. 64 | * 65 | * 66 | * The Vector3 array. 67 | * 68 | */ 69 | public static string StringWithVector3Array(Vector3[] points) 70 | { 71 | string str = ""; 72 | for(int i = 0; i < points.Length; i++) 73 | { 74 | str += points[i] + "\n"; 75 | } 76 | return str; 77 | } 78 | 79 | /*! 80 | * Reads a string of Vector3 data and returns an array of Vector3s. 81 | * 82 | * 83 | * The text asset to extract data from. 84 | * 85 | */ 86 | public static Vector3[] Vector3ArrayWithFile(TextAsset txt) 87 | { 88 | string[] lines = txt.text.Split('\n'); 89 | List points = new List(); 90 | 91 | for(int i = 0; i < lines.Length; i++) 92 | { 93 | points.Add(StringUtility.Vector3WithString(lines[i])); 94 | } 95 | 96 | return points.ToArray(); 97 | } 98 | 99 | /*! 100 | * Given a string, this method attempts to parse a Vector3 from it. 101 | * 102 | * 103 | * The string to attempt parsing. 104 | * 105 | */ 106 | public static Vector3 Vector3WithString(string str) 107 | { 108 | str = str.Replace("(", ""); 109 | str = str.Replace(")", ""); 110 | string[] split = str.Split(','); 111 | if(split.Length < 3) 112 | return new Vector3(0f, 0f, 0f); 113 | 114 | float v0 = float.Parse(split[0], System.Globalization.CultureInfo.InvariantCulture); 115 | float v1 = float.Parse(split[1], System.Globalization.CultureInfo.InvariantCulture); 116 | float v2 = float.Parse(split[2], System.Globalization.CultureInfo.InvariantCulture); 117 | return new Vector3(v0, v1, v2); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/Art Credits.txt: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | Platformer graphics by Kenney Vleugels (www.kenney.nl) 4 | 5 | You may use these graphics in personal and commercial projects. 6 | Credit (www.kenney.nl) would be nice but is not mandatory. 7 | 8 | -- 9 | 10 | ## Update 1 ## 11 | 12 | Added a character (+ poses), walk animation and two enemies 13 | 14 | -- -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/Click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/Click.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/block.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/blue.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/bonus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/bonus.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/bonus_used.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/bonus_used.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/bush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/bush.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/character/front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/character/front.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/character/jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/character/jump.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/character/side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/character/side.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/character/walk_animation.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/character/walk_animation.fla -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/character/walk_animation.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/character/walk_animation.swf -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/cloud_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/cloud_1.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/cloud_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/cloud_2.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/cloud_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/cloud_3.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/coin.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/crate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/crate.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/enemies/fly_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/enemies/fly_dead.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/enemies/fly_fly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/enemies/fly_fly.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/enemies/fly_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/enemies/fly_normal.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/enemies/slime_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/enemies/slime_dead.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/enemies/slime_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/enemies/slime_normal.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/enemies/slime_walk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/enemies/slime_walk.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/fence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/fence.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/fence_broken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/fence_broken.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/grass.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/ground.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/ground_cave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/ground_cave.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/hill_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/hill_long.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/hill_short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/hill_short.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/shroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/shroom.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/spikes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/spikes.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Art/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Art/water.png -------------------------------------------------------------------------------- /Demo/Sidescroller/Sidescroller.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Sidescroller/Sidescroller.unity -------------------------------------------------------------------------------- /Demo/Sidescroller/SidescrollerDebugPoints.txt: -------------------------------------------------------------------------------- 1 | (-5.6, -2.2, -0.1) 2 | (-5.4, -2.2, -0.6) 3 | (-4.8, -2.2, -0.1) 4 | (-4.7, -2.1, -0.6) 5 | (-4.2, -2.0, -1.1) 6 | (-3.3, -2.1, -0.1) 7 | (-2.5, -2.1, -0.1) 8 | (-2.0, -1.9, -0.1) 9 | (-2.8, -1.4, -0.1) 10 | (-1.6, -0.4, -0.1) 11 | (-2.3, -0.2, -0.1) 12 | (-0.9, 0.0, -0.1) 13 | (-0.4, -0.4, -0.1) 14 | (-0.7, -0.8, -0.6) 15 | (2.5, -2.1, -0.1) 16 | (1.8, -2.1, -0.1) 17 | (2.5, -1.1, -0.1) 18 | (1.9, -1.2, -0.1) 19 | (2.5, -1.6, -0.6) 20 | (3.5, -2.2, -0.1) 21 | (3.7, -1.3, -0.1) 22 | -------------------------------------------------------------------------------- /Demo/Top Down/Art/Materials/dirt.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Top Down/Art/Materials/dirt.mat -------------------------------------------------------------------------------- /Demo/Top Down/Art/Materials/grass.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Top Down/Art/Materials/grass.mat -------------------------------------------------------------------------------- /Demo/Top Down/Art/dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Top Down/Art/dirt.png -------------------------------------------------------------------------------- /Demo/Top Down/Art/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Top Down/Art/grass.png -------------------------------------------------------------------------------- /Demo/Top Down/TopDownScene.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Demo/Top Down/TopDownScene.unity -------------------------------------------------------------------------------- /Demo/Top Down/TopDownSceneDebugPoints.txt: -------------------------------------------------------------------------------- 1 | (-46.9, 50.9, 51.9) 2 | (-33.0, 50.9, 45.5) 3 | (-19.2, 50.9, 57.4) 4 | (9.2, 50.9, 65.4) 5 | (35.0, 48.9, 48.7) 6 | (35.7, 48.9, 41.6) 7 | (59.8, 48.9, 9.0) 8 | (61.1, 48.9, 2.3) 9 | (20.5, 50.9, -8.4) 10 | (-6.9, 50.9, -9.0) 11 | (53.7, 48.9, 20.0) 12 | (-16.6, 50.9, 56.4) 13 | (-42.4, 51.9, -31.3) 14 | (-93.6, 51.5, -39.3) 15 | (-106.8, 50.9, -64.2) 16 | (-54.9, 51.9, -81.9) 17 | (-12.1, 53.7, -80.6) 18 | (-63.6, 51.9, -79.6) 19 | (-46.9, 51.2, -78.7) 20 | (39.2, 49.4, -85.7) 21 | (71.1, 50.9, -73.2) 22 | (77.9, 51.6, -56.4) 23 | (83.7, 49.0, -28.7) 24 | (80.5, 48.9, -3.2) 25 | (80.1, 48.9, 1.6) 26 | (40.5, 50.9, -11.6) 27 | (84.7, 48.9, 7.7) 28 | (93.4, 48.9, 35.5) 29 | (107.2, 48.9, 66.1) 30 | (103.3, 48.9, 57.7) 31 | (89.8, 48.9, 10.0) 32 | (45.3, 50.9, -3.2) 33 | (24.0, 50.9, -4.5) 34 | (67.2, 48.9, -6.4) 35 | (29.8, 50.9, 1.6) 36 | (48.5, 49.7, 9.7) 37 | (47.6, 50.4, -1.6) 38 | (37.6, 49.1, -23.2) 39 | (33.4, 48.9, -25.1) 40 | (34.4, 50.7, -16.8) 41 | (88.8, 49.4, -34.2) 42 | (82.1, 50.6, -37.1) 43 | -------------------------------------------------------------------------------- /Editor/EditorExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Adds context menu item to create Text Asset 3 | * Karl Henkel 4 | */ 5 | 6 | using UnityEditor; 7 | using UnityEngine; 8 | using System.IO; 9 | using System.Collections.Generic; 10 | 11 | public class EditorExtensions : Editor { 12 | 13 | [MenuItem("Assets/Create/Text Asset")] 14 | public static void Create(MenuCommand command) 15 | { 16 | string path = AssetDatabase.GetAssetPath(Selection.activeObject); 17 | 18 | if(path == "") 19 | path = "Assets/"; 20 | 21 | // If path is a file, get the parent directory 22 | if(File.Exists(path)) 23 | path = Path.GetDirectoryName(path); 24 | 25 | path += "/New Text Asset.txt"; 26 | 27 | path = AssetDatabase.GenerateUniqueAssetPath(path); 28 | 29 | File.CreateText(path); 30 | 31 | AssetDatabase.ImportAsset(path); 32 | 33 | Selection.activeObject = AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset)); 34 | } 35 | 36 | [MenuItem("GameObject/Create/Sprite _%k")] 37 | public static void CreateSprite() 38 | { 39 | List imgs = new List(); 40 | foreach(Texture2D tex in Selection.GetFiltered(typeof(Texture2D), SelectionMode.Deep)) 41 | imgs.Add(tex); 42 | 43 | if(imgs.Count > 0) 44 | { 45 | foreach(Texture2D img in imgs) 46 | CreateSpriteMesh(img); 47 | } 48 | else 49 | CreateSpriteMesh(null); 50 | 51 | } 52 | 53 | public static void CreateSpriteMesh(Texture2D img) 54 | { 55 | GameObject go = new GameObject(); 56 | go.name = "New Sprite Object"; 57 | 58 | ScreenCenter(go); 59 | 60 | Mesh m = new Mesh(); 61 | m.name = "Sprite"; 62 | 63 | float scale = (img) ? (float)img.height/(float)img.width : 1f; 64 | Vector3[] v = new Vector3[4] { 65 | new Vector3(-.5f, -.5f * scale, 0f), 66 | new Vector3(.5f, -.5f * scale, 0f), 67 | new Vector3(-.5f, .5f * scale, 0f), 68 | new Vector3(.5f, .5f * scale, 0f) 69 | }; 70 | int[] t = new int[6] { 71 | 2, 1, 0, 72 | 2, 3, 1 73 | }; 74 | Vector2[] u = new Vector2[4] { 75 | new Vector2(0f, 0f), 76 | new Vector2(1f, 0f), 77 | new Vector2(0f, 1f), 78 | new Vector2(1f, 1f) 79 | }; 80 | 81 | m.vertices = v; 82 | m.triangles = t; 83 | m.uv = u; 84 | m.RecalculateNormals(); 85 | m.Optimize(); 86 | 87 | go.AddComponent().sharedMesh = m; 88 | go.AddComponent(); 89 | Material mat = new Material(Shader.Find("Unlit/Transparent")); 90 | if(img) mat.mainTexture = img; 91 | go.GetComponent().sharedMaterial = mat; 92 | } 93 | 94 | public static void ScreenCenter(GameObject _gameObject) 95 | { 96 | // If in the unity editor, attempt to center the object the sceneview or main camera, in that order 97 | if(SceneView.lastActiveSceneView) 98 | _gameObject.transform.position = SceneView.lastActiveSceneView.pivot; 99 | else 100 | _gameObject.transform.position = ((SceneView)SceneView.sceneViews[0]).pivot; 101 | 102 | Selection.activeObject = _gameObject; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Editor/HeatmapEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System.Collections; 4 | 5 | public class HeatmapEditor : EditorWindow { 6 | 7 | public enum CameraOrientation { 8 | DOWN, 9 | FORWARD, 10 | MANUAL 11 | } 12 | CameraOrientation cameraOrientation = CameraOrientation.DOWN; 13 | CameraOrientation previousOrientation; 14 | 15 | public static Camera cam; 16 | 17 | Bounds worldBounds; 18 | 19 | // heatmap settings 20 | int pointRadius = 30; 21 | Texture2D heatmapOverlay; 22 | 23 | [MenuItem("Window/Heatmap/Heatmap Editor")] 24 | public static void InitHeatmapWindow() 25 | { 26 | ScriptableObject.CreateInstance().ShowUtility(); 27 | } 28 | 29 | public void OnEnable() 30 | { 31 | CreateCamera(); 32 | previousOrientation = cameraOrientation; 33 | worldBounds = GetWorldBounds(); 34 | AutosizeCamera(cam, cameraOrientation); 35 | } 36 | 37 | public void OnDisable() 38 | { 39 | if(cam) { 40 | DestroyImmediate(cam.gameObject); 41 | } 42 | 43 | Heatmap.DestroyHeatmapObjects(); 44 | } 45 | 46 | public void CreateCamera() 47 | { 48 | if(HeatmapEditor.cam == null) { 49 | cam = new GameObject().AddComponent(); 50 | cam.gameObject.name = "Heatmap Camera"; 51 | } 52 | cam.orthographic = true; 53 | } 54 | 55 | TextAsset heatmapTextAsset; 56 | public void OnGUI() 57 | { 58 | if(cam == null) 59 | CreateCamera(); 60 | 61 | GUILayout.Label("Heatmap Text Asset", EditorStyles.boldLabel); 62 | heatmapTextAsset = (TextAsset)EditorGUILayout.ObjectField(heatmapTextAsset, typeof(TextAsset), true); 63 | 64 | GUILayout.Space(2); 65 | 66 | GUILayout.Label("Camera Orientation", EditorStyles.boldLabel); 67 | 68 | cameraOrientation = (CameraOrientation)EditorGUILayout.EnumPopup(cameraOrientation); 69 | 70 | if(previousOrientation != cameraOrientation) 71 | { 72 | AutosizeCamera(cam, cameraOrientation); 73 | previousOrientation = cameraOrientation; 74 | } 75 | 76 | // Camera Utility 77 | if(cameraOrientation == CameraOrientation.MANUAL) 78 | { 79 | cam = (Camera)EditorGUILayout.ObjectField(cam, typeof(Camera), true); 80 | } 81 | else 82 | { 83 | if( cameraOrientation == CameraOrientation.FORWARD) 84 | cam.transform.rotation = Quaternion.Euler( Vector3.zero ); 85 | 86 | if( cameraOrientation == CameraOrientation.DOWN ) 87 | cam.transform.rotation = Quaternion.Euler( new Vector3(90f, 0f, 0f) ); 88 | 89 | if(GUI.Button(new Rect(0, Screen.height - 22, Screen.width, 20), "Force Update World Bounds")) 90 | worldBounds = GetWorldBounds(); 91 | } 92 | 93 | pointRadius = EditorGUILayout.IntField("Point Radius", pointRadius); 94 | 95 | GUILayout.Label("cam - " + cam.name + ": " + cam.pixelWidth + ", " + cam.pixelHeight + "\nscreen: " + Screen.width + ", " + Screen.height); 96 | 97 | // Heatmap tools! 98 | if(GUILayout.Button("Refresh Heatmap")) { 99 | if(heatmapTextAsset == null) { 100 | Debug.LogWarning("No Heatmap log selected!"); 101 | return; 102 | } 103 | EditorApplication.ExecuteMenuItem("Window/Game"); 104 | heatmapOverlay = Heatmap.CreateHeatmap(StringUtility.Vector3ArrayWithFile(heatmapTextAsset), cam, pointRadius); 105 | } 106 | 107 | if(GUILayout.Button("Screenshot")) 108 | Heatmap.Screenshot("Assets/ImAHeatmap.png", cam); 109 | 110 | if(heatmapOverlay) 111 | GUILayout.Label(heatmapOverlay); 112 | } 113 | 114 | public void AutosizeCamera(Camera cam, CameraOrientation orientation) 115 | { 116 | switch(orientation) 117 | { 118 | case CameraOrientation.DOWN: 119 | cam.transform.position = new Vector3(worldBounds.center.x, worldBounds.max.y + 1f, worldBounds.center.z); 120 | cam.orthographicSize = (worldBounds.extents.x > worldBounds.extents.z) ? worldBounds.extents.x : worldBounds.extents.z; 121 | break; 122 | 123 | case CameraOrientation.FORWARD: 124 | cam.transform.position = new Vector3(worldBounds.center.x, worldBounds.center.y, worldBounds.min.z - 1f); 125 | cam.orthographicSize = (worldBounds.extents.x > worldBounds.extents.y) ? worldBounds.extents.x : worldBounds.extents.y; 126 | break; 127 | } 128 | } 129 | 130 | public Bounds GetWorldBounds() 131 | { 132 | Object[] allGameObjects = GameObject.FindSceneObjectsOfType(typeof(GameObject)); 133 | 134 | GameObject p = new GameObject(); 135 | foreach(GameObject g in allGameObjects) 136 | g.transform.parent = p.transform; 137 | 138 | Component[] meshFilters = p.GetComponentsInChildren(typeof(MeshFilter)); 139 | 140 | Vector3 min, max; 141 | if(meshFilters.Length > 0) 142 | { 143 | min = ((MeshFilter)meshFilters[0]).gameObject.transform.TransformPoint(((MeshFilter)meshFilters[0]).sharedMesh.vertices[0]); 144 | max = min; 145 | } 146 | else 147 | { 148 | return new Bounds(); 149 | } 150 | 151 | foreach(MeshFilter mf in meshFilters) { 152 | Vector3[] v = mf.sharedMesh.vertices; 153 | 154 | for(int i = 0; i < v.Length; i++) 155 | { 156 | Vector3 w = mf.gameObject.transform.TransformPoint(v[i]); 157 | if(w.x > max.x) max.x = w.x; 158 | if(w.x < min.x) min.x = w.x; 159 | 160 | if(w.y > max.y) max.y = w.y; 161 | if(w.y < min.y) min.y = w.y; 162 | 163 | if(w.z > max.z) max.z = w.z; 164 | if(w.z < min.z) min.z = w.z; 165 | } 166 | } 167 | 168 | p.transform.DetachChildren(); 169 | DestroyImmediate(p); 170 | 171 | Vector3 size = new Vector3( (max.x - min.x), (max.y - min.y), (max.z - min.z) ); 172 | 173 | return new Bounds(size/2f + min, size); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Karl Henkel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | unity-heatmap 2 | ============= 3 | 4 | A utility for creating Heatmap images in Unity. 5 | -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | Heatmaps are created via script, using the command Heatmap.CreateHeatmap(). Currently there is no user interface. 4 | 5 | Example scripts are Tracker.cs and MouseClickDemo.cs, both of which have code that creates and takes a screenshot of a heatmap. 6 | 7 | Online link to documentation: 8 | http://paraboxstudios.com/heatmap/docs/annotated.html 9 | 10 | Or go to Windows/Heatmap Documentation -------------------------------------------------------------------------------- /Resources/UnlitMaterial.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-/unity-heatmap/591f519d705df1afb1cc44a9a7c98665f676d543/Resources/UnlitMaterial.mat -------------------------------------------------------------------------------- /Scripts/MouseClickDemo.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | #if UNITY_EDITOR 3 | using UnityEditor; 4 | #endif 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | 9 | public class MouseClickDemo : MonoBehaviour { 10 | 11 | // public Texture2D tex; 12 | List points = new List(); //!< A list of all points to be fed to the heatmap generator. 13 | 14 | 15 | // Heatmap Settings 16 | public int pointRadius = 40; //!< How large of a radius should a point on the heatmap be? 17 | 18 | #if UNITY_EDITOR && !UNITY_WEBPLAYER 19 | 20 | public TextAsset presetCoordinatesFile; 21 | public bool usePresetCoordinates = false; //!< If true, this attempts to parse the presetCoordinatesFile and draw a heatmap 22 | [HideInInspector] 23 | public bool generatePresetCoordinates = false; //!< If set to true, clicks will be logged to a text file. 24 | /// Clicks will be logged to a text file that can then be loaded w/ the toggleable usePresetCoordinates (nice for debugging). Commented out as this may be confusing for someone first playing around with the editor. 25 | #endif 26 | 27 | public bool createPrimitive = false; //!< If true, a primitive will be drawn the raycast collision point 28 | 29 | public new Camera camera; //!< Which camera to render the heatmap from -- best to use orthographic. 30 | private GameObject p; //!< Parent GameObject if primitives are generated. 31 | 32 | Rect resetRect = new Rect(5, 5, 100, 30); 33 | Rect screenshotRect = new Rect(5, 40, 100, 30); 34 | 35 | public void OnGUI() 36 | { 37 | if(GUI.Button(resetRect, "Reset")) { 38 | Heatmap.DestroyHeatmapObjects(); 39 | ClearPoints(); 40 | #if !UNITY_WEBPLAYER 41 | usePresetCoordinates = false; 42 | #endif 43 | } 44 | 45 | #if !UNITY_WEBPLAYER 46 | if(GUI.Button(screenshotRect, "Screenshot")) 47 | { 48 | StartCoroutine(TakeScreenshot()); 49 | } 50 | #endif 51 | } 52 | 53 | public void Start() 54 | { 55 | p = new GameObject(); 56 | 57 | #if UNITY_EDITOR && !UNITY_WEBPLAYER 58 | if(!usePresetCoordinates || presetCoordinatesFile == null) 59 | return; 60 | 61 | Vector3[] positionArray = StringUtility.Vector3ArrayWithFile(presetCoordinatesFile); 62 | 63 | for(int i = 0; i < positionArray.Length; i++) 64 | { 65 | GameObject go = (createPrimitive) ? GameObject.CreatePrimitive(PrimitiveType.Cube) : new GameObject(); 66 | go.transform.position = positionArray[i]; 67 | go.name = "Point: " + i; 68 | go.transform.parent = p.transform; 69 | points.Add(go); 70 | } 71 | 72 | Texture2D heatmapImage = Heatmap.CreateHeatmap(positionArray, null, pointRadius); 73 | Heatmap.CreateRenderPlane(heatmapImage); 74 | #endif 75 | } 76 | 77 | public Vector3[] PositionArrayWithGameObjects(GameObject[] g) 78 | { 79 | Vector3[] v = new Vector3[g.Length]; 80 | for(int i = 0; i < g.Length; i++) 81 | v[i] = g[i].transform.position; 82 | return v; 83 | } 84 | 85 | public void Update() { 86 | #if UNITY_EDITOR && !UNITY_WEBPLAYER 87 | if(usePresetCoordinates) 88 | return; 89 | #endif 90 | if(Input.GetMouseButtonDown(0)) 91 | { 92 | if( GUIToScreenRect(resetRect).Contains(Input.mousePosition) || GUIToScreenRect(screenshotRect).Contains(Input.mousePosition) ) 93 | return; 94 | 95 | Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 96 | RaycastHit hit; 97 | if (Physics.Raycast (ray, out hit, Mathf.Infinity)) { 98 | AddPoint(hit.point); 99 | } 100 | } 101 | } 102 | 103 | void AddPoint(Vector3 pos) 104 | { 105 | GameObject go = (createPrimitive) ? GameObject.CreatePrimitive(PrimitiveType.Cube) : new GameObject(); 106 | go.transform.position = pos; 107 | go.transform.parent = p.transform; 108 | 109 | points.Add(go); 110 | #if UNITY_EDITOR && !UNITY_WEBPLAYER 111 | if(generatePresetCoordinates) 112 | { 113 | string str = StringUtility.StringWithGameObjects(points.ToArray()); 114 | 115 | string path = presetCoordinatesFile ? AssetDatabase.GetAssetPath(presetCoordinatesFile) : AssetDatabase.GenerateUniqueAssetPath("Assets/DebugPoints.txt"); 116 | 117 | File.WriteAllText(path, str); 118 | 119 | // Heatmap.CreateHeatmap(PositionArrayWithGameObjects(points.ToArray()), camera, pointRadius); 120 | } 121 | else 122 | #endif 123 | { 124 | Texture2D heatmapImage = Heatmap.CreateHeatmap(PositionArrayWithGameObjects(points.ToArray()), camera, pointRadius); 125 | Heatmap.CreateRenderPlane(heatmapImage); 126 | } 127 | } 128 | 129 | void ClearPoints() 130 | { 131 | for(int i = 0; i < points.Count; i++) 132 | DestroyImmediate(points[i]); 133 | points.Clear(); 134 | } 135 | 136 | public Rect GUIToScreenRect(Rect guiRect) 137 | { 138 | return new Rect(guiRect.x, Screen.height - (guiRect.y + guiRect.height), guiRect.width, guiRect.height); 139 | } 140 | 141 | public IEnumerator TakeScreenshot() 142 | { 143 | int i = 0; 144 | while(File.Exists("Assets/Screenshot" + i + ".png")) { 145 | i++; 146 | yield return 0; 147 | } 148 | string path = "Assets/Screenshot" + i + ".png"; 149 | Heatmap.Screenshot(path, camera); 150 | } 151 | 152 | #if UNITY_EDITOR 153 | 154 | public void OnDisable() 155 | { 156 | AssetDatabase.Refresh(); 157 | } 158 | 159 | #endif 160 | } 161 | -------------------------------------------------------------------------------- /Scripts/Tracker.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | /*! \An example tracking script that logs the world position of the object it is attached to. 6 | * 7 | * Set the logs per second and where to output the saved positions. These can be read by the Heatmap class and turned into a Heatmap. 8 | */ 9 | public class Tracker : MonoBehaviour { 10 | 11 | public float logsPerSecond = 1f; // Default to one log per second 12 | private float logSplit; 13 | private float timer = 0f; 14 | public List points = new List(); 15 | public string HeatmapTextAssetPath = "Assets/PlayerPoints.txt"; 16 | 17 | public void Start() 18 | { 19 | logSplit = 1f/logsPerSecond; 20 | } 21 | 22 | public void Update() 23 | { 24 | timer += Time.deltaTime; 25 | 26 | if(timer > logSplit) 27 | { 28 | timer = 0f; 29 | LogPosition(gameObject.transform.position); 30 | } 31 | } 32 | 33 | public void OnDisable() 34 | { 35 | StringUtility.Vector3ArrayToTextAsset(points.ToArray(), HeatmapTextAssetPath); 36 | } 37 | 38 | public void LogPosition(Vector3 position) 39 | { 40 | points.Add(position); 41 | } 42 | } 43 | --------------------------------------------------------------------------------