Initial commit

This commit is contained in:
2026-06-02 18:57:47 -04:00
commit 59d26a915d
268 changed files with 41240 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
{
"name": "BracerLib",
"rootNamespace": "BracerLib",
"references": [
"GUID:75469ad4d38634e559750d17036d5f7c"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a60bafe2eaba2f24fbb33ab1ab52999b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1e57509c8f334614782377c50d3b006b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,31 @@
using System;
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Utility.Coroutines
{
[ExcludeFromCoverage]
public class CancellableWaitForSeconds : CustomYieldInstruction
{
private float remaining;
private readonly Func<bool> predicate;
public override bool keepWaiting => ShouldKeepWaiting();
public CancellableWaitForSeconds(float wait, Func<bool> predicate)
{
remaining = wait;
this.predicate = predicate;
}
private bool ShouldKeepWaiting()
{
remaining -= Time.deltaTime;
var check = CheckCustomPredicate();
return !(check || remaining < 0f);
}
private bool CheckCustomPredicate() => predicate?.Invoke() ?? false;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c1f806820d3cd904399807656f490d62
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 095d1626aba91364faf64598b4f35f64
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
using System;
namespace BracerLib.DI
{
public struct Dependency
{
public Type Type { get; }
public FactoryFunc Factory { get; }
public DependencyLifetime Lifetime { get; }
public Dependency(Type type, FactoryFunc provider, DependencyLifetime lifetime)
{
Type = type;
Factory = provider;
Lifetime = lifetime;
}
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 08b41999af4aec5459aa82b9a53bcf46
+16
View File
@@ -0,0 +1,16 @@
using System.Collections;
using System.Collections.Generic;
namespace BracerLib.DI
{
public class DependencyCollection : IEnumerable<Dependency>
{
private readonly IList<Dependency> dependencies = new List<Dependency>();
public void Add(Dependency dependency) => dependencies.Add(dependency);
public IEnumerator<Dependency> GetEnumerator() => dependencies.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => dependencies.GetEnumerator();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a09894721d4f3714aa39bc4335147866
+28
View File
@@ -0,0 +1,28 @@
using UnityEngine;
namespace BracerLib.DI
{
[DefaultExecutionOrder(-1)]
public abstract class DependencyContext : MonoBehaviour
{
private DependencyProvider dependencyProvider;
protected readonly DependencyCollection dependencyCollection = new();
protected abstract void Setup();
protected abstract void Configure();
private void Awake()
{
Setup();
dependencyProvider = new DependencyProvider(dependencyCollection);
var children = GetComponentsInChildren<MonoBehaviour>(true);
foreach (var child in children)
dependencyProvider.Inject(child);
Configure();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: af6df49e6c26ffe41a2c34e13afdb851
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Runtime.Serialization;
using UnityEngine;
using Object = UnityEngine.Object;
namespace BracerLib.DI
{
public delegate object FactoryFunc(DependencyProvider provider);
public static class DependencyFactory
{
public static FactoryFunc FromClass<T>() where T : class, new()
{
return provider =>
{
var type = typeof(T);
var obj = FormatterServices.GetUninitializedObject(type);
provider.Inject(obj);
type.GetConstructor(Type.EmptyTypes)?.Invoke(obj, null);
return (T)obj;
};
}
public static FactoryFunc FromGameObject<T>(T instance) where T : MonoBehaviour
{
return dependencies =>
{
var children = instance.GetComponentsInChildren<MonoBehaviour>(true);
foreach (var child in children)
dependencies.Inject(child);
return instance;
};
}
public static FactoryFunc FromPrefab<T>(T prefab) where T : MonoBehaviour
{
return provider =>
{
var prefabObj = prefab.gameObject;
var wasActive = prefabObj.activeSelf;
prefabObj.SetActive(false);
var instance = Object.Instantiate(prefab);
prefabObj.SetActive(wasActive);
var children = instance.GetComponentsInChildren<MonoBehaviour>(true);
foreach (var child in children)
provider.Inject(child);
instance.gameObject.SetActive(wasActive);
return instance.GetComponent<T>();
};
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4dd8b23ba952434428cdbc308db727b1
+8
View File
@@ -0,0 +1,8 @@
namespace BracerLib.DI
{
public enum DependencyLifetime
{
Transient = 0,
Singleton
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3d180c5acba365c4c9381bc61d6c6781
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace BracerLib.DI
{
public class DependencyProvider
{
private IDictionary<Type, Dependency> dependencyMap = new Dictionary<Type, Dependency>();
private IDictionary<Type, object> singletons = new Dictionary<Type, object>();
public DependencyProvider(DependencyCollection collection)
{
foreach (var d in collection)
dependencyMap.Add(d.Type, d);
}
public object Get(Type type)
{
if (!dependencyMap.ContainsKey(type))
throw new ArgumentException($"Type is not a dependency: {type.FullName}");
var dependency = dependencyMap[type];
switch (dependency.Lifetime)
{
case DependencyLifetime.Transient: return dependency.Factory(this);
case DependencyLifetime.Singleton:
if (!singletons.ContainsKey(type))
singletons.Add(type, dependency.Factory(this));
return singletons[type];
default: throw new ArgumentOutOfRangeException($"Unavailable lifetime for dependency: {type.FullName}");
}
}
public T Get<T>() => (T)Get(typeof(T));
public object Inject(object dependant)
{
var type = dependant.GetType();
while (type != null)
{
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly |
BindingFlags.Instance);
foreach (var f in fields)
{
if (f.GetCustomAttribute<InjectFieldAttribute>(false) == null)
continue;
f.SetValue(dependant, Get(f.FieldType));
}
type = type.BaseType;
}
return dependant;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0857e79315720b44eb82767785bfa5b4
@@ -0,0 +1,6 @@
using System;
namespace BracerLib.DI
{
public class InjectFieldAttribute : Attribute { }
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1f10c710f6a498a468c7c3d5a3f7f429
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b3050982b0392a94aa176a80fc5a54e1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+40
View File
@@ -0,0 +1,40 @@
using System;
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Data
{
[Serializable, ExcludeFromCoverage]
public struct Layer : IEquatable<Layer>
{
public static implicit operator int(Layer layer)
{
return layer.layerIndex;
}
[SerializeField]
private int layerIndex;
public int LayerIndex
{
get => layerIndex;
set
{
if (value > 0 && value < 32)
layerIndex = value;
}
}
public int Mask => 1 << layerIndex;
public bool Equals(Layer other) => layerIndex == other.layerIndex;
public override bool Equals(object obj) => obj is Layer other && Equals(other);
public override int GetHashCode() => layerIndex;
public static bool operator ==(Layer left, Layer right) => left.Equals(right);
public static bool operator !=(Layer left, Layer right) => !left.Equals(right);
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5c4f8fe70c14aea4ea15d53ddb93f727
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 777c7569a062abb4cbf6f9bd72906506
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
{
"name": "BracerLib.Editor",
"rootNamespace": "BracerLib.Editor",
"references": [
"GUID:a60bafe2eaba2f24fbb33ab1ab52999b"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ed2fb3312eb6acc4e90f5b6ac5964596
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0d3660d7d1878fd4dab8b7a904c7a0be
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,26 @@
using BracerLib.Data;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Editor.Data
{
/// <summary>
/// Referenced via: https://discussions.unity.com/t/select-only-one-layer-in-the-inspector-select-only-one-layer-in-the-inspector/230727
/// </summary>
[CustomPropertyDrawer(typeof(Layer))]
[ExcludeFromCoverage]
public class LayerPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, GUIContent.none, property);
var layerIndex = property.FindPropertyRelative("layerIndex");
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
if (layerIndex != null)
layerIndex.intValue = EditorGUI.LayerField(position, layerIndex.intValue);
EditorGUI.EndProperty();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b603be9d2db54a6ba8f16f73927f322c
timeCreated: 1773804726
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aebda41daf072e0438978241a08410d4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
using System;
using BracerLib.UI;
using UnityEngine;
using UnityEditor;
using UnityEditor.UI;
using UnityEngine.TestTools;
namespace BracerLib.Editor.UI
{
//Don't forget to put this file inside a 'Editor' folder
[CanEditMultipleObjects]
[CustomEditor(typeof(NonDrawingGraphic), false)]
[ExcludeFromCoverage]
public class NonDrawingGraphicEditor : GraphicEditor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(m_Script, Array.Empty<GUILayoutOption>());
EditorGUI.EndDisabledGroup();
// skipping AppearanceControlsGUI
RaycastControlsGUI();
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3bea4dc700684cb5953636c68a1543e7
timeCreated: 1686367053
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dfe5690b14e7536429ffc5eee664df2c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,92 @@
using System;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Editor.Utility
{
/// <summary>
/// Referenced via https://github.com/marijnz/unity-autocomplete-search-field
/// </summary>
[Serializable]
[Obsolete("Use Odin or some attribute-based item instead.")]
[ExcludeFromCoverage]
public class EditorSearchField
{
private static class Styles
{
public const float RESULT_HEIGHT = 20f;
public const float RESULT_BORDER_WIDTH = 2f;
public const float RESULT_MARGIN = 15f;
public const float RESULT_LABEL_OFFSET = 2f;
public static readonly GUIStyle ENTRY_EVEN;
public static readonly GUIStyle ENTRY_ODD;
public static readonly GUIStyle LABEL_STYLE;
public static readonly GUIStyle RESULTS_BORDER_STYLE;
static Styles()
{
ENTRY_EVEN = new GUIStyle("CN EntryBackEven");
ENTRY_ODD = new GUIStyle("CN EntryBackOdd");
RESULTS_BORDER_STYLE = new GUIStyle("hostview");
LABEL_STYLE = new GUIStyle
{
alignment = TextAnchor.MiddleLeft,
richText = true
};
}
}
private static void RepaintFocusedWindow()
{
if (EditorWindow.focusedWindow != null)
EditorWindow.focusedWindow.Repaint();
}
private SearchField searchField;
private string searchString;
public event Action<string> OnInputChanged;
public bool HasSearchString => !string.IsNullOrEmpty(SearchString);
public string SearchString
{
get => searchString;
set
{
searchString = value;
OnGUI();
}
}
public void OnGUI()
{
var rect = GUILayoutUtility.GetRect(1, 1, 18, 18, GUILayout.ExpandWidth(true));
GUILayout.BeginHorizontal();
DoSearchField(rect);
GUILayout.EndHorizontal();
}
private void DoSearchField(Rect rect)
{
if (searchField == null)
searchField = new SearchField();
var result = searchField.OnGUI(rect, searchString);
if (result != searchString)
OnInputChanged?.Invoke(result);
searchString = result;
if (HasSearchbarFocused())
RepaintFocusedWindow();
}
private bool HasSearchbarFocused() => GUIUtility.keyboardControl == searchField.searchFieldControlID;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0320c64c0c142a999d375385c65efee
timeCreated: 1727491490
@@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Editor.Utility
{
/// <summary>
/// A set-up for a component that can be a searchable list. Deprecated but fun to create.
/// Doesn't work for entry adds.
/// </summary>
/// <typeparam name="T"></typeparam>
[ExcludeFromCoverage]
[Obsolete]
public class SearchableScrollView<T>
{
public delegate void DataDelegate(int index, string originalValue, string newValue);
public delegate bool ValidationDelegate(string entry);
private static class Styles
{
public static readonly GUIStyle SELECTED;
static Styles()
{
var t = new Texture2D(1, 1);
t.SetPixel(0, 0, new Color(0.4f, 0.4f, 0.4f));
t.Apply();
SELECTED = new GUIStyle(EditorStyles.label);
SELECTED.normal.textColor = Color.white;
SELECTED.normal.background = t;
}
}
private static readonly string EDIT_FIELD = "selectedTextField";
private readonly EditorSearchField searchField;
private IList<T> data;
private IList<string> dataLabels = new List<string>();
private IList<string> searchResults;
private readonly IDictionary<int, int> filteredLabelsToData = new Dictionary<int, int>();
private EventType prevEventType;
private int selectedIndex = -1;
private bool isEditing;
private bool isNewEntry;
private bool cancelEdit;
private bool editingFinished;
private string originalEntryText;
private string editedEntryText;
private Vector2 scrollPosition;
public float MaxWidth { get; set; }
public IList<T> Data
{
set
{
if (value == null)
return;
data = value;
searchField.SearchString = string.Empty;
dataLabels.Clear();
dataLabels = data.Select(d => d.ToString()).ToList();
filteredLabelsToData.Clear();
}
}
/// <summary>
/// The modified index of the selection in the list with the search results taken into account.
/// </summary>
public int SelectedIndex => filteredLabelsToData.ContainsKey(selectedIndex) ? filteredLabelsToData[selectedIndex] : selectedIndex;
public ValidationDelegate ValidateEntry { get; set; }
public DataDelegate DataEntryAdded { get; set; }
public DataDelegate DataEntryEdited { get; set; }
public DataDelegate DataEntryRemoved { get; set; }
private IList<string> TargetData => (searchResults?.Count ?? 0) > 0 ? searchResults : dataLabels;
public SearchableScrollView()
{
searchField = new EditorSearchField();
searchField.OnInputChanged += SearchFieldInputChanged;
}
public void OnGUI()
{
HandleEditing();
GUILayout.BeginVertical();
GUILayout.BeginVertical(EditorStyles.helpBox);
searchField.OnGUI();
scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, GUILayout.MaxWidth(MaxWidth));
GUILayout.BeginVertical();
// Output all keys in the list, with some being modified in style or interact-ability by way of flags
var newEntryIndex = -1;
var targetData = TargetData;
for (var i = 0; i < (targetData?.Count ?? 0); i++)
{
var target = targetData![i];
var selected = selectedIndex == i;
if (isEditing && selected)
{
if (originalEntryText == null)
{
originalEntryText = target;
editedEntryText = target;
}
GUI.SetNextControlName(EDIT_FIELD);
editedEntryText = GUILayout.TextField(editedEntryText, Styles.SELECTED);
GUI.FocusControl(EDIT_FIELD);
}
else if (cancelEdit && selected)
{
cancelEdit = false;
if (isNewEntry)
newEntryIndex = i;
else
targetData[i] = originalEntryText;
originalEntryText = null;
editedEntryText = null;
}
else if (editingFinished && selected)
{
editingFinished = false;
UpdateListValue(i, originalEntryText, editedEntryText);
originalEntryText = null;
editedEntryText = null;
}
else if (GUILayout.Button(target, selected ? Styles.SELECTED : EditorStyles.label) && !isEditing)
selectedIndex = selected ? -1 : i;
}
GUILayout.FlexibleSpace();
GUILayout.EndVertical();
GUILayout.EndScrollView();
GUILayout.EndVertical();
GUILayout.Label("F2 = Edit entry\nEnter = Accept and apply changes\nEscape = Cancel entry edit", EditorStyles.helpBox);
GUILayout.EndVertical();
}
public void AddNewEntry()
{
searchField.SearchString = string.Empty;
dataLabels.Add(string.Empty);
selectedIndex = dataLabels.Count - 1;
isEditing = true;
isNewEntry = true;
}
public void DeleteEntry()
{
if (selectedIndex < 0)
return;
dataLabels.RemoveAt(SelectedIndex);
DataEntryRemoved?.Invoke(SelectedIndex, null, null);
searchField.SearchString = string.Empty;
}
private void FinalizeNewEntry()
{
if (!isEditing)
return;
isEditing = false;
editingFinished = true;
}
private void HandleEditing()
{
var eventType = Event.current.type;
var isKeyDown = eventType == EventType.KeyDown;
if (selectedIndex < 0 || !isKeyDown || eventType == prevEventType)
{
if (!isKeyDown)
prevEventType = eventType;
return;
}
prevEventType = eventType;
var keyCode = Event.current.keyCode;
switch (keyCode)
{
case KeyCode.Escape:
if (!isEditing)
return;
isEditing = false;
cancelEdit = true;
break;
case KeyCode.Return:
case KeyCode.KeypadEnter:
FinalizeNewEntry();
break;
case KeyCode.F2:
isEditing = true;
break;
}
}
private void UpdateListValue(int index, string origText, string editedText)
{
if (editedText == origText || (ValidateEntry != null && !ValidateEntry(editedText)))
{
if (isNewEntry)
{
isNewEntry = false;
dataLabels.RemoveAt(dataLabels.Count - 1);
}
return;
}
if (filteredLabelsToData.TryGetValue(index, out var origIndex))
{
searchResults[index] = editedText;
dataLabels[origIndex] = editedText;
index = origIndex;
}
else
dataLabels[index] = editedText;
if (isNewEntry)
{
isNewEntry = false;
DataEntryAdded?.Invoke(index, null, editedText);
}
else
DataEntryEdited?.Invoke(index, origText, editedText);
}
private void SearchFieldInputChanged(string searchString)
{
searchResults ??= new List<string>();
searchResults.Clear();
filteredLabelsToData.Clear();
if (searchString != null)
{
var newIndex = 0;
searchResults = dataLabels.Where((label, index) =>
{
var result = label.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0;
if (result)
filteredLabelsToData.Add(newIndex++, index);
return result;
}).ToList();
}
else
searchResults = dataLabels;
if (selectedIndex >= searchResults.Count)
selectedIndex = -1;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7685e09f2a5b45dc82e8010310780446
timeCreated: 1727611465
@@ -0,0 +1,21 @@
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Editor.Utility
{
[ExcludeFromCoverage]
public static class TextureUtility
{
public static Texture2D MakeTexture(int width, int height, Color color)
{
var pixels = new Color[width * height];
for (var i = 0; i < pixels.Length; i++)
pixels[i] = color;
var tex = new Texture2D(width, height);
tex.SetPixels(pixels);
tex.Apply();
return tex;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 73762c73e5e84089b53c84bbf6e66665
timeCreated: 1716170448
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d63f7bdfd6b25a54e854ed23430eba56
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+23
View File
@@ -0,0 +1,23 @@
using System;
using UnityEngine.TestTools;
namespace BracerLib.Events
{
[ExcludeFromCoverage]
public class GameEventArgs : EventArgs
{
public new static GameEventArgs Empty => new GameEventArgs();
public object[] Args { get; }
public GameEventArgs(object eventArg)
{
Args = new[] { eventArg };
}
public GameEventArgs(params object[] args)
{
Args = args;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 590da2e23275974478933650ab2f2157
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c52142ef5bc2ae4ea59e76830055910
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
using System;
namespace BracerLib.Events.Interfaces
{
public interface IGameEvent
{
event Action<GameEventArgs> BeforeEvent;
event Action<GameEventArgs> DuringEvent;
event Action<GameEventArgs> AfterEvent;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5495905009a14d24ad885fa5d12da919
timeCreated: 1777815885
@@ -0,0 +1,9 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace BracerLib.Events
{
[Serializable]
public class UnityInteractionEvent : UnityEvent<Collider2D, Collider2D> { }
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3153b3a444cdb9f48ab31c24120b4504
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c05a6a317b434df4ab536aed5fef6ba1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using BracerLib.Utility;
namespace BracerLib.StateManagement
{
/// <summary>
/// Representation of a GameState enum that can be expanded without issue
/// and into other classes of a base GameState.
/// </summary>
public class GameState : Enumeration
{
public GameState(int id, string name) : base(id, name) { }
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c91277dea27634a4a849d3f5a0013533
@@ -0,0 +1,349 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using BracerLib.StateManagement.Interfaces;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace BracerLib.StateManagement
{
public delegate IEnumerator GameStateEventHandler(GameState state);
[RequireComponent(typeof(StateMap))]
[DefaultExecutionOrder(-999)]
public class GameStateController : MonoBehaviour
{
/// <summary>
/// Priority-specific holder of <see cref="GameStateEventHandler"/> events that need to trigger.
/// </summary>
private class GameStatePriorityEventHolder
{
/// <summary>
/// Event that houses all registered delegates to be invoked.
/// </summary>
public event GameStateEventHandler GameStateEventTrigger;
/// <summary>
/// Priority reference of this particular event holder.
/// </summary>
public GameStatePriority Priority { get; private set; }
/// <summary>
/// The list of delegates that can be invoked and yielded piece-meal rather than all at once via the assigned event.
/// </summary>
public IEnumerable<Delegate> InvocationList => GameStateEventTrigger?.GetInvocationList() ?? Array.Empty<Delegate>();
public GameStatePriorityEventHolder(GameStatePriority priority) => Priority = priority;
~GameStatePriorityEventHolder() => Clear();
/// <summary>
/// Get rid of all the attached delegates before clearing this out so no (less?) memory leaks
/// </summary>
public void Clear()
{
var invocations = InvocationList;
foreach (var i in invocations)
GameStateEventTrigger -= (GameStateEventHandler)i;
}
}
/// <summary>
/// Tracks priority events for a given state.
/// </summary>
private class GameStateEvent : IGameStateTrigger
{
/// <summary>
/// Pre-allocation capacity for required states.
/// </summary>
private const int MAXSTATECAPACITY = 2;
public IDictionary<GameStatePriority, GameStatePriorityEventHolder> PrioritizedHandlers;
private bool isCompleted;
private IList<IGameStateTrigger> requiredStates;
private IGameStateTrigger[] cachedRequiredStates;
/// <summary>
/// <see cref="GameState"/> this event represents.
/// </summary>
public GameState State { get; }
/// <summary>
/// The <see cref="GameState"/>s required before this state can be triggered.
/// </summary>
public IGameStateTrigger[] RequiredStates => cachedRequiredStates ??= requiredStates.ToArray();
/// <summary>
/// <see cref="GameState"/> is being processed. Can only be set true if all required
/// triggers have been completed. If triggered prematurely, set into a queue state to be polled
/// by <see cref="GameStateController"/> until it can be triggered.
/// </summary>
public bool IsTriggered { get; private set; }
/// <summary>
/// Check to see if <see cref="GameState"/> has been completed. Will rely on Coroutines or async behavior.
/// </summary>
public bool IsComplete => IsTriggered && isCompleted;
public GameStateEvent(GameState state)
{
State = state;
requiredStates = new List<IGameStateTrigger>(MAXSTATECAPACITY);
PrioritizedHandlers = new Dictionary<GameStatePriority, GameStatePriorityEventHolder>();
var priorities = Enum.GetValues(typeof(GameStatePriority));
foreach (GameStatePriority p in priorities)
PrioritizedHandlers.Add(p, new GameStatePriorityEventHolder(p));
}
~GameStateEvent() => Clear();
/// <summary>
/// Add a state requirement for this event to follow.
/// </summary>
/// <param name="gameGameState">A state that must be complete before these events can be invoked.</param>
public void AddRequiredState(IGameStateTrigger gameGameState)
{
if (!requiredStates.Contains(gameGameState))
requiredStates.Add(gameGameState);
}
public void Reset() => IsTriggered = false;
public void Clear()
{
Reset();
requiredStates.Clear();
foreach (var pair in PrioritizedHandlers)
pair.Value.Clear();
}
/// <summary>
/// Upon being triggered, go through and wait for each registered coroutine to run and complete.
/// </summary>
public IEnumerator WaitForGameStateEvents()
{
if (IsTriggered || isCompleted)
yield break;
IsTriggered = true;
yield return null;
foreach (var p in PriorityStates)
{
var invocations = PrioritizedHandlers[p].InvocationList;
foreach (var @delegate in invocations)
{
var handler = (GameStateEventHandler)@delegate;
// TODO: Figure out a way to get status or result from this
yield return handler.Invoke(State);
}
}
isCompleted = true;
}
private bool AllRequiredStatesTriggered()
{
var result = true;
foreach (var s in requiredStates)
result &= s.IsTriggered;
return result;
}
private bool AllRequiredStatesCompleted()
{
var result = true;
foreach (var s in requiredStates)
result &= s.IsComplete;
return result;
}
}
private static GameStateController INSTANCE;
private static IDictionary<GameState, GameStateEvent> STATE_EVENTS = new Dictionary<GameState, GameStateEvent>();
public static GameStateController Instance
{
get
{
if (INSTANCE == null)
#if UNITY_6000_0_OR_NEWER
INSTANCE = FindAnyObjectByType<GameStateController>();
#else
INSTANCE = FindObjectOfType<GameStateController>();
#endif
return INSTANCE;
}
}
public static GameStatePriority[] PriorityStates { get; } = (GameStatePriority[])Enum.GetValues(typeof(GameStatePriority));
/// <summary>
/// Attempts to run the available action if a <see cref="GameState"/> has been triggered, meaning all its
/// dependencies have been triggered as well.
/// </summary>
/// <param name="state">The target <see cref="GameState"/> state.</param>
/// <param name="handler">The <see cref="GameStateEventHandler"/> handler or delegate to run.</param>
/// <param name="priority">The <see cref="GameStatePriority"/> priority that the invocation should occur with.</param>
/// <returns>A <see cref="GameStateInvocationStatus"/> status the user can act upon, if needed.</returns>
public static GameStateInvocationStatus RunOrDefer(GameState state, GameStateEventHandler handler, GameStatePriority priority = GameStatePriority.Medium)
{
var triggered = IsGameStateTriggered(state) || IsGameStateComplete(state);
if (triggered)
{
Instance.StartCoroutine(handler(state));
return GameStateInvocationStatus.Success;
}
if (Instance == null && STATE_EVENTS.Count == 0)
return GameStateInvocationStatus.Failure;
if (!STATE_EVENTS.TryGetValue(state, out var gameStateEvent))
{
Debug.LogError($"GameState invocation failure: Could not defer invocation of ${handler.Method.Name}.");
return GameStateInvocationStatus.Failure;
}
STATE_EVENTS[state].PrioritizedHandlers[priority].GameStateEventTrigger += handler;
return GameStateInvocationStatus.Deferred;
}
public static bool IsGameStateTriggered(GameState state) => STATE_EVENTS.TryGetValue(state, out var stateEvent) && stateEvent.IsTriggered;
public static bool IsGameStateComplete(GameState state) => STATE_EVENTS.TryGetValue(state, out var stateEvent) && stateEvent.IsComplete;
private static void ResetStateFlags()
{
foreach (var entry in STATE_EVENTS)
entry.Value.Reset();
}
private StateMap stateMap;
private bool isTriggered;
private bool isCompleted;
private Coroutine initCoroutine;
private YieldInstruction frameWait;
private Stopwatch stateStopwatch;
private Stopwatch overallStopwatch;
/// <summary>
/// Bootstrap all state dependencies upon awake so they can be registered first
/// </summary>
private void Awake()
{
INSTANCE = this;
stateStopwatch = new Stopwatch();
overallStopwatch = new Stopwatch();
if (STATE_EVENTS.Count > 0)
{
Debug.LogError("No GameStates recognized. Exiting state registration. You've more than likely done something very wrong.");
return;
}
frameWait = new WaitForEndOfFrame();
stateMap = GetComponent<StateMap>();
foreach (var v in stateMap.GetGameStateMap())
STATE_EVENTS.Add(v, new GameStateEvent(v));
// Add state dependency map. Left-hand GameState requires right-hand GameState to be finished before proceeding
// Supports multiple-required states. Read as "LeftHandState requires RightHandState."
// New states can be added in this chain, but they must be linked from one end to the other
// e.g. (GameState.B, GameState.A), -> GameState.B requires GameState.A
// (GameState.C, GameState.B) -> GameState.C requires GameState.B
foreach (var t in stateMap.GetGameStateRequirements())
STATE_EVENTS[t.Item1].AddRequiredState(STATE_EVENTS[t.Item2]);
}
private void Start() => TriggerInitialization();
private void OnDestroy()
{
INSTANCE = null;
STATE_EVENTS.Clear();
foreach (var entry in STATE_EVENTS)
entry.Value.Clear();
}
private void TriggerInitialization()
{
if (initCoroutine != null)
return;
initCoroutine = StartCoroutine(PerformInitialization());
}
/// <summary>
/// Run events attached to all states running up the chain of requirements for <see cref="GameStateEvent"/>.
/// </summary>
private IEnumerator PerformInitialization()
{
ResetStateFlags();
// Allow all other objects in scene to wake up
yield return new WaitForSeconds(0.5f);
var readyState = stateMap.GetReadyState();
IGameStateTriggerInvoker targetEvent;
while ((targetEvent = NextAvailableGameStateEvent(readyState)) != null)
{
Debug.Log($"Running GameState: {targetEvent.State}");
stateStopwatch.Restart();
yield return StartCoroutine(targetEvent.WaitForGameStateEvents());
stateStopwatch.Stop();
yield return frameWait;
var span = TimeSpan.FromMilliseconds(stateStopwatch.ElapsedMilliseconds);
Debug.Log($"{targetEvent.State}: {span.Minutes:D2} minutes, {span.Seconds:D2} seconds, {span.Milliseconds:D3} ms");
}
var overallSpan = TimeSpan.FromMilliseconds(stateStopwatch.ElapsedMilliseconds);
Debug.Log($"Finish initializing all GameStates\n{overallSpan.Minutes:D2} minutes, {overallSpan.Seconds:D2} seconds, {overallSpan.Milliseconds:D3} ms");
}
/// <summary>
/// Recursively traverse the required <see cref="GameStateEvent"/> that each event may have until it reaches
/// the state that needs to be run next.
/// </summary>
/// <param name="state"><see cref="GameState"/> State to start traversal from.</param>
/// <returns>The earliest <see cref="GameStateEvent"/> event whose events have not yet triggered.</returns>
private IGameStateTrigger NextAvailableGameStateEvent(GameState state)
{
var currentEvent = STATE_EVENTS[state];
var states = currentEvent.RequiredStates;
var index = 0;
while (index < states.Length)
{
var target = NextAvailableGameStateEvent(states[index++].State);
if (target != null && !target.IsTriggered)
return target;
}
return !currentEvent.IsTriggered ? currentEvent : null;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9e7c95ba8e0d6d24b8d1492525d538ae
@@ -0,0 +1,9 @@
namespace BracerLib.StateManagement
{
public enum GameStateInvocationStatus
{
Failure,
Deferred,
Success
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 21f5fc392c0ba374b9eb8f0950b6a799
@@ -0,0 +1,11 @@
namespace BracerLib.StateManagement
{
public enum GameStatePriority
{
Highest,
High,
Medium,
Low,
Lowest
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 140bdf34ce09b7b47b9a681fb52bf8b6
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 79e978ffe1b3a3249a3a451d36aeb3c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
namespace BracerLib.StateManagement.Interfaces
{
public interface IGameStateIdentifier
{
GameState State { get; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b426d591d7141ce9762bc4a762a865d
timeCreated: 1697030279
@@ -0,0 +1,4 @@
namespace BracerLib.StateManagement.Interfaces
{
public interface IGameStateTrigger : IGameStateTriggerStatus, IGameStateTriggerInvoker { }
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8400e946acf14bfd89ee59f69c4a44ae
timeCreated: 1697030243
@@ -0,0 +1,9 @@
using System.Collections;
namespace BracerLib.StateManagement.Interfaces
{
public interface IGameStateTriggerInvoker : IGameStateIdentifier
{
IEnumerator WaitForGameStateEvents();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 90d8238af53645aca1a06d611b4c15c4
timeCreated: 1697030264
@@ -0,0 +1,8 @@
namespace BracerLib.StateManagement.Interfaces
{
public interface IGameStateTriggerStatus
{
bool IsTriggered { get; }
bool IsComplete { get; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 42365a62862043f693a71a439c00a693
timeCreated: 1697030254
@@ -0,0 +1,27 @@
using System;
using UnityEngine;
namespace BracerLib.StateManagement
{
/// <summary>
/// Base StateMap component that can be extended and used for start up or other state loads.
/// </summary>
[DefaultExecutionOrder(-1000)]
public class StateMap : MonoBehaviour
{
/// <summary>
/// Method to send back the number of states to be utilized.
/// </summary>
public virtual GameState[] GetGameStateMap() => Array.Empty<GameState>();
/// <summary>
/// The running requirement set for all <see cref="GameState"/> state entries.
/// </summary>
public virtual Tuple<GameState, GameState>[] GetGameStateRequirements() => Array.Empty<Tuple<GameState, GameState>>();
/// <summary>
/// What is considered the last game ready state to run before starting the game.
/// </summary>
public virtual GameState GetReadyState() => null;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d3f8dd6850d6fa144b4aabaceaadfeac
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 20492f3cdddb8734884b33efce400a39
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
{
"name": "BracerLib.Tests",
"rootNamespace": "BracerLib.Tests",
"references": [
"UnityEngine.TestRunner",
"BracerLib"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll",
"Moq.dll"
],
"autoReferenced": true,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f994df8a4a2196f499e769b079127c12
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Tests
{
[ExecuteInEditMode]
[ExcludeFromCoverage]
public class MonoBehaviourTester : MonoBehaviour, IMonoBehaviourTest
{
public virtual bool IsTestFinished { get; set; }
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7e2baafc97e51834e89c53aaa65ddd67
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0ca20ad462434c06ad77f83ad433b8e3
timeCreated: 1778953578
@@ -0,0 +1,130 @@
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Assert = UnityEngine.Assertions.Assert;
namespace BracerLib.Tests.Objects
{
public class ObjectLifetimeTests : TestBase
{
private class SomeDisposable : IDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose()
{
if (IsDisposed)
return;
IsDisposed = true;
GC.SuppressFinalize(this);
}
}
private SomeDisposable disposableObject;
private SomeDisposable disposableFuncObject;
private GameObject singleTestLifetimeObject;
private GameObject singleTestLifetimeFuncObject;
private GameObject completeTestLifetimeObject;
private GameObject completeTestLifetimeFuncObject;
private bool singleTestWasCreated;
[UnityTest, Order(1)]
public IEnumerator SetUpOneTimeObjects()
{
completeTestLifetimeObject = new GameObject("full lifetime");
RegisterOneTimeTestObject(completeTestLifetimeObject);
yield return null;
Assert.IsTrue(completeTestLifetimeObject != null);
completeTestLifetimeFuncObject = RegisterOneTimeTestObject(GetNewObject);
yield return null;
Assert.IsTrue(completeTestLifetimeFuncObject != null);
}
/// <summary>
/// Not here to test Unity code, but want to run through a basic shift of the object position.
/// </summary>
[UnityTest, Order(2)]
public IEnumerator TempUnityObjectMovesCorrectly()
{
Assert.IsFalse(singleTestWasCreated);
singleTestLifetimeObject = new GameObject("singleTestLifetime");
RegisterTempTestObject(singleTestLifetimeObject);
singleTestLifetimeFuncObject = RegisterTempTestObject(GetNewObject);
singleTestWasCreated = true;
Assert.IsTrue(singleTestWasCreated);
yield return null;
var testTransform = singleTestLifetimeObject.transform;
var tPos = testTransform.position;
var lastPos = tPos;
tPos = Vector3.one;
testTransform.position = tPos;
Assert.IsTrue(tPos != lastPos);
testTransform.Translate(Vector3.up);
yield return null;
tPos = testTransform.position;
Assert.IsTrue(tPos != lastPos);
}
[UnityTest, Order(3)]
public IEnumerator TempUnityObjectNoLongerExistsAfterPreviousTest()
{
Assert.IsTrue(singleTestWasCreated);
Assert.IsNull(singleTestLifetimeObject);
Assert.IsNull(singleTestLifetimeFuncObject);
yield return null;
}
[UnityTest, Order(4)]
public IEnumerator OneTimeObjectStillExists()
{
Assert.IsTrue(completeTestLifetimeObject != null);
Assert.IsTrue(completeTestLifetimeFuncObject != null);
yield return null;
}
[Test, Order(5)]
public void RegisterDisposableObject()
{
disposableObject = new SomeDisposable();
RegisterDisposableTempTestObject(disposableObject);
Assert.IsNotNull(disposableObject);
Assert.IsFalse(disposableObject.IsDisposed);
disposableFuncObject = RegisterDisposableTempTestObject(() => new SomeDisposable());
Assert.IsNotNull(disposableFuncObject);
Assert.IsFalse(disposableFuncObject.IsDisposed);
}
[Test, Order(6)]
public void CheckIfDisposableObjectHasDisposed()
{
Assert.IsNotNull(disposableObject);
Assert.IsTrue(disposableObject.IsDisposed);
disposableObject = null;
Assert.IsNotNull(disposableFuncObject);
Assert.IsTrue(disposableFuncObject.IsDisposed);
disposableObject = null;
}
private GameObject GetNewObject() => new GameObject();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 78520c491186453a91097eac4579ed75
timeCreated: 1778931474
@@ -0,0 +1,55 @@
using System.Collections;
using Moq;
using NUnit.Framework;
using UnityEngine;
namespace BracerLib.Tests.Objects
{
public interface ITester
{
void Test();
}
public class Tester : ITester
{
public void Test() {}
}
public class MockComponent : MonoBehaviour
{
private ITester tester;
public void DoTest()
{
tester.Test();
}
}
public class ObjectMockTests : TestBase
{
private MockComponent mockComponent;
private Mock<ITester> testerMock;
protected override IEnumerator UnityOneTimeSetUp()
{
testerMock = new Mock<ITester>();
var go = new GameObject("mock test obj");
mockComponent = go.AddComponent<MockComponent>();
RegisterOneTimeTestObject(go);
SetReflectedValue(mockComponent, "tester", testerMock.Object);
yield return null;
}
[Test]
public void TestMock()
{
testerMock.Setup(x => x.Test());
mockComponent.DoTest();
testerMock.Verify(x => x.Test(), Times.Once);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45e688432fb9423e8efb7c88cdbd9785
timeCreated: 1779762115
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 82e81a731adf4d6fbab5315db59006d2
timeCreated: 1778983978
@@ -0,0 +1,130 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.TestTools;
namespace BracerLib.Tests.Properties
{
public class PropertyTests : TestBase
{
public class SomeObject : MonoBehaviour
{
public DependencyA dependencyA;
[SerializeField]
private DependencyB dependencyB1;
[SerializeField]
private DependencyB dependencyB2;
[SerializeField]
private DependencyB dependencyB3;
private DependencyC dependencyC;
private DependencyD dependencyD;
private ValueDependency valueDependency;
private int someValue;
public DependencyB DependencyB1 => dependencyB1;
public DependencyB DependencyB2 => dependencyB2;
public DependencyB DependencyB3 => dependencyB3;
public DependencyC DependencyC => dependencyC;
public DependencyD DependencyD => dependencyD;
public ValueDependency ValueDependency => valueDependency;
public int SomeValue => someValue;
}
public class DependencyA : MonoBehaviour
{
public int Value => 1;
}
public class DependencyB : MonoBehaviour
{
public int Value => 2;
}
public class DependencyC : MonoBehaviour
{
public int Value => 3;
}
[Serializable]
public class DependencyD { }
public struct ValueDependency
{
public int Value;
}
private GameObject setupGameObject;
private SomeObject setupSomeObject;
private DependencyA setupDependencyA;
protected override IEnumerator UnityOneTimeSetUp()
{
setupGameObject = new GameObject("setup");
setupSomeObject = setupGameObject.AddComponent<SomeObject>();
setupDependencyA = setupGameObject.AddComponent<DependencyA>();
setupSomeObject.dependencyA = setupDependencyA;
RegisterOneTimeTestObject(setupSomeObject);
yield return null;
}
[UnityTest]
public IEnumerator TestProperties()
{
Assert.IsNotNull(setupSomeObject);
Assert.IsNotNull(setupDependencyA);
// Set Serialized private Mono
var depB = new[] { setupGameObject.AddComponent<DependencyB>(), setupGameObject.AddComponent<DependencyB>(), setupGameObject.AddComponent<DependencyB>() };
SetObjectReference(setupSomeObject, "dependencyB1", depB[0]);
SetObjectReferences(setupSomeObject, ("dependencyB2", depB[1]), ("dependencyB3", depB[2]));
// Set Reflected private Mono
var depC = setupGameObject.AddComponent<DependencyC>();
SetReflectedValue(setupSomeObject, "dependencyC", depC);
// Set Reflected private POCO
// Set Reflected private value
var depD = new DependencyD();
var valueDependency = new ValueDependency { Value = 255 };
SetReflectedValues(setupSomeObject, ("dependencyD", depD), ("valueDependency", valueDependency));
yield return null;
Assert.IsNotNull(depB[0]);
Assert.IsNotNull(setupSomeObject.DependencyB1);
Assert.AreEqual(depB[0], setupSomeObject.DependencyB1);
Assert.IsNotNull(depB[1]);
Assert.IsNotNull(setupSomeObject.DependencyB2);
Assert.AreEqual(depB[1], setupSomeObject.DependencyB2);
Assert.IsNotNull(depB[2]);
Assert.IsNotNull(setupSomeObject.DependencyB3);
Assert.AreEqual(depB[2], setupSomeObject.DependencyB3);
Assert.IsNotNull(depC);
Assert.IsNotNull(setupSomeObject.DependencyC);
Assert.AreEqual(depC, setupSomeObject.DependencyC);
Assert.IsNotNull(depD);
Assert.IsNotNull(setupSomeObject.DependencyD);
Assert.AreEqual(depD, setupSomeObject.DependencyD);
Assert.AreEqual(setupSomeObject.ValueDependency.Value, valueDependency.Value);
}
[UnityTest]
public IEnumerator TestPropertyErrors()
{
yield return null;
SetReflectedValue(setupSomeObject, "someFakeValue", 1);
LogAssert.Expect(LogType.Warning, "There is no property someFakeValue to the target object.");
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ce9835c5996a47e6a286fabb5d421d3e
timeCreated: 1778983994
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f662576f6c6c422d810fcd5290ec107c
timeCreated: 1778977478
+45
View File
@@ -0,0 +1,45 @@
using System.Collections;
using UnityEngine.Assertions;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace BracerLib.Tests.Scenes
{
public class SceneTests : TestBase
{
private Scene oneTimeScene;
private Scene testScene;
[UnityTest]
public IEnumerator CloseSceneWithZeroScenesRegistered()
{
var sceneCount = SceneManager.loadedSceneCount;
Assert.IsTrue(sceneCount > 0);
yield return CloseLatestScene();
Assert.IsTrue(SceneManager.loadedSceneCount == sceneCount);
}
[UnityTest]
public IEnumerator CreateScenesWithSpecificLifetimes()
{
yield return OpenScene("Scenes/Tests/Test_Empty", true);
oneTimeScene = SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1);
Assert.IsTrue(oneTimeScene.isLoaded);
yield return OpenScene("Scenes/Tests/Test_Empty");
testScene = SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1);
Assert.IsTrue(testScene.isLoaded);
}
[UnityTest]
public IEnumerator TestSceneLifetimes()
{
yield return null;
Assert.IsTrue(oneTimeScene.isLoaded);
Assert.IsFalse(testScene.isLoaded);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6c5d9b3d87fe4f1dab685f84ed847fdf
timeCreated: 1778977483
+413
View File
@@ -0,0 +1,413 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Moq;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace BracerLib.Tests
{
[ExcludeFromCodeCoverage, ExcludeFromCoverage]
public class TestBase
{
// private readonly IDictionary<Object, SerializedObject> globalPropertyCache;
private readonly IDictionary<Object, SerializedObject> objectPropertyCache;
private readonly Queue<Object> destroyOnTestEnd;
private readonly Stack<Scene> closeOnTestEnd;
private readonly Queue<IDisposable> disposeOnTestEnd;
private readonly Queue<Object> destroyOnOneTimeEnd;
private readonly Stack<Scene> closeOnOneTimeEnd;
private readonly Queue<IDisposable> disposeOnOneTimeEnd;
[ExcludeFromCoverage]
protected TestBase()
{
// globalPropertyCache = new Dictionary<Object, SerializedObject>();
objectPropertyCache = new Dictionary<Object, SerializedObject>();
destroyOnTestEnd = new Queue<Object>();
closeOnTestEnd = new Stack<Scene>();
disposeOnTestEnd = new Queue<IDisposable>();
destroyOnOneTimeEnd = new Queue<Object>();
closeOnOneTimeEnd = new Stack<Scene>();
disposeOnOneTimeEnd = new Queue<IDisposable>();
}
/// <summary>
/// Called as part of NUnit framework. Override <see cref="OneTimeSetUp"/> instead.
/// </summary>
[OneTimeSetUp, ExcludeFromCoverage]
public void DoOneTimeSetUp()
{
OneTimeSetUp();
}
[UnityOneTimeSetUp, ExcludeFromCoverage]
public IEnumerator DoUnityOneTimeSetUp()
{
yield return UnityOneTimeSetUp();
}
/// <summary>
/// Called as part of NUnit framework. Override <see cref="SetUp"/> instead.
/// </summary>
[SetUp, ExcludeFromCoverage]
public void DoSetUp()
{
SetUp();
}
[UnitySetUp, ExcludeFromCoverage]
public IEnumerator DoUnitySetUp()
{
yield return UnitySetUp();
}
/// <summary>
/// Called as part of NUnit framework. Override <see cref="TearDown"/> instead.
/// </summary>
[TearDown, ExcludeFromCoverage]
public void DoTearDown()
{
TearDown();
objectPropertyCache.Clear();
}
[UnityTearDown, ExcludeFromCoverage]
public IEnumerator DoUnityTearDown()
{
yield return UnityTearDown();
Reset();
while (closeOnTestEnd.Count > 0)
yield return CloseLatestScene();
}
/// <summary>
/// Called as part of NUnit framework. Override <see cref="OneTimeTearDown"/> instead.
/// </summary>
[OneTimeTearDown, ExcludeFromCoverage]
public void DoOneTimeTearDown()
{
OneTimeTearDown();
}
[UnityOneTimeTearDown, ExcludeFromCoverage]
public IEnumerator DoUnityOneTimeTearDown()
{
yield return UnityOneTimeTearDown();
CleanupUnityObjects(destroyOnOneTimeEnd);
CleanupDisposableObjects(disposeOnOneTimeEnd);
while (closeOnOneTimeEnd.Count > 0)
yield return CloseLatestScene(true);
}
/// <summary>
/// Pass a Unity object that will be destroyed at the end of the test.
/// </summary>
protected T RegisterTempTestObject<T>(T obj) where T : Object
{
destroyOnTestEnd.Enqueue(obj);
return obj;
}
/// <summary>
/// Pass a function that generates or returns a Unity object that will be destroyed at the end of the test.
/// </summary>
protected T RegisterTempTestObject<T>(Func<T> generator) where T : Object
{
var obj = generator();
return RegisterTempTestObject(obj);
}
/// <summary>
/// Pass a Unity object that will be destroyed at the end of all the tests in a given suite.
/// </summary>
protected T RegisterOneTimeTestObject<T>(T obj) where T : Object
{
destroyOnOneTimeEnd.Enqueue(obj);
return obj;
}
/// <summary>
/// Pass a function that generates or returns a Unity object that will be destroyed at the end of all the tests in a given suite.
/// </summary>
protected T RegisterOneTimeTestObject<T>(Func<T> generator) where T : Object
{
var obj = generator();
return RegisterOneTimeTestObject(obj);
}
/// <summary>
/// Pass a disposable object that will be cleaned up at the end of the test.
/// </summary>
protected T RegisterDisposableTempTestObject<T>(T obj) where T : IDisposable
{
disposeOnTestEnd.Enqueue(obj);
return obj;
}
/// <summary>
/// Pass a function that generates or returns a disposable object that will be cleaned up at the end of the test.
/// </summary>
/// <param name="generator"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected T RegisterDisposableTempTestObject<T>(Func<T> generator) where T : IDisposable
{
var obj = generator();
return RegisterDisposableTempTestObject(obj);
}
/// <summary>
/// Pass a disposable object that will be cleaned up at the end of the test.
/// </summary>
protected T RegisterDisposableOneTimeTestObject<T>(T obj) where T : IDisposable
{
disposeOnOneTimeEnd.Enqueue(obj);
return obj;
}
/// <summary>
/// Pass a function that generates or returns a disposable object that will be cleaned up at the end of the test.
/// </summary>
/// <param name="generator"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected T RegisterDisposableOneTimeTestObject<T>(Func<T> generator) where T : IDisposable
{
var obj = generator();
return RegisterDisposableOneTimeTestObject(obj);
}
protected IEnumerator OpenScene(string scenePath, bool isOneTime = false)
{
var asyncOp = SceneManager.LoadSceneAsync(scenePath, LoadSceneMode.Additive);
asyncOp!.completed += SetLoadedSceneActive;
yield return asyncOp;
var loadedScene = SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1);
if (!isOneTime)
closeOnTestEnd.Push(loadedScene);
else
closeOnOneTimeEnd.Push(loadedScene);
}
protected IEnumerator CloseLatestScene(bool isOneTime = false)
{
var targetStack = !isOneTime ? closeOnTestEnd : closeOnOneTimeEnd;
if (!targetStack.TryPop(out var targetScene))
yield break;
var asyncOp = SceneManager.UnloadSceneAsync(targetScene);
asyncOp!.completed += SetLoadedSceneActive;
yield return asyncOp;
}
protected void SetLoadedSceneActive(AsyncOperation asyncOperation)
{
SceneManager.SetActiveScene(SceneManager.GetSceneAt(SceneManager.loadedSceneCount - 1));
}
[ExcludeFromCoverage]
protected virtual void OneTimeSetUp() { }
[ExcludeFromCoverage]
protected virtual IEnumerator UnityOneTimeSetUp()
{
yield return null;
}
protected virtual void SetUp() { }
protected virtual IEnumerator UnitySetUp()
{
yield return null;
}
protected virtual void TearDown() { }
protected virtual IEnumerator UnityTearDown()
{
yield return null;
}
[ExcludeFromCoverage]
protected virtual void OneTimeTearDown() { }
[ExcludeFromCoverage]
protected virtual IEnumerator UnityOneTimeTearDown()
{
yield return null;
}
/// <summary>
/// Set a private value member of an object via C# reflection.
/// </summary>
/// <example><code>
/// public class SomeObject {
/// private int item;
/// }
///
/// var obj = new SomeObject();
/// SetReflectedValue(obj, "item", 25);
/// </code></example>
private protected void SetReflectedValue(object targetObject, string targetProperty, object targetValue)
{
SetReflectedValue(targetObject, targetObject.GetType(), targetProperty, targetValue);
}
private protected void SetReflectedValues(object targetObject, params ValueTuple<string, object>[] properties)
{
var type = targetObject.GetType();
for (var i = 0; i < properties.Length; i++)
SetReflectedValue(targetObject, type, properties[i].Item1, properties[i].Item2);
}
/// <summary>
/// Set a non-interface reference member of a MonoBehaviour via Unity reflection.
/// Will be applied depending on context of Test setup being used.
/// </summary>
/// <example><code>
/// public class SomeOtherObject { }
///
/// public class SomeObject : MonoBehaviour {
/// private SomeOtherObject item;
/// }
///
/// var gameObj = new GameObject("SomeObj");
/// var c = gameObj.AddComponent(typeof(SomeObject));
/// var someOtherObj = new SomeOtherObj();
///
/// SetObjectReference(c, "item", someOtherObj);
/// </code></example>
private protected void SetObjectReference(Object targetObj, string targetValue, Object value)
{
if (!objectPropertyCache.TryGetValue(targetObj, out var serializedObject))
{
serializedObject = new SerializedObject(targetObj);
objectPropertyCache.Add(targetObj, serializedObject);
}
var property = serializedObject.FindProperty(targetValue);
property.objectReferenceValue = value;
serializedObject.ApplyModifiedProperties();
}
/// <summary>
/// Set a non-interface reference member of a MonoBehaviour via Unity reflection.
/// Will be applied depending on context of Test setup being used.
/// </summary>
/// <example><code>
/// public class SomeObject : MonoBehaviour {
/// private SomeOtherObject item1;
/// private SomeAdditionalObject item2;
/// }
///
/// var gameObj = new GameObject("SomeObj");
/// var c = gameObj.AddComponent(typeof(SomeObject));
///
/// SetObjectReference(c, a);
/// </code></example>
private protected void SetObjectReferences(Object targetObj, params ValueTuple<string, Object>[] properties)
{
for (var i = 0; i < properties.Length; i++)
SetObjectReference(targetObj, properties[i].Item1, properties[i].Item2);
}
private void SetReflectedValue(object targetObject, Type type, string targetProperty, object targetValue)
{
var fieldProperty = type.GetField(targetProperty, BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldProperty == null)
{
Debug.LogWarning($"There is no property {targetProperty} to the target object.");
return;
}
fieldProperty.SetValue(targetObject, targetValue);
}
private void Reset()
{
ResetAllMocks();
CleanupDisposableObjects(disposeOnTestEnd);
CleanupUnityObjects(destroyOnTestEnd);
}
private void CleanupUnityObjects(Queue<Object> unityObjects)
{
if (unityObjects.Count == 0)
return;
while (unityObjects.Count != 0)
{
var temp = unityObjects.Dequeue();
if (temp != null)
Object.DestroyImmediate(temp);
}
}
private void CleanupDisposableObjects(Queue<IDisposable> disposableObjects)
{
if (disposableObjects.Count == 0)
return;
while (disposableObjects.Count != 0)
{
var temp = disposableObjects.Dequeue();
temp?.Dispose();
}
}
[ExcludeFromCoverage]
private void ResetAllMocks()
{
var mocks = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(Mock)).Select(f => (Mock)f.GetValue(this));
foreach (var m in mocks)
m.Reset();
}
// [ExcludeFromCoverage]
// private void Apply(bool isGlobal = false)
// {
// ApplyPropertyCache(objectPropertyCache);
// }
//
// [ExcludeFromCoverage]
// private void ApplyPropertyCache(IDictionary<Object, SerializedObject> cache)
// {
// if (cache.Count == 0)
// return;
//
// foreach (var pair in cache)
// pair.Value.ApplyModifiedProperties();
// }
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f1863291fabd7fb43837115239feede3
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c3b38a9a1e7f41b8849eb925b8dda6ef
timeCreated: 1686834699
@@ -0,0 +1,63 @@
using System.Collections;
using BracerLib.Utility;
using NUnit.Framework;
using UnityEngine;
namespace BracerLib.Tests.Utility
{
public class MathUtilityTests : TestBase
{
private static IEnumerable BoundValues()
{
yield return new TestCaseData(2, 1, 3, 2).SetName("Value is between bounds");
yield return new TestCaseData(0, 1, 3, 3).SetName("Value is below bounds");
yield return new TestCaseData(4, 1, 3, 1).SetName("Value is above bounds");
}
private static IEnumerable BetweenValuesInt()
{
yield return new TestCaseData(0, -1, 1, BoundsInclusivity.None, true).SetName("Value is between bounds");
yield return new TestCaseData(-2, -1, 1, BoundsInclusivity.None, false).SetName("Value is below bounds");
yield return new TestCaseData(2, -1, 1, BoundsInclusivity.None, false).SetName("Value is above bounds");
yield return new TestCaseData(-1, -1, 1, BoundsInclusivity.Both, true).SetName("Value is at bound, inclusive both");
yield return new TestCaseData(-1, -1, 1, BoundsInclusivity.Left, true).SetName("Value is at bound, inclusive left");
yield return new TestCaseData(1, -1, 1, BoundsInclusivity.Right, true).SetName("Value is at bound, inclusive right");
yield return new TestCaseData(1, -1, 1, BoundsInclusivity.Both, true).SetName("Value is at bound, exclusive");
}
private static IEnumerable BetweenValuesFloat()
{
yield return new TestCaseData(0f, -1f, 1f, BoundsInclusivity.None, true).SetName("Value is between bounds");
yield return new TestCaseData(-2f, -1f, 1f, BoundsInclusivity.None, false).SetName("Value is below bounds");
yield return new TestCaseData(2f, -1f, 1f, BoundsInclusivity.None, false).SetName("Value is above bounds");
yield return new TestCaseData(-1f, -1f, 1f, BoundsInclusivity.Both, true).SetName("Value is at bound, inclusive both");
yield return new TestCaseData(-1f, -1f, 1f, BoundsInclusivity.Left, true).SetName("Value is at bound, inclusive left");
yield return new TestCaseData(1f, -1f, 1f, BoundsInclusivity.Right, true).SetName("Value is at bound, inclusive right");
yield return new TestCaseData(1f, -1f, 1f, BoundsInclusivity.Both, true).SetName("Value is at bound, exclusive");
}
[Test]
[TestCaseSource(nameof(BoundValues))]
public void ValuesAreWithinBounds(int value, int min, int max, int expected)
{
var result = MathUtility.KeepWithinBounds(value, min, max);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
[TestCaseSource(nameof(BetweenValuesInt))]
public void ValuesBetweenBounds(int value, int min, int max, BoundsInclusivity inclusivity, bool expected)
{
var result = MathUtility.IsBetween(value, min, max, inclusivity);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
[TestCaseSource(nameof(BetweenValuesFloat))]
public void ValuesBetweenBoundsFloat(float value, float min, float max, BoundsInclusivity inclusivity, bool expected)
{
var result = MathUtility.IsBetween(value, min, max, inclusivity);
Assert.That(result, Is.EqualTo(expected));
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4fcf35728772424c9c2c84e29290ac5e
timeCreated: 1780244918
@@ -0,0 +1,129 @@
using System;
using System.Collections;
using System.Collections.Generic;
using BracerLib.Utility;
using NUnit.Framework;
using UnityEngine;
namespace BracerLib.Tests.Utility
{
public class TriangulatorTest : TestBase
{
private static IEnumerable AreaValueTests()
{
var points = new List<Vector2>();
yield return new TestCaseData(points.ToArray(), 0f).SetName("No points to calculate");
points.AddRange(new[]
{
Vector2.one,
new Vector2(5f, 7f),
new Vector2(-2f, 8f)
});
yield return new TestCaseData(points.ToArray(), 23f).SetName("Area of a triangle");
points.Add(new Vector2(3f, 5f));
yield return new TestCaseData(points.ToArray(), 10f).SetName("Area of a quad");
}
private static IEnumerable InsideTrianglePointsTest()
{
yield return new TestCaseData(
Vector2.zero,
new Vector2(3f, 3f),
new Vector2(3f, 0f),
new Vector2(2f, 1f),
true
).SetName("Point inside triangle");
yield return new TestCaseData(
Vector2.zero,
new Vector2(3f, 3f),
new Vector2(3f, 0f),
new Vector2(1f, 0f),
true
).SetName("Point on the line");
yield return new TestCaseData(
Vector2.zero,
new Vector2(3f, 3f),
new Vector2(3f, 0f),
new Vector2(3f, 0f),
false
).SetName("Point is one of triangle points");
yield return new TestCaseData(
Vector2.zero,
new Vector2(3f, 3f),
new Vector2(3f, 0f),
new Vector2(-2f, 1f),
false
).SetName("Point outside triangle");
}
private static IEnumerable TriangulatePointsTest()
{
yield return new TestCaseData(
new[]
{
Vector2.zero,
Vector2.right,
Vector2.right + Vector2.up,
Vector2.up,
Vector2.left + Vector2.up,
Vector2.left
},
new[] { 5,3,2,2,0,5,5,4,3,2,1,0 }
).SetName("Positive triangulation");
yield return new TestCaseData(
new[]
{
Vector2.zero,
Vector2.right,
Vector2.right + Vector2.down,
Vector2.down,
Vector2.left + Vector2.down,
Vector2.left
},
new[] { 1,3,4,4,0,1,1,2,3,4,5,0 }
).SetName("Negative triangulation");
yield return new TestCaseData(
new[] { Vector2.zero, Vector2.right },
Array.Empty<int>()
).SetName("Empty triangulation");
}
[Test]
[TestCaseSource(nameof(AreaValueTests))]
public void AreaCalculatesCorrectly(Vector2[] points, float expected)
{
var result = Triangulator.Area(points);
Assert.AreEqual(expected, result);
}
[Test]
[TestCaseSource(nameof(InsideTrianglePointsTest))]
public void PointsInsideTriangleCorrectly(Vector2 a, Vector2 b, Vector2 c, Vector2 p, bool expected)
{
var result = Triangulator.InsideTriangle(a, b, c, p);
Assert.AreEqual(expected, result);
}
[Test]
[TestCaseSource(nameof(TriangulatePointsTest))]
public void TriangulatePoints(Vector2[] points, int[] expected)
{
var result = Triangulator.Triangulate(points);
Assert.AreEqual(expected.Length, result.Length);
for (var i = 0; i < result.Length; i++)
{
Assert.AreEqual(expected[i], result[i]);
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0a7aaa2ed31747e5ae172f296a2808e3
timeCreated: 1686834708
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: efcb2963a18fd8646a6840e5596275f6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+21
View File
@@ -0,0 +1,21 @@
using UnityEngine.TestTools;
using UnityEngine.UI;
namespace BracerLib.UI
{
/// A concrete subclass of the Unity UI `Graphic` class that just skips drawing.
/// Useful for providing a raycast target without actually drawing anything.
[ExcludeFromCoverage]
public class NonDrawingGraphic : Graphic
{
public override void SetMaterialDirty() { }
public override void SetVerticesDirty() { }
/// Probably not necessary since the chain of calls `Rebuild()`->`UpdateGeometry()`->`DoMeshGeneration()`->`OnPopulateMesh()` won't happen; so here really just as a fail-safe.
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b78fc1af781b54e4392ff1040214c054
+88
View File
@@ -0,0 +1,88 @@
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace BracerLib.UI
{
public class SceneLoadingManager : MonoBehaviour
{
private static SceneLoadingManager _instance;
public static void LoadScenes(string[] scenePath, LoadSceneMode sceneMode)
{
if (!ValidInstance())
return;
_instance.TryLoadScenes(scenePath, sceneMode);
}
public static void Show()
{
if (ValidInstance())
_instance.Toggle(true);
}
public static void Hide()
{
if (ValidInstance())
_instance.Toggle(false);
}
private static bool ValidInstance()
{
if (_instance != null)
return true;
Debug.LogError("No Load Screen Manager found.");
return false;
}
[SerializeField]
private GameObject _loadingScreenGameObject;
private Coroutine _sceneLoadingCoroutine;
private void Awake()
{
DontDestroyOnLoad(_loadingScreenGameObject);
if (_instance != null)
{
Debug.LogError("Multiple LoadScreenManagers found. Please delete one", this);
Destroy(this);
return;
}
_instance = this;
}
private void TryLoadScenes(string[] scenePaths, LoadSceneMode sceneMode)
{
if (_sceneLoadingCoroutine != null)
{
Debug.LogError("SceneLoadingManager is already loading scenes.");
return;
}
_sceneLoadingCoroutine = StartCoroutine(LoadSceneCoroutine(scenePaths, sceneMode));
}
private IEnumerator LoadSceneCoroutine(string[] scenePaths, LoadSceneMode sceneMode)
{
Show();
for (var i = 0; i < scenePaths.Length; i++)
yield return SceneManager.LoadSceneAsync(scenePaths[i], sceneMode);
Hide();
_sceneLoadingCoroutine = null;
}
private void Toggle(bool state)
{
_loadingScreenGameObject.SetActive(state);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7e94d21e86e94de46b81f938aa50a29e
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d6041568ca07d940ae4a4f10cda4b8a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Collections;
using UnityEngine.TestTools;
namespace BracerLib.Utility
{
[ExcludeFromCoverage]
public static class ArrayExtensions
{
public static bool Contains<T>(this T[] array, T value) where T : IComparable
{
for (var i = 0; i < array.Length; i++)
{
if (array[i].CompareTo(value) == 0)
return true;
}
return false;
}
public static bool Contains<T, U>(this T[] array, U value, Func<T, U, bool> predicate)
{
for (var i = 0; i < array.Length; i++)
{
if (predicate(array[i], value))
return true;
}
return false;
}
public static T[] Subset<T>(this T[] data, int index, int length)
{
var result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
public static bool HasIndex(this IList list, int index) => index >= 0 && index < list.Count;
public static bool HasIndex(this Array array, int index) => index >= 0 && index < array.Length;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5fe12305b0d411b41bb552e80e21d2c5
+31
View File
@@ -0,0 +1,31 @@
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.TestTools;
namespace BracerLib.Utility
{
[ExcludeFromCoverage]
public static class CameraUtility
{
private static Camera mainCamera;
public static Camera MainCamera
{
get
{
if (mainCamera == null)
mainCamera = Camera.main;
return mainCamera;
}
}
public static Vector3 MouseToWorldPoint()
{
var screenPosition = (Vector3)Mouse.current.position.ReadValue();
screenPosition.z = mainCamera.nearClipPlane;
return mainCamera.ScreenToWorldPoint(screenPosition);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 16fe30152b82df3439d6cd584b5603f7

Some files were not shown because too many files have changed in this diff Show More