1
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ using UnityEngine;
|
||||
|
||||
namespace Net.BITKit.Impact
|
||||
{
|
||||
|
||||
public class ScriptableImpact : ScriptableObject,ITag
|
||||
{
|
||||
[SerializeReference, SubclassSelector] private IReference[] tags;
|
||||
|
@@ -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;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c15a9900918e4324991bcf53015b006e
|
||||
guid: 8a606fb5272e8a84dac37bae47771409
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
@@ -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>>
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bc2bf5dce5a1fa418a53e9616109677
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree.Items
|
||||
{
|
||||
public abstract class GameObjectItem : GameObjectItemBase<GameObjectItem, Node<GameObjectItem>>
|
||||
{
|
||||
protected override GameObjectItem This() => this;
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bb93ae6561edaa49a599f7ccf090da1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34e0c8684cc1c224a91b831b8c99c968
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e6be24bac4fd4a409194655d1cf0665
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b92b133d7eeb95b4d95b27cf8ef679b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
86
Src/Unity/Scripts/Quadtree/MonoQuadtreeDrawer.cs
Normal file
86
Src/Unity/Scripts/Quadtree/MonoQuadtreeDrawer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 097957ac1375f8141a80aa94f32030db
|
||||
guid: 6f51bcded3e1e574187f641567d12ff8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@@ -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,
|
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1193c2664d97cc049a6e4c486c6bce71
|
||||
guid: c39447ec3de56774087b5dc77d510181
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
@@ -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>>
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 836cada8514e07d4d958f972ccf9a0e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2bca5a4f0f3b554ea3c3f9ba067918b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
82
Src/Unity/Scripts/Quadtree/QuadTreeService.cs
Normal file
82
Src/Unity/Scripts/Quadtree/QuadTreeService.cs
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ab96e72850290b47b83ba45e5f1d02e
|
||||
guid: 7865070e56eaeb1439c92757b6ee00b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 016ed1871ab14284db00aa21f490908c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 244873571ee2c644b9ee21f4848b63b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -43,6 +43,7 @@ namespace BITKit
|
||||
|
||||
public string Get()
|
||||
{
|
||||
|
||||
if (DictionaryReferenceScriptableObject.Dictionary.TryGetValue(index, out var value))
|
||||
{
|
||||
return value;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2048d435d8387842b5b0c9bf5498a1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f97e02abd61aa274683a80be45fec110
|
||||
guid: c849275eb4fd266468253c3fb286f6c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
73
Src/Unity/Scripts/UX/Localization/UXLocalization.cs
Normal file
73
Src/Unity/Scripts/UX/Localization/UXLocalization.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 625588063be34ab419d783b87c474a44
|
||||
guid: 01963dbed2bec23468d2a4b617bdcfc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -17,7 +17,9 @@
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"defineConstraints": [
|
||||
"Disabled"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
Reference in New Issue
Block a user