Created
November 29, 2024 13:50
-
-
Save RiskyWilhelm/1ad143dc07764a01b3d2ae6e868c5485 to your computer and use it in GitHub Desktop.
Unity Utils (C# >= 9)
This file contains hidden or 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
using System; | |
public static class DateTimeUtils | |
{ | |
/// <summary> By default, <see cref="DateTimeExtensions"/> and <see cref="DateTimeUtils"/> will offset the hour by this value to match Cardinal Direction. Means, degree of zero will equal to 18:00 in a circle </summary> | |
public const float DefaultOffsetHour = 18f; | |
public const float ExactHourDegree = (360f / 24f); // 1 hour = 15 degree | |
public const float ExactMinutePoint = (ExactHourDegree / 60f); // 1 min = 0,25 degree | |
public const float ExactSecondPoint = (ExactMinutePoint / 60f); // 1 second = 0,004166 degree | |
// TODO: Find Year Month and Day from degree | |
/// <remarks> Assumes the time 12:00 equals North(Y+ when facing to Z+) in cardinal direction. Means, degree of zero will equal to 18:00 in a circle </remarks> | |
public static DateTime AngleDegreeToDateTime(float timeDegree, sbyte offsetHour = 0) | |
{ | |
float offsetHourDegree = (offsetHour + DefaultOffsetHour) * ExactHourDegree; | |
float verticallyMirroredDegree = (360f - timeDegree); // Angle is not inverted. Mirrored the angle in a circle vertically. That way, time will grow if rotation getting negative | |
float calculatedDegree = (verticallyMirroredDegree + offsetHourDegree) % 360f; | |
int hour = (int)(calculatedDegree / ExactHourDegree); | |
calculatedDegree -= (hour * ExactHourDegree); | |
int minute = (int)(calculatedDegree / ExactMinutePoint); | |
calculatedDegree -= (minute * ExactMinutePoint); | |
int second = (int)(calculatedDegree / ExactSecondPoint); | |
return new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, hour, minute, second); | |
} | |
} |
This file contains hidden or 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
using System; | |
public static class EnumUtils | |
{ | |
/// <summary> Throws exception on mismatch </summary> | |
public static void ValidateUnderlyingType<EnumType, EnumUnderlyingType>() | |
where EnumType : Enum | |
where EnumUnderlyingType : struct | |
{ | |
if (Enum.GetUnderlyingType(typeof(EnumType)) != typeof(EnumUnderlyingType)) | |
throw new NotSupportedException("Underlying value Type of EnumType and EnumUnderlyingType must be same and enums only support; byte, sbyte, short, ushort, int, uint, long, or ulong"); | |
} | |
public static void ValidateFlagEnum<T>() | |
where T : Enum | |
{ | |
if (!typeof(T).IsDefined(typeof(FlagsAttribute), inherit: false)) | |
throw new NotSupportedException("Only flag enums with Flags attribute supported"); | |
} | |
} |
This file contains hidden or 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
using System; | |
using UnityEngine; | |
public static class EventReflectorUtils | |
{ | |
/// <returns> If found, returns the reflected GameObject, else self </returns> | |
public static bool TryGetReflectedGameObject(GameObject gameObject, out GameObject reflectedTo) | |
{ | |
if (gameObject.TryGetComponent<EventReflector>(out EventReflector foundEventReflector)) | |
{ | |
reflectedTo = foundEventReflector.reflected; | |
return true; | |
} | |
reflectedTo = gameObject; | |
return false; | |
} | |
/// <summary> Same with <see cref="GameObject.TryGetComponent{T}(out T)"/> except it reflects the method to desired <see cref="GameObject"/> via <see cref="EventReflector"/> if there is any </summary> | |
public static bool TryGetComponentByEventReflector<TargetType>(GameObject searchGameObject, out TargetType foundTarget) | |
{ | |
// Check if event wants to reflect the collision. If there is no EventReflector, it is the main object that wants the event | |
TryGetReflectedGameObject(searchGameObject, out searchGameObject); | |
// Try Get AI target | |
return searchGameObject.TryGetComponent<TargetType>(out foundTarget); | |
} | |
/// <summary> Same with <see cref="GameObject.TryGetComponent(System.Type, out Component)"/> except it reflects the method to desired <see cref="GameObject"/> via <see cref="EventReflector"/> if there is any </summary> | |
public static bool TryGetComponentByEventReflector(Type targetType, GameObject searchGameObject, out Component foundTarget) | |
{ | |
// Try get the reflected GameObject otherwise return same | |
TryGetReflectedGameObject(searchGameObject, out searchGameObject); | |
// Try Get AI target | |
return searchGameObject.TryGetComponent(targetType, out foundTarget); | |
} | |
} |
This file contains hidden or 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
using System; | |
using System.IO; | |
using Cysharp.Text; | |
using Newtonsoft.Json; | |
using UnityEngine; | |
// Save&Load Copyright belongs to: https://github.com/shapedbyrainstudios/save-load-system - I upgraded the code | |
public static class IOUtils | |
{ | |
public const string encryptionWord = "Lightout"; | |
public const string backupExtension = ".backup"; | |
/// <summary> Replaces '/' with <see cref="Path.DirectorySeparatorChar"/> </summary> | |
public static string FixPathByCorrectDirectorySeperator(string path) | |
{ | |
return path.Replace('/', Path.DirectorySeparatorChar); | |
} | |
/// <inheritdoc cref="FixPathByCorrectDirectorySeperator(string)"/> | |
public static string FixPathByCorrectDirectorySeperator(ref string path) | |
=> path = FixPathByCorrectDirectorySeperator(path); | |
/// <summary> Uses Newtonsoft JSON to deserialize </summary> | |
public static bool Load<LoadObjectType>(string fullPathWithExtension, out LoadObjectType loadedData, bool useDecryption = false, bool allowRestoreFromBackup = true) | |
{ | |
loadedData = default; | |
FixPathByCorrectDirectorySeperator(ref fullPathWithExtension); | |
// Try to load backup if file does not exists | |
if (!File.Exists(fullPathWithExtension)) | |
{ | |
Debug.LogError($"Failed to load file at path: {fullPathWithExtension} File does not exists"); | |
return allowRestoreFromBackup && TrySaveRollbackAsMainFile(fullPathWithExtension) && Load<LoadObjectType>(fullPathWithExtension, out loadedData, useDecryption, false); | |
} | |
// load the serialized data from the file | |
try | |
{ | |
string dataToLoad = ""; | |
using (var stream = new FileStream(fullPathWithExtension, FileMode.Open)) | |
{ | |
using var reader = new StreamReader(stream); | |
dataToLoad = reader.ReadToEnd(); | |
} | |
if (useDecryption) | |
dataToLoad = EncryptDecrypt(dataToLoad); | |
loadedData = JsonConvert.DeserializeObject<LoadObjectType>(dataToLoad); | |
return true; | |
} | |
// Try to load backup if any exists | |
catch (Exception e) | |
{ | |
if (allowRestoreFromBackup) | |
{ | |
Debug.LogWarning($"Failed to load file at path: {fullPathWithExtension} Attempting to rollback. Error occured: {e}"); | |
if (TrySaveRollbackAsMainFile(fullPathWithExtension) && Load<LoadObjectType>(fullPathWithExtension, out loadedData, useDecryption, false)) | |
return true; | |
} | |
// if we hit here, one possibility is that the backup file is also corrupt | |
Debug.LogError($"Failed to load file at path: {fullPathWithExtension} and backup did not work. Maybe the file is corrupted. Error occured: {e}"); | |
} | |
return false; | |
} | |
/// <summary> Uses Newtonsoft JSON to serialize </summary> | |
public static void Save<SaveObjectType>(SaveObjectType data, string fullPathWithExtension, bool useEncryption = false, bool createBackup = true) | |
{ | |
FixPathByCorrectDirectorySeperator(ref fullPathWithExtension); | |
try | |
{ | |
Directory.CreateDirectory(Path.GetDirectoryName(fullPathWithExtension)); | |
string dataToStore = JsonConvert.SerializeObject(data); | |
if (useEncryption) | |
dataToStore = EncryptDecrypt(dataToStore); | |
// write the serialized data to the file | |
using (var stream = new FileStream(fullPathWithExtension, FileMode.Create)) | |
{ | |
using var writer = new StreamWriter(stream); | |
writer.Write(dataToStore); | |
} | |
// verify the newly saved file can be loaded successfully | |
// if the data can be verified, back it up | |
if (Load<SaveObjectType>(fullPathWithExtension, out _, useEncryption, false) && createBackup) | |
File.Copy(fullPathWithExtension, ZString.Concat(fullPathWithExtension, backupExtension), true); | |
else | |
throw new Exception($"Save file could not be verified and backup could not be created at path: {fullPathWithExtension}"); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError($"Error occured when trying to save data to file at path: {fullPathWithExtension} Error occured: {e}"); | |
} | |
} | |
public static void Delete(string fullPathWithExtension, bool deleteBackup = true) | |
{ | |
if (!File.Exists(fullPathWithExtension)) | |
{ | |
Debug.LogError($"Failed to delete file at path: {fullPathWithExtension} File does not exists"); | |
return; | |
} | |
if (deleteBackup) | |
File.Delete(ZString.Concat(fullPathWithExtension, backupExtension)); | |
File.Delete(fullPathWithExtension); | |
} | |
/// <summary> Simple implementation of XOR encryption </summary> | |
private static string EncryptDecrypt(string data) | |
{ | |
using var stringBuilder = ZString.CreateStringBuilder(); | |
stringBuilder.TryGrow(data.Length); | |
for (int i = 0; i < data.Length; i++) | |
stringBuilder.Append((char)(data[i] ^ encryptionWord[i % encryptionWord.Length])); | |
return stringBuilder.ToString(); | |
} | |
private static bool TrySaveRollbackAsMainFile(string fullPathWithExtension) | |
{ | |
string backupFilePath = ZString.Concat(fullPathWithExtension, backupExtension); | |
FixPathByCorrectDirectorySeperator(ref backupFilePath); | |
try | |
{ | |
if (!File.Exists(backupFilePath)) | |
{ | |
Debug.LogError("Tried to Rollback but no backup file exists to roll back to."); | |
return false; | |
} | |
File.Copy(backupFilePath, fullPathWithExtension, true); | |
Debug.Log($"Saved backup as main file to: {fullPathWithExtension}"); | |
return true; | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError($"Failed to Rollback when trying to roll back to backup file at: {backupFilePath} Error Occured: {e}"); | |
return false; | |
} | |
} | |
} |
This file contains hidden or 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
using System; | |
using UnityEngine; | |
public static class LuckUtils | |
{ | |
public static LuckType Generate() | |
{ | |
var generatedLuck = LuckType.VeryCommon; | |
var generatedValue = UnityEngine.Random.value; | |
// Check the possibilites. | |
// For example: | |
// (generatedValue = 0,7) and (posibility = 0.9) | |
// In that case, (inner posibility = 0,9 - (0,9 * 0,25)) which equals (inner posibility = 0,675) | |
for (int i = (int)LuckType.Impossible; i >= (int)LuckType.VeryCommon; i >>= 1) | |
{ | |
// Get rid of division by zero error | |
if (i == 0) | |
continue; | |
if (Enum.IsDefined(typeof(LuckType), i)) | |
{ | |
var possibility = (1f / Math.Abs(i)); | |
var selectionSize = 0.25f; | |
var innerPosibility = Mathf.Clamp01(possibility - (possibility * selectionSize)); | |
var isValid = (generatedValue <= possibility) && (generatedValue >= innerPosibility); | |
if (isValid) | |
{ | |
generatedLuck = (LuckType)i; | |
break; | |
} | |
} | |
} | |
return generatedLuck; | |
} | |
} |
This file contains hidden or 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
using System; | |
using UnityEngine; | |
public static class MathfUtils | |
{ | |
/// <summary> Returns the angle in 2PI radians whose Tan is y/x. Same as <see cref="Mathf.Atan2(float, float)"/> but returns in 2PI radians (0-360 degree) instead of PI radians (-180~180 degree) </summary> | |
public static float Atan2_360(float y, float x) | |
{ | |
var radian = MathF.Atan2(y, x); | |
// Same as adding 360 degree to a angle in degrees | |
if (radian < 0f) | |
radian += (Mathf.PI * 2); | |
return radian; | |
} | |
public static float InvertAngle_360(float angleInDegrees) | |
{ | |
return (angleInDegrees + (180 * Math.Sign(angleInDegrees))) % 360; | |
} | |
public static float InvertAngle(float angleInRadians) | |
{ | |
return (angleInRadians + (Mathf.PI * Math.Sign(angleInRadians))) % (Mathf.PI * 2); | |
} | |
} |
This file contains hidden or 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
using System; | |
using UnityEngine; | |
public static class VectorUtils | |
{ | |
private static readonly System.Random randomizer = new(); | |
public static Vector3 RandomRange(Vector3 minInclusive, Vector3 maxExclusive) | |
{ | |
var x = randomizer.NextFloat(minInclusive.x, maxExclusive.x); | |
var y = randomizer.NextFloat(minInclusive.y, maxExclusive.y); | |
var z = randomizer.NextFloat(minInclusive.z, maxExclusive.z); | |
return new Vector3(x, y, z); | |
} | |
public static Vector2 RadianToNormalizedVector(float angleInRadians) | |
{ | |
return new Vector2(MathF.Cos(angleInRadians), MathF.Sin(angleInRadians)); | |
} | |
public static Vector3 RadianToVector(float angleInRadians, Vector3 axisToRotateAround) | |
=> DegreeToVector(angleInRadians * Mathf.Rad2Deg, axisToRotateAround); | |
public static Vector2 DegreeToNormalizedVector(float angleInDegrees) | |
=> RadianToNormalizedVector(angleInDegrees * Mathf.Deg2Rad); | |
public static Vector3 DegreeToVector(float angleInDegrees, Vector3 axisToRotateAround) | |
=> Quaternion.AngleAxis(angleInDegrees, axisToRotateAround).eulerAngles; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment