1
This commit is contained in:
@@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AYellowpaper.SerializedCollections;
|
||||
using UnityEngine;
|
||||
using YooAsset;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
public class AddressableHelper:MonoBehaviour
|
||||
{
|
||||
private static IDictionary<ulong,string> PathsById { get; } = new Dictionary<ulong, string>();
|
||||
public static T Get<T>(ulong id) where T : Object
|
||||
{
|
||||
var task = YooAssets.LoadAssetAsync<T>(PathsById[id]);
|
||||
task.WaitForAsyncComplete();
|
||||
return task.AssetObject.As<T>();
|
||||
}
|
||||
[SerializeField] private SerializedDictionary<ulong,string> pathsById;
|
||||
private void Start()
|
||||
{
|
||||
PathsById.Clear();
|
||||
foreach (var x in pathsById)
|
||||
{
|
||||
PathsById.Add(x.Key,x.Value);
|
||||
}
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
[BIT]
|
||||
private void BuildCache()
|
||||
{
|
||||
var guids = AssetDatabase.FindAssets($"t:Object",new[] {"Assets"});
|
||||
var paths = guids.Select(AssetDatabase.GUIDToAssetPath);
|
||||
|
||||
var objects = new List<IAddressable>();
|
||||
var stringBuilder = new System.Text.StringBuilder();
|
||||
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
|
||||
switch (asset)
|
||||
{
|
||||
case GameObject go when go.TryGetComponent(out IAddressable addressable):
|
||||
objects.Add(addressable);
|
||||
break;
|
||||
case IAddressable addressable:
|
||||
objects.Add(addressable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stringBuilder.AppendLine($"所有资源数量:{guids.Length},其中包含{objects.Count}个Addressable资源");
|
||||
|
||||
pathsById.Clear();
|
||||
foreach (var x in objects)
|
||||
{
|
||||
if (x is not Object unityObject) continue;
|
||||
|
||||
//if (x.AddressableId is ulong.MinValue or ulong.MaxValue)
|
||||
{
|
||||
x.AddressableId = RandomUlong.NextUlong;
|
||||
EditorUtility.SetDirty(unityObject);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (x.AddressableId is not ulong.MinValue && !string.IsNullOrEmpty(x.AddressablePath))
|
||||
{
|
||||
if (pathsById.TryAdd(x.AddressableId, x.AddressablePath))
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.AppendLine($"资源{unityObject.name}的AddressableId:{x.AddressableId}已经存在");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.AppendLine($"{unityObject.name}的AddressableId或AddressablePath为空");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
stringBuilder.AppendLine($"{unityObject.name}遇到了错误:{e.Message}");
|
||||
}
|
||||
}
|
||||
EditorUtility.SetDirty(this);
|
||||
Debug.Log(stringBuilder);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class RandomUlong
|
||||
{
|
||||
private static readonly System.Random _random = new();
|
||||
|
||||
public static ulong NextUlong
|
||||
{
|
||||
get
|
||||
{
|
||||
var buf = new byte[8];
|
||||
_random.NextBytes(buf);
|
||||
return BitConverter.ToUInt64(buf, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,13 +10,17 @@ namespace BITKit
|
||||
private void OnAnimatorMove()
|
||||
{
|
||||
if (root)
|
||||
root.SendMessage(nameof(OnAnimatorMove));
|
||||
root.SendMessageUpwards(nameof(OnAnimatorMove),SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
private void AIAnimationEvent(string actionName)
|
||||
{
|
||||
|
||||
if (root)
|
||||
root.SendMessage(nameof(AIAnimationEvent), actionName);
|
||||
root.SendMessage(nameof(AIAnimationEvent), actionName,SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
public void AnimationEvent(string eventName)
|
||||
{
|
||||
if(root)
|
||||
root.SendMessage(nameof(AnimationEvent), eventName,SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
}
|
||||
}
|
90
Assets/BITKit/Unity/Scripts/Assets/AddressableHelper.cs
Normal file
90
Assets/BITKit/Unity/Scripts/Assets/AddressableHelper.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AYellowpaper.SerializedCollections;
|
||||
using BITKit.IO;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YooAsset;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
public class AddressableHelper:MonoBehaviour
|
||||
{
|
||||
private static IDictionary<ulong,string> PathsById { get; } = new Dictionary<ulong, string>();
|
||||
public static T Get<T>(ulong id) where T : Object
|
||||
{
|
||||
var task = YooAssets.LoadAssetAsync<T>(PathsById[id]);
|
||||
task.WaitForAsyncComplete();
|
||||
return task.AssetObject.As<T>();
|
||||
}
|
||||
private void Start()
|
||||
{
|
||||
PathsById.Clear();
|
||||
|
||||
YooAssetUtils.OnPackageRegistered += OnRegister;
|
||||
|
||||
foreach (var registeredResource in YooAssetUtils.RegisteredResourcePackages)
|
||||
{
|
||||
OnRegister(registeredResource.PackageName);
|
||||
}
|
||||
|
||||
destroyCancellationToken.Register(() =>
|
||||
{
|
||||
YooAssetUtils.OnPackageRegistered -= OnRegister;
|
||||
});
|
||||
}
|
||||
|
||||
private static async void OnRegister(string obj)
|
||||
{
|
||||
Stopwatch stopWatcher = new();
|
||||
stopWatcher.Start();
|
||||
|
||||
BIT4Log.Log<AddressableHelper>($"开始注册:{obj}的资源");
|
||||
var package = YooAssets.GetPackage(obj);
|
||||
var assetInfos = package.GetAssetInfos(nameof(IAddressable));
|
||||
|
||||
var reportBuilder = new StringBuilder();
|
||||
foreach (var x in assetInfos)
|
||||
{
|
||||
var asyncHandle = YooAssets.LoadAssetAsync(x.AssetPath);
|
||||
await asyncHandle;
|
||||
if (asyncHandle.AssetObject is IAddressable addressable)
|
||||
{
|
||||
if (PathsById.TryAdd(addressable.AddressableId, addressable.AddressablePath) is false)
|
||||
{
|
||||
var existing = PathsById[addressable.AddressableId];
|
||||
BIT4Log.Warning<AddressableHelper>($"{addressable.AddressablePath}的Id:{addressable.AddressableId}已被注册为{existing}");
|
||||
}
|
||||
reportBuilder.AppendLine($"{addressable.AddressablePath}的Id:{addressable.AddressableId}");
|
||||
}
|
||||
}
|
||||
BIT4Log.Log<AddressableHelper>($"注册:{obj}的资源完成,耗时:{stopWatcher.ElapsedMilliseconds}ms");
|
||||
BIT4Log.Log<AddressableHelper>(reportBuilder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static class RandomUlong
|
||||
{
|
||||
private static readonly System.Random _random = new();
|
||||
|
||||
public static ulong NextUlong
|
||||
{
|
||||
get
|
||||
{
|
||||
var buf = new byte[8];
|
||||
_random.NextBytes(buf);
|
||||
return BitConverter.ToUInt64(buf, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,6 @@ namespace BITKit
|
||||
public float Sensitivity = 1.81f;
|
||||
public float TouchSensitivity = 0.22f;
|
||||
public float M_Yaw = 0.022f;
|
||||
public float Fov = 75;
|
||||
public float Fov = 90;
|
||||
}
|
||||
}
|
32
Assets/BITKit/Unity/Scripts/DataBind/IBindableData.cs
Normal file
32
Assets/BITKit/Unity/Scripts/DataBind/IBindableData.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.IData
|
||||
{
|
||||
public interface IBindableData
|
||||
{
|
||||
object Data { get; }
|
||||
}
|
||||
public interface IBindableData<T>:IBindableData
|
||||
{
|
||||
new T Data { get; }
|
||||
}
|
||||
public readonly struct BindableData:IBindableData
|
||||
{
|
||||
public BindableData(object data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
public object Data { get; }
|
||||
}
|
||||
public readonly struct BindableData<T>:IBindableData<T>
|
||||
{
|
||||
public T Data { get; }
|
||||
object IBindableData.Data => Data;
|
||||
public BindableData(T data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using BITKit.Sensors;
|
||||
namespace BITKit.Entities
|
||||
{
|
||||
public class EntityAudioObject : EntityBehavior, IAudioObject
|
||||
{
|
||||
float volume;
|
||||
public override void OnStart()
|
||||
{
|
||||
UnityEntity.AddListener<AudioSO>(OnAuioSO);
|
||||
}
|
||||
public override void OnFixedUpdate(float deltaTime)
|
||||
{
|
||||
volume = Mathf.Lerp(volume, 0, deltaTime);
|
||||
}
|
||||
public float GetVolume()
|
||||
{
|
||||
return volume;
|
||||
}
|
||||
void OnAuioSO(AudioSO so)
|
||||
{
|
||||
if (so.distance > volume)
|
||||
volume = so.distance;
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,14 +10,12 @@ namespace BITKit.Entities
|
||||
{
|
||||
|
||||
public interface IDamageType { }
|
||||
|
||||
public interface IDamageService
|
||||
{
|
||||
public event Action<DamageMessage> OnEntityDamaged;
|
||||
public event Action<DamageMessage> OnEntityKilled;
|
||||
void Execute(DamageMessage damageMessage);
|
||||
}
|
||||
|
||||
public struct MeleeDamageMessage : IDamageType
|
||||
{
|
||||
public bool Bleeding;
|
||||
@@ -112,6 +110,7 @@ namespace BITKit.Entities
|
||||
}
|
||||
public interface IDamagable
|
||||
{
|
||||
string Tag { get; }
|
||||
IHealth Health { get; }
|
||||
IUnityEntity UnityEntity { get; }
|
||||
Rigidbody Rigidbody { get; }
|
||||
@@ -119,7 +118,7 @@ namespace BITKit.Entities
|
||||
}
|
||||
public class DamageService:MonoBehaviour,IDamageService
|
||||
{
|
||||
internal static IDamageService Singleton { get; set; }
|
||||
public static IDamageService Singleton { get;private set; }
|
||||
private readonly Queue<DamageMessage> Messages = new();
|
||||
|
||||
[SerializeReference,SubclassSelector] private INetClient netClient;
|
||||
|
@@ -105,7 +105,7 @@ namespace BITKit.Entities
|
||||
else
|
||||
{
|
||||
var damage = damageMessage.Damage;
|
||||
foreach (var x in OnDamageFactory.CastAsFunc().Reverse())
|
||||
foreach (var x in OnDamageFactory.CastAsFunc())
|
||||
{
|
||||
damage = x.Invoke(damageMessage, damage);
|
||||
if (damage <= 0)
|
||||
|
@@ -4,19 +4,55 @@ using UnityEngine;
|
||||
using BITKit;
|
||||
namespace BITKit.Entities
|
||||
{
|
||||
public class EntityHitbox : EntityBehavior,IDamagable
|
||||
public class EntityHitbox : MonoBehaviour,IDamagable
|
||||
{
|
||||
public IHealth Health => _health;
|
||||
public string Tag => tag is null ? base.tag : tag.Value;
|
||||
|
||||
public IHealth Health
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigure();
|
||||
return _health;
|
||||
}
|
||||
}
|
||||
|
||||
public IUnityEntity UnityEntity
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigure();
|
||||
return _unityEntity;
|
||||
}
|
||||
}
|
||||
public Rigidbody Rigidbody
|
||||
{
|
||||
get=>m_rigidbody;
|
||||
set=>m_rigidbody=value;
|
||||
}
|
||||
|
||||
IUnityEntity IDamagable.UnityEntity => UnityEntity;
|
||||
public Rigidbody Rigidbody => m_rigidbody;
|
||||
public void GiveDamage(DamageMessage message)
|
||||
{
|
||||
UnityEntity.Invoke(message);
|
||||
if (_unityEntity is not null)
|
||||
UnityEntity.Invoke(message);
|
||||
}
|
||||
|
||||
[SerializeField]private Rigidbody m_rigidbody;
|
||||
[SerializeReference, SubclassSelector] private new IReference tag;
|
||||
|
||||
[Inject]
|
||||
[Inject(true)]
|
||||
private IHealth _health;
|
||||
|
||||
private IUnityEntity _unityEntity;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
private void EnsureConfigure()
|
||||
{
|
||||
if (_initialized) return;
|
||||
_unityEntity = GetComponentInParent<IUnityEntity>(true);
|
||||
_unityEntity?.Inject(this);
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITFALL.Rig;
|
||||
using BITKit.Entities;
|
||||
using Cysharp.Threading.Tasks.Triggers;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
public class EntityHitboxBuilder : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Collider[] colliders;
|
||||
[SerializeField] private EntityHitbox[] hitboxes;
|
||||
|
||||
[SerializeReference,SubclassSelector] private IReference[] tagReferences;
|
||||
[BIT]
|
||||
private void Build()
|
||||
{
|
||||
foreach (var x in hitboxes)
|
||||
{
|
||||
DestroyImmediate(x.gameObject);
|
||||
}
|
||||
hitboxes = new EntityHitbox[colliders.Length];
|
||||
for (var i = 0; i < colliders.Length; i++)
|
||||
{
|
||||
var collider = colliders[i];
|
||||
var newGameObject = new GameObject($"Hitbox_{collider.gameObject.name}");
|
||||
|
||||
newGameObject.layer = gameObject.layer;
|
||||
|
||||
newGameObject.transform.SetParent(transform);
|
||||
var transform1 = collider.transform;
|
||||
newGameObject.transform.position = transform1.position;
|
||||
newGameObject.transform.rotation = transform1.rotation;
|
||||
var hitbox =hitboxes[i] = newGameObject.AddComponent<EntityHitbox>();
|
||||
var tag = newGameObject.AddComponent<Tag>();
|
||||
|
||||
var tickOverride = newGameObject.AddComponent<TickOverrideTransform>();
|
||||
|
||||
tickOverride.Source = newGameObject.transform;
|
||||
tickOverride.Target = collider.transform;
|
||||
|
||||
if (collider.TryGetComponent<Rigidbody>(out var newRigidbody))
|
||||
{
|
||||
hitbox.Rigidbody = newRigidbody;
|
||||
}
|
||||
|
||||
switch (collider)
|
||||
{
|
||||
case BoxCollider boxCollider:
|
||||
var box = newGameObject.AddComponent<BoxCollider>();
|
||||
box.size = boxCollider.size;
|
||||
box.center = boxCollider.center;
|
||||
break;
|
||||
case SphereCollider sphereCollider:
|
||||
var sphere = newGameObject.AddComponent<SphereCollider>();
|
||||
sphere.radius = sphereCollider.radius;
|
||||
sphere.center = sphereCollider.center;
|
||||
break;
|
||||
case CapsuleCollider capsuleCollider:
|
||||
var capsule = newGameObject.AddComponent<CapsuleCollider>();
|
||||
capsule.radius = capsuleCollider.radius;
|
||||
capsule.height = capsuleCollider.height;
|
||||
capsule.direction = capsuleCollider.direction;
|
||||
capsule.center = capsuleCollider.center;
|
||||
break;
|
||||
}
|
||||
|
||||
tag.SetTags(tagReferences);
|
||||
EditorUtility.SetDirty(hitbox);
|
||||
EditorUtility.SetDirty(tag);
|
||||
}
|
||||
EditorUtility.SetDirty(this);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,144 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BITKit.Entities.Physics;
|
||||
using UnityEngine;
|
||||
namespace BITKit.Entities
|
||||
{
|
||||
[CustomType(typeof(IEntityPhysics))]
|
||||
public class EntityPhysics : EntityBehavior,IEntityPhysics
|
||||
{
|
||||
[SerializeField] private Animator animator;
|
||||
[SerializeField] private Rigidbody[] rigidbodies;
|
||||
[SerializeField] private Collider[] ragdollColliders;
|
||||
[SerializeField] private Joint[] joints;
|
||||
[SerializeField] private new Rigidbody rigidbody;
|
||||
|
||||
[Inject(true)]
|
||||
private IHealth _health;
|
||||
|
||||
[Inject(true)] private IEntityOverride _override;
|
||||
|
||||
private readonly Dictionary<Joint,ConfigurableJointMotion> _jointXMotions=new();
|
||||
private readonly Dictionary<Joint,ConfigurableJointMotion> _jointYMotions=new();
|
||||
private readonly Dictionary<Joint,ConfigurableJointMotion> _jointZMotions=new();
|
||||
private readonly Dictionary<Joint,ConfigurableJointMotion> _jointAngularXMotions=new();
|
||||
private readonly Dictionary<Joint,ConfigurableJointMotion> _jointAngularYMotions=new();
|
||||
private readonly Dictionary<Joint,ConfigurableJointMotion> _jointAngularZMotions=new();
|
||||
|
||||
public override void OnAwake()
|
||||
{
|
||||
_health.OnSetAlive += OnSetAlive;
|
||||
foreach (var x in joints)
|
||||
{
|
||||
switch (x)
|
||||
{
|
||||
case ConfigurableJoint configurableJoint:
|
||||
_jointXMotions.Add(configurableJoint, configurableJoint.xMotion);
|
||||
_jointYMotions.Add(configurableJoint, configurableJoint.yMotion);
|
||||
_jointZMotions.Add(configurableJoint, configurableJoint.zMotion);
|
||||
_jointAngularXMotions.Add(configurableJoint, configurableJoint.angularXMotion);
|
||||
_jointAngularYMotions.Add(configurableJoint, configurableJoint.angularYMotion);
|
||||
_jointAngularZMotions.Add(configurableJoint, configurableJoint.angularZMotion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_override is not null)
|
||||
_override.OnOverride += OnOverride;
|
||||
}
|
||||
|
||||
private void OnOverride(bool obj)
|
||||
{
|
||||
EnsureConf();
|
||||
}
|
||||
|
||||
private void OnSetAlive(bool alive)
|
||||
{
|
||||
EnsureConf();
|
||||
}
|
||||
|
||||
private async void EnsureConf()
|
||||
{
|
||||
var allow = (_health, _override) switch
|
||||
{
|
||||
(not null,not null)=>_health.IsAlive || _override.IsOvering,
|
||||
(not null,null)=>_health.IsAlive,
|
||||
(null,not null)=>_override.IsOvering,
|
||||
_=>false,
|
||||
};
|
||||
if (animator)
|
||||
{
|
||||
animator.enabled = allow;
|
||||
}
|
||||
try
|
||||
{
|
||||
await Task.Delay(10, destroyCancellationToken);
|
||||
if (destroyCancellationToken.IsCancellationRequested) return;
|
||||
foreach (var x in rigidbodies)
|
||||
{
|
||||
//x.isKinematic = alive;
|
||||
x.gameObject.SetActive(!allow);
|
||||
}
|
||||
foreach (var x in ragdollColliders)
|
||||
{
|
||||
x.enabled = !allow;
|
||||
}
|
||||
OnSetPhysics?.Invoke(!allow);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
foreach (var joint in joints)
|
||||
{
|
||||
switch (joint)
|
||||
{
|
||||
case ConfigurableJoint configurableJoint:
|
||||
configurableJoint.xMotion = allow ? _jointXMotions[joint] : ConfigurableJointMotion.Free;
|
||||
configurableJoint.yMotion = allow ? _jointYMotions[joint] : ConfigurableJointMotion.Free;
|
||||
configurableJoint.zMotion = allow ? _jointZMotions[joint] : ConfigurableJointMotion.Free;
|
||||
configurableJoint.angularXMotion =
|
||||
allow ? _jointAngularXMotions[joint] : ConfigurableJointMotion.Free;
|
||||
configurableJoint.angularYMotion =
|
||||
allow ? _jointAngularYMotions[joint] : ConfigurableJointMotion.Free;
|
||||
configurableJoint.angularZMotion =
|
||||
allow ? _jointAngularZMotions[joint] : ConfigurableJointMotion.Free;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public Vector3 Center => rigidbody.worldCenterOfMass;
|
||||
public bool IsPhysics { get; private set; }
|
||||
public Vector3 Velocity
|
||||
{
|
||||
get=>rigidbody.velocity;
|
||||
set
|
||||
{
|
||||
foreach (var x in rigidbodies)
|
||||
{
|
||||
x.velocity = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public Action<bool> OnSetPhysics { get; set; }
|
||||
public void AddForce(Vector3 force, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
foreach (var x in rigidbodies)
|
||||
{
|
||||
x.AddForce(force, mode);
|
||||
}
|
||||
}
|
||||
public void AddTorque(Vector3 torque, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
foreach (var x in rigidbodies)
|
||||
{
|
||||
x.AddTorque(torque, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -36,7 +36,7 @@ namespace BITKit.Entities
|
||||
foreach (var fieldInfo in obj
|
||||
.GetType()
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(fieldInfo=>fieldInfo.GetCustomAttribute<InjectAttribute>() is not null))
|
||||
.Where(fieldInfo=>fieldInfo.GetCustomAttribute<InjectAttribute>(true) is not null))
|
||||
{
|
||||
var type = fieldInfo.FieldType;
|
||||
var attribute = fieldInfo.GetCustomAttribute<InjectAttribute>();
|
||||
|
@@ -7,7 +7,8 @@
|
||||
"GUID:d525ad6bd40672747bde77962f1c401e",
|
||||
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
|
||||
"GUID:9354affc93e0f3e4a904785e7d4c0f59",
|
||||
"GUID:c56f2ae4d67b9b947a600c84225206a2"
|
||||
"GUID:c56f2ae4d67b9b947a600c84225206a2",
|
||||
"GUID:296866320aab85a42a0403bf684bac59"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "BITKit.Entities.Physics.Runtime",
|
||||
"name": "BITKit.Entities.World.Runtime",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||
"GUID:709caf8d7fb6ef24bbba0ab9962a3ad0",
|
||||
"GUID:a3de65b07192e7d49bad7b4032d681de",
|
||||
"GUID:7efac18f239530141802fb139776f333"
|
||||
"GUID:1193c2664d97cc049a6e4c486c6bce71",
|
||||
"GUID:bdb069e155d2f944cb1bf28602b6d4c1"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
24
Assets/BITKit/Unity/Scripts/Entity/World/EntityLOD.cs
Normal file
24
Assets/BITKit/Unity/Scripts/Entity/World/EntityLOD.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITKit.Entities;
|
||||
using Quadtree;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
public class EntityLOD : EntityBehavior,IWorldChunkObject
|
||||
{
|
||||
public Bounds GetBounds()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Node<IWorldChunkObject> ParentNode { get; set; }
|
||||
public void QuadTree_Root_Initialized(IQuadtreeRoot<IWorldChunkObject, Node<IWorldChunkObject>> root)
|
||||
{
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
public int Lod { get; set; }
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "I18N",
|
||||
"name": "Plugins.I18N",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
@@ -16,6 +17,10 @@ namespace BITKit
|
||||
[SerializeField] private bool disabledPosition;
|
||||
|
||||
[SerializeField] private Vector3 positionWeight=Vector3.one;
|
||||
|
||||
[SerializeField] private bool debug;
|
||||
|
||||
[SerializeField,ReadOnly(HideLabel = true)] private string debugInfo;
|
||||
|
||||
private Vector3 currentPosition;
|
||||
private Quaternion currentRotation=Quaternion.identity;
|
||||
@@ -25,6 +30,8 @@ namespace BITKit
|
||||
|
||||
private Transform _transform;
|
||||
|
||||
private readonly StringBuilder reportBuilder = new();
|
||||
|
||||
public Vector3 Position
|
||||
{
|
||||
get => _transform.localPosition;
|
||||
@@ -36,9 +43,13 @@ namespace BITKit
|
||||
set => _transform.localRotation = value;
|
||||
}
|
||||
|
||||
public void AddPosition(Vector3 value)
|
||||
public void AddPosition(Vector3 value,string info=null)
|
||||
{
|
||||
currentPosition += value;
|
||||
if (debug)
|
||||
{
|
||||
reportBuilder.AppendLine($"{info}:{value}");
|
||||
}
|
||||
}
|
||||
|
||||
// public void AddEuler(Vector3 value)
|
||||
@@ -46,11 +57,16 @@ namespace BITKit
|
||||
// //currentEuler += value;
|
||||
// currentRotation *= Quaternion.Euler(value);
|
||||
// }
|
||||
public void AddRotation(Quaternion value)
|
||||
public void AddRotation(Quaternion value, string info = null)
|
||||
{
|
||||
currentRotation *= value;
|
||||
if (debug)
|
||||
{
|
||||
reportBuilder.AppendLine($"{info}:{value}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetGlobalPosition(Vector3 value)
|
||||
{
|
||||
globalPosition = value;
|
||||
@@ -103,6 +119,12 @@ namespace BITKit
|
||||
currentRotation = Quaternion.identity;
|
||||
|
||||
currentPosition = Vector3.zero;
|
||||
|
||||
if (debug)
|
||||
{
|
||||
debugInfo = reportBuilder.ToString();
|
||||
reportBuilder.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,11 +16,14 @@ namespace BITKit
|
||||
[SerializeField] private Vector3 positionWeight = Vector3.one;
|
||||
|
||||
[SerializeReference,SubclassSelector] private IReference autoReference;
|
||||
[SerializeReference,SubclassSelector] private IReference nameReference;
|
||||
//[SerializeField] private Vector3 rotationAdditive = Vector3.one;
|
||||
private Transform Transform;
|
||||
|
||||
private string nameCache;
|
||||
private void Start()
|
||||
{
|
||||
nameCache = nameReference is not null?nameReference.Value:gameObject.name;
|
||||
Transform = transform;
|
||||
var components =
|
||||
GetComponentsInParent<LocationAdditive>(true)
|
||||
@@ -39,7 +42,6 @@ namespace BITKit
|
||||
reportBuilder.AppendLine(x.autoReference is not null ? $"{x.autoReference.Value}@{x.transform.name}" : x.name);
|
||||
}
|
||||
BIT4Log.Log<LocationAdditiveElement>($"找不到对应的LocationAdditive:{autoReference.Value} \n{reportBuilder}");
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,11 +60,12 @@ namespace BITKit
|
||||
|
||||
private void Tick()
|
||||
{
|
||||
if (!locationAdditive) return;
|
||||
var localPosition = Transform.localPosition;
|
||||
|
||||
locationAdditive.AddPosition(Vector3.Scale(localPosition, positionWeight));
|
||||
locationAdditive.AddPosition(Vector3.Scale(localPosition, positionWeight), nameReference?.Value);
|
||||
//locationAdditive.AddRotation(Transform.localRotation * Quaternion.Euler(rotationAdditive));
|
||||
locationAdditive.AddRotation(Transform.localRotation);
|
||||
locationAdditive.AddRotation(Transform.localRotation, nameCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BITKit.UX;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
@@ -18,7 +19,10 @@ namespace BITKit.Mod
|
||||
[SerializeField] private bool loadLocalPackageOnStart;
|
||||
private async void Start()
|
||||
{
|
||||
|
||||
DI.TryGet<IUXWaiting>(out var waiting);
|
||||
var handle = waiting?.Get();
|
||||
handle?.SetMessage("正在初始化Mod服务");
|
||||
|
||||
if (Application.isEditor is false)
|
||||
{
|
||||
BIT4Log.Log<UnityModService>($"UnityPlayer所在位置:{Application.dataPath}");
|
||||
@@ -71,6 +75,8 @@ namespace BITKit.Mod
|
||||
{
|
||||
ModService.OnPackageLoad-=OnPackageLoad;
|
||||
});
|
||||
|
||||
waiting?.Release(handle);
|
||||
}
|
||||
|
||||
private void OnPackageLoad(ModPackage obj)
|
||||
@@ -106,8 +112,9 @@ namespace BITKit.Mod
|
||||
{
|
||||
dll = Path.Combine(folder,"Data", "MonoBleedingEdge", "lib","mono","unityjit-win32","Facades",dllName);
|
||||
}
|
||||
|
||||
#else
|
||||
var dll = System.IO.Path.Combine(Environment.CurrentDirectory,$"{Application.productName}_Data", "Managed", dllName);
|
||||
dll = System.IO.Path.Combine(Environment.CurrentDirectory,$"{Application.productName}_Data", "Managed", dllName);
|
||||
#endif
|
||||
return File.Exists(dll);
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"name": "BITKit.WorkdChunk",
|
||||
"name": "BITKit.MotionMatching",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:a9eec99827e569e45bfe3e5ea7494591",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b"
|
||||
"GUID:d525ad6bd40672747bde77962f1c401e",
|
||||
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
|
||||
"GUID:e34a5702dd353724aa315fb8011f08c3",
|
||||
"GUID:296866320aab85a42a0403bf684bac59"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.Animations
|
||||
{
|
||||
public interface IMotionMatchingObject
|
||||
{
|
||||
|
||||
}
|
||||
public interface IMotionMatchingService : IObjectMatcher<string,IMotionMatchingObject>
|
||||
{
|
||||
|
||||
}
|
||||
public interface IMotionMatchingClip
|
||||
{
|
||||
public AnimationClip Clip { get; }
|
||||
}
|
||||
public interface IMotionMatchingDualClip
|
||||
{
|
||||
public AnimationClip Clip1 { get; }
|
||||
public AnimationClip Clip2 { get; }
|
||||
}
|
||||
|
||||
public interface IMotionMatchingSequence
|
||||
{
|
||||
public AnimationClip[] Sequence { get; }
|
||||
}
|
||||
[Serializable]
|
||||
public struct MotionMatchingClip:IMotionMatchingObject,IMotionMatchingClip
|
||||
{
|
||||
[SerializeField] private AnimationClip clip;
|
||||
public AnimationClip Clip => clip;
|
||||
}
|
||||
[Serializable]
|
||||
public struct MotionMatchingSequence:IMotionMatchingObject,IMotionMatchingSequence
|
||||
{
|
||||
[SerializeField] private AnimationClip[] sequence;
|
||||
public AnimationClip[] Sequence => sequence;
|
||||
}
|
||||
[Serializable]
|
||||
public struct MotionMatchingDualClip:IMotionMatchingObject,IMotionMatchingDualClip
|
||||
{
|
||||
[SerializeField] private AnimationClip clip1;
|
||||
[SerializeField] private AnimationClip clip2;
|
||||
public AnimationClip Clip1 => clip1;
|
||||
public AnimationClip Clip2 => clip2;
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.Animations
|
||||
{
|
||||
public class ScriptableMotionMatchingObject : ScriptableObject,IObjectElement<string,IMotionMatchingObject>
|
||||
{
|
||||
[SerializeReference,SubclassSelector] private IReference[] tags;
|
||||
[SerializeReference,SubclassSelector] private IMotionMatchingObject value;
|
||||
public bool IsMatch(string[] searchKey)=>MathE.Contains(tags.Cast(),searchKey);
|
||||
public IMotionMatchingObject GetValue() => value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using BITKit.Mod;
|
||||
using UnityEngine;
|
||||
using YooAsset;
|
||||
|
||||
namespace BITKit.Animations
|
||||
{
|
||||
[Serializable]
|
||||
public struct ScriptableMotionMatchingSingleton:IMotionMatchingService
|
||||
{
|
||||
public bool TryMatch(out IMotionMatchingObject value, string[] key)=>ScriptableMotionMatchingService.Singleton.TryMatch(out value,key);
|
||||
}
|
||||
public class ScriptableMotionMatchingService : MonoBehaviour,IMotionMatchingService
|
||||
{
|
||||
internal static ScriptableMotionMatchingService Singleton { get; private set; }
|
||||
internal static readonly ConcurrentDictionary<int, IMotionMatchingObject> Cache = new();
|
||||
|
||||
private ObjectMatcher<string,IMotionMatchingObject> _objects;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Singleton = this;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
ModService.OnReloaded += Rebuild;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ModService.OnReloaded -= Rebuild;
|
||||
}
|
||||
|
||||
public bool TryMatch(out IMotionMatchingObject value, string[] key)
|
||||
{
|
||||
value = Cache.GetOrAdd(MathE.GetHash(key),Add);
|
||||
return value is not null;
|
||||
|
||||
IMotionMatchingObject Add(int hash)
|
||||
{
|
||||
if (_objects.TryMatch(out IMotionMatchingObject x, key) is false)
|
||||
{
|
||||
BIT4Log.Log<ScriptableMotionMatchingService>($"找不到对应的MotionMatchingObject:{string.Join(",", key)}");
|
||||
return null;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
private void Rebuild()
|
||||
{
|
||||
Cache.Clear();
|
||||
var list = new List<ScriptableMotionMatchingObject>();
|
||||
var tags = new []{nameof(IMotionMatchingObject)};
|
||||
foreach (var x in YooAssets.GetAssetInfos(tags))
|
||||
{
|
||||
var assetHandle =YooAssets.LoadAssetSync<ScriptableMotionMatchingObject>(x.AssetPath);
|
||||
assetHandle.WaitForAsyncComplete();
|
||||
list.Add(assetHandle.AssetObject.As<ScriptableMotionMatchingObject>());
|
||||
}
|
||||
_objects = new ObjectMatcher<string, IMotionMatchingObject>
|
||||
{
|
||||
list = list.ToArray(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -16,7 +16,9 @@
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"defineConstraints": [
|
||||
"UNITY_EDITOR"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
namespace BITKit
|
||||
{
|
||||
public interface IClosePoint
|
||||
{
|
||||
Collider Collider { get; }
|
||||
bool TryGetClosePoint(out Vector3 vector3);
|
||||
}
|
||||
[System.Serializable]
|
||||
@@ -14,12 +17,15 @@ namespace BITKit
|
||||
public Transform root;
|
||||
public LayerMask layerMask;
|
||||
public float distance;
|
||||
public Collider Collider { get; set; }
|
||||
|
||||
public bool TryGetClosePoint(out Vector3 vector3)
|
||||
{
|
||||
vector3 = default;
|
||||
if (UnityEngine.Physics.Raycast(root.position, root.forward, out var raycastHit, distance, layerMask))
|
||||
{
|
||||
var collider = raycastHit.collider;
|
||||
if (collider.isTrigger) return false;
|
||||
switch (collider)
|
||||
{
|
||||
case MeshCollider meshCollider:
|
||||
@@ -42,6 +48,8 @@ namespace BITKit
|
||||
if(hit!= collider)
|
||||
return false;
|
||||
}
|
||||
|
||||
Collider = collider;
|
||||
return true;
|
||||
//return vector3.y >= collider.bounds.center.y + collider.bounds.extents.y;
|
||||
//return true;
|
||||
@@ -65,14 +73,31 @@ namespace BITKit
|
||||
|
||||
public Vector3 StartPosition;
|
||||
public Vector3 EndPosition;
|
||||
|
||||
public Collider Collider { get; set; }
|
||||
private Rigidbody rigidbody;
|
||||
|
||||
private bool isInitialized;
|
||||
|
||||
public bool TryGetClosePoint(out Vector3 vector3)
|
||||
{
|
||||
if (isInitialized is false)
|
||||
{
|
||||
rigidbody = groundReference.GetComponent<Rigidbody>();
|
||||
isInitialized = true;
|
||||
}
|
||||
if (rigidbody)
|
||||
{
|
||||
vector3 = default;
|
||||
if (rigidbody.velocity.GetLength() < 0.1f) return false;
|
||||
}
|
||||
|
||||
var reportBuilder = new System.Text.StringBuilder();
|
||||
|
||||
var forward = root.forward;
|
||||
var startPosition = groundReference.position;
|
||||
startPosition.y = root.position.y;
|
||||
var sourceStartPosition = groundReference.position;
|
||||
sourceStartPosition.y = root.position.y;
|
||||
var startPosition = sourceStartPosition;
|
||||
|
||||
var collider = UnityEngine.Physics.OverlapSphere(startPosition, radius, layerMask);
|
||||
|
||||
@@ -80,17 +105,23 @@ namespace BITKit
|
||||
|
||||
foreach (var hit in collider)
|
||||
{
|
||||
reportBuilder.AppendLine();
|
||||
|
||||
var top = hit.bounds.center + hit.bounds.extents;
|
||||
|
||||
if(top.y<sourceStartPosition.y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
reportBuilder.AppendLine();
|
||||
reportBuilder.AppendLine($">{hit.name}");
|
||||
|
||||
if(top.y>groundReference.transform.position.y+maxHeight)
|
||||
{
|
||||
reportBuilder.AppendLine("高度超出可翻越高度");
|
||||
continue;
|
||||
}
|
||||
|
||||
var start = startPosition+forward*distance;
|
||||
var start = sourceStartPosition+forward*8;
|
||||
//start.y = hit.bounds.center.y;
|
||||
|
||||
var ray = new Ray(start, -forward);
|
||||
@@ -105,16 +136,35 @@ namespace BITKit
|
||||
|
||||
EndPosition = colliderHit.point + colliderHit.normal*0.4f;
|
||||
EndPosition.y = top.y;
|
||||
|
||||
Debug.DrawLine(ray.origin, colliderHit.point, Color.green, 8);
|
||||
var lineDistance = Vector3.Distance(start, colliderHit.point);
|
||||
if(lineDistance > maxVaultDistance)
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
reportBuilder.AppendLine("长度超出可翻越距离");
|
||||
|
||||
BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
|
||||
foreach (var x in UnityEngine
|
||||
.Physics
|
||||
.OverlapSphere(EndPosition, 0.1f, layerMask))
|
||||
{
|
||||
if(Equals(x, hit))
|
||||
continue;
|
||||
throw new OperationCanceledException($"终点有其他碰撞体{x.name}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
reportBuilder.AppendLine(e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(UnityEngine.Physics.Raycast(EndPosition,Vector3.down,out _,1.6f,layerMask) is false)
|
||||
{
|
||||
Debug.DrawRay(EndPosition, Vector3.down*1.6f, Color.red, 8);
|
||||
reportBuilder.AppendLine("未检测到地面,跳过");
|
||||
continue;
|
||||
//Debug.DrawRay(EndPosition, Vector3.down, Color.red, 8);
|
||||
}
|
||||
|
||||
|
||||
|
||||
var fixdPosition = colliderHit.point;
|
||||
fixdPosition.y=hit.bounds.center.y;
|
||||
@@ -126,7 +176,7 @@ namespace BITKit
|
||||
{
|
||||
reportBuilder.AppendLine($"检测到了障碍物{downHit.collider.name}");
|
||||
|
||||
BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
|
||||
//BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -144,6 +194,19 @@ namespace BITKit
|
||||
StartPosition = colliderHit.point;
|
||||
StartPosition.y = top.y;
|
||||
StartPosition += colliderHit.normal * 0.4f;
|
||||
|
||||
var closeStart = hit.ClosestPoint(StartPosition);
|
||||
var closeEnd = hit.ClosestPoint(EndPosition);
|
||||
var lineDistance = Vector3.Distance(closeStart, closeEnd);
|
||||
if(lineDistance > maxVaultDistance)
|
||||
{
|
||||
reportBuilder.AppendLine($"长度{lineDistance}超出可翻越距离{maxVaultDistance}");
|
||||
|
||||
Debug.DrawLine(closeStart,closeEnd, Color.yellow, 4);
|
||||
|
||||
//BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
vector3 = colliderHit.point;
|
||||
|
||||
|
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||
using BITKit.Core.Tuple;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.Physics
|
||||
namespace BITKit
|
||||
{
|
||||
[Serializable]
|
||||
public class JointConfigure
|
||||
|
@@ -1,10 +1,9 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITKit.Events;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.Physics
|
||||
namespace BITKit
|
||||
{
|
||||
public class Prop_Physics : MonoBehaviour
|
||||
{
|
||||
|
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "BITKit.Quadtree"
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using Quadtree.Items;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Spatial partitioning/Quadtree/Root node (for GameObjects)")]
|
||||
public class GameObjectQuadtreeRoot : QuadtreeMonoRoot<GameObjectItem, Node<GameObjectItem>>
|
||||
{
|
||||
}
|
||||
}
|
118
Assets/BITKit/Unity/Scripts/Quadtree/INode.cs
Normal file
118
Assets/BITKit/Unity/Scripts/Quadtree/INode.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
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);
|
||||
}
|
||||
}
|
70
Assets/BITKit/Unity/Scripts/Quadtree/IQuadtreeRoot.cs
Normal file
70
Assets/BITKit/Unity/Scripts/Quadtree/IQuadtreeRoot.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
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();
|
||||
}
|
||||
}
|
22
Assets/BITKit/Unity/Scripts/Quadtree/IntraLocation.cs
Normal file
22
Assets/BITKit/Unity/Scripts/Quadtree/IntraLocation.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
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
|
||||
};
|
||||
}
|
10
Assets/BITKit/Unity/Scripts/Quadtree/Items/GameObjectItem.cs
Normal file
10
Assets/BITKit/Unity/Scripts/Quadtree/Items/GameObjectItem.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree.Items
|
||||
{
|
||||
public abstract class GameObjectItem : GameObjectItemBase<GameObjectItem, Node<GameObjectItem>>
|
||||
{
|
||||
protected override GameObjectItem This() => this;
|
||||
}
|
||||
}
|
152
Assets/BITKit/Unity/Scripts/Quadtree/Items/GameObjectItemBase.cs
Normal file
152
Assets/BITKit/Unity/Scripts/Quadtree/Items/GameObjectItemBase.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
29
Assets/BITKit/Unity/Scripts/Quadtree/Items/IItem.cs
Normal file
29
Assets/BITKit/Unity/Scripts/Quadtree/Items/IItem.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
||||
}
|
46
Assets/BITKit/Unity/Scripts/Quadtree/Items/RendererItem.cs
Normal file
46
Assets/BITKit/Unity/Scripts/Quadtree/Items/RendererItem.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
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;
|
||||
}
|
||||
}
|
12
Assets/BITKit/Unity/Scripts/Quadtree/Node.cs
Normal file
12
Assets/BITKit/Unity/Scripts/Quadtree/Node.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Quadtree.Items;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Single quadtree node.
|
||||
/// </summary>
|
||||
public class Node<TItem> : NodeBase<TItem, Node<TItem>>
|
||||
where TItem : IItem<TItem, Node<TItem>>
|
||||
{
|
||||
}
|
||||
}
|
416
Assets/BITKit/Unity/Scripts/Quadtree/NodeBase.cs
Normal file
416
Assets/BITKit/Unity/Scripts/Quadtree/NodeBase.cs
Normal file
@@ -0,0 +1,416 @@
|
||||
using Quadtree.Items;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
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 (displayNumberOfItems)
|
||||
Handles.Label(Bounds.center, _items.Count.ToString());
|
||||
|
||||
Gizmos.DrawWireCube(Bounds.center, Bounds.size);
|
||||
foreach (var subNode in SubNodes)
|
||||
subNode.DrawBounds(displayNumberOfItems);
|
||||
}
|
||||
}
|
||||
}
|
102
Assets/BITKit/Unity/Scripts/Quadtree/QuadtreeMonoRoot.cs
Normal file
102
Assets/BITKit/Unity/Scripts/Quadtree/QuadtreeMonoRoot.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
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();
|
||||
}
|
||||
}
|
148
Assets/BITKit/Unity/Scripts/Quadtree/QuadtreeRoot.cs
Normal file
148
Assets/BITKit/Unity/Scripts/Quadtree/QuadtreeRoot.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
141
Assets/BITKit/Unity/Scripts/Rig/TickOverrideTranformService.cs
Normal file
141
Assets/BITKit/Unity/Scripts/Rig/TickOverrideTranformService.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BITKit;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Jobs;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace BITFALL.Rig{
|
||||
public class TickOverrideTranformService : MonoBehaviour
|
||||
{
|
||||
//[BurstCompile]
|
||||
// public struct CopyTransformJob : IJobParallelForTransform
|
||||
// {
|
||||
// [Unity.Collections.ReadOnly]
|
||||
// public NativeArray<float3> positions;
|
||||
// [Unity.Collections.ReadOnly]
|
||||
// public NativeArray<quaternion> rotations;
|
||||
//
|
||||
//
|
||||
// // The code actually running on the job
|
||||
// public void Execute(int index, TransformAccess transform)
|
||||
// {
|
||||
// transform.SetPositionAndRotation(positions[index],rotations[index]);
|
||||
// }
|
||||
// }
|
||||
public static void Register(int id,TickOverrideTransform tickOverrideTransform)
|
||||
{
|
||||
Dictionary.Add(id,tickOverrideTransform);
|
||||
IsDirty = true;
|
||||
}
|
||||
public static void UnRegister(int id)
|
||||
{
|
||||
Dictionary.Remove(id);
|
||||
IsDirty = true;
|
||||
}
|
||||
private static readonly Dictionary<int, TickOverrideTransform> Dictionary = new();
|
||||
private static bool IsDirty;
|
||||
|
||||
private static Transform[] Sources;
|
||||
private static Transform[] Targets;
|
||||
|
||||
[SerializeReference, SubclassSelector] private ITicker ticker;
|
||||
|
||||
// private TransformAccessArray m_AccessArray;
|
||||
// private NativeArray<quaternion> _rotations;
|
||||
// private NativeArray<float3> _positions;
|
||||
// private JobHandle _jobHandle;
|
||||
// private InitializationState _initializationState;
|
||||
private void OnEnable()
|
||||
{
|
||||
ticker.Add(Tick);
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
ticker.Remove(Tick);
|
||||
}
|
||||
|
||||
// private void OnDestroy()
|
||||
// {
|
||||
// if (_initializationState is not InitializationState.Initializing) return;
|
||||
// _jobHandle.Complete();
|
||||
// _rotations.Dispose();
|
||||
// _positions.Dispose();
|
||||
// }
|
||||
|
||||
private void Tick(float obj)
|
||||
{
|
||||
// switch (_initializationState)
|
||||
// {
|
||||
// case InitializationState.Initializing when _jobHandle.IsCompleted:
|
||||
// _jobHandle.Complete();
|
||||
// _rotations.Dispose();
|
||||
// _positions.Dispose();
|
||||
// _initializationState = InitializationState.Initialized;
|
||||
// break;
|
||||
// case InitializationState.None:
|
||||
// break;
|
||||
// default:
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (IsDirty)
|
||||
{
|
||||
var newLength = Dictionary.Count;
|
||||
Sources = new Transform[newLength];
|
||||
Targets = new Transform[newLength];
|
||||
|
||||
//_positions = new NativeArray<float3>(newLength, Allocator.Persistent);
|
||||
//_rotations = new NativeArray<quaternion>(newLength, Allocator.Persistent);
|
||||
|
||||
var index = 0;
|
||||
foreach (var x in Dictionary.Values)
|
||||
{
|
||||
Sources[index] = x.Source;
|
||||
Targets[index] = x.Target;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
var length =Sources?.Length ?? 0;
|
||||
|
||||
if(length is 0) return;
|
||||
|
||||
// m_AccessArray = new TransformAccessArray(length);
|
||||
// m_AccessArray.SetTransforms(Sources);
|
||||
|
||||
|
||||
for (var j = 0; j < length; j++)
|
||||
{
|
||||
Sources[j].SetPositionAndRotation(Targets[j].position,Targets[j].rotation);
|
||||
}
|
||||
// foreach (var x in Targets)
|
||||
// {
|
||||
// _positions[i] = x.position;
|
||||
// _rotations[i] = x.rotation;
|
||||
// i++;
|
||||
// }
|
||||
//
|
||||
// foreach (var x in Sources)
|
||||
// {
|
||||
// x.position = _positions[i];
|
||||
// x.rotation = _rotations[i];
|
||||
// }
|
||||
// var _job = new CopyTransformJob()
|
||||
// {
|
||||
// positions = _positions,
|
||||
// rotations = _rotations
|
||||
// };
|
||||
// _jobHandle = _job.Schedule(m_AccessArray);
|
||||
//
|
||||
// _initializationState = InitializationState.Initializing;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
34
Assets/BITKit/Unity/Scripts/Rig/TickOverrideTransform.cs
Normal file
34
Assets/BITKit/Unity/Scripts/Rig/TickOverrideTransform.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITFALL.Rig
|
||||
{
|
||||
public class TickOverrideTransform : MonoBehaviour
|
||||
{
|
||||
public Transform Source;
|
||||
public Transform Target;
|
||||
|
||||
private int Id;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Id = GetInstanceID();
|
||||
TickOverrideTranformService.Register(Id,this);
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
TickOverrideTranformService.UnRegister(Id);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if(!Source)Source = transform;
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
@@ -41,6 +41,15 @@ namespace BITKit.SceneManagement
|
||||
/// 场景加载完成的回调
|
||||
/// </summary>
|
||||
event Action<string> OnSceneLoaded;
|
||||
/// <summary>
|
||||
/// 注册加载任务
|
||||
/// </summary>
|
||||
void RegisterLoadTaskAsync(Func<UniTask> task);
|
||||
/// <summary>
|
||||
/// 注销加载任务
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
void UnRegisterLoadTaskAsync(Func<UniTask> task);
|
||||
/// <summary>
|
||||
/// 当开始卸载场景时
|
||||
/// </summary>
|
||||
@@ -89,6 +98,16 @@ namespace BITKit.SceneManagement
|
||||
remove => _sceneServiceImplementation.OnSceneLoaded -= value;
|
||||
}
|
||||
|
||||
|
||||
public void RegisterLoadTaskAsync(Func<UniTask> task)
|
||||
{
|
||||
_sceneServiceImplementation.RegisterLoadTaskAsync(task);
|
||||
}
|
||||
public void UnRegisterLoadTaskAsync(Func<UniTask> task)
|
||||
{
|
||||
_sceneServiceImplementation.UnRegisterLoadTaskAsync(task);
|
||||
}
|
||||
|
||||
public event Action<string> OnUnloadScene
|
||||
{
|
||||
add => _sceneServiceImplementation1.OnUnloadScene += value;
|
||||
|
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
@@ -19,6 +21,8 @@ namespace BITKit
|
||||
private Button buildButton;
|
||||
private Label _titleLabel;
|
||||
private VisualElement _container;
|
||||
|
||||
private MeshRenderer[] _renderers=Array.Empty<MeshRenderer>();
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
@@ -38,22 +42,52 @@ namespace BITKit
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
var active = UnityEditor.Selection.activeGameObject;
|
||||
var actives = UnityEditor.Selection.gameObjects;
|
||||
|
||||
_container.Clear();
|
||||
|
||||
_titleLabel.text = active ? active.name : "未选择物体";
|
||||
if (!active) return;
|
||||
foreach (var x in active.GetComponentsInChildren<MeshRenderer>(true))
|
||||
|
||||
_renderers = actives.SelectMany(x => x.GetComponentsInChildren<MeshRenderer>()).ToArray();
|
||||
|
||||
var materials = _renderers.SelectMany(x => x.sharedMaterials).Distinct().ToArray();
|
||||
|
||||
_titleLabel.text = actives is not {Length:0} ?$"选择了{actives.Length }个物体,{materials.Length}个材质" : "未选择物体";
|
||||
|
||||
foreach (var material in materials)
|
||||
{
|
||||
foreach (var material in x.sharedMaterials)
|
||||
var filed = _container.Create<ObjectField>();
|
||||
filed.label = material.name;
|
||||
filed.objectType = typeof(Material);
|
||||
filed.allowSceneObjects = false;
|
||||
filed.value = material;
|
||||
filed.RegisterValueChangedCallback(OnChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged(ChangeEvent<Object> evt)
|
||||
{
|
||||
if(evt.newValue is not Material value) return;
|
||||
var list = new List<Object>();
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
var sharedMaterials = renderer.sharedMaterials;
|
||||
var isDirty = false;
|
||||
for (var i = 0; i < sharedMaterials.Length; i++)
|
||||
{
|
||||
var filed = rootVisualElement.Create<ObjectField>();
|
||||
filed.label = material.name;
|
||||
filed.objectType = typeof(Material);
|
||||
filed.value = material;
|
||||
var current = sharedMaterials[i];
|
||||
if(current != evt.previousValue) continue;
|
||||
sharedMaterials[i] = value;
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
|
||||
if (!isDirty) continue;
|
||||
renderer.sharedMaterials = sharedMaterials;
|
||||
list.Add(renderer);
|
||||
EditorUtility.SetDirty(renderer);
|
||||
}
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
OnSelectionChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -58,6 +58,16 @@ namespace BITKit.SceneManagement
|
||||
remove => SceneService.OnSceneLoaded -= value;
|
||||
}
|
||||
|
||||
public void RegisterLoadTaskAsync(Func<UniTask> task)
|
||||
{
|
||||
_sceneServiceImplementation.RegisterLoadTaskAsync(task);
|
||||
}
|
||||
|
||||
public void UnRegisterLoadTaskAsync(Func<UniTask> task)
|
||||
{
|
||||
_sceneServiceImplementation.UnRegisterLoadTaskAsync(task);
|
||||
}
|
||||
|
||||
public event Action<string> OnUnloadScene
|
||||
{
|
||||
add => _sceneServiceImplementation.OnUnloadScene += value;
|
||||
@@ -198,9 +208,15 @@ namespace BITKit.SceneManagement
|
||||
OnSceneLoadProgress?.Invoke(sceneName, progress);
|
||||
}
|
||||
LoadedObjects.Add(sceneName, handle.SceneObject);
|
||||
|
||||
OnSceneLoadProgress?.Invoke(sceneName, 1);
|
||||
await Task.Delay(384, cancellationToken);
|
||||
|
||||
foreach (var x in _onSceneLoadedAsyncList.ToArray())
|
||||
{
|
||||
await x.Invoke();
|
||||
if (destroyCancellationToken.IsCancellationRequested) return;
|
||||
}
|
||||
|
||||
OnSceneLoaded?.Invoke(sceneName);
|
||||
stopwatchWatcher.Stop();
|
||||
// if (activateOnLoad is false)
|
||||
@@ -252,6 +268,16 @@ namespace BITKit.SceneManagement
|
||||
remove => OnSceneLoaded -= value;
|
||||
}
|
||||
|
||||
private readonly List<Func<UniTask>> _onSceneLoadedAsyncList=new();
|
||||
public void RegisterLoadTaskAsync(Func<UniTask> task)
|
||||
{
|
||||
_onSceneLoadedAsyncList.Add(task);
|
||||
}
|
||||
public void UnRegisterLoadTaskAsync(Func<UniTask> task)
|
||||
{
|
||||
_onSceneLoadedAsyncList.Remove(task);
|
||||
}
|
||||
|
||||
public event Action<string> OnUnloadScene;
|
||||
public event Action<string> OnSceneUnloaded;
|
||||
}
|
||||
|
@@ -1,49 +1,49 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using System.Linq;
|
||||
using UnityEditor.Search;
|
||||
|
||||
namespace BITKit.Sensors
|
||||
{
|
||||
public interface IAudioObject
|
||||
{
|
||||
float GetVolume();
|
||||
}
|
||||
public class AudioSensor : Sensor
|
||||
public class AudioSensor : MonoBehaviour,ISensor
|
||||
{
|
||||
[Header(Constant.Header.Settings)]
|
||||
public float radius;
|
||||
[Header(Constant.Header.InternalVariables)]
|
||||
IAudioObject currentAudioObject;
|
||||
Collider currentCollider;
|
||||
Collider[] colliders = new Collider[32];
|
||||
public override IEnumerable<Transform> Get() => detected;
|
||||
public override UniTask Execute()
|
||||
[SerializeField] private bool autoUpdate;
|
||||
[SerializeField]private float radius;
|
||||
private readonly CacheList<Transform> cache = new();
|
||||
private void OnEnable()
|
||||
{
|
||||
var cacheList = ListPool<Transform>.Get();
|
||||
for (int i = 0; i < Physics.OverlapSphereNonAlloc(transform.position, radius, colliders, detectLayer); i++)
|
||||
Id = GetInstanceID();
|
||||
SensorQueue.Register(Id,this);
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
SensorQueue.UnRegister(Id);
|
||||
}
|
||||
public UniTask Execute(float delta)
|
||||
{
|
||||
var position = transform.position;
|
||||
cache.Clear();
|
||||
foreach (var x in AudioSensorService.QuadtreeRoot.Find(new Bounds(position, Vector3.one * radius)))
|
||||
{
|
||||
currentCollider = colliders[i];
|
||||
if (IsValid(currentCollider))
|
||||
{
|
||||
cacheList.Add(currentCollider.transform);
|
||||
}
|
||||
var distance = Vector3.Distance(position, x.Position);
|
||||
if(distance>radius) continue;
|
||||
cache.Add(x.Transform);
|
||||
}
|
||||
detected = cacheList.ToArray();
|
||||
ListPool<Transform>.Release(cacheList);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
public override bool IsValid(Collider _collider)
|
||||
{
|
||||
if (ignoreColliders.Contains(_collider) is false)
|
||||
if (Vector3.Distance(transform.position, _collider.transform.position) <= radius)
|
||||
if (_collider.TryGetComponent<IAudioObject>(out currentAudioObject))
|
||||
{
|
||||
return currentAudioObject.GetVolume() >= 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public override float GetDistance() => radius;
|
||||
public int Id { get; set; }
|
||||
public IEnumerable<Transform> Get() => cache.ValueArray;
|
||||
public bool IsValid(Collider _collider) => false;
|
||||
public float GetDistance() => radius;
|
||||
public bool AutoUpdate=>autoUpdate;
|
||||
}
|
||||
}
|
67
Assets/BITKit/Unity/Scripts/Sensor/AudioSensorService.cs
Normal file
67
Assets/BITKit/Unity/Scripts/Sensor/AudioSensorService.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using kcp2k;
|
||||
using Quadtree;
|
||||
using Quadtree.Items;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace BITKit.Sensors
|
||||
{
|
||||
public class AudioSensorService : MonoBehaviour
|
||||
{
|
||||
public class AudioSensorData:IItem<AudioSensorData,Node<AudioSensorData>>
|
||||
{
|
||||
public int Id;
|
||||
public Vector3 Position;
|
||||
public float Radius;
|
||||
public Transform Transform;
|
||||
public Bounds Bounds;
|
||||
public ITag Tag;
|
||||
public Bounds GetBounds() => Bounds;
|
||||
public Node<AudioSensorData> ParentNode { get; set; }
|
||||
public void QuadTree_Root_Initialized(IQuadtreeRoot<AudioSensorData, Node<AudioSensorData>> root){}
|
||||
}
|
||||
internal static readonly QuadtreeRoot<AudioSensorData, Node<AudioSensorData>> QuadtreeRoot =
|
||||
new(default, Vector3.one * 2048);
|
||||
|
||||
private static Pool<AudioSensorData> pool = new(()=>new(), x=>{}, 1000);
|
||||
private static int count;
|
||||
public static async void MakeNoise(Vector3 position,Transform transform)
|
||||
{
|
||||
var data = pool.Take();
|
||||
data.Id = count++;
|
||||
data.Position = position;
|
||||
data.Transform = transform;
|
||||
data.Bounds = new Bounds(position, Vector3.one *1);
|
||||
data.Tag = transform.GetComponent<ITag>();
|
||||
QuadtreeRoot.Insert(data);
|
||||
await UniTask.Delay(3000);
|
||||
if (disposed) return;
|
||||
QuadtreeRoot.Remove(data);
|
||||
pool.Return(data);
|
||||
}
|
||||
private static bool disposed;
|
||||
[SerializeReference, SubclassSelector] private ITicker ticker;
|
||||
private void Start()
|
||||
{
|
||||
disposed = false;
|
||||
ticker.Add(OnTick);
|
||||
destroyCancellationToken.Register(Dispose);
|
||||
pool.Clear();
|
||||
}
|
||||
private void Dispose()
|
||||
{
|
||||
ticker.Remove(OnTick);
|
||||
disposed = true;
|
||||
QuadtreeRoot.Clear();
|
||||
}
|
||||
private void OnTick(float obj)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -9,7 +9,8 @@
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||
"GUID:be17a8778dbfe454890ed8279279e153",
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:9400d40641bab5b4a9702f65bf5c6eb5"
|
||||
"GUID:9400d40641bab5b4a9702f65bf5c6eb5",
|
||||
"GUID:1193c2664d97cc049a6e4c486c6bce71"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
@@ -17,6 +17,7 @@ namespace BITKit.Sensors
|
||||
/// </summary>
|
||||
public interface ISensor
|
||||
{
|
||||
int Id { get; }
|
||||
/// <summary>
|
||||
/// 自动更新
|
||||
/// </summary>
|
||||
@@ -45,7 +46,7 @@ namespace BITKit.Sensors
|
||||
/// 传感器执行检测
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
UniTask Execute();
|
||||
UniTask Execute(float delta);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -53,6 +54,8 @@ namespace BITKit.Sensors
|
||||
{
|
||||
[SerializeField] private GameObject gameObject;
|
||||
private ISensor _sensorImplementation => gameObject.GetComponent<ISensor>();
|
||||
public int Id => _sensorImplementation.Id;
|
||||
|
||||
public IEnumerable<Transform> Get()
|
||||
{
|
||||
return _sensorImplementation.Get();
|
||||
@@ -67,10 +70,9 @@ namespace BITKit.Sensors
|
||||
{
|
||||
return _sensorImplementation.GetDistance();
|
||||
}
|
||||
|
||||
public UniTask Execute()
|
||||
public UniTask Execute(float delta)
|
||||
{
|
||||
return _sensorImplementation.Execute();
|
||||
return _sensorImplementation.Execute(delta);
|
||||
}
|
||||
}
|
||||
[System.Serializable]
|
||||
@@ -78,6 +80,8 @@ namespace BITKit.Sensors
|
||||
{
|
||||
[SerializeField] private MonoBehaviour monoBehaviour;
|
||||
private ISensor _sensorImplementation=>monoBehaviour as ISensor;
|
||||
public int Id => _sensorImplementation.Id;
|
||||
|
||||
public IEnumerable<Transform> Get()
|
||||
{
|
||||
return _sensorImplementation.Get();
|
||||
@@ -93,9 +97,9 @@ namespace BITKit.Sensors
|
||||
return _sensorImplementation.GetDistance();
|
||||
}
|
||||
|
||||
public UniTask Execute()
|
||||
public UniTask Execute(float delta)
|
||||
{
|
||||
return _sensorImplementation.Execute();
|
||||
return _sensorImplementation.Execute(delta);
|
||||
}
|
||||
}
|
||||
public abstract class Sensor : MonoBehaviour, ISensor
|
||||
@@ -106,16 +110,15 @@ namespace BITKit.Sensors
|
||||
[Header(Constant.Header.Gameobjects)]
|
||||
public Collider[] ignoreColliders;
|
||||
[Header(Constant.Header.InternalVariables)]
|
||||
[NonSerialized]
|
||||
[SerializeField,ReadOnly]
|
||||
public Transform[] detected = Array.Empty<Transform>();
|
||||
public abstract IEnumerable<Transform> Get();
|
||||
public abstract bool IsValid(Collider _collider);
|
||||
public abstract UniTask Execute();
|
||||
public abstract float GetDistance();
|
||||
|
||||
public virtual UniTask Execute(float delta)=>UniTask.CompletedTask;
|
||||
public int Id { get; private set; }
|
||||
bool ISensor.AutoUpdate => autoUpdate;
|
||||
|
||||
protected int Id;
|
||||
protected Transform Transform;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
|
16
Assets/BITKit/Unity/Scripts/Sensor/Core/SensorTarget.cs
Normal file
16
Assets/BITKit/Unity/Scripts/Sensor/Core/SensorTarget.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.Sensors
|
||||
{
|
||||
public interface ISensorTarget
|
||||
{
|
||||
public int Id { get; }
|
||||
Bounds Bounds { get; }
|
||||
Transform Transform { get; }
|
||||
void Detected(float weight,ISensor sensor,object sender);
|
||||
event Action<float, ISensor, object> OnDetected;
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ namespace BITKit.Sensors
|
||||
public override IEnumerable<Transform> Get()=>sensors.SelectMany(x => x.Get());
|
||||
public override bool IsValid(Collider _collider) => sensors.Any(x => x.IsValid(_collider) is false) is false;
|
||||
public override float GetDistance() => sensors.Min(x => x.GetDistance());
|
||||
public override UniTask Execute() => UniTask.WhenAll(sensors.Select(x => x.Execute()));
|
||||
public override UniTask Execute(float delta) => UniTask.WhenAll(sensors.Select(x => x.Execute(delta)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,24 +7,25 @@ using Cysharp.Threading.Tasks;
|
||||
using UnityEngine.Jobs;
|
||||
using UnityEngine.Pool;
|
||||
using UnityEngine.Profiling;
|
||||
using Physics=UnityEngine.Physics;
|
||||
|
||||
namespace BITKit.Sensors
|
||||
{
|
||||
public class RangeSensor : Sensor
|
||||
{
|
||||
[Header(Constant.Header.Settings)]
|
||||
public float radius;
|
||||
[Header(Constant.Header.Settings)] public float radius;
|
||||
public int fov;
|
||||
public bool requireSight;
|
||||
|
||||
[Header(Constant.Header.Settings)]
|
||||
public LayerMask blockLayer;
|
||||
[Header(Constant.Header.InternalVariables)]
|
||||
private FrameUpdate frameUpdater;
|
||||
private readonly Collider[] colliders = new Collider[32];
|
||||
private RaycastHit[] hits;
|
||||
|
||||
private bool isExecuting;
|
||||
[Header(Constant.Header.Settings)] public LayerMask blockLayer;
|
||||
|
||||
[Header(Constant.Header.Debug)] [Header(Constant.Header.InternalVariables)]
|
||||
private FrameUpdate frameUpdater;
|
||||
|
||||
private readonly Collider[] colliders = new Collider[32];
|
||||
private RaycastHit[] hits = new RaycastHit[32];
|
||||
private readonly HashSet<int> tempHashSet = new();
|
||||
|
||||
public override IEnumerable<Transform> Get()
|
||||
{
|
||||
if (!_detectedDoubleBuffer.TryGetRelease(out var newRelease)) return _detectedBuffer;
|
||||
@@ -33,12 +34,13 @@ namespace BITKit.Sensors
|
||||
Profiler.EndSample();
|
||||
return _detectedBuffer;
|
||||
}
|
||||
private readonly DoubleBuffer<IEnumerable<Transform>> _detectedDoubleBuffer=new();
|
||||
private IEnumerable<Transform> _detectedBuffer;
|
||||
|
||||
|
||||
public override UniTask Execute()
|
||||
private readonly DoubleBuffer<IEnumerable<Transform>> _detectedDoubleBuffer = new();
|
||||
private IEnumerable<Transform> _detectedBuffer=Array.Empty<Transform>();
|
||||
|
||||
public override UniTask Execute(float delta)
|
||||
{
|
||||
tempHashSet.Clear();
|
||||
var length = Physics.OverlapSphereNonAlloc(Transform.position, radius, colliders, detectLayer);
|
||||
Profiler.BeginSample("Filter Detected Colliders");
|
||||
var _newDetected = from x in colliders.Take(length) where IsValid(x) select x.transform;
|
||||
@@ -54,7 +56,7 @@ namespace BITKit.Sensors
|
||||
case var _ when ignoreColliders.Contains(_collider):
|
||||
return false;
|
||||
case var _ when fov > 0 && CheckFov(ref _collider) is false:
|
||||
case var _ when requireSight && CheckSight(ref _collider) is false:
|
||||
case var _ when requireSight && CheckSight(ref _collider) is false:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
@@ -62,18 +64,21 @@ namespace BITKit.Sensors
|
||||
}
|
||||
|
||||
public override float GetDistance() => radius;
|
||||
|
||||
private bool CheckFov(ref Collider _collider)
|
||||
{
|
||||
var _dir = _collider.transform.position - transform.position;
|
||||
if (_dir.sqrMagnitude <= 0) return false;
|
||||
var dir = Quaternion.LookRotation(_dir);
|
||||
return Vector3.Dot(transform.forward, _dir) > 0 && fov > Quaternion.Angle(transform.rotation, dir);
|
||||
var _dir = _collider.bounds.center - transform.position;
|
||||
if (_dir.sqrMagnitude <= 0) return false;
|
||||
var dir = Quaternion.LookRotation(_dir);
|
||||
return Vector3.Dot(transform.forward, _dir) > 0 && fov > Quaternion.Angle(transform.rotation, dir);
|
||||
}
|
||||
|
||||
private bool CheckSight(ref Collider _collider)
|
||||
{
|
||||
if (!requireSight) return false;
|
||||
var transform1 = _collider.transform;
|
||||
var position = transform1.position;
|
||||
var position = _collider.bounds.center;
|
||||
//是检测bounds的顶部
|
||||
position.y += _collider.bounds.extents.y;
|
||||
var location = new Location(Transform);
|
||||
var length = Physics.RaycastNonAlloc(
|
||||
location.position,
|
||||
@@ -82,23 +87,32 @@ namespace BITKit.Sensors
|
||||
Vector3.Distance(location, position),
|
||||
blockLayer
|
||||
);
|
||||
|
||||
switch (length)
|
||||
{
|
||||
case 0:
|
||||
Debug.DrawLine(location, position, Color.green, 1);
|
||||
return true;
|
||||
case 1:
|
||||
if (hits[0].collider == _collider)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (hits.Take(length).Any(x => ignoreColliders.Contains(x.collider) is false))
|
||||
return hits[0].collider == _collider;
|
||||
default:
|
||||
var collider1 = _collider;
|
||||
if (hits.Take(length).Any(Predicate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
bool Predicate(RaycastHit x)
|
||||
{
|
||||
var result = ignoreColliders.Contains(x.collider) is false && x.collider != collider1;
|
||||
if (result)
|
||||
{
|
||||
Debug.DrawLine(location, x.point, Color.red, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ namespace BITKit.Sensors
|
||||
public float distance;
|
||||
public override IEnumerable<Transform> Get() => detected;
|
||||
private readonly RaycastHit[] raycasts = new RaycastHit[16];
|
||||
public override UniTask Execute()
|
||||
public override UniTask Execute(float delta)
|
||||
{
|
||||
var _transform = transform;
|
||||
var forward = _transform.forward;
|
||||
@@ -46,7 +46,7 @@ namespace BITKit.Sensors
|
||||
{
|
||||
if (autoUpdate)
|
||||
{
|
||||
Execute().Forget();
|
||||
Execute(Time.deltaTime).Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
@@ -10,6 +11,7 @@ namespace BITKit.Sensors
|
||||
public class SensorQueue : MonoBehaviour
|
||||
{
|
||||
internal static readonly Dictionary<int,ISensor> Sensors=new();
|
||||
internal static readonly ConcurrentDictionary<int, float> LastDetectedTime = new();
|
||||
private static bool IsDirty;
|
||||
|
||||
[SerializeField,ReadOnly] private int _position;
|
||||
@@ -32,10 +34,25 @@ namespace BITKit.Sensors
|
||||
}
|
||||
|
||||
[SerializeField] private MonoBehaviour[] sensors;
|
||||
|
||||
private void Update()
|
||||
[SerializeReference,SubclassSelector] private ITicker ticker;
|
||||
|
||||
private bool _isBusy;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ticker.Add(OnTick);
|
||||
destroyCancellationToken.Register(Dispose);
|
||||
}
|
||||
private void Dispose()
|
||||
{
|
||||
ticker.Remove(OnTick);
|
||||
}
|
||||
private async void OnTick(float obj)
|
||||
{
|
||||
if (_isBusy) return;
|
||||
if (SensorGlobalSettings.Enabled is false) return;
|
||||
|
||||
_isBusy = true;
|
||||
if(IsDirty)
|
||||
{
|
||||
_position = 0;
|
||||
@@ -44,11 +61,25 @@ namespace BITKit.Sensors
|
||||
sensors = Sensors.Values.Where(IsEnabled).OfType<MonoBehaviour>().ToArray();
|
||||
}
|
||||
|
||||
if(Sensors.Count is 0) return;
|
||||
|
||||
Sensors.ElementAt(_position++).Value.Execute().Forget();
|
||||
if (Sensors.Count is 0)
|
||||
{
|
||||
_isBusy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var current = Sensors.ElementAt(_position++).Value;
|
||||
var currentUpdateTime = LastDetectedTime.GetOrAdd(current.Id,Time.time);
|
||||
await current.Execute(Time.time-currentUpdateTime);
|
||||
float UpdateValueFactory(int key, float old) => Time.time;
|
||||
LastDetectedTime.AddOrUpdate(current.Id,Time.time,UpdateValueFactory);
|
||||
|
||||
if (destroyCancellationToken.IsCancellationRequested) {
|
||||
_isBusy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_position %= Sensors.Count;
|
||||
_isBusy = false;
|
||||
}
|
||||
private bool IsEnabled(ISensor sensor)
|
||||
{
|
||||
@@ -60,4 +91,4 @@ namespace BITKit.Sensors
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using BITKit.Sensors.States;
|
||||
using BITKit.StateMachine;
|
||||
using Cysharp.Threading.Tasks;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
@@ -16,212 +18,38 @@ using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.Sensors
|
||||
{
|
||||
public interface ISmartTargetProperty{}
|
||||
public interface ISmartTargetState:IState{}
|
||||
/// <summary>
|
||||
/// 智能目标传感器,根据条件,智能选择目标
|
||||
/// </summary>
|
||||
public class SmartTargetSensor :MonoBehaviour,ISensor,IAction
|
||||
public class SmartTargetSensor :StateBasedMonoBehaviour<ISmartTargetState>,ISensor
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动更新
|
||||
/// </summary>
|
||||
[Header(Constant.Header.Settings)]
|
||||
[SerializeField] private bool autoUpdate;
|
||||
[SerializeField] private Optional<string[]> ignoreTags;
|
||||
[SerializeField] private Optional<IntervalUpdate> optionalRetargetInterval;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 主传感器
|
||||
/// </summary>
|
||||
[Header(nameof(Sensor))]
|
||||
[SerializeField,SerializeReference,SubclassSelector] private ISensor sensor;
|
||||
|
||||
[Header(Constant.Header.Debug)]
|
||||
[SerializeReference, ReadOnly] private int updateCount;
|
||||
|
||||
public IEnumerable<Transform> Get() =>CurrentTarget is not null ? new[] { CurrentTarget }:Enumerable.Empty<Transform>();
|
||||
|
||||
bool ISensor.AutoUpdate => autoUpdate;
|
||||
|
||||
internal StringBuilder reportBuilder;
|
||||
internal DoubleBuffer<string> report=new();
|
||||
|
||||
public bool IsValid(Collider _collider)
|
||||
{
|
||||
if (!_collider) return false;
|
||||
if (ignoreTags.Allow)
|
||||
{
|
||||
if (_collider.TryGetComponent<ITag>(out var iTags))
|
||||
{
|
||||
var tags = iTags.GetTags();
|
||||
foreach (var x in ignoreTags.Value)
|
||||
{
|
||||
if (tags.Contains(x))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public float GetDistance() => sensor.GetDistance();
|
||||
public Transform CurrentTarget { get; private set; }
|
||||
private IEnumerable<Transform> detected = ArraySegment<Transform>.Empty;
|
||||
private Vector3 _currentPosition;
|
||||
private int Id;
|
||||
|
||||
private CancellationTokenSource timeoutCancellationTokenSource=new();
|
||||
|
||||
[SerializeField] private float radius;
|
||||
[SerializeField] private RangeSensor rangeSensor;
|
||||
[SerializeField] private AudioSensor audioSensor;
|
||||
public int Id { get; set; }
|
||||
private readonly CacheList<Transform> _detected=new();
|
||||
private void OnEnable()
|
||||
{
|
||||
SensorQueue.Register(Id=GetInstanceID(),this);
|
||||
Id = GetInstanceID();
|
||||
SensorQueue.Register(Id,this);
|
||||
}
|
||||
private void Start()
|
||||
{
|
||||
TransitionState<Idle>();
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
SensorQueue.UnRegister(Id);
|
||||
}
|
||||
public async UniTask Execute()
|
||||
public IEnumerable<Transform> Get() => _detected.ValueArray;
|
||||
public bool IsValid(Collider _collider) => false;
|
||||
public float GetDistance() => radius;
|
||||
public UniTask Execute(float delta)
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentPosition = transform.position;
|
||||
updateCount++;
|
||||
timeoutCancellationTokenSource?.Cancel();
|
||||
timeoutCancellationTokenSource = new CancellationTokenSource();
|
||||
await sensor.Execute();
|
||||
|
||||
reportBuilder?.AppendLine($"-----BEGIN------{updateCount}");
|
||||
|
||||
Profiler.BeginSample("Release Detected Buffer");
|
||||
var newDetected = sensor.Get();
|
||||
|
||||
if (reportBuilder is not null)
|
||||
{
|
||||
reportBuilder.AppendLine($"Detected:{newDetected.Count()}");
|
||||
foreach (var x in newDetected)
|
||||
{
|
||||
reportBuilder.AppendLine(x.name);
|
||||
}
|
||||
reportBuilder.AppendLine();
|
||||
}
|
||||
Profiler.EndSample();
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
if (newDetected.Contains(CurrentTarget))
|
||||
{
|
||||
if (optionalRetargetInterval.Allow && optionalRetargetInterval.Value.AllowUpdate)
|
||||
{
|
||||
reportBuilder?.AppendLine("Retarget Interval Catch,Search New Target");
|
||||
}
|
||||
else
|
||||
{
|
||||
reportBuilder?.AppendLine("Current Target Detected,Keep Target");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Profiler.BeginSample("Filter Detected");
|
||||
|
||||
foreach (var x in newDetected.OrderBy(KeySelector))
|
||||
{
|
||||
if(IsValid(x.GetComponent<Collider>()) is false)continue;
|
||||
CurrentTarget = x;
|
||||
reportBuilder?.AppendLine($"New Target:{x.name},Is oder by distance");
|
||||
break;
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
reportBuilder?.AppendLine($"-----Complete------{updateCount}");
|
||||
|
||||
if(reportBuilder is not null)
|
||||
{
|
||||
report.Release(reportBuilder.ToString());
|
||||
reportBuilder.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
report.Clear();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
|
||||
}
|
||||
private float KeySelector(Transform x)
|
||||
{
|
||||
var distance = Vector3.Distance(_currentPosition, x.position);
|
||||
reportBuilder?.AppendLine($"Distance:{distance}@{x.name}");
|
||||
return distance;
|
||||
}
|
||||
void IAction.Execute()
|
||||
{
|
||||
Execute().Forget();
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CurrentTarget) return;
|
||||
Gizmos.DrawLine(transform.position,CurrentTarget.position);
|
||||
}
|
||||
catch (UnassignedReferenceException)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(SmartTargetSensor))]
|
||||
public class SmartTargetSensorInspector:BITInspector<SmartTargetSensor>
|
||||
{
|
||||
private ObjectField _objectField;
|
||||
private Label _reportLabel;
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
FillDefaultInspector();
|
||||
|
||||
CreateSubTitle("Editor Debug Field");
|
||||
|
||||
_objectField = root.Create<ObjectField>();
|
||||
|
||||
_objectField.objectType = typeof(Transform);
|
||||
|
||||
_objectField.SetEnabled(false);
|
||||
|
||||
_reportLabel = root.Create<Label>();
|
||||
|
||||
_reportLabel.text = "Waiting agent report";
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override void OnEnabled()
|
||||
{
|
||||
base.OnEnabled();
|
||||
agent.reportBuilder = new();
|
||||
}
|
||||
|
||||
protected override void OnDisabled()
|
||||
{
|
||||
base.OnDisabled();
|
||||
agent.reportBuilder = null;
|
||||
}
|
||||
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
if (_objectField is not null)
|
||||
{
|
||||
_objectField.value = agent.CurrentTarget;
|
||||
}
|
||||
|
||||
if (agent.reportBuilder is not null && _reportLabel is not null && agent.report.TryGetRelease(out var value))
|
||||
{
|
||||
_reportLabel.text = value;
|
||||
}
|
||||
CurrentState?.OnStateUpdate(delta);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITKit.StateMachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.Sensors.States
|
||||
{
|
||||
public abstract class SmartTargetSensorStates : ISmartTargetState
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
public virtual void OnStateEntry(IState old)
|
||||
{
|
||||
}
|
||||
public virtual void OnStateUpdate(float deltaTime)
|
||||
{
|
||||
}
|
||||
public virtual void OnStateExit(IState old, IState newState)
|
||||
{
|
||||
}
|
||||
}
|
||||
[Serializable]
|
||||
public sealed class Idle : SmartTargetSensorStates
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -61,6 +61,8 @@ namespace BITKit.Sensors
|
||||
onLost.Invoke(_collider);
|
||||
}
|
||||
}
|
||||
public int Id => _id is 0 ? _id = GetInstanceID() : _id;
|
||||
private int _id;
|
||||
|
||||
public IEnumerable<Transform> Get()
|
||||
{
|
||||
@@ -89,13 +91,11 @@ namespace BITKit.Sensors
|
||||
if (ignores.Contains(detectedObject)) return false;
|
||||
return !detectedLayer.Allow || detectedLayer.Value.Includes(_collider.gameObject.layer);
|
||||
}
|
||||
|
||||
public float GetDistance()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public UniTask Execute()=>
|
||||
public UniTask Execute(float delta = 0)=>
|
||||
UniTask.CompletedTask;
|
||||
|
||||
private void Update()
|
||||
|
@@ -51,10 +51,10 @@ namespace BITKit.StateMachine
|
||||
state.Initialize();
|
||||
StateDictionary.Add(state.GetType(), state);
|
||||
}
|
||||
if (states.Count > 0)
|
||||
{
|
||||
TransitionState(states[0]);
|
||||
}
|
||||
// if (states.Count > 0)
|
||||
// {
|
||||
// TransitionState(states[0]);
|
||||
// }
|
||||
}
|
||||
public void UpdateState(float deltaTime)
|
||||
{
|
||||
@@ -104,9 +104,8 @@ namespace BITKit.StateMachine
|
||||
{
|
||||
BIT4Log.Log<MonoStateMachine<TState>>($"TransitionState from {_currentStateName} to {newState.GetType().Name}");
|
||||
}
|
||||
|
||||
newState.OnStateEntry(oldState);
|
||||
CurrentState = newState;
|
||||
newState.OnStateEntry(oldState);
|
||||
OnStateChanged?.Invoke(oldState, newState);
|
||||
_currentStateName = newState.GetType().Name;
|
||||
newState.Enabled = true;
|
||||
|
@@ -13,8 +13,14 @@ namespace BITKit
|
||||
[SerializeField] private string[] tags;
|
||||
[Tooltip("Disable when tags is not empty")]
|
||||
[SerializeReference,SubclassSelector] private IReference[] reference;
|
||||
|
||||
public int Hash => _id is 0 ? _id = MathE.GetHash(GetTags()) : _id;
|
||||
private int _id;
|
||||
public string[] GetTags() => CacheTags ??= reference?.Length > 0 ? reference.Select(x => x.Value).ToArray() : tags;
|
||||
private string[] CacheTags;
|
||||
public void SetTags(IReference[] newReference)
|
||||
{
|
||||
reference = newReference;
|
||||
CacheTags = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,9 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Timers;
|
||||
using Cysharp.Threading.Tasks;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit
|
||||
@@ -71,9 +74,16 @@ namespace BITKit
|
||||
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();
|
||||
}
|
||||
#endif
|
||||
while (_ActionQueue.TryDequeue(out var action))
|
||||
{
|
||||
action?.Invoke();
|
||||
@@ -95,10 +105,16 @@ _TickEvents?.Invoke(delta);
|
||||
BIT4Log.LogException(exception);
|
||||
}
|
||||
|
||||
if (isConcurrent is false && destroyCancellationToken.IsCancellationRequested is false)
|
||||
Restart();
|
||||
return;
|
||||
void Restart()
|
||||
{
|
||||
_timer.Start();
|
||||
if (isConcurrent is false && destroyCancellationToken.IsCancellationRequested is false)
|
||||
{
|
||||
_timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@ using System.Collections.Generic;
|
||||
using System.Timers;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
@@ -48,11 +51,23 @@ namespace BITKit
|
||||
{
|
||||
public event Action<float> Tick;
|
||||
public float Interval { get; set; }
|
||||
|
||||
public async void Invoke(object sender, ElapsedEventArgs args)
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
Tick?.Invoke(Interval);
|
||||
#if UNITY_EDITOR
|
||||
if (EditorApplication.isPlaying is false || EditorApplication.isPaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
try
|
||||
{
|
||||
Tick?.Invoke(Interval);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void Add(Action<float> action, float interval)
|
||||
@@ -81,7 +96,6 @@ namespace BITKit
|
||||
{
|
||||
Interval = totalMilliseconds,
|
||||
};
|
||||
timer.AutoReset = true;
|
||||
_CreateTimes.TryAdd(interval, GameTickService.TickCount);
|
||||
|
||||
var action = _Actions.GetOrAdd(interval, CreateAction);
|
||||
@@ -91,6 +105,13 @@ namespace BITKit
|
||||
|
||||
BIT4Log.Log<IntervalTickService>($"已创建Tick,Interval[{totalMilliseconds}]");
|
||||
return timer;
|
||||
|
||||
void Tick(object sender, ElapsedEventArgs args)
|
||||
{
|
||||
action.Invoke(sender, args);
|
||||
if (timer.Enabled)
|
||||
timer.Start();
|
||||
}
|
||||
}
|
||||
private static DelegateWrapper CreateAction(float interval)=>new()
|
||||
{
|
||||
|
@@ -101,7 +101,7 @@ namespace BITKit.UX
|
||||
[UXBindPath(UXConstant.TitleLabel)]
|
||||
private Label _titleLabel;
|
||||
[UXBindPath(UXConstant.ContextLabel)]
|
||||
private Label _contextLabel;
|
||||
private TextElement _contextLabel;
|
||||
[UXBindPath(UXConstant.MainButton)]
|
||||
private Button _comfirmButton;
|
||||
[UXBindPath(UXConstant.SecButton)]
|
||||
|
@@ -41,8 +41,14 @@ public class UXBuilder : MonoBehaviour
|
||||
public UXContainer BuildAsContainer(Func<VisualElement> createFactory) => new(Build<VisualElement>(createFactory));
|
||||
public UXContainer BuildAsContainer() => new(Build<VisualElement>());
|
||||
|
||||
public void Clear()
|
||||
public void Clear(bool all=false)
|
||||
{
|
||||
if (all)
|
||||
{
|
||||
visualElementProvider.GetVisualElement().Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var x in instances)
|
||||
{
|
||||
x.RemoveFromHierarchy();
|
||||
|
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "BITKit.UX.Chart.Runtime",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008",
|
||||
"GUID:d525ad6bd40672747bde77962f1c401e",
|
||||
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:6de01b04fa4e14662b03fa46366da151",
|
||||
"GUID:f19bbd83e3c264a5680926bf75d7e494"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"_SkiaSharp"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
202
Assets/BITKit/Unity/Scripts/UX/Chart/SkiaChart.cs
Normal file
202
Assets/BITKit/Unity/Scripts/UX/Chart/SkiaChart.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using SkiaSharp;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class SkiaChart : VisualElement
|
||||
{
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
private readonly UxmlStringAttributeDescription m_JsonAttribute = new ()
|
||||
{
|
||||
name = "Json"
|
||||
};
|
||||
private readonly UxmlBoolAttributeDescription m_allowWarningsAttribute = new ()
|
||||
{
|
||||
name = "allowWarnings",
|
||||
defaultValue = false
|
||||
};
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var x = (SkiaChart)ve;
|
||||
x.Json = m_JsonAttribute.GetValueFromBag(bag, cc);
|
||||
x.AllowWarning = m_allowWarningsAttribute.GetValueFromBag(bag, cc);
|
||||
|
||||
}
|
||||
}
|
||||
public new class UxmlFactory : UxmlFactory<SkiaChart, UxmlTraits> { }
|
||||
public SkiaChart()
|
||||
{
|
||||
RegisterCallback<GeometryChangedEvent>(x =>
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
Rebuild();
|
||||
}
|
||||
}); RegisterCallback<CustomStyleResolvedEvent>(OnCustomStyleResolved);
|
||||
|
||||
style.flexDirection = FlexDirection.Row;
|
||||
_dataContainer = this.Create<VisualElement>();
|
||||
_chartContainer = this.Create<VisualElement>();
|
||||
_chartContainer.style.flexGrow = 1;
|
||||
|
||||
_dataContainer.style.justifyContent = Justify.SpaceBetween;
|
||||
|
||||
_dataContainer.name = "Data";
|
||||
_chartContainer.name = "Chart";
|
||||
}
|
||||
|
||||
private void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
|
||||
{
|
||||
if(evt.customStyle.TryGetValue(new CustomStyleProperty<Color>("--chart-secondary-color"), out var helpLineColor))
|
||||
{
|
||||
_secondaryColor = helpLineColor;
|
||||
}
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
public string Json
|
||||
{
|
||||
get => _json;
|
||||
set
|
||||
{
|
||||
_json = value;
|
||||
Rebuild();
|
||||
}
|
||||
}
|
||||
private string _json;
|
||||
public bool AllowWarning { get; set; }
|
||||
public Color SecondaryColor
|
||||
{
|
||||
get => _secondaryColor;
|
||||
set
|
||||
{
|
||||
_secondaryColor = value;
|
||||
Rebuild();
|
||||
}
|
||||
}
|
||||
private Color _secondaryColor;
|
||||
|
||||
private readonly VisualElement _dataContainer;
|
||||
private readonly VisualElement _chartContainer;
|
||||
private void Rebuild()
|
||||
{
|
||||
var data = Array.Empty<float>();
|
||||
try
|
||||
{
|
||||
data = JsonConvert.DeserializeObject<float[]>(Json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (AllowWarning)
|
||||
Debug.LogException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if(float.IsNaN(layout.width) || float.IsNaN(layout.height))
|
||||
return;
|
||||
|
||||
if (data is null or { Length: 0 }) return;
|
||||
|
||||
|
||||
|
||||
_dataContainer.Clear();
|
||||
_chartContainer.Clear();
|
||||
|
||||
var max = data.Max();
|
||||
var min = data.Min();
|
||||
//假如min是x,max是y,将中间的差用data.Length划分
|
||||
|
||||
_dataContainer.style.flexDirection = FlexDirection.ColumnReverse;
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
{
|
||||
var label = _dataContainer.Create<Label>();
|
||||
|
||||
var value = (max - min) / (data.Length - 1) * i + min;
|
||||
label.text = value.ToString();
|
||||
}
|
||||
|
||||
|
||||
if(_chartContainer.layout.width is float.NaN or <10 || _chartContainer.layout.height is float.NaN or <10)
|
||||
return;
|
||||
var info = new SKImageInfo(
|
||||
width: (int)_chartContainer.layout.width,
|
||||
height: (int)_chartContainer.layout.height,
|
||||
colorType: SKColorType.Rgba8888,
|
||||
alphaType: SKAlphaType.Premul
|
||||
);
|
||||
|
||||
using var surface = SKSurface.Create(info);
|
||||
|
||||
using var canvas = surface.Canvas;
|
||||
|
||||
using var linePaint = new SKPaint();
|
||||
linePaint.Color = resolvedStyle.color.ToSKColor();
|
||||
linePaint.StrokeWidth = resolvedStyle.unityTextOutlineWidth;
|
||||
linePaint.IsAntialias = true;
|
||||
linePaint.Style = SKPaintStyle.Stroke;
|
||||
|
||||
using var helpLinePaint = new SKPaint();
|
||||
helpLinePaint.Color = resolvedStyle.unityTextOutlineColor.ToSKColor();
|
||||
helpLinePaint.StrokeWidth = 1;
|
||||
helpLinePaint.Style = SKPaintStyle.Stroke;
|
||||
|
||||
using var fillPaint = new SKPaint();
|
||||
fillPaint.Color = new SKColor(200, 200, 200, 200);
|
||||
fillPaint.Style=SKPaintStyle.Fill;
|
||||
|
||||
DoubleBuffer<SKPoint> buffer = new();
|
||||
|
||||
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
|
||||
path.MoveTo(0,0);
|
||||
path.LineTo(0,0);
|
||||
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
{
|
||||
var value = data[i];
|
||||
var posX = (float)info.Width / (data.Length - 1) * (i);
|
||||
var d = max - min;
|
||||
var p = (value - min) / d;
|
||||
var poxY = info.Height * p;
|
||||
|
||||
var currentPoint = new SKPoint(posX, poxY);
|
||||
if (buffer.TryGetRelease(out var previousPoint))
|
||||
{
|
||||
canvas.DrawLine(previousPoint, currentPoint, linePaint);
|
||||
}
|
||||
|
||||
var label = _chartContainer.Create<Label>();
|
||||
label.text = value.ToString(CultureInfo.InvariantCulture);
|
||||
label.style.position = Position.Absolute;
|
||||
label.style.left = posX;//-label.layout.width/2;
|
||||
label.style.bottom = poxY;//- label.layout.height/2;
|
||||
|
||||
canvas.DrawLine(posX, 0, posX, poxY, helpLinePaint);
|
||||
|
||||
path.LineTo(posX,poxY);
|
||||
|
||||
buffer.Release(currentPoint);
|
||||
}
|
||||
|
||||
path.LineTo(info.Width,0);
|
||||
path.Close();
|
||||
|
||||
|
||||
//canvas.DrawPath(path,filePaint);
|
||||
|
||||
var texture = info.ToTexture2D(surface);
|
||||
|
||||
_chartContainer.style.backgroundImage = texture;
|
||||
}
|
||||
}
|
||||
}
|
35
Assets/BITKit/Unity/Scripts/UX/Chart/SkiaExtensions.cs
Normal file
35
Assets/BITKit/Unity/Scripts/UX/Chart/SkiaExtensions.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using SkiaSharp;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public static class SkiaExtensions
|
||||
{
|
||||
public static Texture2D ToTexture2D(this SKImageInfo info,SKSurface surface)
|
||||
{
|
||||
// Okay, we're finished drawing. Now we create a Unity texture.
|
||||
TextureFormat format = (info.ColorType == SKColorType.Rgba8888) ? TextureFormat.RGBA32 : TextureFormat.BGRA32;
|
||||
var texture = new Texture2D(info.Width, info.Height, format, false, true);
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
// Pull a Skia image object out of the canvas...
|
||||
var pixmap = surface.PeekPixels();
|
||||
// Copy it to the Unity texture...
|
||||
texture.LoadRawTextureData(pixmap.GetPixels(), pixmap.RowBytes * pixmap.Height);
|
||||
texture.Apply(false, true);
|
||||
// And drop it into the RawImage object.
|
||||
|
||||
return texture;
|
||||
}
|
||||
public static SKColor ToSKColor(this Color color,byte? alpha=null)
|
||||
{
|
||||
return new SKColor((byte)(color.r * 255), (byte)(color.g * 255), (byte)(color.b * 255), alpha??(byte)(color.a * 255));
|
||||
}
|
||||
public static SKColor ToSKColor(this Color32 color)
|
||||
{
|
||||
return new SKColor(color.r, color.g, color.b, color.a);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "BITKit.UX.Chart.Tests",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008",
|
||||
"GUID:d525ad6bd40672747bde77962f1c401e",
|
||||
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:6de01b04fa4e14662b03fa46366da151",
|
||||
"GUID:f19bbd83e3c264a5680926bf75d7e494",
|
||||
"GUID:994a3fb33a5627740b0712e7c483cc1f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"_SkiaSharp"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using SkiaSharp;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class SKPaintComponent : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Texture2D texture;
|
||||
[SerializeField] private float[] data;
|
||||
|
||||
[BIT]
|
||||
private void Draw()
|
||||
{
|
||||
var info = new SKImageInfo(
|
||||
width: 384,
|
||||
height: 128,
|
||||
colorType: SKColorType.Rgba8888,
|
||||
alphaType: SKAlphaType.Premul
|
||||
);
|
||||
|
||||
using var surface = SKSurface.Create(info);
|
||||
|
||||
|
||||
using var canvas = surface.Canvas;
|
||||
|
||||
//canvas.Clear(new Color32(31,31,31,255).ToSKColor());
|
||||
|
||||
using var linePaint = new SKPaint();
|
||||
linePaint.Color = new SKColor(255, 0, 0, 255);
|
||||
linePaint.StrokeWidth = 8;
|
||||
linePaint.IsAntialias = true;
|
||||
linePaint.Style = SKPaintStyle.Stroke;
|
||||
|
||||
using var helpLinePaint = new SKPaint();
|
||||
helpLinePaint.Color = new SKColor(200, 200, 200, 200);
|
||||
helpLinePaint.StrokeWidth = 4;
|
||||
helpLinePaint.Style = SKPaintStyle.Stroke;
|
||||
|
||||
using var textPaint = new SKPaint();
|
||||
textPaint.TextAlign = SKTextAlign.Center;
|
||||
textPaint.TextSize = 14;
|
||||
textPaint.ColorF = new SKColor(0, 255, 0, 255);
|
||||
|
||||
using var filePaint = new SKPaint();
|
||||
filePaint.Color = new SKColor(200, 200, 200, 200);
|
||||
filePaint.Style=SKPaintStyle.Fill;
|
||||
|
||||
var min = data.Min();
|
||||
var max = data.Max();
|
||||
|
||||
DoubleBuffer<SKPoint> buffer = new();
|
||||
|
||||
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
|
||||
path.MoveTo(0,0);
|
||||
path.LineTo(0,0);
|
||||
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
{
|
||||
var value = data[i];
|
||||
var posX = (float)info.Width / (data.Length - 1) * (i);
|
||||
var d = max - min;
|
||||
var p = (value - min) / d;
|
||||
var poxY = info.Height * p;
|
||||
|
||||
var currentPoint = new SKPoint(posX, poxY);
|
||||
if (buffer.TryGetRelease(out var previousPoint))
|
||||
{
|
||||
canvas.DrawLine(previousPoint, currentPoint, linePaint);
|
||||
}
|
||||
|
||||
canvas.DrawText(
|
||||
value.ToString()
|
||||
, currentPoint
|
||||
, new SKFont(
|
||||
SKTypeface.FromFile(@"D:\Iris\Documents\GitHub\iFactory-YL106.Unity\Assets\BITKit\Unity\Art\Fonts\TTF\Roboto\Roboto-Regular.ttf")
|
||||
), textPaint);
|
||||
canvas.DrawLine(posX, 0, posX, poxY, helpLinePaint);
|
||||
|
||||
path.LineTo(posX,poxY);
|
||||
|
||||
buffer.Release(currentPoint);
|
||||
}
|
||||
|
||||
path.LineTo(info.Width,0);
|
||||
path.Close();
|
||||
|
||||
|
||||
//canvas.DrawPath(path,filePaint);
|
||||
|
||||
texture = info.ToTexture2D(surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,10 +30,18 @@ namespace BITKit.UX
|
||||
container = this.visualElement.Q(UXConstant.ContextContainer);
|
||||
toggle = this.visualElement.Q<Toggle>(UXConstant.Toggle);
|
||||
}
|
||||
public T Get<T>(int index) where T : VisualElement => visualElement.Q<T>($"{typeof(T).Name}--{index}");
|
||||
public T Get<T>(int index = 0) where T : VisualElement => visualElement.Q<T>($"{typeof(T).Name}--{index}");
|
||||
public void SetProcess(float process)
|
||||
{
|
||||
visualElement.Q(UXConstant.ProcessBarFill).style.width =Length.Percent(Mathf.Clamp(process * 100,0,100)) ;
|
||||
var radialProgress = visualElement.Q<RadialProgress>();
|
||||
if (radialProgress is not null)
|
||||
{
|
||||
radialProgress.progress = Mathf.Clamp((int)(process*100), 0, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
visualElement.Q(UXConstant.ProcessBarFill).style.width =Length.Percent(Mathf.Clamp(process * 100,0,100)) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,9 @@
|
||||
"GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008",
|
||||
"GUID:d525ad6bd40672747bde77962f1c401e",
|
||||
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
|
||||
"GUID:90b448749ba9be04ebf2eb5953b53caf"
|
||||
"GUID:90b448749ba9be04ebf2eb5953b53caf",
|
||||
"GUID:e0cd26848372d4e5c891c569017e11f1",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
153
Assets/BITKit/Unity/Scripts/UX/DataBind/UXDataBindingHandler.cs
Normal file
153
Assets/BITKit/Unity/Scripts/UX/DataBind/UXDataBindingHandler.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using BITKit.UX;
|
||||
using Newtonsoft.Json;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using Cursor = UnityEngine.UIElements.Cursor;
|
||||
|
||||
namespace BITKit.IData
|
||||
{
|
||||
public interface IUXDataBinder:IDisposable
|
||||
{
|
||||
Type TargetType { get; }
|
||||
|
||||
void OnTick(float deltaTime);
|
||||
|
||||
VisualElement CreateUI(object target,FieldInfo fieldInfo);
|
||||
VisualElement VisualElement { get; }
|
||||
}
|
||||
public abstract class UXDataBinder<TDataType> : IUXDataBinder
|
||||
{
|
||||
public Type TargetType => typeof(TDataType);
|
||||
public VisualElement VisualElement { get; private set; }
|
||||
protected object target{ get; private set; }
|
||||
protected TDataType currentValue { get; set; }
|
||||
protected FieldInfo fieldInfo{ get; private set; }
|
||||
protected string Name { get; private set; }
|
||||
|
||||
public virtual void OnTick(float deltaTime)
|
||||
{
|
||||
var newValue = (TDataType)fieldInfo.GetValue(target);
|
||||
if(Equals(newValue,currentValue))return;
|
||||
OnValueChanged(currentValue,newValue);
|
||||
}
|
||||
protected virtual void OnValueChanged(TDataType oldValue,TDataType newValue)
|
||||
{
|
||||
currentValue = newValue;
|
||||
fieldInfo.SetValue(target,currentValue);
|
||||
|
||||
if (VisualElement is INotifyValueChanged<TDataType> notifyValueChanged)
|
||||
notifyValueChanged.SetValueWithoutNotify(newValue);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
target = null;
|
||||
fieldInfo = null;
|
||||
VisualElement.RemoveFromHierarchy();
|
||||
VisualElement = null;
|
||||
}
|
||||
|
||||
public VisualElement CreateUI(object target, FieldInfo fieldInfo)
|
||||
{
|
||||
this.target = target;
|
||||
this.fieldInfo = fieldInfo;
|
||||
currentValue = (TDataType)fieldInfo.GetValue(target);
|
||||
|
||||
VisualElement = OnCreateUI();
|
||||
|
||||
var attribute = fieldInfo.GetCustomAttribute<ExportAttribute>();
|
||||
|
||||
if(attribute != null)
|
||||
{
|
||||
Name = attribute.Name;
|
||||
VisualElement.GetType().GetProperty("label",ReflectionHelper.Flags)?.SetValue(VisualElement, Name);
|
||||
}
|
||||
if (fieldInfo.GetCustomAttribute<ReadOnlyAttribute>() is not null)
|
||||
{
|
||||
VisualElement.SetEnabled(false);
|
||||
}
|
||||
if(VisualElement is INotifyValueChanged<TDataType> notifyValueChanged)
|
||||
{
|
||||
notifyValueChanged.SetValueWithoutNotify(currentValue);
|
||||
notifyValueChanged.RegisterValueChangedCallback(x =>
|
||||
{
|
||||
fieldInfo.SetValue(target,x.newValue);
|
||||
currentValue = x.newValue;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return VisualElement;
|
||||
}
|
||||
|
||||
|
||||
protected abstract VisualElement OnCreateUI();
|
||||
}
|
||||
|
||||
public sealed class UXIntBinder:UXDataBinder<int>
|
||||
{
|
||||
protected override VisualElement OnCreateUI() => new IntegerField();
|
||||
}
|
||||
public sealed class UXFloatBinder:UXDataBinder<float>
|
||||
{
|
||||
protected override VisualElement OnCreateUI() => new FloatField();
|
||||
}
|
||||
public sealed class UXVector3Binder:UXDataBinder<Vector3>
|
||||
{
|
||||
protected override VisualElement OnCreateUI() => new Vector3Field();
|
||||
}
|
||||
public sealed class UXVector2Binder:UXDataBinder<Vector2>
|
||||
{
|
||||
protected override VisualElement OnCreateUI() => new Vector2Field();
|
||||
}
|
||||
public sealed class UXStringBinder:UXDataBinder<string>
|
||||
{
|
||||
protected override VisualElement OnCreateUI()
|
||||
{
|
||||
if (this.fieldInfo.GetCustomAttribute<ReadOnlyAttribute>() is not null)
|
||||
{
|
||||
return new Label();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new TextField()
|
||||
{
|
||||
isDelayed = true,
|
||||
multiline = true
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public sealed class UXFloat3Binder:UXDataBinder<float3>
|
||||
{
|
||||
protected override VisualElement OnCreateUI()
|
||||
{
|
||||
var field = new Vector3Field
|
||||
{
|
||||
label = Name
|
||||
};
|
||||
field.SetValueWithoutNotify(currentValue);
|
||||
field.RegisterCallback<ChangeEvent<Vector3>>(x =>
|
||||
{
|
||||
fieldInfo.SetValue(target,(float3)x.newValue);
|
||||
});
|
||||
return field;
|
||||
}
|
||||
protected override void OnValueChanged(float3 oldValue, float3 newValue)
|
||||
{
|
||||
currentValue = newValue;
|
||||
VisualElement.As<INotifyValueChanged<Vector3>>().SetValueWithoutNotify(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using BITKit.UX;
|
||||
using Newtonsoft.Json;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
@@ -11,163 +16,126 @@ using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.IData
|
||||
{
|
||||
public sealed class UXDataBindingHandler:IDisposable
|
||||
{
|
||||
internal unsafe UXDataBindingHandler(void* ptr, object value)
|
||||
{
|
||||
ValueObject = ptr;
|
||||
SafeValueObject = value;
|
||||
|
||||
visualElement = value switch
|
||||
{
|
||||
Vector3 => new Vector3Field(),
|
||||
float => new FloatField(),
|
||||
int => new IntegerField(),
|
||||
_ => throw new InvalidCastException("未支持的数据类型"),
|
||||
};
|
||||
switch (visualElement)
|
||||
{
|
||||
case INotifyValueChanged<int> intField:
|
||||
intField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
//UnsafeUtility.AsRef<int>(ValueObject) = evt.newValue;
|
||||
//Unsafe.Write(ValueObject,evt.newValue);
|
||||
UnsafeUtility.AsRef<int>(ValueObject) = evt.newValue;
|
||||
});
|
||||
break;
|
||||
case INotifyValueChanged<float> floatField:
|
||||
floatField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
//Unsafe.Write(ValueObject,evt.newValue);
|
||||
UnsafeUtility.AsRef<float>(ValueObject) = evt.newValue;
|
||||
});
|
||||
break;
|
||||
case INotifyValueChanged<Vector3> vector3Field:
|
||||
vector3Field.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
//Unsafe.Write(ValueObject,evt.newValue);
|
||||
UnsafeUtility.AsRef<Vector3>(ValueObject) = evt.newValue;
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
internal readonly unsafe void* ValueObject;
|
||||
internal object SafeValueObject;
|
||||
public VisualElement visualElement { get; }
|
||||
public void Dispose()
|
||||
{
|
||||
visualElement.RemoveFromHierarchy();
|
||||
}
|
||||
public unsafe void OnValueChanged()
|
||||
{
|
||||
SafeValueObject = SafeValueObject switch
|
||||
{
|
||||
int => UnsafeUtility.AsRef<int>(ValueObject),
|
||||
float => UnsafeUtility.AsRef<float>(ValueObject),
|
||||
Vector3 => UnsafeUtility.AsRef<Vector3>(ValueObject),
|
||||
_ => null
|
||||
};
|
||||
switch (visualElement)
|
||||
{
|
||||
case INotifyValueChanged<int> intField:
|
||||
intField.SetValueWithoutNotify((int)SafeValueObject);
|
||||
break;
|
||||
case INotifyValueChanged<float> floatField:
|
||||
floatField.SetValueWithoutNotify((float)SafeValueObject);
|
||||
break;
|
||||
case INotifyValueChanged<Vector3> vector3Field:
|
||||
vector3Field.SetValueWithoutNotify((Vector3)SafeValueObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UXDataBindingService : MonoBehaviour
|
||||
{
|
||||
private static readonly List<UXDataBindingHandler> Handlers = new();
|
||||
|
||||
public static unsafe UXDataBindingHandler CreateBinding(void* ptr, object value)
|
||||
[RuntimeInitializeOnLoadMethod]
|
||||
private static void Reload()
|
||||
{
|
||||
var handler = new UXDataBindingHandler(ptr, value);
|
||||
Handlers.Add(handler);
|
||||
return handler;
|
||||
_Binders.Clear();
|
||||
_Instances.Clear();
|
||||
}
|
||||
public static unsafe UXDataBindingHandler CreateBinding(TypedReference typedReference)
|
||||
private static readonly Dictionary<Type,IUXDataBinder> _Binders = new();
|
||||
private static readonly CacheList<IUXDataBinder> _Instances = new();
|
||||
|
||||
public static VisualElement Create(object target)
|
||||
{
|
||||
UXDataBindingHandler handler = null;
|
||||
var value = TypedReference.ToObject(typedReference);
|
||||
var visualElement = new VisualElement();
|
||||
var fields = target.GetType().GetFields(ReflectionHelper.Flags);
|
||||
foreach (var fieldInfo in fields.Where(x=>x.GetCustomAttribute<ExportAttribute>() is not null))
|
||||
{
|
||||
var handler = UXDataBindingService.Create(target,fieldInfo);
|
||||
var ui = handler.CreateUI(target, fieldInfo);
|
||||
visualElement.Add(ui);
|
||||
}
|
||||
|
||||
foreach (var methodInfo in target.GetType().GetMethods(ReflectionHelper.Flags))
|
||||
{
|
||||
if (Attribute.IsDefined(methodInfo, typeof(ExportAttribute)) is false) continue;
|
||||
var exportAttribute = methodInfo.GetCustomAttribute<ExportAttribute>();
|
||||
|
||||
var button = visualElement.Create<Button>();
|
||||
button.text =string.IsNullOrEmpty(exportAttribute.Name) ? methodInfo.Name:exportAttribute.Name;
|
||||
|
||||
button.clicked += OnClicked;
|
||||
|
||||
continue;
|
||||
void OnClicked()
|
||||
{
|
||||
try
|
||||
{
|
||||
methodInfo.Invoke(target, null);
|
||||
}
|
||||
catch (TargetInvocationException targetInvocationException)
|
||||
{
|
||||
if (targetInvocationException.InnerException is InGameException e is false) return;
|
||||
switch (e)
|
||||
{
|
||||
case {InnerException:not null}:
|
||||
Alert.Print(e.Message,e.InnerException.Message);
|
||||
break;
|
||||
default:
|
||||
Alert.Print(e.Message,e.Source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visualElement;
|
||||
}
|
||||
|
||||
public static IUXDataBinder Create(object target,FieldInfo fieldInfo)
|
||||
{
|
||||
var value = fieldInfo.GetValue(target);
|
||||
var type = value?.GetType() ?? fieldInfo.FieldType;
|
||||
|
||||
// if (value is IEnumerable)
|
||||
// {
|
||||
// //type = typeof(IEnumerable<>).MakeGenericType(fieldInfo.FieldType.GetElementType());
|
||||
// //type = typeof(IEnumerable<>).MakeGenericType(type.GetGenericArguments());
|
||||
// }
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case int:
|
||||
{
|
||||
ref var rawValue = ref __refvalue(typedReference, int);
|
||||
handler = new UXDataBindingHandler(UnsafeUtility.AddressOf(ref rawValue), value);
|
||||
}
|
||||
case Array:
|
||||
type = typeof(IEnumerable<>).MakeGenericType(fieldInfo.FieldType.GetElementType());
|
||||
break;
|
||||
case float:
|
||||
{
|
||||
ref var rawValue = ref __refvalue(typedReference, float);
|
||||
handler = new UXDataBindingHandler(UnsafeUtility.AddressOf(ref rawValue), value);
|
||||
}
|
||||
break;
|
||||
case Vector3:
|
||||
{
|
||||
ref var rawValue = ref __refvalue(typedReference, Vector3);
|
||||
handler = new UXDataBindingHandler(UnsafeUtility.AddressOf(ref rawValue), value);
|
||||
}
|
||||
case IList:
|
||||
type = typeof(IEnumerable<>).MakeGenericType(type.GetGenericArguments());
|
||||
break;
|
||||
}
|
||||
|
||||
// var handler = (value) switch
|
||||
// {
|
||||
// case int=> new UXDataBindingHandler(UnsafeUtility.AddressOf(ref value), value),
|
||||
// _ => throw new InvalidCastException("未支持的数据类型"),
|
||||
// };
|
||||
|
||||
//var handler = new UXDataBindingHandler(&value, __refvalue(typedReference, object));
|
||||
Handlers.Add(handler);
|
||||
return handler;
|
||||
if (_Binders.TryGetValue(type, out var binder) is false)
|
||||
throw new NotImplementedException($"未支持的类型:{type.Name}");
|
||||
|
||||
var instance = (IUXDataBinder)Activator.CreateInstance(binder.GetType());
|
||||
_Instances.Add(instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
[SerializeReference, SubclassSelector] private ITicker ticker;
|
||||
|
||||
[SerializeReference,SubclassSelector] private ITicker ticker;
|
||||
|
||||
[SerializeField,ReadOnly] private int bindingCount;
|
||||
private void Start()
|
||||
private async void Start()
|
||||
{
|
||||
ticker.Add(OnTick);
|
||||
destroyCancellationToken.Register(() => { ticker.Remove(OnTick); });
|
||||
}
|
||||
|
||||
var binders = await ReflectionHelper.GetTypes<IUXDataBinder>();
|
||||
if(destroyCancellationToken.IsCancellationRequested)return;
|
||||
|
||||
private void OnTick(float obj)
|
||||
{
|
||||
foreach (var handler in Handlers.ToArray())
|
||||
foreach (var x in binders)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (handler.visualElement.parent is null)
|
||||
{
|
||||
Handlers.Remove(handler);
|
||||
continue;
|
||||
}
|
||||
|
||||
object value = handler.SafeValueObject switch
|
||||
{
|
||||
int => UnsafeUtility.AsRef<int>(handler.ValueObject),
|
||||
float => UnsafeUtility.AsRef<float>(handler.ValueObject),
|
||||
Vector3 => UnsafeUtility.AsRef<Vector3>(handler.ValueObject),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (value != handler.SafeValueObject)
|
||||
{
|
||||
handler.OnValueChanged();
|
||||
}
|
||||
}
|
||||
var instance = (IUXDataBinder)Activator.CreateInstance(x);
|
||||
if (_Binders.TryAdd(instance.TargetType, instance)) ;
|
||||
}
|
||||
|
||||
ticker.Add(OnTick);
|
||||
|
||||
destroyCancellationToken.Register(() =>
|
||||
{
|
||||
ticker.Remove(OnTick);
|
||||
});
|
||||
}
|
||||
private static void OnTick(float obj)
|
||||
{
|
||||
var array = _Instances.ValueArray;
|
||||
foreach (var x in array)
|
||||
{
|
||||
if (x.VisualElement.panel is null)
|
||||
{
|
||||
_Instances.Remove(x);
|
||||
continue;
|
||||
}
|
||||
x.OnTick(obj);
|
||||
}
|
||||
bindingCount = Handlers.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.IData
|
||||
{
|
||||
public abstract class UXIEnumerableBindingHandler<TDataType> : IUXDataBinder
|
||||
{
|
||||
|
||||
public Type TargetType =>typeof(IEnumerable<TDataType>);
|
||||
public VisualElement VisualElement { get;private set; }
|
||||
protected object target { get; private set; }
|
||||
protected FieldInfo fieldInfo{ get; private set; }
|
||||
protected VisualElement container { get; private set; }
|
||||
protected Button addButton;
|
||||
protected readonly List<TDataType> interfaceList=new();
|
||||
|
||||
public VisualElement CreateUI(object target, FieldInfo fieldInfo)
|
||||
{
|
||||
if(VisualElement is not null){throw new InvalidOperationException($"已经创建了Inspector<{VisualElement.GetType().Name}>");}
|
||||
var exportAttribute = fieldInfo.GetCustomAttribute<ExportAttribute>();
|
||||
|
||||
this.fieldInfo = fieldInfo;
|
||||
this.target = target;
|
||||
|
||||
// VisualElement = new Foldout();
|
||||
//
|
||||
// VisualElement.Create<Label>().text = exportAttribute switch
|
||||
// {
|
||||
// not null when string.IsNullOrEmpty(exportAttribute.Name) is false => exportAttribute.Name,
|
||||
// _=>fieldInfo.Name
|
||||
// };
|
||||
|
||||
VisualElement = new Foldout()
|
||||
{
|
||||
text = exportAttribute switch
|
||||
{
|
||||
not null when string.IsNullOrEmpty(exportAttribute.Name) is false => exportAttribute.Name,
|
||||
_=>fieldInfo.Name
|
||||
}
|
||||
};
|
||||
VisualElement.AddToClassList("--button");
|
||||
|
||||
container = VisualElement.Create<VisualElement>();
|
||||
addButton = VisualElement.Create<Button>();
|
||||
|
||||
addButton.text = "+";
|
||||
|
||||
addButton.clicked += Add;
|
||||
|
||||
Rebuild();
|
||||
|
||||
return VisualElement;
|
||||
}
|
||||
|
||||
|
||||
private void Add()
|
||||
{
|
||||
interfaceList.Add(default);
|
||||
|
||||
switch (fieldInfo.GetValue(target))
|
||||
{
|
||||
case List<TDataType> list:
|
||||
list.Add(default);
|
||||
break;
|
||||
case TDataType[] array:
|
||||
Array.Resize(ref array, array.Length + 1);
|
||||
fieldInfo.SetValue(target, array);
|
||||
break;
|
||||
}
|
||||
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
protected void Rebuild()
|
||||
{
|
||||
interfaceList.Clear();
|
||||
interfaceList.AddRange((IEnumerable<TDataType>)fieldInfo.GetValue(target));
|
||||
|
||||
container.Clear();
|
||||
|
||||
for (var index = 0; index < interfaceList.Count; index++)
|
||||
{
|
||||
var index1 = index;
|
||||
var entryContainer = container.Create<VisualElement>();
|
||||
|
||||
entryContainer.style.flexDirection = FlexDirection.Row;
|
||||
|
||||
var x = interfaceList[index];
|
||||
|
||||
var textField = entryContainer.Create<TextField>();
|
||||
|
||||
textField.isDelayed = true;
|
||||
|
||||
if(x is not null)
|
||||
textField.SetValueWithoutNotify(JsonConvert.SerializeObject(x));
|
||||
|
||||
textField.style.flexGrow = 1;
|
||||
textField.style.marginLeft = 0;
|
||||
|
||||
textField.RegisterValueChangedCallback(OnTextFieldOnValueChanged);
|
||||
|
||||
var removeButton = entryContainer.Create<Button>();
|
||||
//removeButton.text = $"-{index}";
|
||||
|
||||
//u know how to do
|
||||
removeButton.style.marginTop = 0;
|
||||
|
||||
removeButton.AddToClassList("icon-remove");
|
||||
|
||||
removeButton.clicked += OnRemoveButtonOnClicked;
|
||||
|
||||
container.Add(entryContainer);
|
||||
|
||||
continue;
|
||||
|
||||
void OnRemoveButtonOnClicked()
|
||||
{
|
||||
switch (fieldInfo.GetValue(target))
|
||||
{
|
||||
case List<TDataType> list:
|
||||
list.RemoveAt(index1);
|
||||
break;
|
||||
case TDataType[] array:
|
||||
interfaceList.RemoveAt(index1);
|
||||
fieldInfo.SetValue(target, interfaceList.ToArray());
|
||||
break;
|
||||
}
|
||||
Rebuild();
|
||||
}
|
||||
void OnTextFieldOnValueChanged(ChangeEvent<string> evt)
|
||||
{
|
||||
Deserialize(textField,index1,evt.newValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void OnTick(float deltaTime)
|
||||
{
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
// TODO 在此释放托管资源
|
||||
}
|
||||
protected void Deserialize(TextField textField,int index,string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newValue = (TDataType)JsonConvert.DeserializeObject(value, typeof(TDataType));
|
||||
switch (fieldInfo.GetValue(target))
|
||||
{
|
||||
case List<TDataType> list:
|
||||
list[index] = newValue;
|
||||
break;
|
||||
case TDataType[] array:
|
||||
array[index] = newValue;
|
||||
fieldInfo.SetValue(target, array);
|
||||
break;
|
||||
}
|
||||
Rebuild();
|
||||
|
||||
textField.EnableInClassList("error",false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
textField.Query<TextElement>().First().tooltip = e.Message;
|
||||
textField.EnableInClassList("error",true);
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class UXStringIEnumerableBindingHandler : UXIEnumerableBindingHandler<string>
|
||||
{
|
||||
}
|
||||
public sealed class UXFloatIEnumerableBindingHandler : UXIEnumerableBindingHandler<float>
|
||||
{
|
||||
}
|
||||
public sealed class UXIntIEnumerableBindingHandler : UXIEnumerableBindingHandler<int>
|
||||
{
|
||||
}
|
||||
public sealed class UXVector3IEnumerableBindingHandler : UXIEnumerableBindingHandler<Vector3>
|
||||
{
|
||||
}
|
||||
public sealed class UXFloat3IEnumerableBindingHandler : UXIEnumerableBindingHandler<float3>
|
||||
{
|
||||
}
|
||||
}
|
@@ -54,7 +54,7 @@ namespace BITKit.UX
|
||||
{
|
||||
Clear();
|
||||
style.flexDirection = FlexDirection.Row;
|
||||
style.justifyContent = Justify.SpaceAround;
|
||||
style.justifyContent = Justify.FlexStart;
|
||||
instanceColumns.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(Json)) return;
|
||||
@@ -64,7 +64,9 @@ namespace BITKit.UX
|
||||
var rowLength = jArray.Count;
|
||||
for (var i = 0; i < colLength; i++)
|
||||
{
|
||||
instanceColumns.Add(this.Create<VisualElement>());
|
||||
var newContainer = this.Create<VisualElement>();
|
||||
newContainer.name = $"{nameof(VisualElement)}-{i}";
|
||||
instanceColumns.Add(newContainer);
|
||||
}
|
||||
for (var y = 0; y < rowLength; y++)
|
||||
{
|
||||
|
84
Assets/BITKit/Unity/Scripts/UX/Library/ProgressBlock.cs
Normal file
84
Assets/BITKit/Unity/Scripts/UX/Library/ProgressBlock.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class ProgressBlock : VisualElement
|
||||
{
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
private readonly UxmlIntAttributeDescription m_ValueAttribute = new ()
|
||||
{
|
||||
name = "Value",defaultValue = 50
|
||||
};
|
||||
private readonly UxmlIntAttributeDescription m_SeparateAttribute = new ()
|
||||
{
|
||||
name = "Separate",defaultValue = 5
|
||||
};
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var x = (ProgressBlock)ve;
|
||||
x.Value = m_ValueAttribute.GetValueFromBag(bag, cc);
|
||||
x.Separate = m_SeparateAttribute.GetValueFromBag(bag, cc);
|
||||
}
|
||||
}
|
||||
public ProgressBlock() : base()
|
||||
{
|
||||
|
||||
}
|
||||
public new class UxmlFactory : UxmlFactory<ProgressBlock, UxmlTraits> { }
|
||||
/// <summary>
|
||||
/// 值,默认50,范围0-100
|
||||
/// </summary>
|
||||
public int Value
|
||||
{
|
||||
get => value;
|
||||
set
|
||||
{
|
||||
this.value = value;
|
||||
Rebuild();
|
||||
}
|
||||
}
|
||||
private int value;
|
||||
/// <summary>
|
||||
/// 分割线,默认5
|
||||
/// </summary>
|
||||
public int Separate
|
||||
{
|
||||
get => separate;
|
||||
set
|
||||
{
|
||||
this.separate = value;
|
||||
Rebuild();
|
||||
}
|
||||
}
|
||||
private int separate;
|
||||
private void Rebuild()
|
||||
{
|
||||
Clear();
|
||||
//if value is 58
|
||||
for (var i = 1; i <= Separate; i++)
|
||||
{
|
||||
var block = this.Create<VisualElement>();
|
||||
block.style.flexGrow = 1;
|
||||
|
||||
if (i * 10 < Value)
|
||||
{
|
||||
block.style.opacity = 1;
|
||||
}else if ((i + 1) * 10 > value)
|
||||
{
|
||||
block.style.opacity = 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
block.style.opacity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -75,13 +75,23 @@ namespace BITKit.UX
|
||||
BITKit.UX.UXUtils.Inject(this);
|
||||
if(IsValid && autoEntry)
|
||||
UXService.Entry(this);
|
||||
|
||||
var returnButton= document.rootVisualElement.Q("return-button");
|
||||
returnButton?.RegisterCallback<MouseDownEvent>(x =>
|
||||
{
|
||||
if (x.button is 0)
|
||||
OnReturn();
|
||||
});
|
||||
}
|
||||
public bool IsEntered { get; set; }
|
||||
public void Entry()
|
||||
{
|
||||
UXService.Entry(this);
|
||||
}
|
||||
|
||||
protected virtual void OnReturn()
|
||||
{
|
||||
UXService.Return();
|
||||
}
|
||||
protected virtual void OnEnable()=>UXService.Register(this);
|
||||
protected virtual void OnDisable()=>UXService.UnRegister(this);
|
||||
protected virtual void OnPanelEntry(){}
|
||||
|
70
Assets/BITKit/Unity/Scripts/UX/UXBarService/UXBarService.cs
Normal file
70
Assets/BITKit/Unity/Scripts/UX/UXBarService/UXBarService.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
[Serializable]
|
||||
public struct UXBarDictionary:IUXBarService
|
||||
{
|
||||
public static readonly ConcurrentDictionary<string, IUXBarService> BarDictionary =new();
|
||||
[SerializeReference, SubclassSelector] private IReference path;
|
||||
|
||||
public void SetOrCreate(int id, string name, float value, object data = null)
|
||||
{
|
||||
BarDictionary[path.Value].SetOrCreate(id, name, value, data);
|
||||
}
|
||||
public void Remove(int id)
|
||||
{
|
||||
BarDictionary[path.Value].Remove(id);
|
||||
}
|
||||
}
|
||||
public class UXBarService : MonoBehaviour, IUXBarService
|
||||
{
|
||||
[SerializeField] private UIDocument document;
|
||||
[SerializeReference, SubclassSelector] private IReference path;
|
||||
[SerializeReference, SubclassSelector] private IReference containerPath;
|
||||
[SerializeField] private VisualTreeAsset template;
|
||||
|
||||
private VisualElement _container;
|
||||
private readonly ConcurrentDictionary<int, UXContainer> _bars = new();
|
||||
private void Start()
|
||||
{
|
||||
_container = document.rootVisualElement.Q<VisualElement>(containerPath.Value);
|
||||
|
||||
_container.Clear();
|
||||
|
||||
UXBarDictionary.BarDictionary.GetOrAdd(path.Value, this);
|
||||
|
||||
destroyCancellationToken.Register(() =>
|
||||
{
|
||||
UXBarDictionary.BarDictionary.TryRemove(path.Value, out _);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void SetOrCreate(int id, string name, float value, object data = null)
|
||||
{
|
||||
var bar = _bars.GetOrAdd(id, Create);
|
||||
var label = bar.Get<Label>(0);
|
||||
if(label is not null)
|
||||
label.text = name;
|
||||
bar.SetProcess(value);
|
||||
}
|
||||
private UXContainer Create(int arg)
|
||||
{
|
||||
var element = new UXContainer(_container.Create(template));
|
||||
return element;
|
||||
}
|
||||
public void Remove(int id)
|
||||
{
|
||||
if (_bars.TryRemove(id, out var bar))
|
||||
{
|
||||
bar.visualElement.RemoveFromHierarchy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -51,7 +51,12 @@ namespace BITKit.UX
|
||||
)
|
||||
{
|
||||
var bindPathAtt = fieldInfo.GetCustomAttribute<UXBindPathAttribute>();
|
||||
var ve = document.rootVisualElement.Q(bindPathAtt.Path);
|
||||
VisualElement ve = document.rootVisualElement;
|
||||
foreach (var path in bindPathAtt.Path.Split("."))
|
||||
{
|
||||
ve = ve.Q(path);
|
||||
}
|
||||
//ve = document.rootVisualElement.Q(bindPathAtt.Path);
|
||||
if(bindPathAtt.CanBeNull is false && ve is null)
|
||||
BIT4Log.LogException(new NullReferenceException($"未找到{bindPathAtt.Path}"));
|
||||
fieldInfo.SetValue(self,ve);
|
||||
|
108
Assets/BITKit/Unity/Scripts/UX/Waiting/UXWaiting.cs
Normal file
108
Assets/BITKit/Unity/Scripts/UX/Waiting/UXWaiting.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BITKit.UX;
|
||||
using UnityEngine;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using kcp2k;
|
||||
using UnityEngine.Pool;
|
||||
using UnityEngine.UIElements;
|
||||
namespace BITKit
|
||||
{
|
||||
public class UXWaiting : MonoBehaviour,IUXWaiting
|
||||
{
|
||||
public sealed class WaitingHandle : IUXWaitingHandle
|
||||
{
|
||||
public string Message
|
||||
{
|
||||
get => label.text;
|
||||
set => SetMessage(value);
|
||||
}
|
||||
public object Container => container;
|
||||
internal VisualElement container;
|
||||
internal Label label;
|
||||
public async void SetMessage(string message)
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
if(container.panel!=null)
|
||||
{
|
||||
label.text = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private VisualTreeAsset handleTemplate;
|
||||
[SerializeField] private bool asGlobal;
|
||||
|
||||
[UXBindPath("waiting-container")]
|
||||
private VisualElement _container;
|
||||
[UXBindPath("waiting-root")]
|
||||
private VisualElement _root;
|
||||
|
||||
private Pool<IUXWaitingHandle> _pool;
|
||||
|
||||
public IUXWaitingHandle Get()
|
||||
{
|
||||
var handle = _pool.Take();
|
||||
Active();
|
||||
return handle;
|
||||
async void Active()
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
handle.As<WaitingHandle>().container.SetActive(true);
|
||||
_root.SetActive(true);
|
||||
}
|
||||
}
|
||||
public async void Release(IUXWaitingHandle handle)
|
||||
{
|
||||
_pool.Return(handle);
|
||||
Check();
|
||||
}
|
||||
private void Awake()
|
||||
{
|
||||
UXUtils.Inject(this);
|
||||
foreach (var x in _container.Children().ToArray())
|
||||
{
|
||||
x.RemoveFromHierarchy();
|
||||
}
|
||||
|
||||
_pool = new Pool<IUXWaitingHandle>(Create,OnReset,10);
|
||||
|
||||
if (asGlobal)
|
||||
{
|
||||
DI.Register<IUXWaiting>(this);
|
||||
}
|
||||
|
||||
_root.SetActive(false);
|
||||
}
|
||||
|
||||
private async void OnReset(IUXWaitingHandle obj)
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
obj.As<WaitingHandle>().container.SetActive(false);
|
||||
}
|
||||
private async void Check()
|
||||
{
|
||||
await UniTask.Yield();
|
||||
await UniTask.SwitchToMainThread();
|
||||
if(destroyCancellationToken.IsCancellationRequested) return;
|
||||
_root.SetActive(_container.Children().Any(x=>x.style.display != DisplayStyle.None));
|
||||
}
|
||||
|
||||
private IUXWaitingHandle Create()
|
||||
{
|
||||
var handle = new WaitingHandle
|
||||
{
|
||||
container = _container.Create(handleTemplate),
|
||||
label = _container.Q<Label>(UXConstant.ContextLabel)
|
||||
};
|
||||
|
||||
handle.SetMessage($"未初始化");
|
||||
|
||||
handle.container.SetActive(false);
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine.UIElements;
|
||||
namespace BITKit
|
||||
{
|
||||
public class WaitingScreen : MonoBehaviour
|
||||
{
|
||||
static ValidHandle isWaiting = new();
|
||||
public static void Excute(object obj)
|
||||
{
|
||||
isWaiting.AddElement(obj);
|
||||
}
|
||||
public static void Complete(object obj)
|
||||
{
|
||||
isWaiting.RemoveElement(obj);
|
||||
}
|
||||
[Header(Constant.Header.Components)]
|
||||
public UIDocument document;
|
||||
void Start()
|
||||
{
|
||||
isWaiting = new();
|
||||
isWaiting.AddListener(SetWaiting);
|
||||
|
||||
isWaiting.Invoke();
|
||||
}
|
||||
async void SetWaiting(bool wait)
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
document.rootVisualElement.SetActive(wait);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
@@ -12,6 +13,14 @@ namespace BITKit
|
||||
public abstract class VisualBehaviour:MonoBehaviour
|
||||
{
|
||||
|
||||
}
|
||||
public abstract class MonoBehaviourSingleton<T> : MonoBehaviour
|
||||
{
|
||||
public static T Singleton { get; private set; }
|
||||
protected void Awake()
|
||||
{
|
||||
if (this is T t) Singleton = t;
|
||||
}
|
||||
}
|
||||
public abstract class BITBehavior : MonoBehaviour, ICustomInspector
|
||||
{
|
||||
|
@@ -41,7 +41,7 @@ namespace BITKit
|
||||
{
|
||||
var obj = self.serializedObject.targetObject;
|
||||
var type = obj.GetType();
|
||||
var field = type.GetField(self.propertyPath, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var field = type.GetField(self.propertyPath, ReflectionHelper.Flags);
|
||||
if (field is null)
|
||||
{
|
||||
throw new NullReferenceException($"Field {self.propertyPath} is null");
|
||||
@@ -51,7 +51,6 @@ namespace BITKit
|
||||
public static void FillDefaultInspector(VisualElement container, SerializedObject serializedObject, bool hideScript)
|
||||
{
|
||||
container.Clear();
|
||||
|
||||
if (serializedObject.targetObject is null)
|
||||
{
|
||||
var label = container.Create<Label>();
|
||||
@@ -69,8 +68,8 @@ namespace BITKit
|
||||
}
|
||||
|
||||
var type = serializedObject.targetObject.GetType();
|
||||
var fieldInfo = serializedObject.targetObject.GetType().GetField(property.propertyPath, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (fieldInfo is not null && Attribute.IsDefined(fieldInfo, typeof(ReadOnlyAttribute)))
|
||||
var fieldInfo = serializedObject.targetObject.GetType().GetField(property.propertyPath, ReflectionHelper.Flags);
|
||||
if (fieldInfo is not null && type == typeof(string) && Attribute.IsDefined(fieldInfo, typeof(ReadOnlyAttribute),true))
|
||||
{
|
||||
var attribute = fieldInfo.GetCustomAttribute<ReadOnlyAttribute>();
|
||||
var _container = container.Create<VisualElement>();
|
||||
@@ -92,16 +91,60 @@ namespace BITKit
|
||||
{
|
||||
//var label = container.Create<Label>();
|
||||
//label.text =$"propertyPath:{property.propertyPath} fieldInfo:{fieldInfo} type:{type} fieldInfo:{fieldInfo}";
|
||||
var _container = container;
|
||||
|
||||
var field = new PropertyField(property)
|
||||
{
|
||||
name = "PropertyField:" + property.propertyPath
|
||||
};
|
||||
if (fieldInfo is not null && Attribute.IsDefined(fieldInfo, typeof(ReadOnlyAttribute), true))
|
||||
{
|
||||
field.SetEnabled(false);
|
||||
field.style.opacity = 1;
|
||||
}
|
||||
|
||||
|
||||
if (property.propertyPath == "m_Script" && serializedObject.targetObject != null)
|
||||
{
|
||||
field.SetEnabled(false);
|
||||
}
|
||||
|
||||
// if (fieldInfo?.FieldType == typeof(Texture2D) && fieldInfo.GetValue(serializedObject) is Texture2D texture && texture)
|
||||
// {
|
||||
// _container = container.Create<VisualElement>();
|
||||
//
|
||||
// var foldout = _container.Create<Foldout>();
|
||||
// foldout.text = property.displayName;
|
||||
//
|
||||
// var icon = foldout.Create<VisualElement>();
|
||||
// var slider = foldout.Create<Slider>();
|
||||
//
|
||||
// var width = 128;
|
||||
// var m = texture.height /width;
|
||||
//
|
||||
// icon.style.width = width;
|
||||
// icon.style.height = width*m;
|
||||
// icon.style.backgroundImage = texture;
|
||||
//
|
||||
// field.style.flexGrow = 1;
|
||||
//
|
||||
// _container.style.backgroundColor =new StyleColor(new Color32(31,31,31,255)) ;
|
||||
// _container.style.borderBottomLeftRadius = 5;
|
||||
// _container.style.borderBottomRightRadius = 5;
|
||||
// _container.style.borderTopLeftRadius = 5;
|
||||
// _container.style.borderTopRightRadius = 5;
|
||||
//
|
||||
// slider.label = "Scale";
|
||||
// slider.lowValue = 1;
|
||||
// slider.highValue = 2;
|
||||
// slider.RegisterValueChangedCallback(x =>
|
||||
// {
|
||||
// icon.style.width = width * x.newValue;
|
||||
// icon.style.height = width*m * x.newValue;
|
||||
// });
|
||||
// }
|
||||
|
||||
container.Add(field);
|
||||
_container.Add(field);
|
||||
}
|
||||
// try
|
||||
// {
|
||||
@@ -123,7 +166,10 @@ namespace BITKit
|
||||
{
|
||||
if (method.GetCustomAttribute<BITAttribute>() is null) continue;
|
||||
if (method.GetParameters().Length is not 0) continue;
|
||||
var button = new Button(() => method.Invoke(serializedObject.targetObject, null))
|
||||
var button = new Button(() =>
|
||||
{
|
||||
method.Invoke(serializedObject.targetObject, null);
|
||||
})
|
||||
{
|
||||
text = method.Name
|
||||
};
|
||||
|
@@ -12,10 +12,6 @@ namespace BITKit
|
||||
[System.Serializable]
|
||||
public class UnityPool<T> where T : Component
|
||||
{
|
||||
public UnityPool()
|
||||
{
|
||||
pool = new ObjectPool<T>(Spawn, OnGet, OnReturn, OnDestroy, maxSize: 16);
|
||||
}
|
||||
[Header(Constant.Header.Settings)]
|
||||
[SerializeField] private int defaultCapacity = 16;
|
||||
[Header(Constant.Header.Prefabs)]
|
||||
@@ -23,17 +19,32 @@ namespace BITKit
|
||||
|
||||
[Header(Constant.Header.Gameobjects)]
|
||||
[SerializeField] private Transform root;
|
||||
|
||||
private ObjectPool<T> pool;
|
||||
|
||||
public Action<T> OnGet { get; set; } = x=>x.gameObject.SetActive(true);
|
||||
public Action<T> OnReturn { get; set; } = x=>x.gameObject.SetActive(false);
|
||||
public Action<T> OnDestroy { get; set; } = x=>Object.Destroy(x.gameObject);
|
||||
private ObjectPool<T> pool=> _pool ??=
|
||||
new ObjectPool<T>
|
||||
(Spawn, OnGet, OnReturn, OnDestroy,defaultCapacity:DefaultCapacity, maxSize:DefaultCapacity);
|
||||
private ObjectPool<T> _pool;
|
||||
private readonly List<T> _list=new();
|
||||
|
||||
public T Prefab
|
||||
{
|
||||
get => prefab;
|
||||
set => prefab = value;
|
||||
}
|
||||
public Transform Root
|
||||
{
|
||||
get => root;
|
||||
set => root = value;
|
||||
}
|
||||
|
||||
public int DefaultCapacity
|
||||
{
|
||||
get => defaultCapacity;
|
||||
set => defaultCapacity = value;
|
||||
}
|
||||
|
||||
public int InstanceCount => _list.Count;
|
||||
public T Get(T element = null, Transform _root = null)
|
||||
{
|
||||
if (_list.Count == defaultCapacity)
|
||||
@@ -64,10 +75,6 @@ namespace BITKit
|
||||
_list.Remove(element);
|
||||
}
|
||||
private T Spawn() => Object.Instantiate(prefab, root);
|
||||
private void OnGet(T element) => element.gameObject.SetActive(true);
|
||||
private void OnReturn(T element) => element.gameObject.SetActive(false);
|
||||
private void OnDestroy(T element) => Object.Destroy(element.gameObject);
|
||||
|
||||
#region 拓展
|
||||
|
||||
private readonly ConcurrentDictionary<string, T> _dictionary=new();
|
||||
|
@@ -146,6 +146,10 @@ namespace BITKit
|
||||
{
|
||||
allowUpdateTime = currentTime + Interval + interval;
|
||||
}
|
||||
else
|
||||
{
|
||||
allowUpdateTime += interval;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -20,11 +21,13 @@ namespace BITKit
|
||||
private static VFXService sinleton;
|
||||
public VFX[] vfxs;
|
||||
private readonly Dictionary<string, UnityPool<Transform>> pools = new();
|
||||
private readonly ConcurrentDictionary<int, Transform> cache = new();
|
||||
|
||||
[SerializeField] private bool debug;
|
||||
[SerializeField] private Optional<int> defaultCapacity;
|
||||
private void Awake()
|
||||
{
|
||||
cache.Clear();
|
||||
sinleton = this;
|
||||
DI.Register(this);
|
||||
}
|
||||
@@ -68,16 +71,22 @@ namespace BITKit
|
||||
|
||||
public bool TryMatch(out Transform value, params string[] key)
|
||||
{
|
||||
foreach (var vfx in vfxs)
|
||||
value=cache.GetOrAdd(MathE.GetHash(key),Add);
|
||||
return value is not null;
|
||||
Transform Add(int arg)
|
||||
{
|
||||
if (vfx.IsMatch(key))
|
||||
foreach (var vfx in vfxs)
|
||||
{
|
||||
value = vfx.prefab;
|
||||
return true;
|
||||
if (vfx.IsMatch(key))
|
||||
{
|
||||
var x = vfx.prefab;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -59,6 +59,7 @@ namespace BITKit.Vehicles
|
||||
private bool isBraking;
|
||||
private readonly ValidHandle highSpeedHandle = new();
|
||||
private IUnityEntity _unityEntity;
|
||||
public Rigidbody Rigidbody => rigidbody;
|
||||
|
||||
[Inject(true)]
|
||||
private IHealth _health;
|
||||
|
12
Assets/BITKit/Unity/Scripts/WorldChunk/AIChunkService.cs
Normal file
12
Assets/BITKit/Unity/Scripts/WorldChunk/AIChunkService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
public class AIChunkService : WorldChunkService<AIChunkService>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "BITKit.WorldChunk",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:1193c2664d97cc049a6e4c486c6bce71",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:d525ad6bd40672747bde77962f1c401e",
|
||||
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
|
||||
"GUID:e34a5702dd353724aa315fb8011f08c3",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||
"GUID:045a42f233e479d41adc32d02b99631e"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
public sealed class BuildingChunkObject : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private ChunkBehaviour chunkBehaviour;
|
||||
[SerializeField] private Renderer[] interiorRenderers;
|
||||
[SerializeField] private Optional<Transform> useRoot;
|
||||
private void Start()
|
||||
{
|
||||
chunkBehaviour.OnLodChangedEvent += OnLodChanged;
|
||||
if(useRoot.Allow is false)return;
|
||||
var renderers = ListPool<Renderer>.Get();
|
||||
renderers.AddRange(interiorRenderers);
|
||||
renderers.AddRange(useRoot.Value.GetComponentsInChildren<Renderer>());
|
||||
interiorRenderers = renderers.Distinct().ToArray();
|
||||
renderers.Clear();
|
||||
ListPool<Renderer>.Release(renderers);
|
||||
}
|
||||
private void OnLodChanged(int oldLod, int newLod)
|
||||
{
|
||||
var show = newLod is 0;
|
||||
foreach (var x in interiorRenderers)
|
||||
{
|
||||
x.enabled = show;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
Assets/BITKit/Unity/Scripts/WorldChunk/ChunkBehaviour.cs
Normal file
46
Assets/BITKit/Unity/Scripts/WorldChunk/ChunkBehaviour.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITKit.OpenWorld;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
public class ChunkBehaviour : ChunkObject
|
||||
{
|
||||
public event Action<int,int> OnLodChangedEvent;
|
||||
protected override void OnLodChanged(int oldLod, int newLod)
|
||||
{
|
||||
base.OnLodChanged(oldLod, newLod);
|
||||
OnLodChangedEvent?.Invoke(oldLod,newLod);
|
||||
}
|
||||
|
||||
[BIT]
|
||||
private void CalculateBounds()
|
||||
{
|
||||
if(TryGetComponent<Collider>(out var _collider))
|
||||
{
|
||||
var bounds1 = _collider.bounds;
|
||||
size = bounds1.size;
|
||||
offset = bounds1.center - transform.position;
|
||||
|
||||
return;
|
||||
}
|
||||
Bounds bounds = new();
|
||||
foreach (var x in GetComponentsInChildren<Collider>())
|
||||
{
|
||||
bounds.Encapsulate(x.bounds);
|
||||
if (x.bounds.size.sqrMagnitude > 64)
|
||||
{
|
||||
Debug.LogWarning($"{x.gameObject.name}:Size is too large, please check the size of the collider");
|
||||
}
|
||||
}
|
||||
size = bounds.size;
|
||||
offset = bounds.center - transform.position;
|
||||
EditorUtility.SetDirty(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
73
Assets/BITKit/Unity/Scripts/WorldChunk/ChunkLodObject.cs
Normal file
73
Assets/BITKit/Unity/Scripts/WorldChunk/ChunkLodObject.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Quadtree;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
public class ChunkLodObject : MonoBehaviour, IWorldChunkObject
|
||||
{
|
||||
[Serializable]
|
||||
public struct Data
|
||||
{
|
||||
public Renderer[] renderers;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private Data[] lodObjects;
|
||||
[SerializeField,ReadOnly] private int _lod = -1;
|
||||
[SerializeField,ReadOnly] private int id;
|
||||
[SerializeField] private Vector3 initialSize;
|
||||
private Bounds _bounds;
|
||||
public Bounds GetBounds() => _bounds;
|
||||
public Node<IWorldChunkObject> ParentNode { get; set; }
|
||||
public void QuadTree_Root_Initialized(IQuadtreeRoot<IWorldChunkObject, Node<IWorldChunkObject>> root)
|
||||
{
|
||||
}
|
||||
public int Id
|
||||
{
|
||||
get=>id;
|
||||
set=>id = value;
|
||||
}
|
||||
public int Lod
|
||||
{
|
||||
get=>_lod;
|
||||
set
|
||||
{
|
||||
if (_lod is not -1)
|
||||
{
|
||||
foreach (var x in lodObjects[_lod].renderers)
|
||||
{
|
||||
if(x)x.enabled = false;
|
||||
}
|
||||
}
|
||||
_lod = value;
|
||||
if (_lod is -1) return;
|
||||
if (lodObjects.Length <= _lod) return;
|
||||
foreach (var x in lodObjects[_lod].renderers)
|
||||
{
|
||||
if(x)x.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_bounds = new Bounds(transform.position, initialSize);
|
||||
foreach (var x in lodObjects)
|
||||
foreach (var xx in x.renderers)
|
||||
{
|
||||
xx.enabled = false;
|
||||
}
|
||||
UnityWorldChunkService.Singleton.Register(this);
|
||||
destroyCancellationToken.Register(Dispose);
|
||||
}
|
||||
|
||||
private void Dispose()
|
||||
{
|
||||
UnityWorldChunkService.Singleton.Unregister(this);
|
||||
}
|
||||
}
|
||||
}
|
55
Assets/BITKit/Unity/Scripts/WorldChunk/ChunkObject.cs
Normal file
55
Assets/BITKit/Unity/Scripts/WorldChunk/ChunkObject.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.NetworkInformation;
|
||||
using Quadtree;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
public class ChunkObject : MonoBehaviour,IWorldChunkObject
|
||||
{
|
||||
[SerializeField,ReadOnly] private int _lod = -1;
|
||||
[SerializeField,ReadOnly] private int id;
|
||||
[SerializeField] protected Vector3 size = Vector3.one;
|
||||
[SerializeField] protected Vector3 offset = Vector3.one * 0.5f;
|
||||
public Bounds GetBounds() => _bounds;
|
||||
private Bounds _bounds;
|
||||
public Node<IWorldChunkObject> ParentNode { get; set; }
|
||||
public void QuadTree_Root_Initialized(IQuadtreeRoot<IWorldChunkObject, Node<IWorldChunkObject>> root)
|
||||
{
|
||||
}
|
||||
public int Id
|
||||
{
|
||||
get=>id;
|
||||
set=>id = value;
|
||||
}
|
||||
public int Lod
|
||||
{
|
||||
get=>_lod;
|
||||
set => OnLodChanged(_lod,_lod=value);
|
||||
}
|
||||
protected virtual void Start()
|
||||
{
|
||||
_bounds= new Bounds(transform.position+transform.rotation * offset,transform.rotation * size);
|
||||
UnityWorldChunkService.Singleton.Register(this);
|
||||
destroyCancellationToken.Register(Dispose);
|
||||
}
|
||||
private void Dispose()
|
||||
{
|
||||
UnityWorldChunkService.Singleton.Unregister(this);
|
||||
}
|
||||
protected virtual void OnLodChanged(int oldLod, int newLod)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
var bounds = GetBounds();
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Quadtree;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
/// <summary>
|
||||
/// 这个应该载入后就销毁,托管给纯class
|
||||
/// </summary>
|
||||
public sealed class ColliderChunkObject : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Collider[] colliders;
|
||||
[SerializeField] private ChunkBehaviour chunkBehaviour;
|
||||
private void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
chunkBehaviour.OnLodChangedEvent += OnLodChanged;
|
||||
if(colliders is {Length:0})colliders = GetComponentsInChildren<Collider>();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.Warning<ColliderChunkObject>(gameObject.name);
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
|
||||
}
|
||||
private void OnLodChanged(int arg1, int arg2)
|
||||
{
|
||||
var enabledCollider = arg2 is 0;
|
||||
foreach (var x in colliders)
|
||||
{
|
||||
try
|
||||
{
|
||||
x.enabled = enabledCollider;
|
||||
}
|
||||
catch (UnassignedReferenceException)
|
||||
{
|
||||
GetCollidersInChildren();
|
||||
OnLodChanged(arg1, arg2);
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.Warning<ColliderChunkObject>(gameObject.name);
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
[BIT]
|
||||
private void GetCollidersInChildren()
|
||||
{
|
||||
colliders = GetComponentsInChildren<Collider>();
|
||||
#if UNITY_EDITOR
|
||||
EditorUtility.SetDirty(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
23
Assets/BITKit/Unity/Scripts/WorldChunk/IChunkService.cs
Normal file
23
Assets/BITKit/Unity/Scripts/WorldChunk/IChunkService.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Quadtree;
|
||||
using Quadtree.Items;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace BITKit.OpenWorld
|
||||
{
|
||||
public interface IWorldChunkObject:IItem<IWorldChunkObject,Node<IWorldChunkObject>>
|
||||
{
|
||||
int Id { get; set; }
|
||||
int Lod { get; set; }
|
||||
}
|
||||
public interface IChunkService
|
||||
{
|
||||
void Register(IWorldChunkObject obj);
|
||||
void Unregister(IWorldChunkObject obj);
|
||||
}
|
||||
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
namespace BITKit.WorldChunk
|
||||
{
|
||||
public interface IWorldChunk
|
||||
{
|
||||
Rect GetRect();
|
||||
void SetActive(bool active);
|
||||
}
|
||||
public abstract class WorldChunk : MonoBehaviour, IWorldChunk
|
||||
{
|
||||
public static void Add(IWorldChunk chunk)
|
||||
{
|
||||
chunks.Add(chunk);
|
||||
ChunksArray = chunks.ToArray();
|
||||
}
|
||||
public static void Remove(IWorldChunk chunk)
|
||||
{
|
||||
chunks.Remove(chunk);
|
||||
ChunksArray = chunks.ToArray();
|
||||
}
|
||||
private static readonly List<IWorldChunk> chunks=new();
|
||||
public static IWorldChunk[] ChunksArray { get; private set; }= Array.Empty<IWorldChunk>();
|
||||
public abstract Rect GetRect();
|
||||
public abstract void SetActive(bool active);
|
||||
private void OnEnable()
|
||||
{
|
||||
chunks.Add(this);
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
chunks.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
namespace BITKit.WorldChunk
|
||||
{
|
||||
public class ObjectChunk : WorldChunk
|
||||
{
|
||||
public Vector2 size;
|
||||
public Renderer[] renderers;
|
||||
Vector2 posCenter
|
||||
{
|
||||
get
|
||||
{
|
||||
var pos = transform.position;
|
||||
return new()
|
||||
{
|
||||
x = pos.x,
|
||||
y = pos.z,
|
||||
};
|
||||
}
|
||||
}
|
||||
public override Rect GetRect()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
position = posCenter,
|
||||
size = size,
|
||||
};
|
||||
}
|
||||
public override void SetActive(bool active)
|
||||
{
|
||||
renderers.ForEach(x =>
|
||||
{
|
||||
x.enabled = active;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
namespace BITKit.WorldChunk
|
||||
{
|
||||
|
||||
public class TerrainChunk : WorldChunk
|
||||
{
|
||||
public Terrain terrain;
|
||||
public override Rect GetRect()
|
||||
{
|
||||
Vector3 size = terrain.terrainData.size;
|
||||
return new(transform.position, size);
|
||||
}
|
||||
public override void SetActive(bool active)
|
||||
{
|
||||
terrain.enabled = active;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user