This commit is contained in:
CortexCore
2025-03-24 14:42:40 +08:00
parent 18239a5ae4
commit 9845d20f7f
99 changed files with 5418 additions and 5512 deletions

View File

@@ -57,6 +57,8 @@ namespace BITKit
}
public class BITAppForUnity : MonoBehaviour
{
public static event Action OnDrawGizmo;
public static event Action OnDrawGizmoSelected;
[Serializable]
public class OpenUrl
{
@@ -224,5 +226,15 @@ namespace BITKit
{
return BITApp.State;
}
private void OnDrawGizmos()
{
OnDrawGizmo?.Invoke();
}
private void OnDrawGizmosSelected()
{
OnDrawGizmoSelected?.Invoke();
}
}
}

View File

@@ -9,6 +9,7 @@ using UnityEngine;
namespace Net.BITKit.Impact
{
public class ScriptableImpact : ScriptableObject,ITag
{
[SerializeReference, SubclassSelector] private IReference[] tags;

View File

@@ -6,6 +6,28 @@ using UnityEngine;
namespace BITKit.Physics
{
public static class PhysicsExtensions
{
public static bool TryGetClosestPointFromCollider(this Collider collider,Vector3 point,out Vector3 closestPoint)
{
closestPoint = default;
switch (collider)
{
case BoxCollider:
case SphereCollider:
case CapsuleCollider:
case MeshCollider { convex: true }:
case TerrainCollider:
closestPoint = collider.ClosestPoint(point);
return true;
case MeshCollider { convex: false } meshCollider when new GetClosestPointFromMesh(meshCollider.sharedMesh,point).TryGetValue(out closestPoint,out _):
closestPoint = collider.transform.TransformPoint(closestPoint);
return true;
}
return false;
}
}
public readonly struct GetClosestPointFromMesh:IClosePointProvider
{
private readonly Vector3 _position;

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c15a9900918e4324991bcf53015b006e
guid: 8a606fb5272e8a84dac37bae47771409
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -1,11 +0,0 @@
using Quadtree.Items;
using UnityEngine;
namespace Quadtree
{
[ExecuteInEditMode]
[AddComponentMenu("Spatial partitioning/Quadtree/Root node (for GameObjects)")]
public class GameObjectQuadtreeRoot : QuadtreeMonoRoot<GameObjectItem, Node<GameObjectItem>>
{
}
}

View File

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

View File

@@ -1,118 +0,0 @@
using Quadtree.Items;
using System.Collections.Generic;
using UnityEngine;
namespace Quadtree
{
/// <summary>
/// Mandatory interface of any single quadtree node.
/// </summary>
public interface INode<TItem, TNode>
where TItem : IItem<TItem, TNode>
where TNode : INode<TItem, TNode>
{
/// <summary>
/// Bounds of this tree node.
/// </summary>
Bounds Bounds { get; set; }
/// <summary>
/// Root of the whole tree.
/// </summary>
IQuadtreeRoot<TItem, TNode> TreeRoot { get; set; }
/// <summary>
/// Reference to parent tree node.
/// </summary>
/// <remarks>
/// Is <c>null</c> for root node of the tree.
/// </remarks>
TNode ParentNode { get; set; }
/// <summary>
/// Child nodes of this node.
/// </summary>
IList<TNode> SubNodes { get; set; }
/// <summary>
/// Verifies whether provided boundaries (<paramref name="bounds"/>) are fully contained within the boundaries of the node.
/// </summary>
///
/// <param name="bounds">Boundaries of an object</param>
/// <returns><c>True</c> if object is fully contained within the node, <c>False</c> otherwise</returns>
bool Contains(Bounds bounds);
/// <summary>
/// Calculates relative internal position of the provided bounds (<paramref name="bounds"/>) within the node.
/// </summary>
/// <remarks>
/// The method expects the boundaries to be fully contained within the node.
/// </remarks>
///
/// <param name="bounds">Boundaries contained within the node</param>
/// <returns>Relative internal position</returns>
IntraLocation Location(Bounds bounds);
/// <summary>
/// Inserts item (<paramref name="item"/>) into the smallest node possible in the subtree.
/// </summary>
/// <remarks>
/// The method expects item boundaries to be fully contained within the node.
/// </remarks>
///
/// <param name="item">Item to be inserted</param>
void Insert(TItem item);
/// <summary>
/// Removes the provided item (<paramref name="item"/>) from the node and its subtree.
/// </summary>
///
/// <param name="item">Item to be removed from the tree</param>
void Remove(TItem item);
/// <summary>
/// Checks whether the node and recursively all its subnodes are empty.
/// </summary>
///
/// <returns><c>True</c> if node and all its subnodes are empty, <c>False</c> otherwise</returns>
bool IsEmpty();
/// <summary>
/// Updates provided item's (<paramref name="item"/>) location within the tree.
/// </summary>
///
/// <param name="item">Item which's location is to be updated</param>
/// <param name="forceInsertionEvaluation"><c>True</c> forces tree to re-insert the item</param>
/// <param name="hasOriginallyContainedItem"><c>True</c> only for the first called node</param>
void Update(TItem item, bool forceInsertionEvaluation = true, bool hasOriginallyContainedItem = true);
/// <summary>
/// Finds items (<paramref name="items"/>) located within provided boundaries (<paramref name="bounds"/>).
/// </summary>
///
/// <param name="bounds">Boundaries to look for items within</param>
/// <param name="items">Output list for found items</param>
void FindAndAddItems(Bounds bounds, ref IList<TItem> items);
/// <summary>
/// Adds all items of this node and its sub-nodes to the provided list of items (<paramref name="items"/>).
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
/// </summary>
///
/// <param name="items">Output list for found items</param>
/// <param name="bounds">Boundaries to look for items within</param>
void AddItems(ref IList<TItem> items, Bounds? bounds = null);
/// <summary>
/// Removes any existing items from the node and removes all of its sub-nodes.
/// </summary>
void Clear();
/// <summary>
/// Displays boundaries of this node and all its sub-nodes and optinally a current number of contained items if <paramref name="displayNumberOfItems"/> is <c>True</c>.
/// </summary>
///
/// <param name="displayNumberOfItems"><c>True</c> if number of node's items should be displayed</param>
void DrawBounds(bool displayNumberOfItems = false);
}
}

View File

@@ -1,70 +0,0 @@
using Quadtree.Items;
using System.Collections.Generic;
using UnityEngine;
namespace Quadtree
{
/// <summary>
/// Main class of the Quadtree structure - it represents the root of the tree.
/// </summary>
public interface IQuadtreeRoot<TItem, TNode>
where TItem : IItem<TItem, TNode>
where TNode : INode<TItem, TNode>
{
/// <summary>
/// The tree has been initialized and is ready to be used.
/// </summary>
bool Initialized { get; }
/// <summary>
/// Node currently acting as a root of the tree.
/// </summary>
TNode CurrentRootNode { get; }
/// <summary>
/// Minimum possible size of any of the nodes.
/// </summary>
/// <remarks>
/// Must always be a positive number or zero for no size limit.
/// </remarks>
float MinimumPossibleNodeSize { get; }
/// <summary>
/// Determines whether or not should number of items in nodes be displayed in gizmos.
/// </summary>
bool DisplayNumberOfItemsInGizmos { get; }
/// <summary>
/// Inserts item to the tree structure.
/// </summary>
///
/// <param name="item">Item to be inserted</param>
void Insert(TItem item);
/// <summary>
/// Expands size of root node.
/// New root node is created and current root node is assigned as its sub-node.
/// </summary>
void Expand();
/// <summary>
/// Finds items located within provided boundaries.
/// </summary>
///
/// <param name="bounds">Boundaries to look for items within</param>
/// <returns>List of items found within provided boundaries</returns>
List<TItem> Find(Bounds bounds);
/// <summary>
/// Removes provided item from the tree.
/// </summary>
///
/// <param name="item">Item to be removed from the tree</param>
void Remove(TItem item);
/// <summary>
/// Clears and resets the whole tree.
/// </summary>
void Clear();
}
}

View File

@@ -1,22 +0,0 @@

namespace Quadtree
{
/// <summary>
/// Describes relative local position in respect to the current node.
/// </summary>
/// <remarks>
/// Integer values of <c>UPPER_LEFT</c>, <c>UPPER_RIGHT</c>, <c>LOWER_RIGHT</c>, <c>LOWER_LEFT</c> do correspond with the indices of the sub-nodes.
/// </remarks>
public enum IntraLocation
{
UPPER_LEFT,
UPPER_RIGHT,
LOWER_RIGHT,
LOWER_LEFT,
SPANNING_LEFT,
SPANNING_RIGHT,
SPANNING_UPPER,
SPANNING_LOWER,
SPANNING
};
}

View File

@@ -1,10 +0,0 @@

using UnityEngine;
namespace Quadtree.Items
{
public abstract class GameObjectItem : GameObjectItemBase<GameObjectItem, Node<GameObjectItem>>
{
protected override GameObjectItem This() => this;
}
}

View File

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

View File

@@ -1,152 +0,0 @@
using UnityEngine;
namespace Quadtree.Items
{
/// <summary>
/// Custom item interface for GameObject quadtree items.
/// </summary>
public abstract class GameObjectItemBase<TItem, TNode> : MonoBehaviour, IItem<TItem, TNode>
where TItem : IItem<TItem, TNode>
where TNode : INode<TItem, TNode>
{
/// <summary>
/// Game object's bounds from last update call.
/// </summary>
private Bounds _lastBounds;
/// <summary>
/// Game object's bounds from last update call.
/// </summary>
private Bounds _safeBounds;
//==========================================================================dd==
// MonoBehaviour METHODS
//==========================================================================dd==
private void Start()
{
Init();
}
private void OnEnable()
{
Init();
}
private void OnDisable()
{
Root = null;
ItemInitialized = false;
ParentNode.Remove(This());
}
private void LateUpdate()
{
var currentBounds = GetBounds();
if (currentBounds != _lastBounds)
{
// the object has moved or changed size
var forceInsertionEvaluation = false;
if (!currentBounds.Intersects(_safeBounds)
|| (currentBounds.size - _lastBounds.size).magnitude > 0)
{
// ...far enough to force re-insertion
forceInsertionEvaluation = true;
_safeBounds = currentBounds;
}
// current object bounds are not the same as last update
// initiate tree update from currently
ParentNode?.Update(This(), forceInsertionEvaluation);
_lastBounds = currentBounds;
}
}
//==========================================================================dd==
// CORE TREE ITEM METHODS
//==========================================================================dd==
/// <summary>
/// <c>True</c> if the item has been initialized.
/// </summary>
protected internal bool ItemInitialized = false;
public IQuadtreeRoot<TItem, TNode> Root { get; set; }
public TNode ParentNode { get; set; }
public abstract Bounds GetBounds();
public void QuadTree_Root_Initialized(IQuadtreeRoot<TItem, TNode> root)
{
Root = root;
if (ItemInitialized)
{
// the item has been initialized before the tree root
root.Insert(This());
}
}
/// <summary>
/// Returns reference to corresponding game object.
/// </summary>
///
/// <returns>Game object instance.</returns>
public abstract GameObject GetGameObject();
/// <summary>
/// Initializes the item instance.
/// </summary>
/// <remarks>
/// This may be called either before or after the initialization of the tree root.
/// </remarks>
protected virtual void Init()
{
// designate item as initialized
ItemInitialized = true;
// set initial last bounds
_lastBounds = GetBounds();
// set initial safe bounds
_safeBounds = _lastBounds;
if (Root == null)
{
if (TryGetComponent(out GameObjectQuadtreeRoot quadtreeRoot) && quadtreeRoot.Initialized)
{
Root = (IQuadtreeRoot<TItem, TNode>)quadtreeRoot;
}
}
if (Root != null)
{
// the tree root has been initialized before the item
Root.Insert(This());
}
}
/// <summary>
/// Overloaded in sub-classes to return correct instance of the item.
/// </summary>
/// <remarks>
/// This method is necessary due to generic typechecking -- <c>this</c> in context of the abstract generic class does not reference TItem itself.
/// </remarks>
///
/// <returns>Instance of the item</returns>
protected abstract TItem This();
/// <summary>
/// Returns unique identifier of the item.
/// </summary>
/// <remarks>
/// It is extremely important to override this method because nodes are using HashSets to store items and the items are changing during the updates and so are the hash codes which can result in program not working properly.
/// </remarks>
///
/// <returns>Unique identifier</returns>
public override int GetHashCode()
{
return GetInstanceID();
}
}
}

View File

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

View File

@@ -1,29 +0,0 @@
using UnityEngine;
namespace Quadtree.Items
{
/// <summary>
/// Mandatory interface of any quadtree item.
/// </summary>
public interface IItem<TItem, TNode>
where TItem : IItem<TItem, TNode>
where TNode : INode<TItem, TNode>
{
/// <summary>
/// Returns object bounds.
/// </summary>
///
/// <returns>Object box bounds.</returns>
Bounds GetBounds();
/// <summary>
/// Node which currently contains the item.
/// </summary>
TNode ParentNode { get; set; }
/// <summary>
/// Receiver method for broadcasted tree initialization message.
/// </summary>
void QuadTree_Root_Initialized(IQuadtreeRoot<TItem, TNode> root);
}
}

View File

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

View File

@@ -1,46 +0,0 @@
using UnityEngine;
namespace Quadtree.Items
{
/// <summary>
/// Boundaries of this quadtree item are determined by present <c>UnityEngine.Renderer</c> component.
/// </summary>
[ExecuteInEditMode]
[DisallowMultipleComponent]
[RequireComponent(typeof(Renderer))]
[AddComponentMenu("Spatial partitioning/Quadtree/Items/Renderer-based Item")]
public class RendererItem : GameObjectItem
{
/// <summary>
/// Determines whether the item should be automatically inserted into the tree upon its initialization.
/// </summary>
///
/// <seealso cref="QuadtreeMonoRoot{TItem}.Insert(TItem)"/>
[SerializeField]
protected bool InsertOnInitialization = true;
private Renderer _renderer;
//==========================================================================dd==
// Quadtree ITEM METHODS
//==========================================================================dd==
/// <summary>
/// Finds and locally stores this <c>GameObject</c>'s <c>Renderer</c> component instance.
/// </summary>
protected override void Init()
{
// load game object renderer component
_renderer = GetComponent<Renderer>();
base.Init();
}
public override Bounds GetBounds()
{
return _renderer.bounds;
}
public override GameObject GetGameObject() => gameObject;
}
}

View File

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

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections;
using System.Collections.Generic;
using BITKit;
using Net.BITKit.Quadtree;
using Unity.Mathematics;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Net.Project.B.Quadtree
{
public class MonoQuadtreeDrawer : MonoBehaviour
{
[SerializeField] private int size;
private BITKit.Quadtree.Quadtree _quadtree;
[BIT]
private void Init()
{
_quadtree = new BITKit.Quadtree.Quadtree(default, new float2(512, 512));
for (var i = 0; i < 512*8; i++)
{
_quadtree.Insert(i,Random.insideUnitCircle * 512,Random.value>0.5f ? Random.insideUnitCircle * 8 :default);
}
_quadtree.Query(default, default);
}
private void OnDrawGizmos()
{
if(_quadtree is null)return;
var repeatedObject = new HashSet<int>();
Draw(_quadtree.Root);
{
Gizmos.color = Color.red;
var worldPos = transform.position;
Gizmos.DrawWireSphere(worldPos, size);
foreach (var id in _quadtree.Query(new float2(worldPos.x,worldPos.z),size))
{
var pos = _quadtree.Positions[id];
Gizmos.DrawSphere(new Vector3(pos.x,0,pos.y),1);
}
}
return;
void Draw(QuadtreeNode quadtreeNode)
{
Gizmos.color = Color.white;
if(quadtreeNode.Size.x is 0)return;
var nodeCenter = quadtreeNode.Center;
var nodeSize = quadtreeNode.Size;
foreach (var (id,rectangle) in _quadtree.Sizes)
{
var pos = _quadtree.Positions[id];
Gizmos.DrawWireCube(new Vector3(pos.x,0,pos.y),new Vector3(rectangle.x,0,rectangle.y));
}
foreach (var id in quadtreeNode.Objects)
{
if (repeatedObject.Add(id) is false)
{
Debug.Log("重复对象");
}
var pos = _quadtree.Positions[id];
var worldPos = new Vector3(pos.x, 0, pos.y);
Gizmos.DrawSphere(worldPos,1);
}
Gizmos.DrawWireCube(new Vector3(nodeCenter.x,0,nodeCenter.y),new Vector3(nodeSize.x,0,nodeSize.y));
foreach (var child in quadtreeNode.Children)
{
Draw(child);
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 097957ac1375f8141a80aa94f32030db
guid: 6f51bcded3e1e574187f641567d12ff8
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,7 +1,11 @@
{
"name": "BITKit.Quadtree",
"name": "Net.BITKit.Quadtree.Unity",
"rootNamespace": "",
"references": [],
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:1193c2664d97cc049a6e4c486c6bce71"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1193c2664d97cc049a6e4c486c6bce71
guid: c39447ec3de56774087b5dc77d510181
AssemblyDefinitionImporter:
externalObjects: {}
userData:

View File

@@ -1,12 +0,0 @@
using Quadtree.Items;
namespace Quadtree
{
/// <summary>
/// Single quadtree node.
/// </summary>
public class Node<TItem> : NodeBase<TItem, Node<TItem>>
where TItem : IItem<TItem, Node<TItem>>
{
}
}

View File

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

View File

@@ -1,420 +0,0 @@
using Quadtree.Items;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Quadtree
{
/// <summary>
/// Base quadtree node implementation.
/// </summary>
public abstract class NodeBase<TItem, TNode> : INode<TItem, TNode>
where TItem : IItem<TItem, TNode>
where TNode : NodeBase<TItem, TNode>, new()
{
public Bounds Bounds { get; set; }
public TNode ParentNode { get; set; }
public IList<TNode> SubNodes { get; set; }
public IQuadtreeRoot<TItem, TNode> TreeRoot { get; set; }
/// <summary>
/// List of inserted items.
/// </summary>
private readonly HashSet<TItem> _items;
public NodeBase()
{
SubNodes = new List<TNode>(4);
_items = new HashSet<TItem>();
}
/// <summary>
/// Verifies whether provided boundaries (<paramref name="bounds"/>) are fully contained within the boundaries of the node.
/// </summary>
///
/// <param name="bounds">Boundaries of an object</param>
/// <returns><c>True</c> if object is fully contained within the node, <c>False</c> otherwise</returns>
public bool Contains(Bounds bounds) =>
bounds.min.x >= Bounds.min.x
&& bounds.min.z >= Bounds.min.z
&& bounds.max.x < Bounds.max.x
&& bounds.max.z < Bounds.max.z;
public IntraLocation Location(Bounds bounds)
{
if (bounds.min.z >= Bounds.center.z)
{
// items are located in top sub-nodes
if (bounds.max.x < Bounds.center.x)
{
// items are located in top left sub-node
return IntraLocation.UPPER_LEFT;
}
else if (bounds.min.x >= Bounds.center.x)
{
// items are located in top right sub-node
return IntraLocation.UPPER_RIGHT;
}
else
{
// item does not fit to either one, but is top
// (max.x is right, min.x is left)
return IntraLocation.SPANNING_UPPER;
}
}
else if (bounds.max.z < Bounds.center.z)
{
// items are located in bottom sub-nodes
if (bounds.max.x < Bounds.center.x)
{
// items are located in bottom left sub-node
return IntraLocation.LOWER_LEFT;
}
else if (bounds.min.x >= Bounds.center.x)
{
// items are located in bottom right sub-node
return IntraLocation.LOWER_RIGHT;
}
else
{
// item does not fit to either one, but is bottom
// (max.x is right, min.x is left)
return IntraLocation.SPANNING_LOWER;
}
}
else
{
// item does not fit to any sub-node
// (max.z is top, min.z is bottom)
if (bounds.min.x >= Bounds.center.x)
{
// bounds span over top right and bottom right nodes
return IntraLocation.SPANNING_RIGHT;
}
else if (bounds.max.x < Bounds.center.x)
{
// bounds span over top left and bottom left nodes
return IntraLocation.SPANNING_LEFT;
}
else
{
// bounds span over all sub-nodes
return IntraLocation.SPANNING;
}
}
}
public void Insert(TItem item)
{
// create new sub-nodes
if (SubNodes.Count == 0)
CreateSubNodes();
// sub-nodes can not be created anymore
if (SubNodes.Count == 0)
{
// insert item into this node
_items.Add(item);
// and designate this node its parent
item.ParentNode = (TNode)this;
return;
}
var itemBounds = item.GetBounds();
var itemBoundsLocation = Location(itemBounds);
switch (itemBoundsLocation)
{
// boundaries are contained within one of the subnodes
case IntraLocation.UPPER_LEFT:
case IntraLocation.UPPER_RIGHT:
case IntraLocation.LOWER_RIGHT:
case IntraLocation.LOWER_LEFT:
SubNodes[(int)itemBoundsLocation].Insert(item);
break;
// boundaries are spanning over 2 or more subnodes
default:
_items.Add(item);
item.ParentNode = (TNode)this;
break;
}
}
public void Remove(TItem item)
{
var itemBounds = item.GetBounds();
var itemBoundsLocation = Location(itemBounds);
switch (itemBoundsLocation)
{
// boundaries are contained within one of the subnodes
case IntraLocation.UPPER_LEFT:
case IntraLocation.UPPER_RIGHT:
case IntraLocation.LOWER_RIGHT:
case IntraLocation.LOWER_LEFT:
SubNodes[(int)itemBoundsLocation].Remove(item);
break;
// boundaries are spanning over 2 or more subnodes
default:
RemoveOwnItem(item);
break;
}
}
/// <summary>
/// Removes provided item (<paramref name="item"/>) from the node.
/// </summary>
///
/// <param name="item">Item to be removed from the node</param>
///
/// <seealso cref="INode{TItem, TNode}.Clear"/>
protected internal void RemoveOwnItem(TItem item)
{
// remove the item from the node
_items.Remove(item);
// update its parent node
item.ParentNode = null;
if (IsEmpty())
{
// remove subnodes if subtree of this node is empty
SubNodes.Clear();
}
}
public bool IsEmpty()
{
if (_items.Count > 0)
return false;
foreach (var subNode in SubNodes)
if (!subNode.IsEmpty())
return false;
return true;
}
public void Update(TItem item, bool forceInsertionEvaluation = true, bool hasOriginallyContainedItem = true)
{
if (Contains(item.GetBounds()))
{
// item is contained by this node
if (hasOriginallyContainedItem)
{
// ...and this node has originally contained the item
if (forceInsertionEvaluation)
{
// ...and insertion evaluation is forced
// this checks whether the item hasn't moved into any of the subnodes
RemoveOwnItem(item);
Insert(item);
}
// item is still contained by its original node, no action necessary
return;
}
// ...but this node is not its original container
// insert item either to this node or any of its children
Insert(item);
// update has been successful
return;
}
// the item is not contained by this node
if (ParentNode == null)
{
// ...and this node does not have any parent - the tree must be expanded
TreeRoot.Expand();
if (ParentNode == null)
{
// the expansion has failed for some reason
Debug.LogError("Tree root expansion failed for item " + item.ToString());
return;
}
}
// the item is not contained by this node
if (hasOriginallyContainedItem)
{
// ...and this node has originally contained the item - it must be removed
RemoveOwnItem(item);
}
// parent is (now) available
ParentNode.Update(item, forceInsertionEvaluation, false);
// the item is now contained by another node, update has been successful
}
/// <summary>
/// Creates sub-nodes for the node.
/// </summary>
protected internal void CreateSubNodes()
{
var subBoundsSize = Bounds.size * .5f;
if (subBoundsSize.x < TreeRoot.MinimumPossibleNodeSize
|| subBoundsSize.z < TreeRoot.MinimumPossibleNodeSize)
{
// new sub-node bounds are too small
return;
}
var centerOffset = subBoundsSize * .5f;
// top left node [-x +z]
centerOffset.x *= -1f;
SubNodes.Insert((int)IntraLocation.UPPER_LEFT, new TNode()
{
TreeRoot = TreeRoot,
ParentNode = (TNode)this,
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
});
// top right node [+x +z]
centerOffset.x *= -1f;
SubNodes.Insert((int)IntraLocation.UPPER_RIGHT, new TNode()
{
TreeRoot = TreeRoot,
ParentNode = (TNode)this,
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
});
// bottom right node [+x -z]
centerOffset.z *= -1f;
SubNodes.Insert((int)IntraLocation.LOWER_RIGHT, new TNode()
{
TreeRoot = TreeRoot,
ParentNode = (TNode)this,
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
});
// bottom left node [-x -z]
centerOffset.x *= -1f;
SubNodes.Insert((int)IntraLocation.LOWER_LEFT, new TNode()
{
TreeRoot = TreeRoot,
ParentNode = (TNode)this,
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
});
}
public void FindAndAddItems(Bounds bounds, ref IList<TItem> items)
{
if (SubNodes.Count == 0)
{
// no sub-nodes exist
AddOwnItems(ref items);
return;
}
// always add any items in this node intersecting with the boundaries
AddOwnItems(ref items, bounds);
var boundsLocation = Location(bounds);
switch (boundsLocation)
{
// boundaries are contained within one of the subnodes
case IntraLocation.UPPER_LEFT:
case IntraLocation.UPPER_RIGHT:
case IntraLocation.LOWER_RIGHT:
case IntraLocation.LOWER_LEFT:
SubNodes[(int)boundsLocation].FindAndAddItems(bounds, ref items);
break;
// boundaries are spanning over left subnodes
case IntraLocation.SPANNING_LEFT:
SubNodes[(int)IntraLocation.UPPER_LEFT].AddItems(ref items, bounds);
SubNodes[(int)IntraLocation.LOWER_LEFT].AddItems(ref items, bounds);
break;
// boundaries are spanning over right subnodes
case IntraLocation.SPANNING_RIGHT:
SubNodes[(int)IntraLocation.UPPER_RIGHT].AddItems(ref items, bounds);
SubNodes[(int)IntraLocation.LOWER_RIGHT].AddItems(ref items, bounds);
break;
// boundaries are spanning over upper subnodes
case IntraLocation.SPANNING_UPPER:
SubNodes[(int)IntraLocation.UPPER_LEFT].AddItems(ref items, bounds);
SubNodes[(int)IntraLocation.UPPER_RIGHT].AddItems(ref items, bounds);
break;
// boundaries are spanning over lower subnodes
case IntraLocation.SPANNING_LOWER:
SubNodes[(int)IntraLocation.LOWER_LEFT].AddItems(ref items, bounds);
SubNodes[(int)IntraLocation.LOWER_RIGHT].AddItems(ref items, bounds);
break;
// boundaries are spanning over all subnodes
case IntraLocation.SPANNING:
default:
AddSubNodeItems(ref items, bounds);
break;
}
}
public void AddItems(ref IList<TItem> items, Bounds? bounds = null)
{
AddOwnItems(ref items, bounds);
AddSubNodeItems(ref items, bounds);
}
/// <summary>
/// Adds all items belonging to this node (ignoring sub-nodes) to the provided list of items (<paramref name="items"/>).
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
/// </summary>
///
/// <param name="items">Output list for found items</param>
/// <param name="bounds">Boundaries to look for items within</param>
protected internal void AddOwnItems(ref IList<TItem> items, Bounds? bounds = null)
{
var itemSource = bounds != null
? _items.Where(item => item.GetBounds().Intersects((Bounds)bounds))
: _items;
foreach (var item in itemSource)
{
items.Add(item);
}
}
/// <summary>
/// Adds all items belonging to sub-nodes (ignoring own items) to the provided list of items (<paramref name="items"/>).
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
/// </summary>
///
/// <param name="items">Output list for found items</param>
/// <param name="bounds">Boundaries to look for items within</param>
protected internal void AddSubNodeItems(ref IList<TItem> items, Bounds? bounds = null)
{
foreach (var subNode in SubNodes)
subNode.AddItems(ref items, bounds);
}
public void Clear()
{
_items.Clear();
SubNodes.Clear();
}
public void DrawBounds(bool displayNumberOfItems = false)
{
#if UNITY_EDITOR
if (displayNumberOfItems)
Handles.Label(Bounds.center, _items.Count.ToString());
Gizmos.DrawWireCube(Bounds.center, Bounds.size);
#endif
foreach (var subNode in SubNodes)
subNode.DrawBounds(displayNumberOfItems);
}
}
}

View File

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

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.Remoting;
using BITKit;
using BITKit.Entities;
using Microsoft.Extensions.DependencyInjection;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Jobs;
namespace Net.BITKit.Quadtree
{
public abstract class QuadTreeService
{
public readonly Quadtree Quadtree = new(default,new float2(2048,2048));
}
public class QuadTreeService<T>:QuadTreeService,IDisposable where T : class
{
private readonly IEntitiesService _entitiesService;
private readonly ConcurrentDictionary<int, Transform> _transforms = new();
private readonly ITicker _ticker;
public QuadTreeService(IEntitiesService entitiesService, ITicker ticker)
{
_entitiesService = entitiesService;
_ticker = ticker;
_entitiesService.OnAdd += OnAdd;
_entitiesService.OnRemove += OnRemove;
foreach (var (entity,t) in _entitiesService.QueryComponents<IEntity,T>())
{
OnAdd(entity,t);
}
_ticker.Add(OnTick);
}
private void OnTick(float obj)
{
foreach (var (id,transform) in _transforms)
{
Quadtree.Remove(id);
Quadtree.Insert(id,((float3)transform.position).xz);
}
}
private void OnRemove(IEntity obj)
{
_transforms.TryRemove(obj.Id,out _);
Quadtree.Remove(obj.Id);
}
private void OnAdd(IEntity obj)
{
if (obj.ServiceProvider.GetService<T>() is { } t)
{
OnAdd(obj,t);
}
}
private void OnAdd(IEntity entity, T t)
{
_transforms.TryAdd(entity.Id, entity.ServiceProvider.GetRequiredService<Transform>());
}
public void Dispose()
{
_entitiesService.OnAdd -= OnAdd;
_entitiesService.OnRemove -= OnRemove;
_ticker.Remove(OnTick);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6ab96e72850290b47b83ba45e5f1d02e
guid: 7865070e56eaeb1439c92757b6ee00b3
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,102 +0,0 @@
using Quadtree.Items;
using System.Collections.Generic;
using UnityEngine;
namespace Quadtree
{
/// <summary>
/// Main class of the Quadtree structure - it represents the root of the tree.
/// </summary>
public abstract class QuadtreeMonoRoot<TItem, TNode> : MonoBehaviour, IQuadtreeRoot<TItem, TNode>
where TItem : IItem<TItem, TNode>
where TNode : INode<TItem, TNode>, new()
{
//==========================================================================dd==
// MonoBehaviour METHODS
//==========================================================================dd==
protected void Start()
{
Init();
}
protected void OnEnable()
{
Init();
}
protected void OnDrawGizmos()
{
DrawBounds();
}
//==========================================================================dd==
// CORE ROOT NODE METHODS
//==========================================================================dd==
/// <summary>
/// Root node containing all items and sub-nodes.
/// </summary>
protected QuadtreeRoot<TItem, TNode> TreeRoot = null;
public bool Initialized => TreeRoot != null && TreeRoot.Initialized;
public TNode CurrentRootNode => TreeRoot.CurrentRootNode;
[SerializeField]
protected Vector3 DefaultRootNodeSize = new Vector3(64f, 0f, 64f);
/// <inheritdoc cref="IQuadtreeRoot{TItem, TNode}.MinimumPossibleNodeSize"/>
[SerializeField]
protected float MinimumPossibleNodeSize = 1f;
float IQuadtreeRoot<TItem, TNode>.MinimumPossibleNodeSize => MinimumPossibleNodeSize;
/// <inheritdoc cref="IQuadtreeRoot{TItem, TNode}.DisplayNumberOfItemsInGizmos"/>
[SerializeField]
private bool DisplayNumberOfItemsInGizmos = false;
bool IQuadtreeRoot<TItem, TNode>.DisplayNumberOfItemsInGizmos => DisplayNumberOfItemsInGizmos;
/// <summary>
/// Initializes Quadtree - creates initial root node and builds the tree (if allowed).
/// </summary>
///
/// <seealso cref="IItem{TItem, TNode}.QuadTree_Root_Initialized(IQuadtreeRoot{TItem, TNode})"/>
protected void Init()
{
if (TreeRoot == null)
{
TreeRoot = new QuadtreeRoot<TItem, TNode>(transform.position, DefaultRootNodeSize);
}
else
{
// root node has already been initialized, clear the tree
TreeRoot.Clear();
}
// send a message to children that the tree root has been initialized
BroadcastMessage("QuadTree_Root_Initialized", this, SendMessageOptions.DontRequireReceiver);
}
/// <summary>
/// Displays Quadtree node boundaries.
/// </summary>
///
/// <seealso cref="INode{TItem, TNode}.DrawBounds(bool)"/>
protected void DrawBounds()
{
TreeRoot?.CurrentRootNode.DrawBounds(DisplayNumberOfItemsInGizmos);
}
public void Insert(TItem item) => TreeRoot.Insert(item);
public void Expand() => TreeRoot.Expand();
public List<TItem> Find(Bounds bounds) => TreeRoot.Find(bounds);
public void Remove(TItem item) => TreeRoot.Remove(item);
public void Clear() => TreeRoot.Clear();
}
}

View File

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

View File

@@ -1,148 +0,0 @@
using Quadtree.Items;
using System.Collections.Generic;
using UnityEngine;
namespace Quadtree
{
/// <summary>
/// Main class of the Quadtree structure - it represents the root of the tree.
/// </summary>
public class QuadtreeRoot<TItem, TNode> : IQuadtreeRoot<TItem, TNode>
where TItem : IItem<TItem, TNode>
where TNode : INode<TItem, TNode>, new()
{
public bool Initialized { get; set; }
public TNode CurrentRootNode { get; internal set; }
public float MinimumPossibleNodeSize => _minimumPossibleNodeSize;
/// <inheritdoc cref="IQuadtreeRoot{TItem}.MinimumPossibleNodeSize"/>
protected float _minimumPossibleNodeSize = 1f;
public bool DisplayNumberOfItemsInGizmos => _displayNumberOfItemsInGizmos;
/// <inheritdoc cref="IQuadtreeRoot{TItem}.DisplayNumberOfItemsInGizmos"/>
protected bool _displayNumberOfItemsInGizmos = false;
/// <summary>
/// Determines side of root node expansion if necessary.
/// </summary>
protected bool ExpansionRight = true;
/// <summary>
/// Initializes Quadtree - creates initial root node and builds the tree (if allowed).
/// </summary>
public QuadtreeRoot(Vector3 center, Vector3 size)
{
CurrentRootNode = new TNode
{
TreeRoot = this,
ParentNode = default,
Bounds = new Bounds(center, size),
};
Initialized = true;
}
public void Insert(TItem item)
{
// get item bounds
var itemBounds = item.GetBounds();
// expand root node if necessary
while (!CurrentRootNode.Contains(itemBounds))
Expand();
// insert item into the tree
CurrentRootNode.Insert(item);
}
public void Expand()
{
// the subnodes will be of the same size as current root node
var subBoundsSize = CurrentRootNode.Bounds.size;
var centerOffset = subBoundsSize * .5f;
// center if expanding to left
var center = CurrentRootNode.Bounds.min;
if (ExpansionRight)
{
// center if expanding to right
center = CurrentRootNode.Bounds.max;
}
var subNodes = new List<TNode>(4);
var newRootNode = new TNode
{
TreeRoot = this,
ParentNode = default,
Bounds = new Bounds(center, subBoundsSize * 2f),
SubNodes = subNodes,
};
CurrentRootNode.ParentNode = newRootNode;
// top left node [-x +y]
centerOffset.x *= -1f;
subNodes.Insert((int)IntraLocation.UPPER_LEFT, new TNode
{
TreeRoot = this,
ParentNode = newRootNode,
Bounds = new Bounds(center + centerOffset, subBoundsSize),
});
// top right node [+x +y]
centerOffset.x *= -1f;
subNodes.Insert((int)IntraLocation.UPPER_RIGHT, !ExpansionRight
? CurrentRootNode
: new TNode
{
TreeRoot = this,
ParentNode = newRootNode,
Bounds = new Bounds(center + centerOffset, subBoundsSize),
});
// bottom right node [+x -y]
centerOffset.z *= -1f;
subNodes.Insert((int)IntraLocation.LOWER_RIGHT, new TNode
{
TreeRoot = this,
ParentNode = newRootNode,
Bounds = new Bounds(center + centerOffset, subBoundsSize),
});
// bottom left node [-x -y]
centerOffset.x *= -1f;
subNodes.Insert((int)IntraLocation.LOWER_LEFT, ExpansionRight
? CurrentRootNode
: new TNode
{
TreeRoot = this,
ParentNode = newRootNode,
Bounds = new Bounds(center + centerOffset, subBoundsSize),
});
// assign new root node
CurrentRootNode = newRootNode;
// toggle expansion side for next expansion
ExpansionRight = !ExpansionRight;
}
public List<TItem> Find(Bounds bounds)
{
IList<TItem> itemList = new List<TItem>();
CurrentRootNode.FindAndAddItems(bounds, ref itemList);
return (List<TItem>)itemList;
}
public void Remove(TItem item)
{
CurrentRootNode.Remove(item);
}
public void Clear()
{
CurrentRootNode.Clear();
}
}
}

View File

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

View File

@@ -43,6 +43,7 @@ namespace BITKit
public string Get()
{
if (DictionaryReferenceScriptableObject.Dictionary.TryGetValue(index, out var value))
{
return value;

View File

@@ -1,18 +1,21 @@
using System.Collections;
using System.Collections.Generic;
using BITKit;
using UnityEngine;
public class UnityAutoDestroyOnStart : MonoBehaviour
{
[SerializeField] private bool inActive;
// Start is called before the first frame update
void Start()
{
Destroy(gameObject);
}
// Update is called once per frame
void Update()
{
if (inActive)
{
gameObject.SetActive(false);
}
else
{
Destroy(gameObject);
}
}
}

View File

@@ -1,18 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

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

View File

@@ -8,6 +8,7 @@ using Cysharp.Threading.Tasks;
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.LowLevel;
namespace BITKit
{
@@ -58,15 +59,18 @@ namespace BITKit
}
[SerializeField] private int tickRate = 32;
[SerializeField] private bool isMainThread;
[SerializeField] private bool isConcurrent;
[SerializeField] private string lastTickTime="0";
private readonly Timer _timer = new();
private float _deltaTime;
private double _lastTime;
private PlayerLoopSystem _playerLoop;
private int _update;
private void Start()
{
_playerLoop = PlayerLoop.GetCurrentPlayerLoop();
tickRate = Mathf.Clamp(tickRate, 1, 128);
_deltaTime = 1f / tickRate;
@@ -81,32 +85,34 @@ namespace BITKit
_timer.Dispose();
});
}
private async void Tick(object sender, ElapsedEventArgs e)
private void Update()
{
TickCount++;
try
{
var delta = (float)(BITApp.Time.TimeAsDouble - _lastTime);
_lastTime = BITApp.Time.TimeAsDouble;
if (isMainThread) await UniTask.SwitchToMainThread(destroyCancellationToken);
#if UNITY_EDITOR
if (EditorApplication.isPlaying is false)
{
Restart();
}
if (EditorApplication.isPlaying is false)
{
_update = 0;
return;
}
#endif
for (var i = 0; i < _update; i++)
{
var delta = Time.deltaTime;
while (_ActionQueue.TryDequeue(out var action))
{
action?.Invoke();
}
_TickEvents?.Invoke(delta);
// using var xEnumerator = _TickActions.GetEnumerator();
// while (xEnumerator.MoveNext())
// {
// xEnumerator.Current!.Invoke(delta);
// }
//_TickEvents?.Invoke((float)delta);
_TickEvents?.Invoke(delta);
}
_update = 0;
}
private void Tick(object sender, ElapsedEventArgs e)
{
TickCount++;
try
{
_update++;
}
catch (OperationCanceledException)
{
@@ -126,7 +132,6 @@ _TickEvents?.Invoke(delta);
_timer.Start();
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: f97e02abd61aa274683a80be45fec110
guid: c849275eb4fd266468253c3fb286f6c1
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BITKit;
using BITKit.UX;
using Net.BITKit.Localization;
using UnityEngine;
using UnityEngine.UIElements;
namespace Net.BITKit.UX
{
public class UXLocalization
{
public string USS { get; set; } = "localized";
private readonly IUXService _uxService;
private readonly ILocalizationService _localizationService;
public UXLocalization(IUXService uxService, ILocalizationService localizationService)
{
_uxService = uxService;
_localizationService = localizationService;
_localizationService.OnLanguageChanged += OnLanguageChanged;
}
private void OnLanguageChanged(string arg1, string arg2)
{
if(_uxService.Root is not VisualElement visualElement)return;
foreach (var x in visualElement.Query<Label>(className:USS).ToList())
{
if (string.IsNullOrEmpty(x.viewDataKey))continue;
x.text = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
foreach (var x in visualElement.Query<Button>(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
x.text = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
foreach (var x in visualElement.Query(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
ContinueWithBaseType(x.GetType());
continue;
void ContinueWithBaseType(Type type)
{
while (true)
{
if (type == null || type == typeof(object)) return;
if (type.GetProperty("label", ReflectionHelper.Flags) is { } propertyInfo)
{
//Debug.Log($"{x.name}:#{x.viewDataKey}");
propertyInfo.SetValue(x, _localizationService.GetLocalizedString('#' + x.viewDataKey));
}
else
{
type = type.BaseType;
continue;
}
break;
}
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 625588063be34ab419d783b87c474a44
guid: 01963dbed2bec23468d2a4b617bdcfc5
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using BITKit.Mod;
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Serialization;
@@ -18,7 +19,7 @@ using Debug = UnityEngine.Debug;
namespace BITKit.UX
{
public abstract class UIToolKitPanel : IUXPanel,IDisposable
public abstract class UIToolKitPanel :StateAsync, IUXPanel,IDisposable
{
public const string USSEntry = "transition_entry";
public const string USSEntryAsync = "transition_entry_async";
@@ -115,7 +116,7 @@ namespace BITKit.UX
//WaitUtilTransitionCompleted = new();
}
protected static readonly InputActionGroup InputActionGroup = new()
protected readonly InputActionGroup InputActionGroup = new()
{
allowGlobalActivation = false,
Source = nameof(UIToolKitPanel)
@@ -137,13 +138,12 @@ namespace BITKit.UX
}
protected virtual void OnPanelEntry(){}
protected virtual void OnPanelExit(){}
void IEntryElement.Entry()
public override void OnStateEntry(IState old)
{
InputActionGroup.allowInput.AddElement(this);
OnEntry?.Invoke();
}
async UniTask IEntryElement.EntryAsync()
public override async UniTask OnStateEntryAsync(IState old)
{
await InitializeAsync();
@@ -193,6 +193,12 @@ namespace BITKit.UX
await UniTask.SwitchToMainThread();
RootVisualElement.AddToClassList(USSEntered);
OnPanelEntry();
OnEntryCompleted?.Invoke();
RootVisualElement.RemoveFromClassList(USSEntry);
RootVisualElement.RemoveFromClassList(USSEntryAsync);
}
private void OnTransitionEnd<TChangeEvent>(TChangeEvent evt)
{
@@ -203,22 +209,15 @@ namespace BITKit.UX
{
return UniTask.CompletedTask;
}
void IEntryElement.Entered()
{
OnPanelEntry();
OnEntryCompleted?.Invoke();
RootVisualElement.RemoveFromClassList(USSEntry);
RootVisualElement.RemoveFromClassList(USSEntryAsync);
}
void IEntryElement.Exit()
public override void OnStateExit(IState old, IState newState)
{
OnPanelExit();
InputActionGroup.allowInput.RemoveElement(this);
OnExit?.Invoke();
}
async UniTask IEntryElement.ExitAsync()
public override async UniTask OnStateExitAsync(IState old, IState newState)
{
RootVisualElement?.RemoveFromClassList(USSEntered);
await UniTask.NextFrame();
@@ -244,9 +243,6 @@ namespace BITKit.UX
{
}
}
void IEntryElement.Exited()
{
RootVisualElement?.RemoveFromClassList(USSExit);
RootVisualElement?.RemoveFromClassList(USSExitAsync);
RootVisualElement?.AddToClassList(USSExited);
@@ -275,6 +271,7 @@ namespace BITKit.UX
public virtual void Dispose()
{
InputActionGroup.Dispose();
RootVisualElement?.RemoveFromHierarchy();
IsDisposed = true;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using UnityEngine;
@@ -67,43 +68,6 @@ namespace BITKit.UX
{ UXUtils.Inject(this,Panel.Root as VisualElement);
}
bool IEntryElement.IsEntered
{
get => Panel.IsEntered;
set => Panel.IsEntered = value;
}
void IEntryElement.Entry()
{
Panel.Entry();
}
UniTask IEntryElement.EntryAsync()
{
return Panel.EntryAsync();
}
void IEntryElement.Entered()
{
Panel.Entered();
}
void IEntryElement.Exit()
{
Panel.Exit();
}
UniTask IEntryElement.ExitAsync()
{
return Panel.ExitAsync();
}
void IEntryElement.Exited()
{
Panel.Exited();
}
bool IUXPanel.IsWindow => Panel.IsWindow;
string IUXPanel.Index => Panel.Index;
@@ -136,6 +100,58 @@ namespace BITKit.UX
{
Panel.OnTick(deltaTime);
}
public bool Enabled
{
get => Panel.Enabled;
set => Panel.Enabled = value;
}
public void Initialize()
{
Panel.Initialize();
}
public void OnStateEntry(IState old)
{
Panel.OnStateEntry(old);
}
public void OnStateUpdate(float deltaTime)
{
Panel.OnStateUpdate(deltaTime);
}
public void OnStateExit(IState old, IState newState)
{
Panel.OnStateExit(old, newState);
}
public int Identifier
{
get => Panel.Identifier;
set => Panel.Identifier = value;
}
public UniTask InitializeAsync()
{
return Panel.InitializeAsync();
}
public UniTask OnStateEntryAsync(IState old)
{
return Panel.OnStateEntryAsync(old);
}
public UniTask OnStateUpdateAsync(float deltaTime)
{
return Panel.OnStateUpdateAsync(deltaTime);
}
public UniTask OnStateExitAsync(IState old, IState newState)
{
return Panel.OnStateExitAsync(old, newState);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using System.Threading;
using BITKit.Mod;
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Unity.Mathematics;
@@ -27,10 +28,9 @@ namespace BITKit.UX
_ticker = ticker;
_serviceProvider = serviceProvider;
_cancellationTokenSource = cancellationTokenSource;
_entryGroup.OnEntry += OnEntry;
_entryGroup.OnStateChanged += OnEntry;
_windowEntryGroup.OnEntry += OnWindowEntry;
_windowEntryGroup.OnExit += OnWindowExit;
_windowEntryGroup.OnStateChanged += OnWindowEntry;
_ticker.Add(OnTick);
}
@@ -41,15 +41,15 @@ namespace BITKit.UX
BITInputSystem.AllowInput.RemoveDisableElements(_windowEntryGroup);
}
private void OnWindowEntry(IUXPanel obj)
private void OnWindowEntry(IUXPanel prev, IUXPanel next)
{
BITAppForUnity.AllowCursor.SetElements(_windowEntryGroup, obj.AllowCursor);
if (obj.AllowInput is false)
BITInputSystem.AllowInput.AddDisableElements(_windowEntryGroup);
BITAppForUnity.AllowCursor.SetElements(_windowEntryGroup, next is { AllowCursor: true });
BITInputSystem.AllowInput.SetDisableElements(_windowEntryGroup, next is { AllowInput: false });
}
private readonly EntryGroup<IUXPanel> _entryGroup = new();
private readonly EntryGroup<IUXPanel> _windowEntryGroup = new();
private readonly AsyncStateMachine<IUXPanel> _entryGroup = new();
private readonly AsyncStateMachine<IUXPanel> _windowEntryGroup = new();
/// <summary>
/// 内部注册面板队列
/// </summary>
@@ -163,9 +163,9 @@ Object.Destroy(gameObject);
public void Return()
{
if(_windowEntryGroup.TryGetEntried(out _))
if (_windowEntryGroup.CurrentState is not null)
{
_windowEntryGroup.Entry(-1);
_windowEntryGroup.DisposeState();
return;
}
if (History.TryPop(out var returnPanel))
@@ -173,16 +173,15 @@ Object.Destroy(gameObject);
_returnBuffer.Release(returnPanel);
}
}
private bool _initialized;
private void OnEntry(IUXPanel obj)
private void OnEntry(IUXPanel prev,IUXPanel next)
{
OnPanelChanged?.Invoke(_currentPanel,obj);
_currentPanel = obj;
OnPanelChanged?.Invoke(_currentPanel,next);
_currentPanel = next;
}
private void OnTick(float delta)
{
@@ -192,20 +191,20 @@ Object.Destroy(gameObject);
while (_registryQueue.TryDequeue(out var result))
{
if (result is null) continue;
_entryGroup.list.Add(result);
_entryGroup.Register(result);
_panels.Set(result.Index, result);
}
while (_unRegistryQueue.TryDequeue(out var result))
{
if (result is null) continue;
_entryGroup.list.Remove(result);
_entryGroup.UnRegister(result);
_panels.Remove(result.Index);
}
if (_returnBuffer.TryGetRelease(out var returnPanel))
{
_entryGroup.Entry(x=>x.Index==returnPanel.Index);
_entryGroup.TransitionState(returnPanel);
BITAppForUnity.AllowCursor.SetElements(this, returnPanel.AllowCursor);
BITInputSystem.AllowInput.SetElements(this, returnPanel.AllowInput);
}
@@ -221,22 +220,23 @@ Object.Destroy(gameObject);
{
if (nextPanel.IsWindow)
{
_windowEntryGroup.Entry(nextPanel);
_windowEntryGroup.TransitionState(nextPanel);
return;
}
_windowEntryGroup.Entry(-1);
_windowEntryGroup.DisposeState();
History.Push(_currentPanel);
_entryGroup.Entry(x=>x.Index==nextPanel.Index);
_entryGroup.TransitionState(nextPanel);
BITAppForUnity.AllowCursor.SetElements(this, nextPanel.AllowCursor);
BITInputSystem.AllowInput.SetElements(this, nextPanel.AllowInput);
}
if (_entryGroup.TryGetEntried(out var currentPanel))
if (_entryGroup.CurrentState is {Enabled:true})
{
currentPanel.OnTick(Time.deltaTime);
_entryGroup.CurrentState.OnTick(delta);
}
if(_windowEntryGroup.TryGetEntried(out var windowPanel))
if (_windowEntryGroup.CurrentState is {Enabled:true})
{
windowPanel.OnTick(Time.deltaTime);
_windowEntryGroup.CurrentState.OnTick(delta);
}
}
catch (Exception e)
@@ -256,13 +256,8 @@ Object.Destroy(gameObject);
}
_ticker.Remove(OnTick);
await UniTask.SwitchToMainThread();
if (_currentPanel is not null)
{
// ReSharper disable once MethodHasAsyncOverload
_currentPanel.Exit();
await _currentPanel.ExitAsync();
_currentPanel.Exited();
}
_entryGroup.Dispose();
_windowEntryGroup.Dispose();
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Net.BITKit.Localization;
using Newtonsoft.Json;
using Unity.Mathematics;
using UnityEngine;
@@ -15,11 +16,13 @@ namespace BITKit.UX.Settings
public interface IUXSettings:IUXPanel{}
public class UXSettings<T,TValue> : UIToolkitSubPanel<T>,IUXSettings where T: IUXPanel
{
private readonly ILocalizationService _localizationService;
private readonly TValue _value;
private readonly IWrapper<TValue> _valueWrapper;
public UXSettings(IServiceProvider serviceProvider, IWrapper<TValue> valueWrapper) : base(serviceProvider)
public UXSettings(IServiceProvider serviceProvider, IWrapper<TValue> valueWrapper, ILocalizationService localizationService) : base(serviceProvider)
{
_valueWrapper = valueWrapper;
_localizationService = localizationService;
_value = _valueWrapper.Value;
}
@@ -36,80 +39,98 @@ namespace BITKit.UX.Settings
_settingsContainer.Clear();
foreach (var propertyInfo in typeof(TValue).GetProperties())
{
if(propertyInfo.SetMethod is null)continue;
var name = propertyInfo.GetDisplayName();
var value = propertyInfo.GetValue(_valueWrapper.Value);
switch (propertyInfo.PropertyType)
if (CreateVisualElement() is { } visualElement)
{
case var type when type == typeof(bool):
visualElement.viewDataKey = name;
visualElement.AddToClassList("localized");
}
VisualElement CreateVisualElement()
{
switch (propertyInfo.PropertyType)
{
var checkBox = _settingsContainer.Create<Toggle>();
checkBox.label = name;
checkBox.SetValueWithoutNotify(value.As<bool>());
checkBox.RegisterValueChangedCallback(x =>
case var type when type == typeof(bool):
{
propertyInfo.SetValue(_value,x.newValue);
Save();
});
}
break;
case var type when type == typeof(float):
{
var slider = _settingsContainer.Create<Slider>();
slider.label = name;
slider.showInputField = true;
slider.showMixedValue = true;
slider.lowValue = 1;
slider.highValue = 100;
slider.SetValueWithoutNotify(value.As<float>());
slider.RegisterValueChangedCallback(x =>
var checkBox = _settingsContainer.Create<Toggle>();
checkBox.label = _localizationService.GetLocalizedString(name);
checkBox.SetValueWithoutNotify(value.As<bool>());
checkBox.RegisterValueChangedCallback(x =>
{
propertyInfo.SetValue(_value, x.newValue);
Save();
});
return checkBox;
}
break;
case var type when type == typeof(float):
{
propertyInfo.SetValue(_value,x.newValue);
Save();
});
}
break;
case var type when type == typeof(int):
{
var slider = _settingsContainer.Create<SliderInt>();
slider.label = name;
slider.showInputField = true;
slider.showMixedValue = true;
slider.lowValue = 1;
slider.highValue = 100;
slider.SetValueWithoutNotify(value.As<int>());
slider.RegisterValueChangedCallback(x =>
var slider = _settingsContainer.Create<Slider>();
slider.label = _localizationService.GetLocalizedString(name);
slider.showInputField = true;
slider.showMixedValue = true;
slider.lowValue = 1;
slider.highValue = 100;
slider.SetValueWithoutNotify(value.As<float>());
slider.RegisterValueChangedCallback(x =>
{
propertyInfo.SetValue(_value, x.newValue);
Save();
});
return slider;
}
break;
case var type when type == typeof(int):
{
propertyInfo.SetValue(_value,x.newValue);
Save();
});
}
break;
case var type when type == typeof(string):
{
var textField = _settingsContainer.Create<TextField>();
textField.label = name;
textField.SetValueWithoutNotify(value.As<string>());
textField.RegisterValueChangedCallback(x =>
var slider = _settingsContainer.Create<SliderInt>();
slider.label = _localizationService.GetLocalizedString(name);
slider.showInputField = true;
slider.showMixedValue = true;
slider.lowValue = 1;
slider.highValue = 100;
slider.SetValueWithoutNotify(value.As<int>());
slider.RegisterValueChangedCallback(x =>
{
propertyInfo.SetValue(_value, x.newValue);
Save();
});
return slider;
}
break;
case var type when type == typeof(string):
{
propertyInfo.SetValue(_value,x.newValue);
Save();
});
}
break;
case var type when type.IsEnum:
{
var enumField = _settingsContainer.Create<EnumField>();
enumField.label = name;
enumField.Init(value as Enum);
enumField.SetValueWithoutNotify(value as Enum);
enumField.RegisterValueChangedCallback(x =>
var textField = _settingsContainer.Create<TextField>();
textField.label = _localizationService.GetLocalizedString(name);
textField.SetValueWithoutNotify(value.As<string>());
textField.RegisterValueChangedCallback(x =>
{
propertyInfo.SetValue(_value, x.newValue);
Save();
});
return textField;
}
break;
case var type when type.IsEnum:
{
propertyInfo.SetValue(_value,x.newValue);
Save();
});
var enumField = _settingsContainer.Create<EnumField>();
enumField.label = _localizationService.GetLocalizedString(name);
enumField.Init(value as Enum);
enumField.SetValueWithoutNotify(value as Enum);
enumField.RegisterValueChangedCallback(x =>
{
propertyInfo.SetValue(_value, x.newValue);
Save();
});
return enumField;
}
break;
}
break;
return null;
}
}
}

View File

@@ -10,6 +10,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;
using System.Text;
using Unity.Mathematics;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
@@ -206,30 +207,11 @@ namespace BITKit
}
public static float GetLength(this Vector2 self)
{
return Mathf.Max(Mathf.Abs(self.x), Mathf.Abs(self.y));
return math.max(math.abs(self.x), math.abs(self.y));
}
public static float GetLength(this Vector3 self)
{
return Mathf.Max(Mathf.Abs(self.x), Mathf.Abs(self.y), Mathf.Abs(self.z));
}
public static float GetValue(this Vector3 self)
{
var addValue = self.x + self.y + self.z;
var subtractValue = self.x - self.y - self.z;
if (Math.Abs(MathF.Abs(addValue) - Mathf.Abs(subtractValue)) < 0.01f)
{
return addValue;
}
else
{
var sb = new StringBuilder();
sb.AppendLine("Not a valid vector");
sb.AppendLine($"{self.x}+{self.y}+{self.z} = {addValue}");
sb.AppendLine($"{self.x}-{self.y}-{self.z} = {subtractValue}");
sb.AppendLine($"{addValue}");
sb.AppendLine($"{subtractValue}");
throw new Exception(sb.ToString());
}
return math.max(math.abs(self.x),math.max(math.abs(self.y), math.abs(self.z)));
}
public static bool InRange(this Vector2Int self, Vector2Int other)
{
@@ -607,6 +589,8 @@ namespace BITKit
}
public static partial class UIToolkitExtensions
{
private static Camera Camera => _camera ? _camera : _camera = Camera.main;
private static Camera _camera;
public static VisualElement Create(this VisualElement self, VisualTreeAsset asset)
{
var clone = asset.CloneTree();
@@ -689,38 +673,21 @@ namespace BITKit
}
public static bool IsVisible(this VisualElement self,Vector3 worldPosition)
{
var cameraTrans = Camera.main.transform;
var cameraTrans = Camera.transform;
return Vector3.Dot(cameraTrans.forward, worldPosition - cameraTrans.position) > 0;
}
public static Vector2 GetScreenPosition(this VisualElement self, Vector3 worldPosition)
{
try
{
var panel = self.panel;
if (panel is null)
{
panel = self.parent.panel;
}
var panel = (self.panel ?? self.parent.panel) ?? self.parent.parent.panel;
if (panel is null)
{
panel = self.parent.parent.panel;
}
var pos = RuntimePanelUtils
.CameraTransformWorldToPanel(panel, worldPosition, Camera);
var pos = RuntimePanelUtils
.CameraTransformWorldToPanel(panel, worldPosition, Camera.main);
pos.x -= self.layout.width / 2;
pos.y -= self.layout.height / 2;
pos.x -= self.layout.width / 2;
pos.y -= self.layout.height / 2;
return pos;
}
catch (Exception e)
{
Debug.LogException(e);
}
return default;
return pos;
}
public static Vector2 GetPosition(this VisualElement self)
@@ -736,7 +703,7 @@ namespace BITKit
self.style.left = screenPosition.x;
self.style.top = screenPosition.y;
}
public static void SetPosition(this VisualElement self,Vector3 worldPosition)
public static Vector2 SetPosition(this VisualElement self,Vector3 worldPosition)
{
var pos = self.GetScreenPosition(worldPosition);
var visible = self.IsVisible(worldPosition);
@@ -749,6 +716,8 @@ namespace BITKit
self.style.top = pos.y;
self.SetOpacity(visible ? 1 : 0);
return pos;
}
public static void ClearTooltipsOnPointerLeave(this VisualElement visualElement)

View File

@@ -17,7 +17,7 @@ namespace Net.BITKit.VFX
public VFXService(IEntitiesService entitiesService)
{
_entitiesService = entitiesService;
_scriptableImpacts = _entitiesService.QueryComponents<ScriptableImpact>();
_scriptableImpacts = _entitiesService.QueryComponents<ScriptableImpact>().ToArray();
}
public AudioClip GetAudioClip(IReadOnlyCollection<string> tags)

View File

@@ -17,7 +17,9 @@
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"defineConstraints": [
"Disabled"
],
"versionDefines": [],
"noEngineReferences": false
}