This commit is contained in:
CortexCore
2024-05-31 01:23:15 +08:00
parent c798b224be
commit 299082fe27
164 changed files with 3604 additions and 2018 deletions

View File

@@ -6,10 +6,10 @@ namespace BITKit
public class AnimatorHelper : MonoBehaviour
{
[SerializeField] private Transform root;
[SerializeField] private bool allowAnimatorMove;
private void OnAnimatorMove()
{
if (root)
if (root && allowAnimatorMove)
root.SendMessageUpwards(nameof(OnAnimatorMove),SendMessageOptions.DontRequireReceiver);
}
private void AIAnimationEvent(string actionName)

View File

@@ -46,6 +46,8 @@ namespace BITKit
[SerializeReference, SubclassSelector] private IRemoteServices remoteServices;
[SerializeReference, SubclassSelector] private IBuildinQueryServices buildinQueryServices;
[SerializeField] private Optional<string> loadEntryScene;
[SerializeField] private UIDocument document;
[SerializeField] private bool isOffline;
@@ -178,12 +180,23 @@ namespace BITKit
YooAssetUtils.RegisterPackage(packageName.Value);
YooAssetUtils.RegisterResourcePackage(package);
if (document)
Destroy(document);
SceneManager.LoadScene(1);
if (loadEntryScene.Allow)
{
_progressLabel.text="正在加载场景...";
await package.LoadSceneAsync(loadEntryScene.Value);
if (document)
Destroy(document);
}
else
{
if (document)
Destroy(document);
SceneManager.LoadScene(1);
}
}
catch (Exception e)
{
await UniTask.SwitchToMainThread();
_progressBar.value =0;
_progressLabel.text = e.Message;
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using YooAsset;
namespace BITKit
{
public class YooAssetVersionEvent : MonoBehaviour
{
[SerializeReference, SubclassSelector] private IReference packageName;
[SerializeField] private UnityEvent<string> output;
private void Start()
{
var package = YooAssets.GetPackage(packageName.Value);
output?.Invoke(package.GetPackageVersion());
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a9619851d703d5b4fba4db3eefcdbf57
guid: d8adb662636da83409ffabc85b2df24e
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,15 +1,16 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace BITKit
{
public class ShowVersion : MonoBehaviour
{
[SerializeField,SerializeReference,SubclassSelector] private IProvider output;
[SerializeField] private UnityEvent<string> output;
private void Start()
{
output?.Set(Application.version);
output?.Invoke(Application.version);
}
}
}

View File

@@ -27,6 +27,12 @@ namespace BITKit.Console
{
Application.logMessageReceivedThreaded += EnqueueLog;
}
[BITCommand]
public static void Console_Exception_Print_StackTrace(int allow)
{
exceptionPrintStackTrace = allow is 1;
}
private static bool exceptionPrintStackTrace = false;
private class CommandSelector
{
public VisualElement Container { get; set; }
@@ -289,12 +295,14 @@ namespace BITKit.Console
break;
case LogType.Exception:
outputString.Add($"<color=red>{condition}</color>");
outputString.Add($"<color=red>{stackTrace}</color>");
if (exceptionPrintStackTrace)
outputString.Add($"<color=red>{stackTrace}</color>");
break;
default:
outputString.Add(condition);
break;
}
var length = outputString.Count;
if (length > logLineLimit)
{

View File

@@ -1,100 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using UnityEngine.Events;
using Cysharp.Threading.Tasks;
using System.Threading;
using System;
namespace BITKit
{
public class DataPlayer : Provider
{
public DataPlayer<string> player = new();
public Provider output;
public Provider outputProgess;
public Provider outputTime;
public Provider outputTotalTime;
public UnityEvent onStart = new();
public UnityEvent<float> onChangeTime = new();
public UnityEvent onStop = new();
CancellationToken cancellationToken;
public override void Set<T>(T obj)
{
switch (obj)
{
case List<string> list:
player.Set(list);
break;
case string str:
player.Set(JsonConvert.DeserializeObject<List<string>>(str));
break;
case bool _boolean:
if (_boolean)
player.Start();
else
player.Stop();
break;
case float _float:
player.SetProgress(_float);
onChangeTime.Invoke(_float);
break;
default:
throw new System.Exception();
}
}
void Awake()
{
cancellationToken = gameObject.GetCancellationTokenOnDestroy();
if (output)
player.output += output.Set;
if (outputProgess)
player.onProgess += outputProgess.Set;
if (outputTime)
player.onTime += outputTime.Set;
if (outputTotalTime)
player.onSetTotalTime += outputTotalTime.Set;
player.onStart += async () =>
{
await UniTask.SwitchToMainThread(cancellationToken);
BIT4Log.Log<DataPlayer>("开始播放");
onStart.Invoke();
};
player.onStop += async () =>
{
await UniTask.SwitchToMainThread(cancellationToken);
BIT4Log.Log<DataPlayer>("停止播放");
onStop.Invoke();
};
}
public void Play()
{
player.Start();
}
public void Stop()
{
player.Stop();
}
public void PlayOrPause()
{
player.PlayOrPause();
}
public void Offset(float time)
{
player.Offset(time);
}
public bool IsPlaying() => player.isPlaying;
public PlayerState GetState() => player.state;
public bool IsPaused() => GetState() is PlayerState.Paused;
void Start()
{
onStop.Invoke();
}
void OnDestroy()
{
player.Stop();
}
}
}

View File

@@ -0,0 +1,106 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using UnityEngine.Events;
using Cysharp.Threading.Tasks;
using System.Threading;
using System;
using UnityEngine.UIElements;
namespace BITKit
{
public class UnityDataPlayer : MonoBehaviour
{
[SerializeField] private int rate;
[SerializeField] internal DataPlayer<string> player = new();
[SerializeField] private UnityEvent<string> output;
[SerializeField] private UnityEvent<float> outputProgess;
[SerializeField] private UnityEvent<string> outputTime;
[SerializeField] private UnityEvent<string> outputTotalTime;
[SerializeField] private UnityEvent onStart = new();
[SerializeField] private UnityEvent<float> onChangeTime = new();
[SerializeField] private UnityEvent onStop = new();
[BIT]
public void Play()
{
player.frameRate = rate;
player.Start();
}
[BIT]
public void Stop()
{
player.Stop();
}
[BIT]
public void PlayOrPause()
{
player.PlayOrPause();
}
public void Offset(float time)
{
player.Offset(time);
}
public void SetData(List<string> data)
{
player.Set(data);
}
public bool IsPlaying() => player.isPlaying;
public PlayerState GetState() => player.state;
public bool IsPaused() => GetState() is PlayerState.Paused;
private void Start()
{
onStop.Invoke();
player.PlayOrPause();
player.output += output.Invoke;
player.onProgess += outputProgess.Invoke;
player.onTime += outputTime.Invoke;
player.onStart += async () =>
{
await UniTask.SwitchToMainThread();
if (destroyCancellationToken.IsCancellationRequested) return;
BIT4Log.Log<UnityDataPlayer>("开始播放");
onStart.Invoke();
};
player.onStop += async () =>
{
await UniTask.SwitchToMainThread();
if (destroyCancellationToken.IsCancellationRequested) return;
BIT4Log.Log<UnityDataPlayer>("停止播放");
onStop.Invoke();
};
player.onSetTotalTime += outputTotalTime.Invoke;
destroyCancellationToken.Register(player.Stop);
}
public void SetTime(float time)
{
player.SetProgress(time);
onChangeTime.Invoke(time);
}
}
#if UNITY_EDITOR
[CustomType(typeof(UnityDataPlayer))]
public sealed class UnityDataPlayerInpsector:BITInspector<UnityDataPlayer>
{
private ProgressBar _progressBar;
public override VisualElement CreateInspectorGUI()
{
FillDefaultInspector();
_progressBar = root.Create<ProgressBar>();
return root;
}
protected override void OnUpdate()
{
_progressBar.value = agent.player.index;
_progressBar.highValue = agent.player.list.Count;
base.OnUpdate();
}
}
#endif
}

View File

@@ -62,6 +62,7 @@ namespace BITKit.Entities
/// </summary>
public interface IEntityMovement:IStateMachine<IEntityMovementState>
{
Vector3 Size => Vector3.one;
float ReferenceSpeed => 2.5f;
Vector3 Position { get; set; }
Quaternion Rotation { get; set; }
@@ -138,5 +139,6 @@ namespace BITKit.Entities
void BeforeUpdateMovement(float deltaTime);
void AfterUpdateMovement(float deltaTime);
void ExecuteCommand<T>(T command);
void DrawGizmos();
}
}

View File

@@ -81,8 +81,8 @@ namespace BITKit.Entities
{
public override DamageMessage ReadBinary(BinaryReader reader)
{
UnityEntitiesService.TryGetEntity(reader.ReadUInt64(),out var initiator);
UnityEntitiesService.TryGetEntity(reader.ReadUInt64(),out var target);
UnityEntitiesService.TryGetEntity(reader.ReadInt32(),out var initiator);
UnityEntitiesService.TryGetEntity(reader.ReadInt32(),out var target);
return new DamageMessage()
{
Initiator = initiator as Entity,
@@ -114,6 +114,7 @@ namespace BITKit.Entities
IHealth Health { get; }
IUnityEntity UnityEntity { get; }
Rigidbody Rigidbody { get; }
Collider Collider { get; }
void GiveDamage(DamageMessage message);
}
public class DamageService:MonoBehaviour,IDamageService

View File

@@ -90,11 +90,6 @@ namespace BITKit.Entities
OnSetAlive?.Invoke(alive);
}
private void AddHP(int hp)
{
OnHealthPointChangedInternal(healthPoint, healthPoint += hp);
}
private void OnGetDamage(DamageMessage damageMessage)
{
if (damageMessage.Target != UnityEntity) return;
@@ -114,11 +109,23 @@ namespace BITKit.Entities
break;
}
}
if (Data.Get<bool>("god") is false)
AddHP(-damage);
damageMessage.Damage = damage;
if (Data.Get<bool>("god") is false)
{
healthPoint-=damage;
}
IsAlive = healthPoint >= 0;
OnSetHealthPoint?.Invoke(healthPoint);
OnDamageRelease?.Invoke(damageMessage);
if (!IsAlive)
{
OnSetAliveInternal(false);
}
}
}
#if UNITY_EDITOR

View File

@@ -27,10 +27,24 @@ namespace BITKit.Entities
}
public Rigidbody Rigidbody
{
get=>m_rigidbody;
get
{
EnsureConfigure();
return m_rigidbody;
}
set=>m_rigidbody=value;
}
public Collider Collider
{
get
{
EnsureConfigure();
return _collider;
}
}
private Collider _collider;
public void GiveDamage(DamageMessage message)
{
if (_unityEntity is not null)
@@ -53,6 +67,7 @@ namespace BITKit.Entities
_unityEntity = GetComponentInParent<IUnityEntity>(true);
_unityEntity?.Inject(this);
_initialized = true;
_collider = GetComponent<Collider>();
}
}
}

View File

@@ -3,7 +3,9 @@ using System.Collections.Generic;
using BITFALL.Rig;
using BITKit.Entities;
using Cysharp.Threading.Tasks.Triggers;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
namespace BITKit
@@ -14,6 +16,7 @@ namespace BITKit
[SerializeField] private EntityHitbox[] hitboxes;
[SerializeReference,SubclassSelector] private IReference[] tagReferences;
#if UNITY_EDITOR
[BIT]
private void Build()
{
@@ -73,5 +76,6 @@ namespace BITKit
}
EditorUtility.SetDirty(this);
}
#endif
}
}

View File

@@ -1,3 +1,16 @@
{
"name": "BITKit.Entities.Physics"
}
"name": "BITKit.Entities.Physics",
"rootNamespace": "",
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -10,6 +10,7 @@ namespace BITKit.Entities.Physics
/// </summary>
public interface IEntityPhysics
{
ValidHandle DisablePhysics { get; }
Vector3 Center { get; }
bool IsPhysics { get; }
Vector3 Velocity { get; set; }

View File

@@ -20,7 +20,7 @@ namespace BITKit.Entities
private readonly GenericEvent genericEvent = new();
public ulong Id { get; set; }
public int Id { get; set; }
public CancellationToken CancellationToken { get; private set; }
public IEntityBehavior[] Behaviors { get;private set; }
public IEntityComponent[] Components => Behaviors.Cast<IEntityComponent>().ToArray();
@@ -33,9 +33,43 @@ namespace BITKit.Entities
IServiceProvider Entities.IEntity.ServiceProvider=> throw new InvalidOperationException("Unity Entity can't register component");
public void Inject(object obj)
{
foreach (var propertyInfo in obj
.GetType()
.GetProperties(ReflectionHelper.Flags)
.Where(x=>x.GetCustomAttribute<InjectAttribute>(true) is not null))
{
var type = propertyInfo.PropertyType;
var attribute = propertyInfo.GetCustomAttribute<InjectAttribute>();
var currentValue = propertyInfo.GetValue(obj);
try
{
switch (currentValue)
{
case null:
break;
case IEntityComponent entityComponent:
if(entityComponent.Entity.Id == Id)
continue;
break;
case MonoBehaviour { destroyCancellationToken: { IsCancellationRequested: false } }:
continue;
case not null:
continue;
}
}
catch (MissingReferenceException){}
if(genericEvent.TryGetObjectDirect(type.FullName,out var value))
{
propertyInfo.SetValue(obj,value,null);
}
else if(attribute?.CanBeNull is false)
{
BIT4Log.Warning<Entity>($"{name}未找到{obj.GetType().Name}需要的{type.FullName}");
}
}
foreach (var fieldInfo in obj
.GetType()
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.GetFields(ReflectionHelper.Flags)
.Where(fieldInfo=>fieldInfo.GetCustomAttribute<InjectAttribute>(true) is not null))
{
var type = fieldInfo.FieldType;
@@ -97,7 +131,7 @@ namespace BITKit.Entities
private void Awake()
{
if (Id.IsDefault())
Id = (ulong)Guid.NewGuid().GetHashCode();
Id = GetInstanceID();
CancellationToken = gameObject.GetCancellationTokenOnDestroy();
Set(CancellationToken);
}

View File

@@ -48,6 +48,13 @@ namespace BITKit.Entities
stateMachine.Initialize();
}
}
public override void OnDestroyComponent()
{
base.OnDestroyComponent();
CurrentState?.OnStateExit(CurrentState,null);
}
void IStateMachine<T>.Initialize()
{
stateMachine.Initialize();

View File

@@ -38,10 +38,10 @@ public class UnityEntitiesServiceSingleton:IEntitiesService
public CancellationToken CancellationToken => UnityEntitiesService.CancellationToken;
public IEntity Get(ulong id) => UnityEntitiesService.Get(id);
public bool TryGetEntity(ulong id, out IEntity entity) => UnityEntitiesService.TryGetEntity(id, out entity);
public IEntity Get(int id) => UnityEntitiesService.Get(id);
public bool TryGetEntity(int id, out IEntity entity) => UnityEntitiesService.TryGetEntity(id, out entity);
public IEntity GetOrAdd(ulong id, Func<ulong, IEntity> factory)=>UnityEntitiesService.GetOrAdd(id,factory);
public IEntity GetOrAdd(int id, Func<int, IEntity> factory)=>UnityEntitiesService.GetOrAdd(id,factory);
public IEntity[] Query<T>()
@@ -85,7 +85,7 @@ public class UnityEntitiesService : MonoBehaviour,IEntitiesService
return false;
}
public static CancellationToken CancellationToken;
private static readonly ConcurrentDictionary<ulong,IEntity> Dictionary=new();
private static readonly ConcurrentDictionary<int,IEntity> Dictionary=new();
private static readonly Queue<IEntity> RegisterQueue = new();
private static readonly Queue<IEntity> UnRegisterQueue = new();
private void Awake()
@@ -126,18 +126,18 @@ public class UnityEntitiesService : MonoBehaviour,IEntitiesService
}
CancellationToken IEntitiesService.CancellationToken => CancellationToken;
public static IEntity Get(ulong id)=>Dictionary[id];
IEntity IEntitiesService.Get(ulong id)=>Get(id);
public static bool TryGetEntity(ulong id, out IEntity entity)
public static IEntity Get(int id)=>Dictionary[id];
IEntity IEntitiesService.Get(int id)=>Get(id);
public static bool TryGetEntity(int id, out IEntity entity)
{
return Dictionary.TryGetValue(id, out entity);
}
bool IEntitiesService.TryGetEntity(ulong id, out IEntity entity)=>TryGetEntity(id,out entity);
public static IEntity GetOrAdd(ulong id, Func<ulong, IEntity> factory)
bool IEntitiesService.TryGetEntity(int id, out IEntity entity)=>TryGetEntity(id,out entity);
public static IEntity GetOrAdd(int id, Func<int, IEntity> factory)
{
return Dictionary.GetOrAdd(id, factory);
}
IEntity IEntitiesService.GetOrAdd(ulong id, Func<ulong, IEntity> factory)=>GetOrAdd(id,factory);
IEntity IEntitiesService.GetOrAdd(int id, Func<int, IEntity> factory)=>GetOrAdd(id,factory);
IEntity[] IEntitiesService.Query<T>()=>Query<T>();
public static IEntity[] Query<T>()

View File

@@ -7,14 +7,14 @@ namespace BITKit.Entities
{
public interface IdComponent
{
ulong Id { get; }
int Id { get; }
string Name { get; }
}
public class UnityIdComponent : EntityBehavior,IdComponent
{
[SerializeField] private ulong id;
[SerializeField] private int id;
[SerializeField] private string unityName;
public ulong Id => id;
public int Id => id;
public string Name => unityName;
public override void Initialize(IEntity _entity)
{

View File

@@ -19,7 +19,7 @@ namespace BITKit.Entities
public struct EntitiesNetSyncBatchCommand
{
public int Length;
public ulong[] Ids;
public int[] Ids;
public ulong[] AddressablePaths;
public EntitiesNetSyncCommand[] Commands;
}
@@ -81,7 +81,7 @@ namespace BITKit.Entities
{
using var ms = new MemoryStream(obj.Data);
using var reader = new BinaryReader(ms);
var id = reader.ReadUInt64();
var id = reader.ReadInt32();
var path = reader.ReadUInt64();
entitiesService.GetOrAdd(id, x => AddEntity(id, path)).TryGetComponent<IEntityBinaryHeader>(out var header);
header.Deserialize(reader);
@@ -100,7 +100,7 @@ namespace BITKit.Entities
header.Deserialize(reader);
}
}
private static IEntity AddEntity(ulong id,ulong addressableId)
private static IEntity AddEntity(int id,ulong addressableId)
{
var entity = AddressableHelper.Get<GameObject>(addressableId);
var instance = Instantiate(entity).GetComponent<Entity>();
@@ -146,7 +146,7 @@ namespace BITKit.Entities
var batchCommand = new EntitiesNetSyncBatchCommand()
{
Length = headers.Length,
Ids = new ulong[headers.Length],
Ids = new int[headers.Length],
AddressablePaths = new ulong[headers.Length],
Commands = new EntitiesNetSyncCommand[headers.Length]
};

View File

@@ -15,8 +15,8 @@ namespace BITKit.Entities.Player
[SerializeField] private MonoStateMachine<T> stateMachine;
public override void OnAwake()
{
stateMachine?.Initialize();
base.OnAwake();
stateMachine?.Initialize();
}
public bool Enabled

View File

@@ -2,12 +2,14 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Events;
using Debug = UnityEngine.Debug;
using Timer = System.Timers.Timer;
namespace BITKit.Http
@@ -22,20 +24,33 @@ namespace BITKit.Http
[Header(Constant.Header.Output)]
[SerializeField] private UnityEvent<string> output;
[SerializeField] private UnityEvent<string> onException;
[SerializeField] private UnityEvent<int> onCalculateLength;
[Header(Constant.Header.Optional)]
[SerializeField] private Optional<string> overrideResponse;
[Header(Constant.Header.Debug)]
[SerializeField, ReadOnly] private double time;
[SerializeField, ReadOnly] private bool isConnected;
private readonly Timer timer = new()
{
AutoReset = true
};
private readonly HttpClient _httpClient = new();
private CancellationToken _cancellationToken;
private bool _enabled;
private void Awake()
private void OnEnable()
{
_cancellationToken = gameObject.GetCancellationTokenOnDestroy();
_enabled = true;
}
private void OnDisable()
{
_enabled = false;
}
private void Start()
{
@@ -50,6 +65,7 @@ AutoReset = true
_ => throw new ArgumentOutOfRangeException()
};
timer.Start();
destroyCancellationToken.ThrowIfCancellationRequested();
}
private void OnDestroy()
{
@@ -58,26 +74,83 @@ AutoReset = true
}
private async void OnUpdate()
{
Stopwatch stopwatch = new();
stopwatch.Start();
//var result = await _httpClient.GetStringAsync(url.Value);
var response = await _httpClient.GetAsync(url.Value, _cancellationToken);
var result =await response.Content.ReadAsStringAsync();
_cancellationToken.ThrowIfCancellationRequested();
try
{
if (_enabled)
{
stopwatch.Stop();
time =System.TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds;
await UniTask.SwitchToMainThread(_cancellationToken);
if (response.StatusCode is not System.Net.HttpStatusCode.OK)
{
onException.Invoke(result);
if (overrideResponse.Allow)
{
await UniTask.SwitchToMainThread();
try
{
output.Invoke(overrideResponse.Value);
}
catch (Exception e)
{
Debug.LogException(e);
}
isConnected = true;
}
else
{
Stopwatch stopwatch = new();
stopwatch.Start();
//var result = await _httpClient.GetStringAsync(url.Value);
var response = await _httpClient.GetAsync(url.Value,destroyCancellationToken);
if (destroyCancellationToken.IsCancellationRequested) return;
var result =await response.Content.ReadAsStringAsync();
if (destroyCancellationToken.IsCancellationRequested) return;
stopwatch.Stop();
time =System.TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds;
await UniTask.SwitchToMainThread();
if (destroyCancellationToken.IsCancellationRequested) return;
try
{
var length = int.Parse(response.Content.Headers.First(h => h.Key.Equals("Content-Length")).Value.First());
onCalculateLength.Invoke(length);
}
catch (InvalidOperationException)
{
}
if (response.StatusCode is not System.Net.HttpStatusCode.OK)
{
onException.Invoke(result);
}
else
{
try
{
output.Invoke(result);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
isConnected = true;
}
}
}
else
catch(OperationCanceledException){}
catch (Exception e)
{
output.Invoke(result);
BIT4Log.LogException(e);
isConnected = false;
}
if(timer.AutoReset==false)
if (timer.AutoReset == false)
{
if(destroyCancellationToken.IsCancellationRequested)return;
timer.Start();
}
}
}
}

View File

@@ -0,0 +1,20 @@
{
"name": "BITKit.MarkSystem.Runtime",
"rootNamespace": "",
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
"GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008",
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:0b8afac07cb6a864385843f87eaa0e3f"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a8172872d3daab147bc7fd15fd3a97a6
guid: 8915fe727b166284f9a8cbe958b03a05
AssemblyDefinitionImporter:
externalObjects: {}
userData:

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 80240497e71068c4f993ece76ba2d522
guid: 208a5380beb526044819d512687a0eaf
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -2,14 +2,11 @@
"name": "BITKit.MarkSystem",
"rootNamespace": "",
"references": [
"GUID:a209c53514018594f9f482516f2a6781",
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
"GUID:66d2ae14764cc7d49aad4b16930747c0",
"GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:be17a8778dbfe454890ed8279279e153",
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
"GUID:9400d40641bab5b4a9702f65bf5c6eb5"
"GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008",
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:d8b63aba1907145bea998dd612889d6b"
],
"includePlatforms": [],
"excludePlatforms": [],
@@ -17,9 +14,7 @@
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [
"ODIN_INSPECTOR"
],
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,28 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BITKit.MarkSystem
{
public interface IMarkObject
{
public int Id { get; }
public Vector3 Position { get; }
public object Object { get; }
}
public interface IMarkSystem
{
public void Register(IMarkObject markObject);
public void UnRegister(IMarkObject markObject);
}
public class MarkObject : IMarkObject
{
public override bool Equals(object obj) => obj is MarkObject x && x.Id == Id;
public override int GetHashCode() => Id.GetHashCode();
private static int count;
public int Id { get; } = count++;
public Vector3 Position { get; set; }
public object Object { get; set; }
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 7869b4bffebc8f6439819f03f741da2b
guid: 26e45143692a3de45b4efef997c74811
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,125 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using BITKit.SubSystems;
using UnityEngine;
namespace BITKit.Mark
{
public interface IMarkObject
{
string GetID();
string GetDisplay();
Vector3 GetPostion();
bool GetAcitve();
}
public interface IMarkSystem
{
event Action<IMarkObject> OnAdd;
event Action<IMarkObject> OnUpdate;
event Action<IMarkObject> OnRemove;
void Add(IMarkObject markObject);
void Remove(IMarkObject markObject);
}
[Serializable]
public class MarkSystemMonoProxy:IMarkSystem
{
[SerializeReference] private MonoBehaviour monoBehaviour;
private IMarkSystem _markSystemImplementation=>monoBehaviour as IMarkSystem;
public event Action<IMarkObject> OnAdd
{
add => _markSystemImplementation.OnAdd += value;
remove => _markSystemImplementation.OnAdd -= value;
}
public event Action<IMarkObject> OnUpdate
{
add => _markSystemImplementation.OnUpdate += value;
remove => _markSystemImplementation.OnUpdate -= value;
}
public event Action<IMarkObject> OnRemove
{
add => _markSystemImplementation.OnRemove += value;
remove => _markSystemImplementation.OnRemove -= value;
}
public void Add(IMarkObject markObject)
{
_markSystemImplementation.Add(markObject);
}
public void Remove(IMarkObject markObject)
{
_markSystemImplementation.Remove(markObject);
}
}
[Serializable]
public class MarkSystemSingleton:IMarkSystem
{
private IMarkSystem _markSystemImplementation=>MarkSystem.Singleton;
public event Action<IMarkObject> OnAdd
{
add => _markSystemImplementation.OnAdd += value;
remove => _markSystemImplementation.OnAdd -= value;
}
public event Action<IMarkObject> OnUpdate
{
add => _markSystemImplementation.OnUpdate += value;
remove => _markSystemImplementation.OnUpdate -= value;
}
public event Action<IMarkObject> OnRemove
{
add => _markSystemImplementation.OnRemove += value;
remove => _markSystemImplementation.OnRemove -= value;
}
public void Add(IMarkObject markObject)
{
_markSystemImplementation.Add(markObject);
}
public void Remove(IMarkObject markObject)
{
_markSystemImplementation.Remove(markObject);
}
}
public class MarkSystem : MonoBehaviour,IMarkSystem
{
internal static IMarkSystem Singleton { get; private set; }
public event Action<IMarkObject> OnAdd;
public event Action<IMarkObject> OnUpdate;
public event Action<IMarkObject> OnRemove;
List<IMarkObject> markObjects = new();
public void Add(IMarkObject markObject)
{
markObjects.Add(markObject);
OnAdd?.Invoke(markObject);
}
public void Remove(IMarkObject markObject)
{
markObjects.TryRemove(markObject);
OnRemove?.Invoke(markObject);
}
private void Awake()
{
Singleton = this;
}
private void Update()
{
foreach (var markObject in markObjects.ToArray())
{
OnUpdate?.Invoke(markObject);
}
}
}
}

View File

@@ -1,129 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BITKit.UX;
using BITKit.SubSystems;
using UnityEngine.UIElements;
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
using System.Threading;
namespace BITKit.Mark
{
public class MarkSystemUIToolkit : MonoBehaviour
{
public UXElement element;
[SerializeField,SerializeReference, SubclassSelector] private References className;
[SerializeField, SerializeReference, SubclassSelector]
private IMarkSystem markSystem;
Dictionary<string, Label> dictionary = new();
IPanel panel;
CancellationToken cancellationToken;
void Awake()
{
cancellationToken = gameObject.GetCancellationTokenOnDestroy();
}
void Start()
{
panel = element.GetVisualElement().panel;
if (markSystem is not null)
{
markSystem.OnAdd += OnAdd;
markSystem.OnUpdate += OnUpdate;
markSystem.OnRemove += OnRemove;
}
}
void OnDestroy()
{
if (markSystem is not null)
{
markSystem.OnAdd -= OnAdd;
markSystem.OnUpdate -= OnUpdate;
markSystem.OnRemove -= OnRemove;
}
}
void OnAdd(IMarkObject markObject)
{
GetOrCreate(markObject.GetID());
}
async void OnRemove(IMarkObject markObject)
{
try
{
await UniTask.SwitchToMainThread(cancellationToken);
if (dictionary.TryGetValue(markObject.GetID(), out var label))
{
element.GetVisualElement().Remove(label);
dictionary.Remove(markObject.GetID());
}
}
catch (System.Exception e)
{
if (e is not OperationCanceledException)
throw;
}
}
async void OnUpdate(IMarkObject markObject)
{
var cameraTrans = Camera.main.transform;
try
{
await UniTask.SwitchToMainThread();
var active = markObject.GetAcitve();
var label = GetOrCreate(markObject.GetID());
if (active)
{
var pos = RuntimePanelUtils
.CameraTransformWorldToPanel(panel, markObject.GetPostion(), Camera.main);
pos.x = (pos.x - label.layout.width / 2);
Rect elementRect = new()
{
position = pos,
size = label.layout.size,
};
label.text = markObject.GetDisplay();
label.transform.position = pos;
if (Vector3.Dot(cameraTrans.forward, markObject.GetPostion() - cameraTrans.position) > 0)
{
label.SetActive(true);
}
else
{
label.SetActive(false);
}
}
else
{
label.SetActive(false);
}
}
catch (OperationCanceledException)
{
}
catch (System.Exception)
{
throw;
}
}
Label GetOrCreate(string id)
{
if (dictionary.TryGetValue(id, out var label))
{
}
else
{
label = new();
label.AddToClassList(className);
label.style.position = Position.Absolute;
dictionary.Add(id, label);
element.GetVisualElement().Add(label);
}
return label;
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using BITKit.MarkSystem;
using I18N.Other;
using UnityEngine;
using UnityEngine.UIElements;
namespace BITKit.UX
{
[CustomType(typeof(IMarkSystem))]
public class UXMarkSystem : MonoBehaviour,IMarkSystem
{
[SerializeField] private VisualTreeAsset template;
[UXBindPath("mark-container")]
private VisualElement _container;
private readonly CacheList<IMarkObject> _markObjects = new();
private readonly ConcurrentDictionary<int,VisualElement> _dictionary = new();
private void OnEnable()
{
DI.Register<IMarkSystem>(this);
UXUtils.Inject(this);
_container.Clear();
}
public void Register(IMarkObject markObject)
{
_markObjects.Add(markObject);
_dictionary.GetOrAdd(markObject.Id,Create);
}
public void UnRegister(IMarkObject markObject)
{
_markObjects.Remove(markObject);
_dictionary.TryRemove(markObject.Id,out var x);
x.RemoveFromHierarchy();
}
private void LateUpdate()
{
foreach (var x in _markObjects.ValueArray)
{
_dictionary[x.Id].SetPosition(x.Position);
}
}
private VisualElement Create(int id)
{
return _container.Create(template);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d538ed72a7d2543d390cf79dbb36413c
guid: 3cd5204b8318b3d408d1e2fc2818bf79
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -16,7 +16,6 @@ namespace BITKit.Mod
public class UnityModService : MonoBehaviour
{
[SerializeReference,SubclassSelector] private IReference[] referencedAssemblies;
[SerializeField] private bool loadLocalPackageOnStart;
private async void Start()
{
DI.TryGet<IUXWaiting>(out var waiting);
@@ -57,8 +56,6 @@ namespace BITKit.Mod
destroyCancellationToken.Register(ModService.Dispose);
if (!loadLocalPackageOnStart) return;
ModService.OnPackageLoad+=OnPackageLoad;

View File

@@ -1,59 +0,0 @@
using System;
using Unity.Mathematics;
namespace NativeQuadTree
{
[Serializable]
public struct AABB2D {
public float2 Center;
public float2 Extents;
public float2 Size => Extents * 2;
public float2 Min => Center - Extents;
public float2 Max => Center + Extents;
public AABB2D(float2 center, float2 extents)
{
Center = center;
Extents = extents;
}
public bool Contains(float2 point) {
if (point[0] < Center[0] - Extents[0]) {
return false;
}
if (point[0] > Center[0] + Extents[0]) {
return false;
}
if (point[1] < Center[1] - Extents[1]) {
return false;
}
if (point[1] > Center[1] + Extents[1]) {
return false;
}
return true;
}
public bool Contains(AABB2D b) {
return Contains(b.Center + new float2(-b.Extents.x, -b.Extents.y)) &&
Contains(b.Center + new float2(-b.Extents.x, b.Extents.y)) &&
Contains(b.Center + new float2(b.Extents.x, -b.Extents.y)) &&
Contains(b.Center + new float2(b.Extents.x, b.Extents.y));
}
public bool Intersects(AABB2D b)
{
//bool noOverlap = Min[0] > b.Max[0] ||
// b.Min[0] > Max[0]||
// Min[1] > b.Max[1] ||
// b.Min[1] > Max[1];
//
//return !noOverlap;
return (math.abs(Center[0] - b.Center[0]) < (Extents[0] + b.Extents[0])) &&
(math.abs(Center[1] - b.Center[1]) < (Extents[1] + b.Extents[1]));
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: aede0b75cc8640798d9a285521f5d6d1
timeCreated: 1578858902

View File

@@ -1,18 +0,0 @@
{
"name": "BITKit.NativeQuadTree.Runtime",
"rootNamespace": "",
"references": [
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:e0cd26848372d4e5c891c569017e11f1",
"GUID:2665a8d13d1b3f18800f46e256720795"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -1,24 +0,0 @@
{
"name": "BITKit.NativeQuadTree.Editor",
"rootNamespace": "",
"references": [
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:e0cd26848372d4e5c891c569017e11f1",
"GUID:a9eec99827e569e45bfe3e5ea7494591",
"GUID:27619889b8ba8c24980f49ee34dbb44a",
"GUID:0acc523941302664db1f4e527237feb3"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": true,
"defineConstraints": [
"UNITY_EDITOR"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -1,61 +0,0 @@
using NativeQuadTree;
using Unity.Collections;
using UnityEditor;
using UnityEngine;
public class QuadTreeDrawer : EditorWindow
{
[MenuItem("Window/QuadTreeDrawer")]
static void Init()
{
GetWindow(typeof(QuadTreeDrawer)).Show();
}
public static void Draw<T>(NativeQuadTree<T> quadTree) where T : unmanaged
{
QuadTreeDrawer window = (QuadTreeDrawer)GetWindow(typeof(QuadTreeDrawer));
window.DoDraw(quadTree, default, default);
}
public static void DrawWithResults<T>(QuadTreeJobs.RangeQueryJob<T> queryJob) where T : unmanaged
{
QuadTreeDrawer window = (QuadTreeDrawer)GetWindow(typeof(QuadTreeDrawer));
window.DoDraw(queryJob);
}
[SerializeField]
Color[][] pixels;
void DoDraw<T>(NativeQuadTree<T> quadTree, NativeList<QuadElement<T>> results, AABB2D bounds) where T : unmanaged
{
pixels = new Color[256][];
for (var i = 0; i < pixels.Length; i++)
{
pixels[i] = new Color[256];
}
NativeQuadTree<T>.Draw(quadTree, results, bounds, pixels);
}
void DoDraw<T>(QuadTreeJobs.RangeQueryJob<T> queryJob) where T : unmanaged
{
DoDraw(queryJob.QuadTree, queryJob.Results, queryJob.Bounds);
}
void OnGUI()
{
if(pixels != null)
{
var texture = new Texture2D(256, 256);
for (var x = 0; x < pixels.Length; x++)
{
for (int y = 0; y < pixels[x].Length; y++)
{
texture.SetPixel(x, y, pixels[x][y]);
}
}
texture.Apply();
GUI.DrawTexture(new Rect(0, 0, position.width, position.height), texture);
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 832305bf6e10412ab212389d06708fea
timeCreated: 1578854647

View File

@@ -1,134 +0,0 @@
using System.Diagnostics;
using NUnit.Framework;
using NativeQuadTree;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Random = UnityEngine.Random;
public class QuadTreeTests
{
AABB2D Bounds => new AABB2D(0, 1000);
float2[] GetValues()
{
Random.InitState(0);
var values = new float2[20000];
for (int x = 0; x < values.Length; x++)
{
var val = new int2((int) Random.Range(-900, 900), (int) Random.Range(-900, 900));
values[x] = val;
}
return values;
}
[Test]
public void InsertTriggerDivideBulk()
{
var values = GetValues();
var elements = new NativeArray<QuadElement<int>>(values.Length, Allocator.TempJob);
for (int i = 0; i < values.Length; i++)
{
elements[i] = new QuadElement<int>
{
pos = values[i],
element = i
};
}
using var quadtree = new NativeQuadTree<int>(Bounds, Allocator.TempJob);
var job = new QuadTreeJobs.AddBulkJob<int>
{
Elements = elements,
QuadTree = quadtree,
};
var s = Stopwatch.StartNew();
job.Run();
s.Stop();
Debug.Log(s.Elapsed.TotalMilliseconds);
QuadTreeDrawer.Draw(quadtree);
elements.Dispose();
}
[Test]
public void RangeQueryAfterBulk()
{
var values = GetValues();
NativeArray<QuadElement<int>> elements = new NativeArray<QuadElement<int>>(values.Length, Allocator.TempJob);
for (int i = 0; i < values.Length; i++)
{
elements[i] = new QuadElement<int>
{
pos = values[i],
element = i
};
}
var quadTree = new NativeQuadTree<int>(Bounds);
quadTree.ClearAndBulkInsert(elements);
var queryJob = new QuadTreeJobs.RangeQueryJob<int>
{
QuadTree = quadTree,
Bounds = new AABB2D(100, 140),
Results = new NativeList<QuadElement<int>>(1000, Allocator.TempJob)
};
var s = Stopwatch.StartNew();
queryJob.Run();
s.Stop();
Debug.Log(s.Elapsed.TotalMilliseconds + " result: " + queryJob.Results.Length);
QuadTreeDrawer.DrawWithResults(queryJob);
quadTree.Dispose();
elements.Dispose();
queryJob.Results.Dispose();
}
[Test]
public void InsertTriggerDivideNonBurstBulk()
{
var values = GetValues();
var positions = new NativeArray<float2>(values.Length, Allocator.TempJob);
var quadTree = new NativeQuadTree<int>(Bounds);
positions.CopyFrom(values);
NativeArray<QuadElement<int>> elements = new NativeArray<QuadElement<int>>(positions.Length, Allocator.Temp);
for (int i = 0; i < positions.Length; i++)
{
elements[i] = new QuadElement<int>
{
pos = positions[i],
element = i
};
}
var s = Stopwatch.StartNew();
quadTree.ClearAndBulkInsert(elements);
s.Stop();
Debug.Log(s.Elapsed.TotalMilliseconds);
QuadTreeDrawer.Draw(quadTree);
quadTree.Dispose();
positions.Dispose();
}
}

View File

@@ -1,70 +0,0 @@
namespace NativeQuadTree
{
public static class LookupTables
{
public static readonly ushort[] MortonLookup = {
// 0 1 100 101 10000 10001 10100 10101
0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015,
// 1000000 1000001 1000100 1000101 1010000 1010001 1010100 1010101
0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, 0x0055,
// etc...
0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115,
0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155,
0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415,
0x0440, 0x0441, 0x0444, 0x0445, 0x0450, 0x0451, 0x0454, 0x0455,
0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515,
0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555,
0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015,
0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055,
0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115,
0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155,
0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415,
0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455,
0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515,
0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555,
0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 0x4015,
0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055,
0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115,
0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155,
0x4400, 0x4401, 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415,
0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455,
0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515,
0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555,
0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 0x5014, 0x5015,
0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055,
0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115,
0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155,
0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415,
0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455,
0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515,
0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, 0x5555
};
public static readonly int[] DepthSizeLookup =
{
0,
1,
1+2*2,
1+2*2+4*4,
1+2*2+4*4+8*8,
1+2*2+4*4+8*8+16*16,
1+2*2+4*4+8*8+16*16+32*32,
1+2*2+4*4+8*8+16*16+32*32+64*64,
1+2*2+4*4+8*8+16*16+32*32+64*64+128*128,
1+2*2+4*4+8*8+16*16+32*32+64*64+128*128+256*256,
};
public static readonly int[] DepthLookup =
{
0,
2,
4,
8,
16,
32,
64,
128,
256,
};
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3bf2086533d64f3b93c5bf841ebc44b6
timeCreated: 1580073533

View File

@@ -1,238 +0,0 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine;
namespace NativeQuadTree
{
// Represents an element node in the quadtree.
public struct QuadElement<T> where T : unmanaged
{
public float2 pos;
public T element;
}
struct QuadNode
{
// Points to this node's first child index in elements
public int firstChildIndex;
// Number of elements in the leaf
public short count;
public bool isLeaf;
}
/// <summary>
/// A QuadTree aimed to be used with Burst, supports fast bulk insertion and querying.
///
/// TODO:
/// - Better test coverage
/// - Automated depth / bounds / max leaf elements calculation
/// </summary>
public unsafe partial struct NativeQuadTree<T> : IDisposable where T : unmanaged
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
// Safety
AtomicSafetyHandle safetyHandle;
[NativeSetClassTypeToNullOnSchedule]
DisposeSentinel disposeSentinel;
#endif
// Data
[NativeDisableUnsafePtrRestriction]
UnsafeList<QuadElement<T>>* elements;
[NativeDisableUnsafePtrRestriction]
UnsafeList<int>* lookup;
[NativeDisableUnsafePtrRestriction]
UnsafeList<QuadNode>* nodes;
int elementsCount;
int maxDepth;
short maxLeafElements;
AABB2D bounds; // NOTE: Currently assuming uniform
/// <summary>
/// Create a new QuadTree.
/// - Ensure the bounds are not way bigger than needed, otherwise the buckets are very off. Probably best to calculate bounds
/// - The higher the depth, the larger the overhead, it especially goes up at a depth of 7/8
/// </summary>
public NativeQuadTree(AABB2D bounds, Allocator allocator = Allocator.Temp, int maxDepth = 6, short maxLeafElements = 16,
int initialElementsCapacity = 256
) : this()
{
this.bounds = bounds;
this.maxDepth = maxDepth;
this.maxLeafElements = maxLeafElements;
elementsCount = 0;
if(maxDepth > 8)
{
// Currently no support for higher depths, the morton code lookup tables would have to support it
throw new InvalidOperationException();
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
// TODO: Find out what the equivalent of this is in latest entities
// CollectionHelper.CheckIsUnmanaged<T>();
DisposeSentinel.Create(out safetyHandle, out disposeSentinel, 1, allocator);
#endif
// Allocate memory for every depth, the nodes on all depths are stored in a single continuous array
var totalSize = LookupTables.DepthSizeLookup[maxDepth+1];
lookup = UnsafeList<int>.Create(
totalSize,
allocator,
NativeArrayOptions.ClearMemory);
nodes = UnsafeList<QuadNode>.Create(
totalSize,
allocator,
NativeArrayOptions.ClearMemory);
elements = UnsafeList<QuadElement<T>>.Create(
initialElementsCapacity,
allocator);
}
public void ClearAndBulkInsert(NativeArray<QuadElement<T>> incomingElements)
{
// Always have to clear before bulk insert as otherwise the lookup and node allocations need to account
// for existing data.
Clear();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(safetyHandle);
#endif
// Resize if needed
if(elements->Capacity < elementsCount + incomingElements.Length)
{
elements->Resize(math.max(incomingElements.Length, elements->Capacity*2));
}
// Prepare morton codes
var mortonCodes = new NativeArray<int>(incomingElements.Length, Allocator.Temp);
var depthExtentsScaling = LookupTables.DepthLookup[maxDepth] / bounds.Extents;
for (var i = 0; i < incomingElements.Length; i++)
{
var incPos = incomingElements[i].pos;
incPos -= bounds.Center; // Offset by center
incPos.y = -incPos.y; // World -> array
var pos = (incPos + bounds.Extents) * .5f; // Make positive
// Now scale into available space that belongs to the depth
pos *= depthExtentsScaling;
// And interleave the bits for the morton code
mortonCodes[i] = (LookupTables.MortonLookup[(int) pos.x] | (LookupTables.MortonLookup[(int) pos.y] << 1));
}
// Index total child element count per node (total, so parent's counts include those of child nodes)
for (var i = 0; i < mortonCodes.Length; i++)
{
int atIndex = 0;
for (int depth = 0; depth <= maxDepth; depth++)
{
// Increment the node on this depth that this element is contained in
(*(int*) ((IntPtr) lookup->Ptr + atIndex * sizeof (int)))++;
atIndex = IncrementIndex(depth, mortonCodes, i, atIndex);
}
}
// Prepare the tree leaf nodes
RecursivePrepareLeaves(1, 1);
// Add elements to leaf nodes
for (var i = 0; i < incomingElements.Length; i++)
{
int atIndex = 0;
for (int depth = 0; depth <= maxDepth; depth++)
{
var node = UnsafeUtility.ReadArrayElement<QuadNode>(nodes->Ptr, atIndex);
if(node.isLeaf)
{
// We found a leaf, add this element to it and move to the next element
UnsafeUtility.WriteArrayElement(elements->Ptr, node.firstChildIndex + node.count, incomingElements[i]);
node.count++;
UnsafeUtility.WriteArrayElement(nodes->Ptr, atIndex, node);
break;
}
// No leaf found, we keep going deeper until we find one
atIndex = IncrementIndex(depth, mortonCodes, i, atIndex);
}
}
mortonCodes.Dispose();
}
int IncrementIndex(int depth, NativeArray<int> mortonCodes, int i, int atIndex)
{
var atDepth = math.max(0, maxDepth - depth);
// Shift to the right and only get the first two bits
int shiftedMortonCode = (mortonCodes[i] >> ((atDepth - 1) * 2)) & 0b11;
// so the index becomes that... (0,1,2,3)
atIndex += LookupTables.DepthSizeLookup[atDepth] * shiftedMortonCode;
atIndex++; // offset for self
return atIndex;
}
void RecursivePrepareLeaves(int prevOffset, int depth)
{
for (int l = 0; l < 4; l++)
{
var at = prevOffset + l * LookupTables.DepthSizeLookup[maxDepth - depth+1];
var elementCount = UnsafeUtility.ReadArrayElement<int>(lookup->Ptr, at);
if(elementCount > maxLeafElements && depth < maxDepth)
{
// There's more elements than allowed on this node so keep going deeper
RecursivePrepareLeaves(at+1, depth+1);
}
else if(elementCount != 0)
{
// We either hit max depth or there's less than the max elements on this node, make it a leaf
var node = new QuadNode {firstChildIndex = elementsCount, count = 0, isLeaf = true };
UnsafeUtility.WriteArrayElement(nodes->Ptr, at, node);
elementsCount += elementCount;
}
}
}
public void RangeQuery(AABB2D bounds, NativeList<QuadElement<T>> results)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(safetyHandle);
#endif
new QuadTreeRangeQuery().Query(this, bounds, results);
}
public void Clear()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(safetyHandle);
#endif
UnsafeUtility.MemClear(lookup->Ptr, lookup->Capacity * UnsafeUtility.SizeOf<int>());
UnsafeUtility.MemClear(nodes->Ptr, nodes->Capacity * UnsafeUtility.SizeOf<QuadNode>());
UnsafeUtility.MemClear(elements->Ptr, elements->Capacity * UnsafeUtility.SizeOf<QuadElement<T>>());
elementsCount = 0;
}
public void Dispose()
{
UnsafeList<QuadElement<T>>.Destroy(elements);
elements = null;
UnsafeList<int>.Destroy(lookup);
lookup = null;
UnsafeList<QuadNode>.Destroy(nodes);
nodes = null;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
DisposeSentinel.Dispose(ref safetyHandle, ref disposeSentinel);
#endif
}
}
}

View File

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

View File

@@ -1,77 +0,0 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine;
namespace NativeQuadTree
{
/// <summary>
/// Editor drawing of the NativeQuadTree
/// </summary>
public unsafe partial struct NativeQuadTree<T> where T : unmanaged
{
public static void Draw(NativeQuadTree<T> tree, NativeList<QuadElement<T>> results, AABB2D range,
Color[][] texture)
{
var widthMult = texture.Length / tree.bounds.Extents.x * 2 / 2 / 2;
var heightMult = texture[0].Length / tree.bounds.Extents.y * 2 / 2 / 2;
var widthAdd = tree.bounds.Center.x + tree.bounds.Extents.x;
var heightAdd = tree.bounds.Center.y + tree.bounds.Extents.y;
for (int i = 0; i < tree.nodes->Capacity; i++)
{
var node = UnsafeUtility.ReadArrayElement<QuadNode>(tree.nodes->Ptr, i);
if(node.count > 0)
{
for (int k = 0; k < node.count; k++)
{
var element =
UnsafeUtility.ReadArrayElement<QuadElement<T>>(tree.elements->Ptr, node.firstChildIndex + k);
texture[(int) ((element.pos.x + widthAdd) * widthMult)]
[(int) ((element.pos.y + heightAdd) * heightMult)] = Color.red;
}
}
}
if (results.IsCreated)
{
foreach (var element in results)
{
texture[(int)((element.pos.x + widthAdd) * widthMult)]
[(int)((element.pos.y + heightAdd) * heightMult)] = Color.green;
}
}
DrawBounds(texture, range, tree);
}
static void DrawBounds(Color[][] texture, AABB2D bounds, NativeQuadTree<T> tree)
{
var widthMult = texture.Length / tree.bounds.Extents.x * 2 / 2 / 2;
var heightMult = texture[0].Length / tree.bounds.Extents.y * 2 / 2 / 2;
var widthAdd = tree.bounds.Center.x + tree.bounds.Extents.x;
var heightAdd = tree.bounds.Center.y + tree.bounds.Extents.y;
var top = new float2(bounds.Center.x, bounds.Center.y - bounds.Extents.y);
var left = new float2(bounds.Center.x - bounds.Extents.x, bounds.Center.y);
for (int leftToRight = 0; leftToRight < bounds.Extents.x * 2; leftToRight++)
{
var poxX = left.x + leftToRight;
texture[(int) ((poxX + widthAdd) * widthMult)][(int) ((bounds.Center.y + heightAdd + bounds.Extents.y) * heightMult)] = Color.blue;
texture[(int) ((poxX + widthAdd) * widthMult)][(int) ((bounds.Center.y + heightAdd - bounds.Extents.y) * heightMult)] = Color.blue;
}
for (int topToBottom = 0; topToBottom < bounds.Extents.y * 2; topToBottom++)
{
var posY = top.y + topToBottom;
texture[(int) ((bounds.Center.x + widthAdd + bounds.Extents.x) * widthMult)][(int) ((posY + heightAdd) * heightMult)] = Color.blue;
texture[(int) ((bounds.Center.x + widthAdd - bounds.Extents.x) * widthMult)][(int) ((posY + heightAdd) * heightMult)] = Color.blue;
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e98d676f818a45d9851041ca067a6eab
timeCreated: 1580073676

View File

@@ -1,110 +0,0 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
namespace NativeQuadTree
{
public unsafe partial struct NativeQuadTree<T> where T : unmanaged
{
struct QuadTreeRangeQuery
{
NativeQuadTree<T> tree;
UnsafeList<T>* fastResults;
int count;
AABB2D bounds;
public void Query(NativeQuadTree<T> tree, AABB2D bounds, NativeList<QuadElement<T>> results)
{
this.tree = tree;
this.bounds = bounds;
count = 0;
// Get pointer to inner list data for faster writing
fastResults = (UnsafeList<T>*) NativeListUnsafeUtility.GetInternalListDataPtrUnchecked(ref results);
RecursiveRangeQuery(tree.bounds, false, 1, 1);
fastResults->Length = count;
}
public void RecursiveRangeQuery(AABB2D parentBounds, bool parentContained, int prevOffset, int depth)
{
if(count + 4 * tree.maxLeafElements > fastResults->Capacity)
{
fastResults->Resize(math.max(fastResults->Capacity * 2, count + 4 * tree.maxLeafElements));
}
var depthSize = LookupTables.DepthSizeLookup[tree.maxDepth - depth+1];
for (int l = 0; l < 4; l++)
{
var childBounds = GetChildBounds(parentBounds, l);
var contained = parentContained;
if(!contained)
{
if(bounds.Contains(childBounds))
{
contained = true;
}
else if(!bounds.Intersects(childBounds))
{
continue;
}
}
var at = prevOffset + l * depthSize;
var elementCount = UnsafeUtility.ReadArrayElement<int>(tree.lookup->Ptr, at);
if(elementCount > tree.maxLeafElements && depth < tree.maxDepth)
{
RecursiveRangeQuery(childBounds, contained, at+1, depth+1);
}
else if(elementCount != 0)
{
var node = UnsafeUtility.ReadArrayElement<QuadNode>(tree.nodes->Ptr, at);
if(contained)
{
var index = (void*) ((IntPtr) tree.elements->Ptr + node.firstChildIndex * UnsafeUtility.SizeOf<QuadElement<T>>());
UnsafeUtility.MemCpy((void*) ((IntPtr) fastResults->Ptr + count * UnsafeUtility.SizeOf<QuadElement<T>>()),
index, node.count * UnsafeUtility.SizeOf<QuadElement<T>>());
count += node.count;
}
else
{
for (int k = 0; k < node.count; k++)
{
var element = UnsafeUtility.ReadArrayElement<QuadElement<T>>(tree.elements->Ptr, node.firstChildIndex + k);
if(bounds.Contains(element.pos))
{
UnsafeUtility.WriteArrayElement(fastResults->Ptr, count++, element);
}
}
}
}
}
}
static AABB2D GetChildBounds(AABB2D parentBounds, int childZIndex)
{
var half = parentBounds.Extents.x * .5f;
switch (childZIndex)
{
case 0: return new AABB2D(new float2(parentBounds.Center.x - half, parentBounds.Center.y + half), half);
case 1: return new AABB2D(new float2(parentBounds.Center.x + half, parentBounds.Center.y + half), half);
case 2: return new AABB2D(new float2(parentBounds.Center.x - half, parentBounds.Center.y - half), half);
case 3: return new AABB2D(new float2(parentBounds.Center.x + half, parentBounds.Center.y - half), half);
default: throw new Exception();
}
}
}
}
}

View File

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

View File

@@ -1,54 +0,0 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
namespace NativeQuadTree
{
/// <summary>
/// Examples on jobs for the NativeQuadTree
/// </summary>
public static class QuadTreeJobs
{
/// <summary>
/// Bulk insert many items into the tree
/// </summary>
[BurstCompile]
public struct AddBulkJob<T> : IJob where T : unmanaged
{
[ReadOnly]
public NativeArray<QuadElement<T>> Elements;
public NativeQuadTree<T> QuadTree;
public void Execute()
{
QuadTree.ClearAndBulkInsert(Elements);
}
}
/// <summary>
/// Example on how to do a range query, it's better to write your own and do many queries in a batch
/// </summary>
[BurstCompile]
public struct RangeQueryJob<T> : IJob where T : unmanaged
{
[ReadOnly]
public AABB2D Bounds;
[ReadOnly]
public NativeQuadTree<T> QuadTree;
public NativeList<QuadElement<T>> Results;
public void Execute()
{
for (int i = 0; i < 1000; i++)
{
QuadTree.RangeQuery(Bounds, Results);
Results.Clear();
}
QuadTree.RangeQuery(Bounds, Results);
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 2815368828aa4ed8934169baddedefd2
timeCreated: 1580072573

View File

@@ -1,2 +0,0 @@
@import url("unity-theme://default");
VisualElement {}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 621cee4dfb9237e41984ec0c2a0a904f
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12388, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

View File

@@ -3,59 +3,140 @@ using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using UnityEngine;
namespace BITKit
{
public interface IClosePoint
{
Vector3 CurrentPoint { get; }
Collider Collider { get; }
bool TryGetClosePoint(out Vector3 vector3);
}
[System.Serializable]
[Serializable]
public class GetClosePointFromCollider : IClosePoint
{
public Transform root;
public LayerMask layerMask;
public float distance;
[SerializeField] private string name;
[SerializeField] private Transform transform;
[SerializeField] private Vector3 offset = Vector3.one;
[SerializeField] private LayerMask layerMask;
[SerializeField] private float distance;
[SerializeField] private float minHeight=0.32f;
[SerializeField] private float maxHeight = 1f;
public Vector3 CurrentPoint { get; set; }
public Collider Collider { get; set; }
private Collider[] _colliders=new Collider[8];
private Collider[] _mainCollider=new Collider[8];
public bool TryGetClosePoint(out Vector3 vector3)
{
vector3 = default;
if (UnityEngine.Physics.Raycast(root.position, root.forward, out var raycastHit, distance, layerMask))
StringBuilder reportBuilder = new();
reportBuilder.AppendLine($"检测任务:{name}");
var position = transform.position + transform.rotation * offset;
var detectedLength = Physics.OverlapSphereNonAlloc(position, distance, _mainCollider, layerMask);
reportBuilder.AppendLine($"检测到了{detectedLength}个碰撞体");
foreach (var collider in _mainCollider.Take(detectedLength).OrderBy(ByTop).Reverse())
//for (var i = 0; i <detectedLength ; i++)
{
var collider = raycastHit.collider;
if (collider.isTrigger) return false;
//reportBuilder.AppendLine($"----------------------------检测到了碰撞体{_mainCollider[i].name}");
//var collider = _mainCollider[i];
if (collider.isTrigger)
{
reportBuilder?.AppendLine("碰撞体是触发器");
continue;
}
switch (collider)
{
case MeshCollider meshCollider:
if (meshCollider.convex is false)
return false;
{
reportBuilder?.AppendLine("MeshCollider未勾选Convex");
continue;
}
break;
}
vector3 = collider.ClosestPoint(root.position + Vector3.up);
var top = collider.bounds.center + collider.bounds.extents;
if (Mathf.Abs(vector3.y - top.y) > 0.16f) return false;
if (UnityEngine.Physics.Linecast(root.position, vector3, out var raycastHit2, layerMask))
var bounds = collider.bounds;
vector3 = collider.ClosestPoint(transform.position + Vector3.up * 64);
var top = bounds.center.y + bounds.extents.y;
Debug.DrawLine(transform.position, transform.position + Vector3.up * top, Color.blue, 8f);
if (transform.position.y + minHeight > top)
{
if(raycastHit2.collider != collider)
return false;
};
foreach(var hit in UnityEngine.Physics.OverlapSphere(vector3, 0.1f, layerMask))
{
if(hit!= collider)
return false;
reportBuilder?.AppendLine("高度不足");
continue;
}
var nextPos = position;
nextPos.y = collider.bounds.center.y;
if (collider.Raycast(new Ray(nextPos, transform.forward), out _, distance) is false)
{
reportBuilder?.AppendLine("未检测到前方");
continue;
}
var height = Mathf.Abs(top - transform.position.y);
if (height > maxHeight)
{
reportBuilder?.AppendLine($"高度差距过大:{height}");
continue;
}
if (Physics.Linecast(transform.position, vector3, out var raycastHit2, layerMask))
{
if (raycastHit2.collider != collider)
{
reportBuilder?.AppendLine($"检测到了其他碰撞体:{raycastHit2.transform.name}");
continue;
}
}
;
var length = Physics.OverlapSphereNonAlloc(vector3, 0.01f, _colliders, layerMask);
switch (length)
{
case 1:
if (_colliders[0] != collider)
{
reportBuilder.AppendLine($"检测到了其他碰撞体{_colliders[0].name}");
continue;
}
break;
case > 1:
reportBuilder.AppendLine("检测到了更多碰撞体");
for (var ii = 0; ii < length; ii++)
{
//Debug.DrawLine(vector3, _colliders[ii].ClosestPoint(vector3), Color.red, 8);
reportBuilder.AppendLine($"\t{_colliders[ii].name}");
}
continue;
}
Collider = collider;
vector3.y = top;
CurrentPoint = vector3;
reportBuilder.AppendLine("<color=green>成功</color>");
//BIT4Log.Log<GetClosePointFromCollider>(reportBuilder.ToString());
return true;
//return vector3.y >= collider.bounds.center.y + collider.bounds.extents.y;
//return true;
}
//BIT4Log.Log<GetClosePointFromCollider>(reportBuilder.ToString());
return false;
}
private float ByTop(Collider arg)
{
return arg.bounds.center.y + arg.bounds.extents.y;
}
}
/// <summary>
/// 获取碰撞点可以翻越后的点
@@ -74,6 +155,7 @@ namespace BITKit
public Vector3 StartPosition;
public Vector3 EndPosition;
public Vector3 CurrentPoint { get; set; }
public Collider Collider { get; set; }
private Rigidbody rigidbody;
@@ -92,7 +174,8 @@ namespace BITKit
if (rigidbody.velocity.GetLength() < 0.1f) return false;
}
var reportBuilder = new System.Text.StringBuilder();
//var reportBuilder = new System.Text.StringBuilder();
StringBuilder reportBuilder=null;
var forward = root.forward;
var sourceStartPosition = groundReference.position;
@@ -101,7 +184,7 @@ namespace BITKit
var collider = UnityEngine.Physics.OverlapSphere(startPosition, radius, layerMask);
reportBuilder.AppendLine($"检测到了{collider.Length}个碰撞体");
reportBuilder?.AppendLine($"检测到了{collider.Length}个碰撞体");
foreach (var hit in collider)
{
@@ -112,12 +195,12 @@ namespace BITKit
continue;
}
reportBuilder.AppendLine();
reportBuilder.AppendLine($">{hit.name}");
reportBuilder?.AppendLine();
reportBuilder?.AppendLine($">{hit.name}");
if(top.y>groundReference.transform.position.y+maxHeight)
{
reportBuilder.AppendLine("高度超出可翻越高度");
reportBuilder?.AppendLine("高度超出可翻越高度");
continue;
}
@@ -128,9 +211,9 @@ namespace BITKit
if (hit.Raycast(ray, out var colliderHit, 8) is false)
{
reportBuilder.AppendLine("未检测到背面,算法错误");
reportBuilder?.AppendLine("未检测到背面,算法错误");
Debug.DrawRay(ray.origin, ray.direction * 8, Color.red, 8);
//Debug.DrawRay(ray.origin, ray.direction * 8, Color.red, 8);
continue;
}
@@ -151,15 +234,15 @@ namespace BITKit
}
catch (OperationCanceledException e)
{
reportBuilder.AppendLine(e.Message);
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("未检测到地面,跳过");
//Debug.DrawRay(EndPosition, Vector3.down*1.6f, Color.red, 8);
reportBuilder?.AppendLine("未检测到地面,跳过");
continue;
//Debug.DrawRay(EndPosition, Vector3.down, Color.red, 8);
}
@@ -174,7 +257,7 @@ namespace BITKit
end2.y -= 0.1f;
if(UnityEngine.Physics.Linecast(start2,end2,out var downHit,layerMask))
{
reportBuilder.AppendLine($"检测到了障碍物{downHit.collider.name}");
reportBuilder?.AppendLine($"检测到了障碍物{downHit.collider.name}");
//BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
continue;
@@ -186,8 +269,8 @@ namespace BITKit
ray = new Ray(start, forward);
if(hit.Raycast(ray,out colliderHit,8) is false)
{
reportBuilder.AppendLine("未检测到正面,算法错误");
Debug.DrawRay(ray.origin, ray.direction * 8, Color.red, 8);
reportBuilder?.AppendLine("未检测到正面,算法错误");
//Debug.DrawRay(ray.origin, ray.direction * 8, Color.red, 8);
continue;
}
@@ -200,14 +283,26 @@ namespace BITKit
var lineDistance = Vector3.Distance(closeStart, closeEnd);
if(lineDistance > maxVaultDistance)
{
reportBuilder.AppendLine($"长度{lineDistance}超出可翻越距离{maxVaultDistance}");
reportBuilder?.AppendLine($"长度{lineDistance}超出可翻越距离{maxVaultDistance}");
Debug.DrawLine(closeStart,closeEnd, Color.yellow, 4);
//Debug.DrawLine(closeStart,closeEnd, Color.yellow, 4);
//BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
continue;
}
if(Physics.Linecast(closeStart+Vector3.up*0.1f,closeEnd+Vector3.up*0.1f,out var _hit,layerMask))
{
//Debug.DrawLine(closeStart+Vector3.up*0.1f,_hit.point, Color.red, 4);
reportBuilder?.AppendLine("检测到了障碍物");
continue;
}
else
{
//Debug.DrawLine(closeStart+Vector3.up*0.1f,closeEnd+Vector3.up*0.1f, Color.green, 4);
}
vector3 = colliderHit.point;
@@ -215,14 +310,19 @@ namespace BITKit
vector3 += colliderHit.normal * 0.4f;
reportBuilder.AppendLine();
reportBuilder?.AppendLine();
if(reportBuilder is not null)
BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
CurrentPoint = vector3;
Collider = hit;
return true;
}
reportBuilder.AppendLine($"failed");
reportBuilder?.AppendLine($"failed");
if(reportBuilder is not null)
BIT4Log.Log<GetVaultPointFromCollider>(reportBuilder.ToString());
vector3 = default;

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@@ -5,12 +6,25 @@ namespace BITKit
{
public class PhysicsImpact : MonoBehaviour
{
private new Collider collider;
public event Action<Collision> OnPhysicsCollisionEnter;
[SerializeField] private Collider[] ignoreColliders;
private readonly HashSet<int> ignoreCollidersHash = new();
private void OnEnable()
{
collider = GetComponent<Collider>();
ignoreCollidersHash.Clear();
if (ignoreColliders is null) return;
foreach (var x in ignoreColliders)
{
ignoreCollidersHash.Add(x.transform.GetInstanceID());
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.transform.TryGetComponent<IPhysicsImpact>(out var impact))
{
impact.AddImpact(collision.relativeVelocity);
}
if(ignoreCollidersHash.Contains(collision.transform.GetInstanceID())) return;
OnPhysicsCollisionEnter?.Invoke(collision);
}
}
}

View File

@@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Quadtree
{
@@ -405,10 +407,12 @@ namespace Quadtree
public void DrawBounds(bool displayNumberOfItems = false)
{
#if UNITY_EDITOR
if (displayNumberOfItems)
Handles.Label(Bounds.center, _items.Count.ToString());
Gizmos.DrawWireCube(Bounds.center, Bounds.size);
#endif
foreach (var subNode in SubNodes)
subNode.DrawBounds(displayNumberOfItems);
}

View File

@@ -234,6 +234,7 @@ namespace BITKit.SceneManagement
public async UniTask UnloadSceneAsync(string sceneName, CancellationToken cancellationToken)
{
await UniTask.SwitchToMainThread();
OnUnloadScene?.Invoke(sceneName);
await Task.Delay(100, destroyCancellationToken);
@@ -246,7 +247,7 @@ namespace BITKit.SceneManagement
SceneManager.LoadScene(1);
destroyCancellationToken.ThrowIfCancellationRequested();
await UniTask.SwitchToMainThread();
OnSceneUnloaded?.Invoke(sceneName);
}

View File

@@ -6,11 +6,12 @@ using BITKit.UX;
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
using BITKit.SceneManagement;
using Task = System.Threading.Tasks.Task;
// ReSharper disable Unity.NoNullPropagation
namespace BITKit.SceneManagement.UX
namespace BITKit.UX
{
public class UXSceneLoading : MonoBehaviour
{
@@ -30,14 +31,13 @@ namespace BITKit.SceneManagement.UX
{
_cancellationToken = gameObject.GetCancellationTokenOnDestroy();
}
private void Start()
{
DI.Inject(this);
sceneService.OnLoadScene += OnLoadScene;
sceneService.OnSceneLoadProgress+=OnSceneLoadProgress;
sceneService.OnSceneLoaded += OnSceneLoaded;
}
private void Update()
{
_currentProgress = Mathf.Lerp(_currentProgress, _progress, Time.deltaTime * 10);
@@ -50,11 +50,11 @@ namespace BITKit.SceneManagement.UX
sceneService.OnSceneLoaded -= OnSceneLoaded;
}
private void OnSceneLoadProgress(string arg1, float arg2)
{
_progress = arg2;
}
private void OnLoadScene(string obj)
{
sceneNameLabel?.Set(obj);
@@ -62,6 +62,7 @@ namespace BITKit.SceneManagement.UX
}
private async void OnSceneLoaded(string obj)
{
if(autoDestroyAfterLoaded is false)return;
backgroundImage.SetActive(false);
try

View File

@@ -5,7 +5,6 @@ using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Pool;
using System.Linq;
using UnityEditor.Search;
namespace BITKit.Sensors
{
@@ -13,37 +12,83 @@ namespace BITKit.Sensors
{
float GetVolume();
}
public class AudioSensor : MonoBehaviour,ISensor
public class AudioSensor : MonoBehaviour, ISensor
{
[Header(Constant.Header.Settings)]
[SerializeField] private bool autoUpdate;
[SerializeField]private float radius;
[Header(Constant.Header.Settings)] [SerializeField]
private bool autoUpdate;
[SerializeField] private float radius;
private readonly CacheList<Transform> cache = new();
private readonly CacheList<AudioSensorService.AudioSensorData> data = new();
public AudioSensorService.AudioSensorData[] Noises => data.ValueArray;
private readonly HashSet<int> _addedSet = new();
private void OnEnable()
{
Id = GetInstanceID();
SensorQueue.Register(Id,this);
SensorQueue.Register(Id, this);
}
private void OnDisable()
{
SensorQueue.UnRegister(Id);
}
public UniTask Execute(float delta)
public async UniTask Execute(float delta)
{
var position = transform.position;
cache.Clear();
foreach (var x in AudioSensorService.QuadtreeRoot.Find(new Bounds(position, Vector3.one * radius)))
AudioSensorService.LockHash.Add(Id);
try
{
var distance = Vector3.Distance(position, x.Position);
if(distance>radius) continue;
cache.Add(x.Transform);
var position = transform.position;
cache.Clear();
data.Clear();
_addedSet.Clear();
await UniTask.SwitchToTaskPool();
AudioSensorService.AudioSensorData[] value;
value = AudioSensorService.QuadtreeRoot.Find(new Bounds(position, Vector3.one * radius)).ToArray();
for (var index = 0; index < value.Length; index++)
{
var x = value[index];
var distance = Vector3.Distance(position, x.Position);
if (distance > radius) continue;
if (Ignores.Contains(x.Id)) continue;
_addedSet.Add(index);
// if (x.Transform)
// cache.Add(x.Transform);
// data.Add(x);
}
await UniTask.SwitchToMainThread();
if (destroyCancellationToken.IsCancellationRequested) return;
foreach (var x in _addedSet)
{
if (value[x].Transform)
cache.Add(value[x].Transform);
data.Add(value[x]);
}
}
return UniTask.CompletedTask;
catch (Exception e)
{
BIT4Log.LogException(e);
}
AudioSensorService.LockHash.Remove(Id);
//return UniTask.CompletedTask;
}
public HashSet<int> Ignores { get; } = new();
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;
public bool AutoUpdate => autoUpdate;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using kcp2k;
@@ -10,8 +11,10 @@ using UnityEngine.Pool;
namespace BITKit.Sensors
{
public struct ClassicNoise{}
public class AudioSensorService : MonoBehaviour
{
public static readonly HashSet<int> LockHash = new();
public class AudioSensorData:IItem<AudioSensorData,Node<AudioSensorData>>
{
public int Id;
@@ -20,6 +23,8 @@ namespace BITKit.Sensors
public Transform Transform;
public Bounds Bounds;
public ITag Tag;
public object NoiseType;
public float ExpirationTime;
public Bounds GetBounds() => Bounds;
public Node<AudioSensorData> ParentNode { get; set; }
public void QuadTree_Root_Initialized(IQuadtreeRoot<AudioSensorData, Node<AudioSensorData>> root){}
@@ -27,40 +32,72 @@ namespace BITKit.Sensors
internal static readonly QuadtreeRoot<AudioSensorData, Node<AudioSensorData>> QuadtreeRoot =
new(default, Vector3.one * 2048);
private static Pool<AudioSensorData> pool = new(()=>new(), x=>{}, 1000);
private static readonly Pool<AudioSensorData> pool = new(()=>new AudioSensorData(), x=>{}, 1000);
private static readonly ConcurrentQueue<AudioSensorData> registerQueue = new();
private static int count;
public static async void MakeNoise(Vector3 position,Transform transform)
private static readonly Queue<int> freeIds = new();
private static readonly ConcurrentDictionary<int, AudioSensorData> dictionary = new();
public static void MakeNoise(Vector3 position, Transform transform, float radius = 1,
object noiseType = null)
{
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);
data.Bounds = new Bounds(position, Vector3.one * 1);
if (transform)
data.Tag = transform.GetComponent<ITag>();
data.Radius = radius;
data.ExpirationTime = Time.time + 0.5f;
registerQueue.Enqueue(data);
}
private static bool disposed;
[SerializeReference, SubclassSelector] private ITicker ticker;
private void Start()
{
disposed = false;
ticker.Add(OnTick);
ticker.Add(OnTick);
destroyCancellationToken.Register(Dispose);
pool.Clear();
}
private void Dispose()
{
registerQueue.Clear();
ticker.Remove(OnTick);
disposed = true;
QuadtreeRoot.Clear();
}
private void OnTick(float obj)
{
if (LockHash.Count > 0) return;
while (registerQueue.TryDequeue(out var data))
{
if (data.ExpirationTime < Time.time)
{
pool.Return(data);
continue;
}
QuadtreeRoot.Insert(data);
dictionary.TryAdd(data.Id, data);
}
var currentTime = Time.time;
foreach (var (id, data) in dictionary)
{
if (data.ExpirationTime > currentTime) continue;
freeIds.Enqueue(id);
QuadtreeRoot.Remove(data);
pool.Return(data);
}
while (freeIds.TryDequeue(out var id))
{
dictionary.TryRemove(id, out _);
}
}
}

View File

@@ -47,6 +47,7 @@ namespace BITKit.Sensors
/// </summary>
/// <returns></returns>
UniTask Execute(float delta);
HashSet<int> Ignores{ get; }
}
[Serializable]
@@ -74,6 +75,7 @@ namespace BITKit.Sensors
{
return _sensorImplementation.Execute(delta);
}
public HashSet<int> Ignores => _sensorImplementation.Ignores;
}
[System.Serializable]
public class SensorMonoProxy:ISensor
@@ -101,6 +103,7 @@ namespace BITKit.Sensors
{
return _sensorImplementation.Execute(delta);
}
public HashSet<int> Ignores => _sensorImplementation.Ignores;
}
public abstract class Sensor : MonoBehaviour, ISensor
{
@@ -109,13 +112,11 @@ namespace BITKit.Sensors
public bool autoUpdate;
[Header(Constant.Header.Gameobjects)]
public Collider[] ignoreColliders;
[Header(Constant.Header.InternalVariables)]
[SerializeField,ReadOnly]
public Transform[] detected = Array.Empty<Transform>();
public abstract IEnumerable<Transform> Get();
public abstract bool IsValid(Collider _collider);
public abstract float GetDistance();
public virtual UniTask Execute(float delta)=>UniTask.CompletedTask;
public HashSet<int> Ignores { get; } = new();
public int Id { get; private set; }
bool ISensor.AutoUpdate => autoUpdate;

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine.Jobs;
using UnityEngine.Pool;
using UnityEngine.Profiling;
@@ -13,19 +14,23 @@ namespace BITKit.Sensors
{
public class RangeSensor : Sensor
{
[Header(Constant.Header.Settings)] public float radius;
public int fov;
public bool requireSight;
[Header(Constant.Header.Settings)] public LayerMask blockLayer;
[Header(Constant.Header.Debug)] [Header(Constant.Header.InternalVariables)]
private FrameUpdate frameUpdater;
[Header(Constant.Header.Settings)]
[SerializeField] private float radius;
[SerializeField] private float detectedHeightWeight=0.5f;
[Header(Constant.Header.Optional)]
[SerializeField] private Optional<int> fov;
[SerializeField] private Optional<LayerMask> requireSight;
[SerializeField] private Optional<float> perceptionRadius;
public float DetectedHeightWeight
{
get=>detectedHeightWeight;
set=>detectedHeightWeight=value;
}
private readonly Collider[] colliders = new Collider[32];
private RaycastHit[] hits = new RaycastHit[32];
private readonly HashSet<int> tempHashSet = new();
private readonly RaycastHit[] hits = new RaycastHit[32];
private float _delta;
public override IEnumerable<Transform> Get()
{
if (!_detectedDoubleBuffer.TryGetRelease(out var newRelease)) return _detectedBuffer;
@@ -40,7 +45,7 @@ namespace BITKit.Sensors
public override UniTask Execute(float delta)
{
tempHashSet.Clear();
_delta = delta;
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;
@@ -53,10 +58,21 @@ namespace BITKit.Sensors
{
switch (_collider)
{
case var _ when Ignores.Contains(_collider.GetInstanceID()):
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:
}
if (perceptionRadius.Allow)
{
var distance = Vector3.Distance(Transform.position, _collider.bounds.center);
if(distance<=perceptionRadius.Value) return !requireSight.Allow || CheckSight(ref _collider);
}
switch (_collider)
{
case var _ when fov.Allow && CheckFov(ref _collider) is false:
case var _ when requireSight.Allow && CheckSight(ref _collider) is false:
return false;
default:
return true;
@@ -75,23 +91,21 @@ namespace BITKit.Sensors
private bool CheckSight(ref Collider _collider)
{
if (!requireSight) return false;
var position = _collider.bounds.center;
//是检测bounds的顶部
position.y += _collider.bounds.extents.y;
var location = new Location(Transform);
var bounds = _collider.bounds;
var position = bounds.center;
position.y += bounds.size.y * detectedHeightWeight /2;
var selfPosition = Transform.position;
var length = Physics.RaycastNonAlloc(
location.position,
position - location,
selfPosition,
position - selfPosition,
hits,
Vector3.Distance(location, position),
blockLayer
Vector3.Distance(selfPosition, position),
requireSight.Value
);
switch (length)
{
case 0:
Debug.DrawLine(location, position, Color.green, 1);
Debug.DrawLine(selfPosition, position, Color.green,_delta);
return true;
case 1:
return hits[0].collider == _collider;
@@ -107,7 +121,8 @@ namespace BITKit.Sensors
var result = ignoreColliders.Contains(x.collider) is false && x.collider != collider1;
if (result)
{
Debug.DrawLine(location, x.point, Color.red, 1);
Debug.DrawLine(selfPosition, x.point, Color.yellow,_delta);
Debug.DrawLine(selfPosition, position, Color.red,_delta);
}
return result;
}

View File

@@ -2,7 +2,9 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
namespace BITKit.Sensors
{
@@ -11,8 +13,11 @@ namespace BITKit.Sensors
[Header(Constant.Header.Settings)]
[SerializeField] private float radius = 0.16f;
public float distance;
public override IEnumerable<Transform> Get() => detected;
public override IEnumerable<Transform> Get() => detected.ValueArray;
private readonly RaycastHit[] raycasts = new RaycastHit[16];
private readonly Queue<(Vector3,Vector3)> _drawGizmosQueue = new();
private readonly CacheList<Transform> detected = new();
public override UniTask Execute(float delta)
{
var _transform = transform;
@@ -28,25 +33,43 @@ namespace BITKit.Sensors
}
else
{
var ray = new Ray(_transform.position,_transform.forward);
var ray = new Ray(_transform.position, _transform.forward);
size = Physics.RaycastNonAlloc(ray, raycasts, distance, detectLayer);
}
detected = raycasts
.Take(size)
.Select(x => x.collider)
.Where(IsValid)
.Select(x=>x.transform)
.ToArray();
//var reportBuilder = new StringBuilder();
detected.Clear();
foreach (var hit in raycasts
.Take(size)
.Where(IsValid)
.OrderBy(ByDistance))
{
detected.Add(hit.transform);
}
//Debug.Log(reportBuilder);
return UniTask.CompletedTask;
float ByDistance(RaycastHit arg)
{
var x = Vector3.Distance(arg.point, position);
//_drawGizmosQueue.Enqueue(new(arg.collider.bounds.center, arg.point));
//reportBuilder.AppendLine($"Name:{arg.collider.name},Distance:{x}");
return x;
}
}
public bool IsValid(RaycastHit hit) => IsValid(hit.collider);
public override bool IsValid(Collider _collider) => ignoreColliders.Contains(_collider) is false;
public override float GetDistance() => distance;
private void Update()
private void OnDrawGizmosSelected()
{
if (autoUpdate)
while (_drawGizmosQueue.TryDequeue(out var p))
{
Execute(Time.deltaTime).Forget();
var (closePoint, point) = p;
Gizmos.DrawWireCube(point,Vector3.one*0.01f);
Gizmos.DrawLine(closePoint,point);
}
}
}

View File

@@ -10,22 +10,20 @@ namespace BITKit.Sensors
{
public class SensorQueue : MonoBehaviour
{
internal static readonly Dictionary<int,ISensor> Sensors=new();
internal static readonly ConcurrentDictionary<int,ISensor> Sensors=new();
internal static readonly ConcurrentDictionary<int, float> LastDetectedTime = new();
private static bool IsDirty;
[SerializeField,ReadOnly] private int _position;
private static int[] _keys;
public static void Register(int id,ISensor sensor)
{
Sensors.Add(id,sensor);
Sensors.TryAdd(id,sensor);
MarkDirty();
}
public static void UnRegister(int id)
{
Sensors.Remove(id);
Sensors.TryRemove(id);
MarkDirty();
}
public static void MarkDirty()
@@ -33,7 +31,6 @@ namespace BITKit.Sensors
IsDirty = true;
}
[SerializeField] private MonoBehaviour[] sensors;
[SerializeReference,SubclassSelector] private ITicker ticker;
private bool _isBusy;
@@ -51,44 +48,46 @@ namespace BITKit.Sensors
{
if (_isBusy) return;
if (SensorGlobalSettings.Enabled is false) return;
_isBusy = true;
if(IsDirty)
try
{
_position = 0;
_keys = Sensors.Where(IsEnabled).Select(x=>x.Key).ToArray();
IsDirty = false;
sensors = Sensors.Values.Where(IsEnabled).OfType<MonoBehaviour>().ToArray();
}
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;
}
_isBusy = true;
if(IsDirty)
{
_position = 0;
IsDirty = false;
}
_position %= Sensors.Count;
if (Sensors.Count is 0)
{
_isBusy = false;
return;
}
var current = Sensors.ElementAt(_position++).Value;
if (current.AutoUpdate)
{
var currentUpdateTime = LastDetectedTime.GetOrAdd(current.Id,Time.time);
await current.Execute(Time.time-currentUpdateTime);
LastDetectedTime.AddOrUpdate(current.Id,Time.time,UpdateValueFactory);
if (destroyCancellationToken.IsCancellationRequested) {
_isBusy = false;
return;
}
}
_position %= Sensors.Count;
float UpdateValueFactory(int key, float old) => Time.time;
}
catch (Exception e)
{
BIT4Log.LogException(e);
}
_isBusy = false;
}
private bool IsEnabled(ISensor sensor)
{
return sensor.AutoUpdate;
}
private bool IsEnabled(KeyValuePair<int,ISensor> pair)
{
return pair.Value.AutoUpdate;
}
}
}

View File

@@ -30,6 +30,7 @@ namespace BITKit.Sensors
[SerializeField] private AudioSensor audioSensor;
public int Id { get; set; }
private readonly CacheList<Transform> _detected=new();
public HashSet<int> Ignores { get; } = new();
private void OnEnable()
{
Id = GetInstanceID();

View File

@@ -28,6 +28,7 @@ namespace BITKit.Sensors
[Header(Constant.Header.InternalVariables)]
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private List<Collider> detected = new();
public HashSet<int> Ignores { get; } = new();
private readonly Queue<Collider> triggerEnterQueue = new();
private readonly Queue<Collider> triggerExitQueue = new();
@@ -171,7 +172,7 @@ namespace BITKit.Sensors
if (!agent || detectedContainer is null) return;
if (BITAppForUnity.IsPlaying is false) return;
detectedContainer.Clear();
foreach (var x in agent.detected)
foreach (var x in agent.Get())
{
ObjectField objectField = new()
{

View File

@@ -91,7 +91,7 @@ namespace BITKit.StateMachine
}
private void TransitionStateInternal(TState newState)
{
if (newState.Equals(CurrentState)) return;
if (Equals(newState,CurrentState)) return;
var oldState = CurrentState;
if (oldState is not null)
@@ -100,15 +100,19 @@ namespace BITKit.StateMachine
oldState.Enabled = false;
}
_currentStateName = newState is not null ? newState.GetType().Name : "null";
if (debug)
{
BIT4Log.Log<MonoStateMachine<TState>>($"TransitionState from {_currentStateName} to {newState.GetType().Name}");
BIT4Log.Log<MonoStateMachine<TState>>($"TransitionState from {_currentStateName} to {_currentStateName}");
}
CurrentState = newState;
newState.OnStateEntry(oldState);
if (newState is not null)
{
newState.Enabled = true;
newState.OnStateEntry(oldState);
}
OnStateChanged?.Invoke(oldState, newState);
_currentStateName = newState.GetType().Name;
newState.Enabled = true;
}
public virtual void Register(TState newState)=>StateMachineUtils.Register(this,newState);
public virtual void UnRegister(TState newState)=>StateMachineUtils.UnRegister(this,newState);

View File

@@ -35,6 +35,7 @@ namespace BITKit.SubSystems.Quest
public State State { get; internal set; }
public string Name { get; internal set; }
public string Description { get; internal set; }
public string UserData { get; set; }
}
public static Dictionary<Guid, Info> quests { get; private set; } = new();
public static event Action<Info> OnQuestCreated;

View File

@@ -8,6 +8,7 @@ using UnityEngine.UIElements;
namespace BITKit.UX
{
#if !UNITY_WEBGL
public class UXApplicationFile : MonoBehaviour
{
[SerializeReference, SubclassSelector] private IApplicationFile applicationFile;
@@ -52,5 +53,5 @@ namespace BITKit.UX
}
}
}
#endif
}

View File

@@ -17,7 +17,8 @@
"GUID:7d3ace4c6aad3684abe11aa38b6cdf99",
"GUID:517785bb4600a5140b47eac5fa49b8fc",
"GUID:838d3286f0973344ab6e99d3951012f7",
"GUID:a11ff146d38b27a44af87b4b4d9c4ecb"
"GUID:a11ff146d38b27a44af87b4b4d9c4ecb",
"GUID:e4d11af1289097a4d9d8987f332a2ae8"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -8,7 +8,8 @@
"GUID:49b49c76ee64f6b41bf28ef951cb0e50",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:6de01b04fa4e14662b03fa46366da151",
"GUID:f19bbd83e3c264a5680926bf75d7e494"
"GUID:f19bbd83e3c264a5680926bf75d7e494",
"GUID:1c2aa13aa706ffc49a1a0044cad55436"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SkiaSharp;
using UnityEngine;
using UnityEngine.UIElements;
@@ -11,192 +12,255 @@ 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()
{
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);
});
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;
}
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()
{
if (float.IsNaN(layout.width) || float.IsNaN(layout.height))
{
if (AllowWarning)
BIT4Log.Log<SkiaChart>("Width or height is NaN");
return;
}
JArray[] array;
float[] data;
try
{
array = JsonConvert.DeserializeObject<JArray[]>(Json);
data = array.SelectMany(x => x.ToObject<float[]>()).ToArray();
}
catch (Exception e)
{
if (AllowWarning)
Debug.LogException(e);
return;
}
if (data is null or { Length: 0 })
{
if (AllowWarning)
BIT4Log.Log<SkiaChart>("Data is null or empty");
return;
}
_dataContainer.Clear();
_chartContainer.Clear();
// _chartContainer.style.marginBottom = 8;
// _chartContainer.style.marginLeft = 8;
// _chartContainer.style.marginRight = 8;
// _chartContainer.style.marginTop = 8;
_dataContainer.style.flexDirection = FlexDirection.ColumnReverse;
//假如min是x,max是y,将中间的差用data.Length划分
var max = data.Max();
var min = data.Min();
var difference = max - min;
var maxLength = array.Select(x => x.Count).Max();
max = max + difference / maxLength;
min = min - difference / maxLength;
difference = max - min;
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划分
//Debug.Log($"min:{min} max:{max} difference:{difference} max:{max}");
_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();
}
for (var i = 0; i <= maxLength; i++)
{
var label = _dataContainer.Create<Label>();
if(_chartContainer.layout.width is float.NaN or <10 || _chartContainer.layout.height is float.NaN or <10)
return;
var info = new SKImageInfo(
//var value = (max - min) / (data.Length - 1) * i + min;
var t = (float)i / maxLength;
var value = Mathf.Lerp(min,max,t);
label.text = Math.Round(value, 2).ToString();
}
if (_chartContainer.layout.width is float.NaN or < 10 || _chartContainer.layout.height is float.NaN or < 10)
{
if (AllowWarning)
BIT4Log.Log<SkiaChart>("Width or height is NaN or less than 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++)
fillPaint.Style = SKPaintStyle.Fill;
for (var index = 0; index < array.Length; index++)
{
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))
var _array = array[index];
DoubleBuffer<SKPoint> buffer = new();
data = _array.ToObject<float[]>();
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
path.MoveTo(0, 0);
path.LineTo(0, 0);
if (_array.Count is not 1 && customStyle.TryGetValue(new CustomStyleProperty<Color>($"--color-{index}"),
out var _myStyle))
{
canvas.DrawLine(previousPoint, currentPoint, linePaint);
linePaint.Color = _myStyle.ToSKColor();
}
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);
else
{
linePaint.Color = resolvedStyle.color.ToSKColor();
}
for (var i = 1; i <= data.Length; i++)
{
var value = data[i-1];
var posX = (float)info.Width / data.Length * (i);
var p = (value - min) / difference;
var poxY = info.Height * p;
var currentPoint = new SKPoint(posX, poxY);
if (buffer.TryGetRelease(out var previousPoint))
{
canvas.DrawLine(previousPoint, currentPoint, linePaint);
}
if (i == data.Length)
{
var label = _chartContainer.Create<Label>();
label.text = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture);
label.style.position = Position.Absolute;
label.style.left = posX; //-label.layout.width/2;
label.style.bottom = poxY; //- label.layout.height/2;
//label.transform.position = new Vector3(-label.layout.width/2, poxY, 0);
}
canvas.DrawLine(posX, 0, posX, poxY, helpLinePaint);
path.LineTo(posX, poxY);
buffer.Release(currentPoint);
}
path.LineTo(info.Width, 0);
path.Close();
}
path.LineTo(info.Width,0);
path.Close();
//canvas.DrawPath(path,filePaint);
var texture = info.ToTexture2D(surface);
_chartContainer.style.backgroundImage = texture;
}
}
}
}
}

View File

@@ -1,35 +0,0 @@
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);
}
}
}

View File

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

View File

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

View File

@@ -1,99 +0,0 @@
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);
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace BITKit.UX
{
public class UXDateTime : MonoBehaviour
{
[SerializeReference,SubclassSelector]private IReference timeFormat;
[SerializeReference, SubclassSelector] private IReference path;
[SerializeField] private bool isTimerNotClock;
private DateTime _dateTime;
private DateTime _startTime;
private Label timeLabel;
private string lastTime;
private void Start()
{
timeLabel = GetComponent<UIDocument>().rootVisualElement.Q<Label>(path.Value);
_startTime = DateTime.Now;
}
private void Update()
{
var time = DateTime.Now;
if (isTimerNotClock)
{
time = new DateTime(DateTime.Now.Ticks - _startTime.Ticks);
}
var str = GetTime();
if(Equals(str,lastTime)) return;
lastTime = str;
timeLabel.text = lastTime;
return;
string GetTime() => timeFormat is not null
? time.ToString(timeFormat.Value)
: time.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using BITKit;
using BITKit.UX;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.UIElements;
public class UXWeather : MonoBehaviour
{
[SerializeReference,SubclassSelector] private ITicker ticker;
[SerializeReference,SubclassSelector] private IReference api;
private readonly HttpClient _httpClient=new();
[UXBindPath("temperature-label")] private Label _temperatureLabel;
private void OnEnable()
{
ticker.Add(OnTick);
}
private void OnDisable()
{
ticker.Remove(OnTick);
}
private void Start()
{
UXUtils.Inject(this);
OnTick(Time.deltaTime);
}
private async void OnTick(float obj)
{
var json =await _httpClient.GetStringAsync(api.Value);
if(destroyCancellationToken.IsCancellationRequested)return;
var jObject = JObject.Parse(json);
var temperature = jObject["lives"]![0]!["temperature"]!.ToObject<string>();
await UniTask.SwitchToMainThread();
if (destroyCancellationToken.IsCancellationRequested) return;
_temperatureLabel.text = $"{temperature}℃";
}
}

View File

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

View File

@@ -6,9 +6,9 @@ namespace BITKit.UX
{
public class UXDrag : UXElement<VisualElement>
{
Vector3 m_IconStartPosition;
Vector3 m_PointerStartPosition;
bool isDraging;
private Vector3 m_IconStartPosition;
private Vector3 m_PointerStartPosition;
protected bool isDraging { get; private set; }
public override void OnStart()
{
base.OnStart();

View File

@@ -8,13 +8,29 @@ namespace BITKit.UX
public class UXScreenJoyStick : UXDrag
{
[Header(Constant.Header.Settings)]
public float range;
public bool resetOnUp;
public bool updateDelta;
[SerializeField] private float range;
[SerializeField] private bool resetOnUp;
[SerializeField] private bool updateDelta;
[SerializeField] private bool raw;
[Header(Constant.Header.Events)]
public UnityEvent<Vector2> onDelta;
[SerializeField] private UnityEvent<Vector2> onDelta;
[Header(Constant.Header.InternalVariables)]
public Vector2 delta;
[SerializeField,ReadOnly] private Vector2 delta;
public override void OnStart()
{
base.OnStart();
visualElement.RegisterCallback<PointerMoveEvent>(OnPointerMove);
}
private void OnPointerMove(PointerMoveEvent evt)
{
if (raw&& isDraging)
{
onDelta.Invoke(evt.deltaPosition);
}
}
protected override Vector2 GetProcessDelta(Vector2 delta)
{
delta = Vector2.ClampMagnitude(delta, range);

View File

@@ -0,0 +1,110 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using ZXing;
using ZXing.Common;
namespace BITKit.UX
{
public class QRVisualElement : VisualElement
{
public new class UxmlTraits:VisualElement.UxmlTraits
{
private readonly UxmlStringAttributeDescription m_TabsAttribute = new ()
{
name = "value"
};
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
var self = (QRVisualElement)ve;
self.Value = m_TabsAttribute.GetValueFromBag(bag, cc);
}
}
public new class UxmlFactory : UxmlFactory<QRVisualElement, UxmlTraits> { }
public QRVisualElement()
{
RegisterCallback<GeometryChangedEvent>(x =>
{
if (visible)
{
Rebuild();
}
});
}
public string Value
{
get => _value;
set
{
_value= value;
Rebuild();
}
}
private string _value;
private Texture _texture;
private void Rebuild()
{
if (float.IsNaN(layout.width) || float.IsNaN(layout.height))return;
if(layout.width <10 || layout.height <10)return;
var target = _value;
if (string.IsNullOrEmpty(_value))
{
target = nameof(QRVisualElement);
}
style.backgroundImage = GenerateQRImageWithColor(target, (int)layout.width, (int)layout.height, Color.black, out var bitMatrix);
Texture2D GenerateQRImageWithColor(string content, int width, int height, Color color, out BitMatrix bitMatrix)
{
width=height=Mathf.Max(width,height);
if (_texture)
{
Object.DestroyImmediate(_texture);
}
// 编码成color32
MultiFormatWriter writer = new MultiFormatWriter();
Dictionary<EncodeHintType, object> hints = new Dictionary<EncodeHintType, object>();
//设置字符串转换格式,确保字符串信息保持正确
hints.Add(EncodeHintType.CHARACTER_SET, "UTF-8");
// 设置二维码边缘留白宽度(值越大留白宽度大,二维码就减小)
hints.Add(EncodeHintType.MARGIN, 1);
hints.Add(EncodeHintType.ERROR_CORRECTION, ZXing.QrCode.Internal.ErrorCorrectionLevel.M);
//实例化字符串绘制二维码工具
bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
// 转成texture2d
int w = bitMatrix.Width;
int h = bitMatrix.Height;
Texture2D texture = new Texture2D(w, h);
for (int x = 0; x < h; x++)
{
for (int y = 0; y < w; y++)
{
if (bitMatrix[x, y])
{
texture.SetPixel(y, x, color);
}
else
{
texture.SetPixel(y, x, Color.white);
}
}
}
texture.Apply();
//byte[] bytes = texture.EncodeToPNG();
//string path = System.IO.Path.Combine(Application.dataPath, "qr.png");
//System.IO.File.WriteAllBytes(path, bytes);
_texture = texture;
return texture;
}
}
}
}

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 614549820bd3c514eaf369e4895d1ef0
guid: 15a54a0341e9ec640a76fb0ae731e876
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -1,5 +1,5 @@
{
"name": "BITKit.UX.Chart.Tests",
"name": "BITKit.UX.Painter.Runtime",
"rootNamespace": "",
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
@@ -9,7 +9,7 @@
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:6de01b04fa4e14662b03fa46366da151",
"GUID:f19bbd83e3c264a5680926bf75d7e494",
"GUID:994a3fb33a5627740b0712e7c483cc1f"
"GUID:1c2aa13aa706ffc49a1a0044cad55436"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a9eec99827e569e45bfe3e5ea7494591
guid: 5cbf6465b63af3e4da531a4e4d26376b
AssemblyDefinitionImporter:
externalObjects: {}
userData:

View File

@@ -0,0 +1,150 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets.Kcp;
using SkiaSharp;
using UnityEngine;
using UnityEngine.UIElements;
namespace BITKit.UX
{
public class SkiaPainter : VisualElement
{
public new class UxmlFactory : UxmlFactory<SkiaPainter, UxmlTraits> { }
public SkiaPainter()
{
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
RegisterCallback<MouseDownEvent>(OnMouseDown);
RegisterCallback<MouseUpEvent>(OnMouseUp);
RegisterCallback<MouseMoveEvent>(OnMouseMove);
}
private bool _isPainting;
private readonly List<Vector2> _points = new();
private SKImageInfo _info;
private SKCanvas _canvas;
private SKSurface _surface;
private Rect _rect;
private Optional<SKColor> _backgroundColor=new ();
// private readonly SKPaint _linePaint = new()
// {
// Style = SKPaintStyle.Stroke,
// Color = SKColors.Red,
// StrokeWidth = 5,
// };
private readonly SKPaint _linePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Black, // 笔画颜色为黑色
StrokeCap = SKStrokeCap.Round, // 笔画末端采用圆形
StrokeJoin = SKStrokeJoin.Round, // 笔画连接处采用圆形
StrokeWidth = 8, // 笔画宽度
IsAntialias = true // 使用抗锯齿
};
public void ClearCanvas()
{
_points.Clear();
Rebuild();
}
private void OnMouseMove(MouseMoveEvent evt)
{
if (_isPainting is false) return;
var pos = evt.localMousePosition;
pos.y = layout.height - pos.y;
var last = _points.LastOrDefault();
if (_rect.Contains(pos) is false)
{
if (last == default) return;
_points.Add(default);
return;
}
var originalPos = pos;
if (last != default)
{
//pos = Vector2.Lerp(last, pos, 0.1f);
_points.Add(Vector2.Lerp(last, pos, 0.1f));
_points.Add(Vector2.Lerp(last, pos, 0.2f));
//_points.Add(Vector2.Lerp(last, pos, 1f));
}
else
{
_points.Add(pos);
}
//_points.Add(pos);
if (originalPos != pos)
{
//_points.Add(originalPos);
}
Rebuild();
}
private void OnMouseUp(MouseUpEvent evt)
{
_isPainting = false;
var last = _points.LastOrDefault();
if (last != default)
_points.Add(default);
}
private void OnMouseDown(MouseDownEvent evt)
{
_isPainting = true;
}
private void OnGeometryChanged(GeometryChangedEvent evt)
{
if(float.IsNaN(layout.width) || layout.width < 16 || float.IsNaN(layout.height) || layout.height<16)
return;
_linePaint.Color = resolvedStyle.color.ToSKColor();
_surface?.Dispose();
_canvas?.Dispose();
_info = new SKImageInfo((int)layout.width, (int)layout.height);
_info.AlphaType = SKAlphaType.Premul;
_surface = SKSurface.Create(_info);
_canvas = _surface.Canvas;
_rect = new Rect(0, 0, layout.width, layout.height);
Rebuild();
}
private void Rebuild()
{
if (_canvas is null) return;
_canvas.Clear(resolvedStyle.backgroundColor.ToSKColor());
DoubleBuffer<Vector2> buffer = new();
_linePaint.StrokeWidth = 4;
foreach (var pos in _points)
{
if (pos == default)
{
buffer.Clear();
}
else
{
if (buffer.TryGetRelease(out var previousPoint))
{
_canvas.DrawLine(previousPoint.x, previousPoint.y, pos.x, pos.y, _linePaint);
}
buffer.Release(pos);
}
}
var texture = _info.ToTexture2D(_surface);
style.backgroundImage = texture;
}
public string Base64 => _surface?.GetBase64();
public byte[] Bytes => _surface?.GetBytes();
}
}

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0fe70d1be19be44f9929fd63250df79f
guid: 8127b9701e668c549abd4e3c131168ce
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,53 @@
using System.Collections;
using System.Collections.Generic;
using BITKit;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;
namespace BITKit.UX
{
public class UXPlayer : MonoBehaviour
{
[SerializeReference, SubclassSelector] private IReference rootPath;
[SerializeField] private UnityEvent<float> onSetTime;
private Slider _playSlider;
private Label _currentLabel;
private Label _totalLabel;
private Button _playButton;
private VisualElement _container;
private void Start()
{
_container = GetComponent<UIDocument>().rootVisualElement.Q(rootPath.Value);
_playSlider = _container.Q<Slider>("play-slider");
_currentLabel = _container.Q<Label>("current-label");
_totalLabel = _container.Q<Label>("total-label");
_playButton = _container.Q<Button>("play-button");
_playSlider.RegisterValueChangedCallback(evt =>
{
onSetTime.Invoke(evt.newValue);
});
}
public void SetProgress(float time)
{
_playSlider.SetValueWithoutNotify(time);
}
public void SetTime(string time)
{
_currentLabel.text = time;
}
public void SetTotalTime(string time)
{
_totalLabel.text = time;
}
public void SetPlaying(bool isPlaying)
{
_playButton.EnableInClassList("playing",isPlaying);
}
public void SetActive(bool active)
{
_container.SetActive(active);
}
}
}

View File

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

View File

@@ -59,7 +59,16 @@ namespace BITKit.UX
//ve = document.rootVisualElement.Q(bindPathAtt.Path);
if(bindPathAtt.CanBeNull is false && ve is null)
BIT4Log.LogException(new NullReferenceException($"未找到{bindPathAtt.Path}"));
fieldInfo.SetValue(self,ve);
if (fieldInfo.FieldType == typeof(UXContainer))
{
fieldInfo.SetValue(self, new UXContainer(ve));
}
else
{
fieldInfo.SetValue(self, ve);
}
}

View File

@@ -10,6 +10,7 @@ using UnityEngine.Pool;
using UnityEngine.UIElements;
namespace BITKit
{
[CustomType(typeof(IUXWaiting))]
public class UXWaiting : MonoBehaviour,IUXWaiting
{
public sealed class WaitingHandle : IUXWaitingHandle
@@ -29,6 +30,10 @@ namespace BITKit
{
label.text = message;
}
else
{
BIT4Log.Log<UXWaiting>("WaitingHandle is destroyed");
}
}
}
@@ -41,11 +46,13 @@ namespace BITKit
private VisualElement _root;
private Pool<IUXWaitingHandle> _pool;
private readonly HashSet<IUXWaitingHandle> _handles = new();
public IUXWaitingHandle Get()
{
var handle = _pool.Take();
Active();
_handles.Add(handle);
return handle;
async void Active()
{
@@ -56,40 +63,42 @@ namespace BITKit
}
public async void Release(IUXWaitingHandle handle)
{
await UniTask.SwitchToMainThread();
if (destroyCancellationToken.IsCancellationRequested) return;
_pool.Return(handle);
_handles.Remove(handle);
Check();
}
private void Awake()
{
UXUtils.Inject(this);
foreach (var x in _container.Children().ToArray())
{
x.RemoveFromHierarchy();
}
_container.Clear();
_pool = new Pool<IUXWaitingHandle>(Create,OnReset,10);
if (asGlobal)
{
DI.Register<IUXWaiting>(this);
}
_root.SetActive(false);
}
private void Start()
{
Check();
}
private async void OnReset(IUXWaitingHandle obj)
{
await UniTask.SwitchToMainThread();
obj.As<WaitingHandle>().container.SetActive(false);
}
[BIT]
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));
_root.SetActive(_handles.Count>0);
}
private IUXWaitingHandle Create()
{
var handle = new WaitingHandle

View File

@@ -23,6 +23,34 @@ namespace BITKit
{
}
[CustomPropertyDrawer(typeof(ReadOnlyAttribute),true)]
public class ReadOnlyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var field = new PropertyField(property)
{
name = "PropertyField:" + property.propertyPath
};
Debug.Log("ReadOnlyAttribute");
field.pickingMode = PickingMode.Ignore;
field.SetEnabled(false);
field.style.opacity = 1;
var x = field.Q("unity-text-input");
var clearColor = new Color(0, 0, 0, 0);
x.style.backgroundColor = clearColor;
x.style.borderTopColor = clearColor;
x.style.borderBottomColor = clearColor;
x.style.borderLeftColor = clearColor;
x.style.borderRightColor = clearColor;
foreach (var visualElement in field.Children())
{
visualElement.pickingMode = PickingMode.Ignore;
}
field.Bind(property.serializedObject);
return field;
}
}
#endif
public class BITEditorUtils
{
@@ -69,38 +97,53 @@ namespace BITKit
var type = serializedObject.targetObject.GetType();
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>();
_container.style.flexDirection = FlexDirection.Row;
if (attribute.HideLabel is false)
{
_container.Create<Label>().text = $"{property.displayName}:";
_container.Create<VisualElement>().style.flexGrow = 1;
}
_container.Create<Label>().bindingPath = property.propertyPath;
}
else if (PropertyDrawerCache.TryGetPropertyDrawer(type, out var drawer))
{
var ve = drawer.CreatePropertyGUI(property);
container.Add(ve);
}
else
// if (fieldInfo is not null && type == typeof(string) && Attribute.IsDefined(fieldInfo, typeof(ReadOnlyAttribute),true))
// {
// var attribute = fieldInfo.GetCustomAttribute<ReadOnlyAttribute>();
// var _container = container.Create<VisualElement>();
// _container.style.flexDirection = FlexDirection.Row;
//
// if (attribute.HideLabel is false)
// {
// _container.Create<Label>().text = $"{property.displayName}:";
// _container.Create<VisualElement>().style.flexGrow = 1;
// }
// _container.Create<Label>().bindingPath = property.propertyPath;
// }
// else
// if (PropertyDrawerCache.TryGetPropertyDrawer(type, out var drawer))
// {
// var ve = drawer.CreatePropertyGUI(property);
// container.Add(ve);
// }
// else
{
//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.pickingMode = PickingMode.Ignore;
field.SetEnabled(false);
field.style.opacity = 1;
var x = field.Q("unity-text-input");
var clearColor = new Color(0, 0, 0, 0);
if (x is not null)
{
x.style.backgroundColor = clearColor;
x.style.borderTopColor = clearColor;
x.style.borderBottomColor = clearColor;
x.style.borderLeftColor = clearColor;
x.style.borderRightColor = clearColor;
foreach (var visualElement in field.Children())
{
visualElement.pickingMode = PickingMode.Ignore;
}
}
}
@@ -144,7 +187,7 @@ namespace BITKit
// });
// }
_container.Add(field);
container.Add(field);
}
// try
// {
@@ -215,7 +258,6 @@ namespace BITKit
Debug.LogException(e);
return base.CreateInspectorGUI();
}
return root;
}
}

View File

@@ -4,17 +4,12 @@ using UnityEngine;
using UnityEngine.Events;
using System.Linq;
using System.Reflection;
using System.IO;
using System.Runtime.CompilerServices;
using UnityEngine.Networking;
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;
using System.Text;
using System.IO.Compression;
using System.Data;
using UnityEngine.UIElements;
namespace BITKit
{
@@ -402,18 +397,6 @@ namespace BITKit
text = text.Remove(0, text.Split('\n')[0].Length + 1);
return text;
}
public static TaskAwaiter GetAwaiter(this AsyncOperation asyncOp)
{
var tcs = new TaskCompletionSource<object>();
asyncOp.completed += obj => { tcs.SetResult(null); };
return ((Task)tcs.Task).GetAwaiter();
}
public static TaskAwaiter GetAwaiter(this System.Action action)
{
var tcs = new TaskCompletionSource<object>();
action += () => { tcs.SetResult(null); };
return ((Task)tcs.Task).GetAwaiter();
}
public static float GetDifference(this IEnumerable<float> self)
{
return self.Max() - self.Min();
@@ -665,13 +648,32 @@ namespace BITKit
public static Vector2 GetScreenPosition(this VisualElement self, Vector3 worldPosition)
{
var pos = RuntimePanelUtils
.CameraTransformWorldToPanel(self.panel, worldPosition, Camera.main);
try
{
var panel = self.panel;
if (panel is null)
{
panel = self.parent.panel;
}
pos.x -= self.layout.width / 2;
pos.y -= self.layout.height / 2;
if (panel is null)
{
panel = self.parent.parent.panel;
}
return pos;
var pos = RuntimePanelUtils
.CameraTransformWorldToPanel(panel, worldPosition, Camera.main);
pos.x -= self.layout.width / 2;
pos.y -= self.layout.height / 2;
return pos;
}
catch (Exception e)
{
Debug.LogException(e);
}
return default;
}
public static Vector2 GetPosition(this VisualElement self)

View File

@@ -22,6 +22,7 @@ namespace BITKit
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);
public Action<T> OnSpawn { get; set; }
private ObjectPool<T> pool=> _pool ??=
new ObjectPool<T>
(Spawn, OnGet, OnReturn, OnDestroy,defaultCapacity:DefaultCapacity, maxSize:DefaultCapacity);
@@ -71,10 +72,21 @@ namespace BITKit
public void Return(T element)
{
pool.Release(element);
_list.Remove(element);
try
{
pool.Release(element);
}
catch (InvalidOperationException){}
_list.TryRemove(element);
}
private T Spawn() => Object.Instantiate(prefab, root);
private T Spawn()
{
var newObject = Object.Instantiate(prefab, root);
OnSpawn?.Invoke(newObject);
return newObject;
}
#region
private readonly ConcurrentDictionary<string, T> _dictionary=new();

View File

@@ -8,9 +8,11 @@ namespace BITKit.IO
{
public class UnityApplicationFile : MonoBehaviour
{
#if !UNITY_WEBGL
[SerializeReference,SubclassSelector] internal IApplicationFile service;
#endif
}
#if UNITY_EDITOR
#if UNITY_EDITOR && !UNITY_WEBGL
[CustomEditor(typeof(UnityApplicationFile))]
public sealed class UnityApplicationFileInspector:BITInspector<UnityApplicationFile>
{

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