Parcourir la source

Initial commit

jamesperet il y a 3 ans
commit
74d852a257

+ 71 - 0
.gitignore

@@ -0,0 +1,71 @@
+# This .gitignore file should be placed at the root of your Unity project directory
+#
+# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
+#
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/[Ll]ogs/
+/[Uu]ser[Ss]ettings/
+
+# MemoryCaptures can get excessive in size.
+# They also could contain extremely sensitive data
+/[Mm]emoryCaptures/
+
+# Asset meta data should only be ignored when the corresponding asset is also ignored
+!/[Aa]ssets/**/*.meta
+
+# Uncomment this line if you wish to ignore the asset store tools plugin
+# /[Aa]ssets/AssetStoreTools*
+
+# Autogenerated Jetbrains Rider plugin
+/[Aa]ssets/Plugins/Editor/JetBrains*
+
+# Visual Studio cache directory
+.vs/
+
+# Gradle cache directory
+.gradle/
+
+# Autogenerated VS/MD/Consulo solution and project files
+ExportedObj/
+.consulo/
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+*.pdb
+*.mdb
+*.opendb
+*.VC.db
+
+# Unity3D generated meta files
+*.pidb.meta
+*.pdb.meta
+*.mdb.meta
+
+# Unity3D generated file on crash reports
+sysinfo.txt
+
+# Builds
+*.apk
+*.aab
+*.unitypackage
+
+# Crashlytics generated file
+crashlytics-build.properties
+
+# Packed Addressables
+/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
+
+# Temporary auto-generated Android Assets
+/[Aa]ssets/[Ss]treamingAssets/aa.meta
+/[Aa]ssets/[Ss]treamingAssets/aa/*

+ 7 - 0
ChangeLog.md

@@ -0,0 +1,7 @@
+# Change Log
+
+### v0.1.2
+
+- Added the Statistics system to the Utilities package. *07/19/2021*
+
+## v0.1.1

+ 7 - 0
ChangeLog.md.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 751a2aa9341c8a743a457788d522d10e
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 15 - 0
Readme.md

@@ -0,0 +1,15 @@
+# 📦 KairoEngine.Utilities v0.1.2
+
+Kairoengine utilities library.
+
+### 🛑Required packages
+
+- `Unity.TextMeshPro`
+- ``UniRx``
+
+### 📄Namespaces
+
+- `KairoEngine.Utilities`
+- `KairoEngine.Utilities.Statistics`
+
+

+ 7 - 0
Readme.md.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e1dde71df737d9e41aa12b72c1823798
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Runtime.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2e4d71e01a670a44da7b53337e11b413
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
Runtime/KairoEngine.Utility.asmdef

@@ -0,0 +1,19 @@
+{
+    "name": "KairoEngine.Utility",
+    "references": [
+        "GUID:7e5ae6a38d1532248b4c890eca668b06",
+        "GUID:6055be8ebefd69e48b49212b09b47b2f",
+        "GUID:560b04d1a97f54a4e82edc0cbbb69285",
+        "GUID:fb24642277b1db2449da7ac148ce939d",
+        "GUID:5f03fc37b95cb644599751ca563336b2"
+    ],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": [],
+    "versionDefines": [],
+    "noEngineReferences": false
+}

+ 7 - 0
Runtime/KairoEngine.Utility.asmdef.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 165d83fc3bb2a4144925c85421871d8e
+AssemblyDefinitionImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 152 - 0
Runtime/MeshUtils.cs

@@ -0,0 +1,152 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KairoEngine.Utility
+{
+	public static class MeshUtils {
+		
+		private static readonly Vector3 Vector3zero = Vector3.zero;
+		private static readonly Vector3 Vector3one = Vector3.one;
+		private static readonly Vector3 Vector3yDown = new Vector3(0,-1);
+
+		
+		private static Quaternion[] cachedQuaternionEulerArr;
+		private static void CacheQuaternionEuler() {
+			if (cachedQuaternionEulerArr != null) return;
+			cachedQuaternionEulerArr = new Quaternion[360];
+			for (int i=0; i<360; i++) {
+				cachedQuaternionEulerArr[i] = Quaternion.Euler(0,0,i);
+			}
+		}
+		private static Quaternion GetQuaternionEuler(float rotFloat) {
+			int rot = Mathf.RoundToInt(rotFloat);
+			rot = rot % 360;
+			if (rot < 0) rot += 360;
+			//if (rot >= 360) rot -= 360;
+			if (cachedQuaternionEulerArr == null) CacheQuaternionEuler();
+			return cachedQuaternionEulerArr[rot];
+		}
+
+
+		public static Mesh CreateEmptyMesh() {
+			Mesh mesh = new Mesh();
+			mesh.vertices = new Vector3[0];
+			mesh.uv = new Vector2[0];
+			mesh.triangles = new int[0];
+			return mesh;
+		}
+
+		public static void CreateEmptyMeshArrays(int quadCount, out Vector3[] vertices, out Vector2[] uvs, out int[] triangles) {
+			vertices = new Vector3[4 * quadCount];
+			uvs = new Vector2[4 * quadCount];
+			triangles = new int[6 * quadCount];
+		}
+			
+		public static Mesh CreateMesh(Vector3 pos, float rot, Vector3 baseSize, Vector2 uv00, Vector2 uv11) {
+			return AddToMesh(null, pos, rot, baseSize, uv00, uv11);
+		}
+
+		public static Mesh AddToMesh(Mesh mesh, Vector3 pos, float rot, Vector3 baseSize, Vector2 uv00, Vector2 uv11) {
+			if (mesh == null) {
+				mesh = CreateEmptyMesh();
+			}
+			Vector3[] vertices = new Vector3[4 + mesh.vertices.Length];
+			Vector2[] uvs = new Vector2[4 + mesh.uv.Length];
+			int[] triangles = new int[6 + mesh.triangles.Length];
+				
+			mesh.vertices.CopyTo(vertices, 0);
+			mesh.uv.CopyTo(uvs, 0);
+			mesh.triangles.CopyTo(triangles, 0);
+
+			int index = vertices.Length / 4 - 1;
+			//Relocate vertices
+			int vIndex = index*4;
+			int vIndex0 = vIndex;
+			int vIndex1 = vIndex+1;
+			int vIndex2 = vIndex+2;
+			int vIndex3 = vIndex+3;
+
+			baseSize *= .5f;
+
+			bool skewed = baseSize.x != baseSize.y;
+			if (skewed) {
+				vertices[vIndex0] = pos+GetQuaternionEuler(rot)*new Vector3(-baseSize.x,  baseSize.y);
+				vertices[vIndex1] = pos+GetQuaternionEuler(rot)*new Vector3(-baseSize.x, -baseSize.y);
+				vertices[vIndex2] = pos+GetQuaternionEuler(rot)*new Vector3( baseSize.x, -baseSize.y);
+				vertices[vIndex3] = pos+GetQuaternionEuler(rot)*baseSize;
+			} else {
+				vertices[vIndex0] = pos+GetQuaternionEuler(rot-270)*baseSize;
+				vertices[vIndex1] = pos+GetQuaternionEuler(rot-180)*baseSize;
+				vertices[vIndex2] = pos+GetQuaternionEuler(rot- 90)*baseSize;
+				vertices[vIndex3] = pos+GetQuaternionEuler(rot-  0)*baseSize;
+			}
+			
+			//Relocate UVs
+			uvs[vIndex0] = new Vector2(uv00.x, uv11.y);
+			uvs[vIndex1] = new Vector2(uv00.x, uv00.y);
+			uvs[vIndex2] = new Vector2(uv11.x, uv00.y);
+			uvs[vIndex3] = new Vector2(uv11.x, uv11.y);
+			
+			//Create triangles
+			int tIndex = index*6;
+			
+			triangles[tIndex+0] = vIndex0;
+			triangles[tIndex+1] = vIndex3;
+			triangles[tIndex+2] = vIndex1;
+			
+			triangles[tIndex+3] = vIndex1;
+			triangles[tIndex+4] = vIndex3;
+			triangles[tIndex+5] = vIndex2;
+				
+			mesh.vertices = vertices;
+			mesh.triangles = triangles;
+			mesh.uv = uvs;
+
+			//mesh.bounds = bounds;
+
+			return mesh;
+		}
+
+		public static void AddToMeshArrays(Vector3[] vertices, Vector2[] uvs, int[] triangles, int index, Vector3 pos, float rot, Vector3 baseSize, Vector2 uv00, Vector2 uv11) {
+			//Relocate vertices
+			int vIndex = index*4;
+			int vIndex0 = vIndex;
+			int vIndex1 = vIndex+1;
+			int vIndex2 = vIndex+2;
+			int vIndex3 = vIndex+3;
+
+			baseSize *= .5f;
+
+			bool skewed = baseSize.x != baseSize.y;
+			if (skewed) {
+				vertices[vIndex0] = pos+GetQuaternionEuler(rot)*new Vector3(-baseSize.x, 0,  baseSize.z);
+				vertices[vIndex1] = pos+GetQuaternionEuler(rot)*new Vector3(-baseSize.x, 0, -baseSize.z);
+				vertices[vIndex2] = pos+GetQuaternionEuler(rot)*new Vector3( baseSize.x, 0, -baseSize.z);
+				vertices[vIndex3] = pos+GetQuaternionEuler(rot)*baseSize;
+			} else {
+				vertices[vIndex0] = pos+GetQuaternionEuler(rot-270)*baseSize;
+				vertices[vIndex1] = pos+GetQuaternionEuler(rot-180)*baseSize;
+				vertices[vIndex2] = pos+GetQuaternionEuler(rot- 90)*baseSize;
+				vertices[vIndex3] = pos+GetQuaternionEuler(rot-  0)*baseSize;
+			}
+			
+			//Relocate UVs
+			uvs[vIndex0] = new Vector2(uv00.x, uv11.y);
+			uvs[vIndex1] = new Vector2(uv00.x, uv00.y);
+			uvs[vIndex2] = new Vector2(uv11.x, uv00.y);
+			uvs[vIndex3] = new Vector2(uv11.x, uv11.y);
+			
+			//Create triangles
+			int tIndex = index*6;
+			
+			triangles[tIndex+0] = vIndex0;
+			triangles[tIndex+1] = vIndex3;
+			triangles[tIndex+2] = vIndex1;
+			
+			triangles[tIndex+3] = vIndex1;
+			triangles[tIndex+4] = vIndex3;
+			triangles[tIndex+5] = vIndex2;
+		}
+	}
+}

+ 11 - 0
Runtime/MeshUtils.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c0a55b369c5abe347a3659a87f9cd196
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Runtime/Statistics.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7bd11ce3e4749b648bf12bf5c7c8487c
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 51 - 0
Runtime/Statistics/StatisticChangeInteger.cs

@@ -0,0 +1,51 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.Utilities.Statistics
+{
+    public enum StatisticChangeIntegerTrigger
+    {
+        OnStart,
+        OnDisable,
+        OnDestroy
+    }
+
+    [HideMonoScript]
+    public class StatisticChangeInteger : MonoBehaviour
+    {
+        [HorizontalGroup()] public string statisticName = "";
+        [HorizontalGroup(80), ReadOnly, HideLabel] public string value;
+
+        public StatisticChangeIntegerTrigger trigger;
+        public int number;
+
+        void Start()
+        {
+            if(Statistics.instance == null) Debug.LogError("Statistics not loaded yet");
+            if(trigger == StatisticChangeIntegerTrigger.OnStart) ChangeInteger();
+        }
+
+        void OnDisable()
+        {
+            if(trigger == StatisticChangeIntegerTrigger.OnDisable) ChangeInteger();
+        }
+
+        void OnDestroy()
+        {
+            if(trigger == StatisticChangeIntegerTrigger.OnDestroy) ChangeInteger();
+        }
+
+        void Update()
+        {
+            value = Statistics.GetData(statisticName).GetInteger().ToString();
+        }
+
+        public void ChangeInteger()
+        {
+            Statistics.GetData(statisticName).AddInteger(number);
+        }
+    }
+}
+

+ 11 - 0
Runtime/Statistics/StatisticChangeInteger.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 71a9a6d00db694e40938597e57bd95cf
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 47 - 0
Runtime/Statistics/StatisticData.cs

@@ -0,0 +1,47 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.Utilities.Statistics
+{
+    public enum StatisticType
+    {
+        time,
+        integer,
+        text
+    }
+
+    [System.Serializable, HideMonoScript]
+    public class StatisticData
+    {
+        [HorizontalGroup(180), HideLabel] public string title;
+
+        [ShowIf("@category == StatisticType.time"), HorizontalGroup(), HideLabel, ShowInInspector] private float time;
+        [ShowIf("@category == StatisticType.integer"), HorizontalGroup(), HideLabel, ShowInInspector] private int integer;
+        [ShowIf("@category == StatisticType.text"), HorizontalGroup(), HideLabel, ShowInInspector] private string text;
+        [HorizontalGroup(80), HideLabel] public StatisticType category;
+        [HorizontalGroup(), HideLabel, Tooltip("Persistent Data?")] public bool persistent = false;
+
+        public void AddTime(float t) => time += t;
+        public void AddInteger(int n) => integer += n;
+
+        public float GetTime() => time;
+        public int GetInteger() => integer;
+        public string GetText() => text;
+
+        public void SetTime(float t) => time = t;
+        public void SetInteger(int n) => integer = n;
+        public void SetText(string t) => text = t;
+
+        
+
+        public void Reset()
+        {
+            time = 0f;
+            integer = 0;
+            text = "";
+        }
+
+    }
+}

+ 11 - 0
Runtime/Statistics/StatisticData.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 919fc0a3cb4d10b4d8651a582a736d4e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 39 - 0
Runtime/Statistics/StatisticElapsedTime.cs

@@ -0,0 +1,39 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.Utilities;
+using Sirenix.OdinInspector;
+using KairoEngine.Core;
+
+namespace KairoEngine.Utilities.Statistics
+{
+    [HideMonoScript]
+    public class StatisticElapsedTime : MonoBehaviour
+    {
+        public string statisticName = "";
+        [ReadOnly] public string elapsedTime;
+
+        [HorizontalGroup()] public bool unscaledDeltaTime = true;
+        [HorizontalGroup()] public bool resetOnStart = true;
+
+        void Start()
+        {
+            if(resetOnStart) Statistics.GetData(statisticName).Reset();
+        }
+
+        void Update()
+        {
+            if(unscaledDeltaTime)
+            {
+                Statistics.GetData(statisticName).AddTime(Time.unscaledDeltaTime);
+            }
+            else
+            {
+                Statistics.GetData(statisticName).AddTime(Time.deltaTime);
+            }
+            float time = Statistics.GetData(statisticName).GetTime();
+            elapsedTime = KairoEngine.Core.Utilities.TimeToString(time);
+        }
+    }
+}
+

+ 11 - 0
Runtime/Statistics/StatisticElapsedTime.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0f810b498e3e89445aa607425e0fdcac
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 109 - 0
Runtime/Statistics/Statistics.cs

@@ -0,0 +1,109 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.Utilities.Statistics
+{
+    [HideMonoScript]
+    public class Statistics : SerializedMonoBehaviour
+    {
+        #region Singleton
+        private static Statistics statistics;
+        public static Statistics instance
+        {
+            get {
+                if(!statistics)
+                {
+                    statistics = FindObjectOfType (typeof(Statistics)) as Statistics;
+                    if(!statistics)
+                    {
+                        Debug.LogError("There need to one active Statistics script on the scene.");
+                        return null;
+                    }
+                }
+                return statistics;
+            }
+        }
+        #endregion
+
+        [ListDrawerSettings(ShowPaging = false)]
+        public List<StatisticData> data = new List<StatisticData>();
+
+        void Awake()
+        {
+            if(instance != null && instance != this) 
+            {
+                Destroy(this.gameObject);
+                return;
+            }
+            LoadStatistics();
+        }
+
+        void OnDestroy()
+        {
+            SaveStatistics();
+        }
+
+        public static StatisticData GetData(string title)
+        {
+            if(instance == null) return null;
+            for (int i = 0; i < instance.data.Count; i++)
+            {
+                if(instance.data[i].title == title) return instance.data[i];
+            }
+            return null;
+        }
+
+        private void LoadStatistics()
+        {
+            if(instance == null) return;
+            for (int i = 0; i < instance.data.Count; i++)
+            {
+                StatisticData stat = instance.data[i];
+                if(stat.persistent == false) continue;
+                switch (stat.category)
+                {
+                    case StatisticType.time:
+                        float time = PlayerPrefs.GetFloat(stat.title, 0f);
+                        stat.SetTime(time);
+                        break;
+                    case StatisticType.integer:
+                        int integer = PlayerPrefs.GetInt(stat.title, 0);
+                        stat.SetInteger(integer);
+                        break;
+                    case StatisticType.text:
+                        string text = PlayerPrefs.GetString(stat.title, "");
+                        stat.SetText(text);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        private void SaveStatistics()
+        {
+            if(instance == null) return;
+            for (int i = 0; i < instance.data.Count; i++)
+            {
+                StatisticData stat = instance.data[i];
+                if(stat.persistent == false) continue;
+                switch (stat.category)
+                {
+                    case StatisticType.time:
+                        PlayerPrefs.SetFloat(stat.title, stat.GetTime());
+                        break;
+                    case StatisticType.integer:
+                        PlayerPrefs.SetInt(stat.title, stat.GetInteger());
+                        break;
+                    case StatisticType.text:
+                        PlayerPrefs.SetString(stat.title, stat.GetText());
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Runtime/Statistics/Statistics.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8a8d253b00a34644980604012c30900d
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 51 - 0
Runtime/Statistics/StatisticsConsoleCommands.cs

@@ -0,0 +1,51 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using KairoEngine.Utilities;
+using QFSW.QC;
+using QFSW.QC.Utilities;
+using KairoEngine.Core;
+
+namespace KairoEngine.Utilities.Statistics
+{
+    public static class StatisticsConsoleCommands
+    {
+        private static readonly Pool<StringBuilder> _builderPool = new Pool<StringBuilder>();
+
+        [Command("statistics-list", "List of statistics names")]
+        private static string PrintStatistics()
+        {
+            QuantumConsole console = QuantumConsole.Instance;
+            string result = "";
+            StringBuilder buffer = _builderPool.GetObject();
+            Color color = Color.white;
+            buffer.Clear();
+            if(console != null)
+            {
+                QuantumTheme theme = console.GetTheme();
+                color = theme ? theme.SuccessColor : Color.white;
+            }
+            foreach (var stat in Statistics.instance.data)
+            {
+                result += stat.title;
+                switch (stat.category)
+                {
+                    case StatisticType.integer:
+                    result += " - " + ColorExtensions.ColorText(stat.GetInteger().ToString(), color);
+                    break;
+                    case StatisticType.time:
+                    result += " - " + ColorExtensions.ColorText(KairoEngine.Core.Utilities.TimeToString(stat.GetTime()), color);
+                    break;
+                    default:
+                    break;
+                }
+                buffer.AppendLine(result);
+                result = "";
+            }
+            result = buffer.ToString();
+            _builderPool.Release(buffer);
+            return result;
+        }
+    }
+}

+ 11 - 0
Runtime/Statistics/StatisticsConsoleCommands.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 00fb781460b49ed49bf1cd22d5794f94
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 104 - 0
Runtime/Utilities.cs

@@ -0,0 +1,104 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using TMPro;
+using UniRx;
+
+namespace KairoEngine.Utility
+{
+    public class Utilities
+    {
+
+        public const int sortingOrderDefault = 5000;
+
+
+        // Create Text in the World
+        public static TextMesh CreateWorldText(string text, Transform parent = null, Vector3 localPosition = default(Vector3), int fontSize = 40, Color? color = null, TextAnchor textAnchor = TextAnchor.UpperLeft, TextAlignment textAlignment = TextAlignment.Left, int sortingOrder = sortingOrderDefault) {
+            if (color == null) color = Color.white;
+            return CreateWorldText(parent, text, localPosition, fontSize, (Color)color, textAnchor, textAlignment, sortingOrder);
+        }
+        
+        // Create Text in the World
+        public static TextMesh CreateWorldText(Transform parent, string text, Vector3 localPosition, int fontSize, Color color, TextAnchor textAnchor, TextAlignment textAlignment, int sortingOrder) {
+            GameObject gameObject = new GameObject("World_Text", typeof(TextMesh));
+            Transform transform = gameObject.transform;
+            transform.SetParent(parent, false);
+            transform.localPosition = localPosition;
+            TextMesh textMesh = gameObject.GetComponent<TextMesh>();
+            textMesh.anchor = textAnchor;
+            textMesh.alignment = textAlignment;
+            textMesh.text = text;
+            textMesh.fontSize = fontSize;
+            textMesh.color = color;
+            textMesh.GetComponent<MeshRenderer>().sortingOrder = sortingOrder;
+            return textMesh;
+        }
+
+        // Get Mouse Position in World with Z = 0f
+        public static Vector3 GetMouseWorldPosition() {
+            Vector3 vec = GetMouseWorldPositionWithZ(Input.mousePosition, Camera.main);
+            vec.z = 0f;
+            return vec;
+        }
+        public static Vector3 GetMouseWorldPositionWithZ() {
+            return GetMouseWorldPositionWithZ(Input.mousePosition, Camera.main);
+        }
+        public static Vector3 GetMouseWorldPositionWithZ(Camera worldCamera) {
+            return GetMouseWorldPositionWithZ(Input.mousePosition, worldCamera);
+        }
+        public static Vector3 GetMouseWorldPositionWithZ(Vector3 screenPosition, Camera worldCamera) {
+            Vector3 worldPosition = worldCamera.ScreenToWorldPoint(screenPosition);
+            return worldPosition;
+        }
+
+        public static Vector3 GetMouseWorldPosition(int mouseColliderLayerMask)
+        {
+            if(Camera.main == null) return new Vector3();
+            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
+            if(Physics.Raycast(ray, out RaycastHit raycastHit, 999f, mouseColliderLayerMask))
+            {
+                return raycastHit.point;
+            }
+            else return Vector3.zero;
+        }
+
+        // Create a Text Popup in the World, no parent
+        public static void CreateWorldTextPopup(string text, Vector3 localPosition) {
+            CreateWorldTextPopup(null, text, localPosition, 40, Color.white, localPosition + new Vector3(0, 20), 1f, true);
+        }
+        
+        // Create a Text Popup in the World
+        public static void CreateWorldTextPopup(Transform parent, string text, Vector3 localPosition, int fontSize, Color color, Vector3 finalPopupPosition, float popupTime, bool centered) {
+            TextMesh textMesh = CreateWorldText(parent, text, localPosition, fontSize, color, TextAnchor.LowerLeft, TextAlignment.Left, sortingOrderDefault);
+            if(centered) textMesh.anchor = TextAnchor.MiddleCenter;
+            Transform transform = textMesh.transform;
+            Vector3 moveAmount = (finalPopupPosition - localPosition) / popupTime;
+            CompositeDisposable disposables = new CompositeDisposable();
+            Observable.EveryUpdate().Subscribe(_ => {
+                transform.position += moveAmount * Time.deltaTime;
+                popupTime -= Time.deltaTime;
+                if (popupTime <= 0f) {
+                    UnityEngine.Object.Destroy(transform.gameObject);
+                    disposables.Clear();
+                } 
+            }).AddTo(disposables);
+        }
+
+        public static float GetAngleFromVectorFloat(Vector3 dir) {
+            dir = dir.normalized;
+            float n = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
+            if (n < 0) n += 360;
+
+            return n;
+        }
+
+        public static Vector3 ApplyRotationToVector(Vector3 vec, Vector3 vecRotation) {
+            return ApplyRotationToVector(vec, GetAngleFromVectorFloat(vecRotation));
+        }
+
+        public static Vector3 ApplyRotationToVector(Vector3 vec, float angle) {
+            return Quaternion.Euler(0,0,angle) * vec;
+        }
+    }
+}
+

+ 11 - 0
Runtime/Utilities.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9bb8635ccb23a7f47aaa1852ccf69fea
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 17 - 0
Runtime/Vector2IntExtensions.cs

@@ -0,0 +1,17 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public static class Vector2IntExtensions
+{
+    public static Vector2Int Rotate(this Vector2Int v, float degrees)
+    {
+        float sin = Mathf.Sin(degrees * Mathf.Deg2Rad);
+        float cos = Mathf.Cos(degrees * Mathf.Deg2Rad);
+        float tx = v.x;
+        float ty = v.y;
+        float x = (cos * tx) - (sin * ty);
+        float y = (sin * tx) + (cos * ty);
+        return new Vector2Int(Mathf.RoundToInt(x), Mathf.RoundToInt(y));
+    }
+}

+ 11 - 0
Runtime/Vector2IntExtensions.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 56a9d74f1faf53447b29b94423c20fa9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 16 - 0
package.json

@@ -0,0 +1,16 @@
+{
+    "name": "at.kairoscope.kairoengine.utilities",
+    "displayName": "KairoEngine Utilities",
+    "version": "0.1.2",
+    "unity": "2020.3",
+    "description": "Kairoengine utilities library.",
+    "dependencies": {
+      "at.kairoscope.thirdparty.tmpro":"1.0.0",
+      "at.kairoscope.thirdparty.unirx":"1.0.0"
+    },
+    "repository": {
+      "type": "git",
+      "url": "https://git.kairoscope.net/kairoengine/utilities.git"
+    },
+    "author": "Kairoscope"
+  }