Loading finalized

This commit is contained in:
Savya Bikram Shah
2026-05-28 16:52:06 +05:45
parent 98fbad9233
commit 09ad3469f2
73 changed files with 12872 additions and 140 deletions

View File

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

Binary file not shown.

View File

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

View File

@@ -0,0 +1,19 @@
# UnityUtils Lib
## Purpose
`Darkmatter.Libs.UnityUtils` contains small Unity-facing helpers and editor/runtime utility code that is generic enough to be reused across slices.
## Public Entry Points
- Shared attributes and helper components under `Assets/Darkmatter/Code/Libs/UnityUtils`
## Dependencies
- Referenced by App and multiple features for inspector safety and lightweight helper behavior
- Should not accumulate gameplay rules or scene-specific orchestration
## Extension Notes
- Keep utilities narrowly scoped and reusable.
- If a helper starts depending on one features data model or service policy, move it back into that slice.

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a63598ae46644c6db94d7a9119c9982a
timeCreated: 1770193562

View File

@@ -0,0 +1,186 @@
using UnityEditor;
using UnityEngine;
namespace Darkmatter.Libs.UnityUtils.Editor
{
[CustomPropertyDrawer(typeof(RequireInterfaceAttribute))]
public class RequireInterfaceDrawer : PropertyDrawer
{
private const float HelpBoxHeight = 40f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Validate property type
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
EditorGUI.HelpBox(position,
"RequireInterface attribute can only be used with Object reference fields.",
MessageType.Error);
return;
}
var requiredAttribute = attribute as RequireInterfaceAttribute;
if (requiredAttribute?.InterfaceType == null)
{
EditorGUI.PropertyField(position, property, label);
return;
}
// Validate that the required type is actually an interface
if (!requiredAttribute.InterfaceType.IsInterface)
{
EditorGUI.HelpBox(position,
$"{requiredAttribute.InterfaceType.Name} is not an interface type.",
MessageType.Error);
return;
}
// Draw the object field
DrawInterfaceObjectField(position, property, label, requiredAttribute);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
// Show error message height if property type is invalid
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
return HelpBoxHeight;
}
var requiredAttribute = attribute as RequireInterfaceAttribute;
if (requiredAttribute?.InterfaceType != null && !requiredAttribute.InterfaceType.IsInterface)
{
return HelpBoxHeight;
}
return EditorGUIUtility.singleLineHeight;
}
private void DrawInterfaceObjectField(
Rect position,
SerializedProperty property,
GUIContent label,
RequireInterfaceAttribute requiredAttribute)
{
// Enhance label with interface type info
var enhancedLabel = new GUIContent(
label.text,
$"Requires: {requiredAttribute.InterfaceType.Name}\n{label.tooltip}"
);
EditorGUI.BeginProperty(position, enhancedLabel, property);
EditorGUI.BeginChangeCheck();
// Draw the object field
var newObj = EditorGUI.ObjectField(
position,
enhancedLabel,
property.objectReferenceValue,
typeof(Object),
true
);
if (EditorGUI.EndChangeCheck())
{
HandleObjectAssignment(property, newObj, requiredAttribute);
}
EditorGUI.EndProperty();
}
private void HandleObjectAssignment(
SerializedProperty property,
Object newObj,
RequireInterfaceAttribute requiredAttribute)
{
// Clear assignment if null
if (newObj == null)
{
property.objectReferenceValue = null;
return;
}
Object validComponent = null;
// Handle GameObject drag
if (newObj is GameObject gameObject)
{
validComponent = FindInterfaceComponent(gameObject, requiredAttribute.InterfaceType);
if (validComponent == null)
{
ShowInterfaceNotFoundWarning(gameObject.name, requiredAttribute.InterfaceType, true);
}
}
// Handle Component drag
else if (newObj is Component component)
{
if (IsValidInterface(component, requiredAttribute.InterfaceType))
{
validComponent = component;
}
else
{
// Try to find interface on the same GameObject
validComponent = FindInterfaceComponent(component.gameObject, requiredAttribute.InterfaceType);
if (validComponent == null)
{
ShowInterfaceNotFoundWarning(component.name, requiredAttribute.InterfaceType, false);
}
}
}
// Handle ScriptableObject or other Object types
else
{
if (IsValidInterface(newObj, requiredAttribute.InterfaceType))
{
validComponent = newObj;
}
else
{
ShowInterfaceNotFoundWarning(newObj.name, requiredAttribute.InterfaceType, false);
}
}
// Only assign if valid
if (validComponent != null)
{
property.objectReferenceValue = validComponent;
}
}
private Object FindInterfaceComponent(GameObject gameObject, System.Type interfaceType)
{
if (gameObject == null) return null;
// Get all components and find the first one that implements the interface
var components = gameObject.GetComponents<Component>();
foreach (var component in components)
{
if (component != null && IsValidInterface(component, interfaceType))
{
return component;
}
}
return null;
}
private bool IsValidInterface(Object obj, System.Type interfaceType)
{
if (obj == null || interfaceType == null) return false;
return interfaceType.IsAssignableFrom(obj.GetType());
}
private void ShowInterfaceNotFoundWarning(string objectName, System.Type interfaceType, bool isGameObject)
{
var objectType = isGameObject ? "GameObject" : "Object";
Debug.LogWarning(
$"[RequireInterface] {objectType} '{objectName}' does not implement interface '{interfaceType.Name}'. " +
$"Assignment rejected.",
Selection.activeObject
);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 85734cfb697d4fbd9b57d0fe3d28660a
timeCreated: 1770193571

View File

@@ -0,0 +1,18 @@
{
"name": "Libs.UnityUtils.Editor",
"rootNamespace": "Darkmatter.Libs.UnityUtils.Editor",
"references": [
"GUID:729fabb77852b4d3eae2417d9564dc37"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

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

View File

@@ -0,0 +1,103 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Darkmatter.Libs.UnityUtils.Editor
{
[InitializeOnLoad]
internal static class ConcaveMeshColliderVisualizer
{
private const string MenuPath = "Tools/Visualize Concave Mesh Colliders";
private const string PrefKey = "Darkmatter.ConcaveMeshColliderVisualizer.Enabled";
private static readonly Color BoundsColor = new Color(1f, 0.35f, 0.15f, 0.85f);
private static readonly Color WireColor = new Color(1f, 0.85f, 0.2f, 1f);
private static readonly List<MeshCollider> Buffer = new List<MeshCollider>(512);
static ConcaveMeshColliderVisualizer()
{
EditorApplication.delayCall += () => ApplyEnabledState(EditorPrefs.GetBool(PrefKey, false));
}
[MenuItem(MenuPath)]
private static void Toggle()
{
bool next = !EditorPrefs.GetBool(PrefKey, false);
EditorPrefs.SetBool(PrefKey, next);
ApplyEnabledState(next);
SceneView.RepaintAll();
}
[MenuItem(MenuPath, validate = true)]
private static bool ToggleValidate()
{
Menu.SetChecked(MenuPath, EditorPrefs.GetBool(PrefKey, false));
return true;
}
private static void ApplyEnabledState(bool enabled)
{
SceneView.duringSceneGui -= OnSceneGUI;
if (enabled) SceneView.duringSceneGui += OnSceneGUI;
Menu.SetChecked(MenuPath, enabled);
}
private static void OnSceneGUI(SceneView view)
{
HashSet<MeshCollider> selected = CollectSelectedConcave();
Buffer.Clear();
Buffer.AddRange(Object.FindObjectsByType<MeshCollider>(FindObjectsInactive.Include, FindObjectsSortMode.None));
for (int i = 0; i < Buffer.Count; i++)
{
MeshCollider mc = Buffer[i];
if (mc == null || mc.convex || mc.sharedMesh == null) continue;
DrawBounds(mc);
if (selected.Contains(mc)) DrawWire(mc);
}
}
private static HashSet<MeshCollider> CollectSelectedConcave()
{
var set = new HashSet<MeshCollider>();
foreach (GameObject go in Selection.gameObjects)
{
foreach (MeshCollider mc in go.GetComponentsInChildren<MeshCollider>(true))
{
if (!mc.convex && mc.sharedMesh != null) set.Add(mc);
}
}
return set;
}
private static void DrawBounds(MeshCollider mc)
{
Bounds b = mc.bounds;
using (new Handles.DrawingScope(BoundsColor))
Handles.DrawWireCube(b.center, b.size);
}
private static void DrawWire(MeshCollider mc)
{
Mesh m = mc.sharedMesh;
Vector3[] verts = m.vertices;
int[] tris = m.triangles;
using (new Handles.DrawingScope(WireColor, mc.transform.localToWorldMatrix))
{
for (int i = 0; i < tris.Length; i += 3)
{
Vector3 a = verts[tris[i]];
Vector3 b = verts[tris[i + 1]];
Vector3 c = verts[tris[i + 2]];
Handles.DrawLine(a, b);
Handles.DrawLine(b, c);
Handles.DrawLine(c, a);
}
}
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ba7dba92fa2af48b8b0eed3560442bec

View File

@@ -0,0 +1,198 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Darkmatter.Libs.UnityUtils.Editor
{
public class SceneRendererOptimizer : EditorWindow
{
const string DefaultNamePattern = "puddle|pPlane|decal|tree|plant|grass|bush|leaf|leaves|foliage|flower|hedge|sticker";
const string DefaultMaterialPattern = "puddle|decal|tree|grass|leaf|leaves|foliage|plant|vegetation";
string namePattern = DefaultNamePattern;
string materialPattern = DefaultMaterialPattern;
bool useTransparentQueueAsDecal = true;
bool onlyActiveRenderers = true;
Vector2 scroll;
string lastReport = "";
[MenuItem("Tools/Darkmatter/Scene Renderer Optimizer")]
public static void ShowWindow()
{
var w = GetWindow<SceneRendererOptimizer>("Scene Renderer Optimizer");
w.minSize = new Vector2(520, 400);
}
void OnGUI()
{
EditorGUILayout.LabelField("Match Filters (regex, case-insensitive)", EditorStyles.boldLabel);
namePattern = EditorGUILayout.TextField("GameObject name", namePattern);
materialPattern = EditorGUILayout.TextField("Material name", materialPattern);
useTransparentQueueAsDecal = EditorGUILayout.Toggle("Treat transparent (queue>=2450) as decal", useTransparentQueueAsDecal);
onlyActiveRenderers = EditorGUILayout.Toggle("Skip inactive renderers", onlyActiveRenderers);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Operations", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"MATCHED renderers (vegetation/decals): remove all Colliders + Cast Shadows = Off.\n" +
"UNMATCHED renderers marked Batching Static: Static Shadow Caster = On.\n" +
"Operates on the currently OPEN scene. Undo supported.",
MessageType.Info);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Preview (dry run)")) Run(dryRun: true);
if (GUILayout.Button("Apply")) Run(dryRun: false);
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Last Report", EditorStyles.boldLabel);
scroll = EditorGUILayout.BeginScrollView(scroll);
EditorGUILayout.TextArea(lastReport, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
}
void Run(bool dryRun)
{
var scene = SceneManager.GetActiveScene();
if (!scene.IsValid())
{
lastReport = "No active scene.";
return;
}
Regex nameRx, matRx;
try
{
nameRx = new Regex(namePattern, RegexOptions.IgnoreCase);
matRx = new Regex(materialPattern, RegexOptions.IgnoreCase);
}
catch (System.Exception e)
{
lastReport = "Bad regex: " + e.Message;
return;
}
var roots = scene.GetRootGameObjects();
var allRenderers = new List<Renderer>();
foreach (var r in roots) allRenderers.AddRange(r.GetComponentsInChildren<Renderer>(includeInactive: !onlyActiveRenderers));
int matchedCount = 0, collidersRemoved = 0, shadowsOff = 0, staticShadowOn = 0, skipped = 0;
var matchedNames = new List<string>();
int undoGroup = -1;
if (!dryRun)
{
Undo.IncrementCurrentGroup();
Undo.SetCurrentGroupName("Scene Renderer Optimizer");
undoGroup = Undo.GetCurrentGroup();
}
foreach (var r in allRenderers)
{
if (!r) continue;
var go = r.gameObject;
bool matches = IsMatch(go, r, nameRx, matRx);
if (matches)
{
matchedCount++;
if (matchedNames.Count < 200) matchedNames.Add(GetHierarchyPath(go.transform));
if (!dryRun)
{
var colliders = go.GetComponents<Collider>();
foreach (var c in colliders)
{
Undo.DestroyObjectImmediate(c);
collidersRemoved++;
}
if (r.shadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off)
{
Undo.RecordObject(r, "Disable Shadow Cast");
r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
shadowsOff++;
EditorUtility.SetDirty(r);
}
}
else
{
collidersRemoved += go.GetComponents<Collider>().Length;
if (r.shadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off) shadowsOff++;
}
}
else
{
var flags = GameObjectUtility.GetStaticEditorFlags(go);
bool isBatchingStatic = (flags & StaticEditorFlags.BatchingStatic) != 0;
if (isBatchingStatic && r.shadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off)
{
if (!r.staticShadowCaster)
{
if (!dryRun)
{
Undo.RecordObject(r, "Enable Static Shadow Caster");
r.staticShadowCaster = true;
EditorUtility.SetDirty(r);
}
staticShadowOn++;
}
}
else
{
skipped++;
}
}
}
if (!dryRun)
{
Undo.CollapseUndoOperations(undoGroup);
EditorSceneManager.MarkSceneDirty(scene);
}
var sb = new System.Text.StringBuilder();
sb.AppendLine(dryRun ? "DRY RUN" : "APPLIED");
sb.AppendLine($"Scene: {scene.name}");
sb.AppendLine($"Renderers scanned: {allRenderers.Count}");
sb.AppendLine($"Matched (vegetation/decal): {matchedCount}");
sb.AppendLine($" Colliders to remove / removed: {collidersRemoved}");
sb.AppendLine($" Shadows off: {shadowsOff}");
sb.AppendLine($"Static Shadow Caster enabled: {staticShadowOn}");
sb.AppendLine($"Skipped (non-static, non-match): {skipped}");
sb.AppendLine();
sb.AppendLine("First matched (up to 200):");
foreach (var n in matchedNames) sb.AppendLine(" " + n);
lastReport = sb.ToString();
}
bool IsMatch(GameObject go, Renderer r, Regex nameRx, Regex matRx)
{
if (nameRx.IsMatch(go.name)) return true;
var mats = r.sharedMaterials;
for (int i = 0; i < mats.Length; i++)
{
var m = mats[i];
if (!m) continue;
if (matRx.IsMatch(m.name)) return true;
if (useTransparentQueueAsDecal && m.renderQueue >= 2450) return true;
}
return false;
}
static string GetHierarchyPath(Transform t)
{
var stack = new Stack<string>();
while (t)
{
stack.Push(t.name);
t = t.parent;
}
return string.Join("/", stack);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 361cb7c2dff8e4e7c905f18797743962

View File

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

View File

@@ -0,0 +1,18 @@
using System;
namespace Darkmatter.Libs.UnityUtils.Runtime.Progress;
public sealed class ActionProgress : IProgress<float>
{
private readonly Action<float> _onReport;
public ActionProgress(Action<float> onReport)
{
_onReport = onReport ?? throw new ArgumentNullException(nameof(onReport));
}
public void Report(float value)
{
_onReport(value);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
using System;
using UnityEngine;
namespace Darkmatter.Libs.UnityUtils
{
/// <summary>
/// Attribute that restricts an Object field to only accept objects implementing a specific interface.
/// Works with GameObjects (will find component), Components, and ScriptableObjects.
/// </summary>
/// <example>
/// [SerializeField, RequireInterface(typeof(IDamageable))]
/// private Object damageableTarget;
/// </example>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class RequireInterfaceAttribute : PropertyAttribute
{
public Type InterfaceType { get; private set; }
/// <summary>
/// Restricts the field to objects implementing the specified interface.
/// </summary>
/// <param name="interfaceType">The interface type that objects must implement.</param>
public RequireInterfaceAttribute(Type interfaceType)
{
if (interfaceType == null)
{
Debug.LogError("[RequireInterface] Interface type cannot be null.");
return;
}
if (!interfaceType.IsInterface)
{
Debug.LogError($"[RequireInterface] {interfaceType.Name} is not an interface type.");
return;
}
InterfaceType = interfaceType;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f8a1a1b07dd1f46eba877a6ec95cc84e

View File

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

View File

@@ -0,0 +1,17 @@
using UnityEngine;
namespace Darkmatter.Libs.UnityUtils.Runtime.Gauges;
public static class AnalogGaugeMath
{
public static float EvaluateAngle(
float inputValue,
float startValue,
float endValue,
float startAngle,
float endAngle)
{
var t = Mathf.Clamp01(Mathf.InverseLerp(startValue, endValue, inputValue));
return Mathf.Lerp(startAngle, endAngle, t);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1bcc103ac595a4c4f80cdc391ed62ebb

View File

@@ -0,0 +1,38 @@
using UnityEngine;
namespace Darkmatter.Libs.UnityUtils.Runtime.Gauges;
public struct SmoothedFloat
{
private float _currentValue;
private bool _isInitialized;
public float CurrentValue => _currentValue;
public bool IsInitialized => _isInitialized;
public float Update(float targetValue, float responseSpeed, float deltaTime)
{
if (!_isInitialized)
return Snap(targetValue);
if (responseSpeed <= 0f)
return Snap(targetValue);
var blend = 1f - Mathf.Exp(-responseSpeed * Mathf.Max(0f, deltaTime));
_currentValue = Mathf.Lerp(_currentValue, targetValue, blend);
return _currentValue;
}
public float Snap(float value)
{
_currentValue = value;
_isInitialized = true;
return _currentValue;
}
public void Reset()
{
_currentValue = 0f;
_isInitialized = false;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 12e35c688b9e54636ad36981b60913f3

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b6e68b7014224451bad76d3a7bd845b1
timeCreated: 1770792751

View File

@@ -0,0 +1,29 @@
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
namespace Darkmatter.Libs.UnityUtils.Runtime.Inputs;
public static class InputUtils
{
public static bool IsPointerOverGameObject()
{
if (EventSystem.current == null) return false;
if (Touchscreen.current == null)
return EventSystem.current.IsPointerOverGameObject();
foreach (var touch in Touchscreen.current.touches)
{
if (touch.press.isPressed)
{
int touchId = touch.touchId.ReadValue();
if (EventSystem.current.IsPointerOverGameObject(touchId))
{
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c7dd336a56054f05b73cea102c916715
timeCreated: 1770792778

View File

@@ -0,0 +1,230 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
namespace Darkmatter.Libs.UnityUtils.Runtime.Inputs;
public sealed class TouchGestureSelector
{
private int _sampleFrame = -1;
private int _activeLookTouchId = -1;
private readonly HashSet<int> _activeTouchIds = new();
private readonly HashSet<int> _busyTouchIds = new();
private readonly List<int> _releasedTouchIds = new();
private readonly List<RaycastResult> _uiRaycastResults = new(8);
private EventSystem _cachedEventSystem;
private PointerEventData _pointerEventData;
public void Reset()
{
_sampleFrame = -1;
_activeLookTouchId = -1;
_activeTouchIds.Clear();
_busyTouchIds.Clear();
_releasedTouchIds.Clear();
_uiRaycastResults.Clear();
_cachedEventSystem = null;
_pointerEventData = null;
}
public bool TryGetLookDelta(float activationDeltaPixels, out Vector2 touchDelta)
{
touchDelta = Vector2.zero;
RefreshOwnership();
Touchscreen touchscreen = Touchscreen.current;
if (touchscreen == null)
{
return false;
}
if (_activeLookTouchId >= 0 &&
!_busyTouchIds.Contains(_activeLookTouchId) &&
TryGetPressedTouchDeltaById(touchscreen, _activeLookTouchId, out Vector2 activeTouchDelta))
{
touchDelta = activeTouchDelta;
return true;
}
_activeLookTouchId = -1;
float activationDeltaSqr = Mathf.Max(0f, activationDeltaPixels);
activationDeltaSqr *= activationDeltaSqr;
foreach (var touchControl in touchscreen.touches)
{
if (!touchControl.press.isPressed)
{
continue;
}
int touchId = touchControl.touchId.ReadValue();
if (_busyTouchIds.Contains(touchId))
{
continue;
}
Vector2 delta = touchControl.delta.ReadValue();
if (delta.sqrMagnitude < activationDeltaSqr)
{
continue;
}
_activeLookTouchId = touchId;
touchDelta = delta;
return true;
}
return false;
}
public bool TryGetPinchPositions(out Vector2 firstTouch, out Vector2 secondTouch)
{
firstTouch = default;
secondTouch = default;
RefreshOwnership();
Touchscreen touchscreen = Touchscreen.current;
if (touchscreen == null)
{
return false;
}
int activeTouchCount = 0;
foreach (var touchControl in touchscreen.touches)
{
if (!touchControl.press.isPressed)
{
continue;
}
int touchId = touchControl.touchId.ReadValue();
if (_busyTouchIds.Contains(touchId))
{
continue;
}
Vector2 position = touchControl.position.ReadValue();
if (activeTouchCount == 0)
{
firstTouch = position;
}
else
{
secondTouch = position;
return true;
}
activeTouchCount++;
}
return false;
}
private void RefreshOwnership()
{
if (_sampleFrame == Time.frameCount)
{
return;
}
_sampleFrame = Time.frameCount;
Touchscreen touchscreen = Touchscreen.current;
if (touchscreen == null)
{
_activeTouchIds.Clear();
_busyTouchIds.Clear();
_releasedTouchIds.Clear();
_activeLookTouchId = -1;
return;
}
_releasedTouchIds.Clear();
foreach (int activeTouchId in _activeTouchIds)
{
_releasedTouchIds.Add(activeTouchId);
}
foreach (var touchControl in touchscreen.touches)
{
if (!touchControl.press.isPressed)
{
continue;
}
int touchId = touchControl.touchId.ReadValue();
if (_releasedTouchIds.Remove(touchId))
{
continue;
}
_activeTouchIds.Add(touchId);
if (IsScreenPositionOverUi(touchControl.position.ReadValue()))
{
_busyTouchIds.Add(touchId);
}
}
for (int i = 0; i < _releasedTouchIds.Count; i++)
{
int releasedTouchId = _releasedTouchIds[i];
_activeTouchIds.Remove(releasedTouchId);
_busyTouchIds.Remove(releasedTouchId);
if (_activeLookTouchId == releasedTouchId)
{
_activeLookTouchId = -1;
}
}
}
private static bool TryGetPressedTouchDeltaById(Touchscreen touchscreen, int touchId, out Vector2 touchDelta)
{
touchDelta = default;
if (touchscreen == null)
{
return false;
}
foreach (var touchControl in touchscreen.touches)
{
if (!touchControl.press.isPressed)
{
continue;
}
if (touchControl.touchId.ReadValue() != touchId)
{
continue;
}
touchDelta = touchControl.delta.ReadValue();
return true;
}
return false;
}
private bool IsScreenPositionOverUi(Vector2 screenPosition)
{
EventSystem eventSystem = EventSystem.current;
if (eventSystem == null)
{
return false;
}
if (_pointerEventData == null || !ReferenceEquals(_cachedEventSystem, eventSystem))
{
_cachedEventSystem = eventSystem;
_pointerEventData = new PointerEventData(eventSystem);
}
_pointerEventData.Reset();
_pointerEventData.position = screenPosition;
_uiRaycastResults.Clear();
eventSystem.RaycastAll(_pointerEventData, _uiRaycastResults);
return _uiRaycastResults.Count > 0;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d0d2b9f1dac7d4b4caaf8e856deae6b2

View File

@@ -0,0 +1,16 @@
{
"name": "Libs.UnityUtils",
"rootNamespace": "Darkmatter.Libs.UnityUtils",
"references": [
"GUID:75469ad4d38634e559750d17036d5f7c"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

@@ -0,0 +1,29 @@
using System;
namespace Darkmatter.Libs.UnityUtils.Runtime.Progress;
public sealed class MappedProgress : IProgress<float>
{
private readonly IProgress<float> _target;
private readonly float _offset;
private readonly float _scale;
public MappedProgress(IProgress<float> target, float offset, float scale)
{
_target = target ?? throw new ArgumentNullException(nameof(target));
_offset = offset;
_scale = scale;
}
public void Report(float value)
{
var mapped = _offset + (value * _scale);
if (mapped < 0f)
mapped = 0f;
else if (mapped > 1f)
mapped = 1f;
_target.Report(mapped);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
namespace Darkmatter.Libs.UnityUtils.Runtime.Transforms;
public enum RotationAxis
{
X,
Y,
Z
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 26da8545c2aed450f96a49533c8724c3

View File

@@ -0,0 +1,26 @@
using UnityEngine;
namespace Darkmatter.Libs.UnityUtils.Runtime.Transforms;
public static class RotationAxisUtility
{
public static float GetAxisValue(Vector3 eulerAngles, RotationAxis axis)
{
return axis switch
{
RotationAxis.X => eulerAngles.x,
RotationAxis.Y => eulerAngles.y,
_ => eulerAngles.z
};
}
public static Vector3 WithAxisValue(Vector3 eulerAngles, RotationAxis axis, float value)
{
return axis switch
{
RotationAxis.X => new Vector3(value, eulerAngles.y, eulerAngles.z),
RotationAxis.Y => new Vector3(eulerAngles.x, value, eulerAngles.z),
_ => new Vector3(eulerAngles.x, eulerAngles.y, value)
};
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fc98172be80b7481795f4745ceee04bd

View File

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