├── 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 |
--------------------------------------------------------------------------------