Created
September 16, 2018 05:51
-
-
Save grimmdev/a9410c3022044fc5b1ee9e597881bc7f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This class contains some helper functions. | |
using UnityEngine; | |
using UnityEngine.Rendering; | |
using Mirror; | |
using UnityEngine.EventSystems; | |
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Linq; | |
using System.Security.Cryptography; | |
using System.Reflection; | |
public class Utils | |
{ | |
// Mathf.Clamp only works for float and int. we need some more versions: | |
public static long Clamp(long value, long min, long max) | |
{ | |
if (value < min) return min; | |
if (value > max) return max; | |
return value; | |
} | |
// is any of the keys UP? | |
public static bool AnyKeyUp(KeyCode[] keys) | |
{ | |
return keys.Any(k => Input.GetKeyUp(k)); | |
} | |
// is any of the keys DOWN? | |
public static bool AnyKeyDown(KeyCode[] keys) | |
{ | |
return keys.Any(k => Input.GetKeyDown(k)); | |
} | |
// is any of the keys PRESSED? | |
public static bool AnyKeyPressed(KeyCode[] keys) | |
{ | |
return keys.Any(k => Input.GetKey(k)); | |
} | |
// detect headless mode (which has graphicsDeviceType Null) | |
public static bool IsHeadless() | |
{ | |
return SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null; | |
} | |
// String.IsNullOrWhiteSpace that exists in NET4.5 | |
// note: can't be an extension because then it can't detect null strings | |
// like null.IsNullOrWhitespace | |
public static bool IsNullOrWhiteSpace(string value) | |
{ | |
return String.IsNullOrEmpty(value) || value.Trim().Length == 0; | |
} | |
// Distance between two ClosestPointOnBounds | |
// this is needed in cases where entites are really big. in those cases, | |
// we can't just move to entity.transform.position, because it will be | |
// unreachable. instead we have to go the closest point on the boundary. | |
// | |
// Vector3.Distance(a.transform.position, b.transform.position): | |
// _____ _____ | |
// | | | | | |
// | x==|======|==x | | |
// |_____| |_____| | |
// | |
// | |
// Utils.ClosestDistance(a.collider, b.collider): | |
// _____ _____ | |
// | | | | | |
// | |x====x| | | |
// |_____| |_____| | |
// | |
public static float ClosestDistance(Collider a, Collider b) | |
{ | |
return Vector3.Distance(a.ClosestPointOnBounds(b.transform.position), | |
b.ClosestPointOnBounds(a.transform.position)); | |
} | |
// raycast while ignoring self (by setting layer to "Ignore Raycasts" first) | |
// => setting layer to IgnoreRaycasts before casting is the easiest way to do it | |
// => raycast + !=this check would still cause hit.point to be on player | |
// => raycastall is not sorted and child objects might have different layers etc. | |
public static bool RaycastWithout(Ray ray, out RaycastHit hit, GameObject ignore) | |
{ | |
// remember layers | |
Dictionary<Transform, int> backups = new Dictionary<Transform, int>(); | |
// set all to ignore raycast | |
foreach (Transform tf in ignore.GetComponentsInChildren<Transform>(true)) | |
{ | |
backups[tf] = tf.gameObject.layer; | |
tf.gameObject.layer = LayerMask.NameToLayer("Ignore Raycast"); | |
} | |
// raycast | |
bool result = Physics.Raycast(ray, out hit); | |
// restore layers | |
foreach (KeyValuePair<Transform, int> kvp in backups) | |
kvp.Key.gameObject.layer = kvp.Value; | |
return result; | |
} | |
// pretty print seconds as hours:minutes:seconds(.milliseconds/100)s | |
public static string PrettySeconds(float seconds) | |
{ | |
TimeSpan t = System.TimeSpan.FromSeconds(seconds); | |
string res = ""; | |
if (t.Days > 0) res += t.Days + "d"; | |
if (t.Hours > 0) res += " " + t.Hours + "h"; | |
if (t.Minutes > 0) res += " " + t.Minutes + "m"; | |
// 0.5s, 1.5s etc. if any milliseconds. 1s, 2s etc. if any seconds | |
if (t.Milliseconds > 0) res += " " + t.Seconds + "." + (t.Milliseconds / 100) + "s"; | |
else if (t.Seconds > 0) res += " " + t.Seconds + "s"; | |
// if the string is still empty because the value was '0', then at least | |
// return the seconds instead of returning an empty string | |
return res != "" ? res : "0s"; | |
} | |
// hard mouse scrolling that is consistent between all platforms | |
// Input.GetAxis("Mouse ScrollWheel") and | |
// Input.GetAxisRaw("Mouse ScrollWheel") | |
// both return values like 0.01 on standalone and 0.5 on WebGL, which | |
// causes too fast zooming on WebGL etc. | |
// normally GetAxisRaw should return -1,0,1, but it doesn't for scrolling | |
public static float GetAxisRawScrollUniversal() | |
{ | |
float scroll = Input.GetAxisRaw("Mouse ScrollWheel"); | |
if (scroll < 0) return -1; | |
if (scroll > 0) return 1; | |
return 0; | |
} | |
// two finger pinch detection | |
// source: https://docs.unity3d.com/Manual/PlatformDependentCompilation.html | |
public static float GetPinch() | |
{ | |
if (Input.touchCount == 2) | |
{ | |
// Store both touches. | |
Touch touchZero = Input.GetTouch(0); | |
Touch touchOne = Input.GetTouch(1); | |
// Find the position in the previous frame of each touch. | |
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition; | |
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition; | |
// Find the magnitude of the vector (the distance) between the touches in each frame. | |
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude; | |
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude; | |
// Find the difference in the distances between each frame. | |
return touchDeltaMag - prevTouchDeltaMag; | |
} | |
return 0; | |
} | |
// universal zoom: mouse scroll if mouse, two finger pinching otherwise | |
public static float GetZoomUniversal() | |
{ | |
if (Input.mousePresent) | |
return Utils.GetAxisRawScrollUniversal(); | |
else if (Input.touchSupported) | |
return GetPinch(); | |
return 0; | |
} | |
// find first valid gameobject from player controllers list | |
public static GameObject GetGameObjectFromPlayerControllers(List<PlayerController> controllers) | |
{ | |
PlayerController controller = controllers.Find(pc => pc.gameObject != null); | |
return controller != null ? controller.gameObject : null; | |
} | |
// find local player (clientsided) | |
public static Player ClientLocalPlayer() | |
{ | |
// note: ClientScene.localPlayers.Count cant be used as check because | |
// nothing is removed from that list, even after disconnect. It still | |
// contains entries like: ID=0 NetworkIdentity NetID=null Player=null | |
// (which might be a UNET bug) | |
GameObject go = GetGameObjectFromPlayerControllers(ClientScene.localPlayers); | |
return go != null ? go.GetComponent<Player>() : null; | |
} | |
// parse first upper cased noun from a string, e.g. | |
// EquipmentWeaponBow => Equipment | |
// EquipmentShield => Equipment | |
public static string ParseFirstNoun(string text) | |
{ | |
MatchCollection matches = new Regex(@"([A-Z][a-z]*)").Matches(text); | |
return matches.Count > 0 ? matches[0].Value : ""; | |
} | |
// parse last upper cased noun from a string, e.g. | |
// EquipmentWeaponBow => Bow | |
// EquipmentShield => Shield | |
public static string ParseLastNoun(string text) | |
{ | |
MatchCollection matches = new Regex(@"([A-Z][a-z]*)").Matches(text); | |
return matches.Count > 0 ? matches[matches.Count-1].Value : ""; | |
} | |
// check if the cursor is over a UI or OnGUI element right now | |
// note: for UI, this only works if the UI's CanvasGroup blocks Raycasts | |
// note: for OnGUI: hotControl is only set while clicking, not while zooming | |
public static bool IsCursorOverUserInterface() | |
{ | |
// IsPointerOverGameObject check for left mouse (default) | |
if (EventSystem.current.IsPointerOverGameObject()) | |
return true; | |
// IsPointerOverGameObject check for touches | |
for (int i = 0; i < Input.touchCount; ++i) | |
if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(i).fingerId)) | |
return true; | |
// OnGUI check | |
return GUIUtility.hotControl != 0; | |
} | |
// PBKDF2 hashing recommended by NIST: | |
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf | |
// salt should be at least 128 bits = 16 bytes | |
public static string PBKDF2Hash(string text, string salt) | |
{ | |
byte[] saltBytes = Encoding.UTF8.GetBytes(salt); | |
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, 10000); | |
byte[] hash = pbkdf2.GetBytes(20); | |
return BitConverter.ToString(hash).Replace("-", string.Empty); | |
} | |
// invoke multiple functions by prefix via reflection. | |
// -> works for static classes too if object = null | |
// -> cache it so it's fast enough for Update calls | |
// -> C# only has Tuple support in 4.6, so we use KeyValuePair instead | |
static Dictionary<KeyValuePair<Type,string>, MethodInfo[]> lookup = new Dictionary<KeyValuePair<Type,string>, MethodInfo[]>(); | |
public static MethodInfo[] GetMethodsByPrefix(Type type, string methodPrefix) | |
{ | |
KeyValuePair<Type,string> key = new KeyValuePair<Type,string>(type, methodPrefix); | |
if (!lookup.ContainsKey(key)) | |
{ | |
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance) | |
.Where(m => m.Name.StartsWith(methodPrefix)) | |
.ToArray(); | |
lookup[key] = methods; | |
} | |
return lookup[key]; | |
} | |
public static void InvokeMany(Type type, object onObject, string methodPrefix, params object[] args) | |
{ | |
foreach (MethodInfo method in GetMethodsByPrefix(type, methodPrefix)) | |
method.Invoke(onObject, args.ToArray()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment