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

View File

@ -5,6 +5,7 @@
<Nullable>disable</Nullable>
<PackageId>BITKit</PackageId>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@ -51,6 +52,7 @@
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.3-beta1" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
<PackageReference Include="UniTask" Version="2.3.3" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />

View File

@ -1,284 +1,623 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Threading;
using kcp2k;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Mathematics;
namespace BITKit.Entities
{
public class EntitiesService:IEntitiesService,IDisposable
public static class EntitiesServiceExtensions
{
private readonly ILogger<EntitiesService> _logger;
private readonly IFixedTicker _ticker;
private static int _count;
private static readonly ConcurrentQueue<IEntity> OnAddQueue = new();
private static ConcurrentDictionary<int, HashSet<int>> TypeCaches = new();
public EntitiesService(ILogger<EntitiesService> logger, IFixedTicker ticker)
internal static T GetRequiredServiceWithId<T>(this IServiceProvider serviceProvider, int id)
{
if (_count > 0)
{
throw new MulticastNotSupportedException();
return serviceProvider.GetRequiredService<T>();
}
}
_count++;
public unsafe class EntitiesService : IEntitiesService, IDisposable
{
private static int _count;
private readonly ILogger<EntitiesService> _logger;
private readonly ITicker _ticker;
private readonly ConcurrentQueue<IEntity> _onAddQueue = new();
private readonly Dictionary<int, HashSet<int>> _typeCaches = new();
private readonly Dictionary<int, ConcurrentDictionary<int, object>> _typeInstances = new();
private readonly ConcurrentDictionary<int, object> _staticCaches = new();
public EntitiesService(ILogger<EntitiesService> logger, ITicker ticker)
{
_logger = logger;
_ticker = ticker;
_ticker.Add(OnTick);
_pool = new(ObjectGenerator, null, 64);
_count++;
logger.LogInformation($"已创建EntitiesService,当前有:{_count}个实例");
_ticker?.Add(OnTick);
}
private void OnTick(float obj)
{
while (OnAddQueue.TryDequeue(out var entity))
while (_onAddQueue.TryDequeue(out var entity))
{
OnAdd?.Invoke(entity);
_staticCaches.Clear();
MakeCache(entity);
}
}
private void MakeCache(IEntity entity)
{
foreach (var serviceDescriptor in entity.ServiceCollection)
{
var typeHash = serviceDescriptor.ServiceType.GetHashCode();
var hashSet = TypeCaches.GetOrCreate(typeHash);
var hashSet = _typeCaches.GetOrCreate(typeHash);
hashSet.Add(entity.Id);
}
}
private readonly Dictionary<int, IEntity> _entitiesInternal = new();
public event Action<IEntity> OnAdd;
public event Action<IEntity> OnRemove;
IReadOnlyDictionary<int, IEntity> IEntitiesService.Entities => _entitiesInternal;
private readonly Pool<HashSet<int>> _pool;
private HashSet<int> ObjectGenerator()
{
return new HashSet<int>(math.max(8192, _typeCaches.Count * 2));
}
public bool Register(IEntity entity)
{
if (!_entitiesInternal.TryAdd(entity.Id, entity)) return false;
if (_ticker is not null)
{
_onAddQueue.Enqueue(entity);
}
else
{
_staticCaches.Clear();
OnAdd?.Invoke(entity);
MakeCache(entity);
}
private static readonly ConcurrentDictionary<int, IEntity> Entities = new();
public event Action<IEntity> OnAdd;
public event Action<IEntity> OnRemove;
IReadOnlyDictionary<int, IEntity> IEntitiesService.Entities => Entities;
public bool Register(IEntity entity)
{
if (!Entities.TryAdd(entity.Id, entity)) return false;
OnAddQueue.Enqueue(entity);
return true;
}
public bool UnRegister(IEntity entity)
{
if (!Entities.TryRemove(entity.Id, out _)) return false;
if (!_entitiesInternal.TryRemove(entity.Id)) return false;
OnRemove?.Invoke(entity);
foreach (var serviceDescriptor in entity.ServiceCollection)
{
var typeHash = serviceDescriptor.ServiceType.GetHashCode();
var hashSet = TypeCaches.GetOrCreate(typeHash);
var hashSet = _typeCaches.GetOrCreate(typeHash);
hashSet.Remove(entity.Id);
}
_typeInstances.TryRemove(entity.Id);
_staticCaches.Clear();
return true;
}
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
private readonly CancellationTokenSource _cancellationTokenSource = new();
public IEntity Get(int id)
{
return Entities[id];
return _entitiesInternal[id];
}
public bool TryGetEntity(int id, out IEntity entity)
{
return Entities.TryGetValue(id, out entity);
return _entitiesInternal.TryGetValue(id, out entity);
}
public IEntity GetOrAdd(int id, Func<int, IEntity> factory)
{
return Entities.GetOrAdd(id, factory);
if (_entitiesInternal.TryGetValue(id, out var current))
{
_entitiesInternal.TryAdd(id, current = factory.Invoke(id));
}
public IEntity[] Query<T>()
{
throw new NotImplementedException("Obsoleted");
return current;
}
public T[] QueryComponents<T>()
public Span<T> QueryComponents<T>() where T : class
{
var list = new List<T>();
foreach (var entity in Entities.Values)
var pool = ArrayPool<T>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var array = pool.Rent(hashset.Count);
var collection = _pool.Take();
collection.Clear();
collection.UnionWith(hashset);
var i = 0;
foreach (var id in collection)
{
if (entity.ServiceProvider.GetService<T>() is { } t1)
var instances = _typeInstances.GetOrCreate(id);
var h0 = typeof(T).GetHashCode();
if (instances.TryGetValue(h0, out var v0) is false)
{
list.Add(t1);
instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T>();
}
array[i] = Unsafe.As<T>(v0);
i++;
}
try
{
return new Span<T>(array, 0, i);
}
finally
{
pool.Return(array);
_pool.Return(collection);
}
}
return list.ToArray();
// return _queryCache.GetOrAdd(typeof(T), type =>
// {
// return _entities.Values.Where(entity => entity.TryGetComponent(out T component)).ToArray();
// }).Cast<T>().ToArray();
public Span<(T, T1)> QueryComponents<T, T1>() where T : class where T1 : class
{
var pool = ArrayPool<(T, T1)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var count = math.max(hashset.Count, t1Set.Count);
var array = pool.Rent(count);
var collection = _pool.Take();
collection.Clear();
collection.UnionWith(hashset);
collection.IntersectWith(t1Set);
var i = 0;
foreach (var id in collection)
{
var instances = _typeInstances.GetOrCreate(id);
var h0 = typeof(T).GetHashCode();
var h1 = typeof(T1).GetHashCode();
if (instances.TryGetValue(h0, out var v0) is false)
{
instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T>();
}
public (T, T1)[] QueryComponents<T, T1>()
if (instances.TryGetValue(h1, out var v1) is false)
{
var list = new List<(T, T1)>();
foreach (var entity in Entities.Values)
instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T1>();
}
array[i] = (Unsafe.As<T>(v0), Unsafe.As<T1>(v1));
i++;
}
try
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2)
return new Span<(T, T1)>(array, 0, i);
}
finally
{
list.Add((t1, t2));
pool.Return(array);
_pool.Return(collection);
}
}
return list.ToArray();
// List<(T, T1)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1))
// list.Add((t, t1));
// }
// return list.ToArray();
public Span<(T, T1, T2)> QueryComponents<T, T1, T2>() where T : class where T1 : class where T2 : class
{
var pool = ArrayPool<(T, T1, T2)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var count = math.max(hashset.Count, math.max(t1Set.Count, t2Set.Count));
var array = pool.Rent(count);
var collection = _pool.Take();
collection.Clear();
collection.UnionWith(hashset);
collection.IntersectWith(t1Set);
collection.IntersectWith(t2Set);
var i = 0;
foreach (var id in collection)
{
var instances = _typeInstances.GetOrCreate(id);
var h0 = typeof(T).GetHashCode();
var h1 = typeof(T1).GetHashCode();
var h2 = typeof(T2).GetHashCode();
if (instances.TryGetValue(h0, out var v0) is false)
{
instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T>();
}
public (T, T1, T2)[] QueryComponents<T, T1, T2>()
if (instances.TryGetValue(h1, out var v1) is false)
{
var list = new List<(T, T1, T2)>();
foreach (var entity in Entities.Values)
instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T1>();
}
if (instances.TryGetValue(h2, out var v2) is false)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3)
instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T2>();
}
array[i] = (Unsafe.As<T>(v0), Unsafe.As<T1>(v1), Unsafe.As<T2>(v2));
i++;
}
try
{
list.Add((t1, t2, t3));
return new Span<(T, T1, T2)>(array, 0, i);
}
finally
{
pool.Return(array);
_pool.Return(collection);
}
}
return list.ToArray();
// List<(T, T1, T2)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2))
// list.Add((t, t1, t2));
// }
// return list.ToArray();
public Span<(T, T1, T2, T3)> QueryComponents<T, T1, T2, T3>()
where T : class where T1 : class where T2 : class where T3 : class
{
var pool = ArrayPool<(T, T1, T2, T3)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var count = math.max(hashset.Count, math.max(t1Set.Count, math.max(t2Set.Count, t3Set.Count)));
var array = pool.Rent(count);
var collection = _pool.Take();
collection.Clear();
collection.UnionWith(hashset);
collection.IntersectWith(t1Set);
collection.IntersectWith(t2Set);
collection.IntersectWith(t3Set);
var i = 0;
foreach (var id in collection)
{
var instances = _typeInstances.GetOrCreate(id);
var h0 = typeof(T).GetHashCode();
var h1 = typeof(T1).GetHashCode();
var h2 = typeof(T2).GetHashCode();
var h3 = typeof(T3).GetHashCode();
if (instances.TryGetValue(h0, out var v0) is false)
{
instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T>();
}
public (T, T1, T2, T3)[] QueryComponents<T, T1, T2, T3>()
if (instances.TryGetValue(h1, out var v1) is false)
{
var list = new List<(T, T1, T2, T3)>();
foreach (var entity in Entities.Values)
instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T1>();
}
if (instances.TryGetValue(h2, out var v2) is false)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4)
instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T2>();
}
if (instances.TryGetValue(h3, out var v3) is false)
{
list.Add((t1, t2, t3, t4));
instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T3>();
}
array[i] = (Unsafe.As<T>(v0), Unsafe.As<T1>(v1), Unsafe.As<T2>(v2), Unsafe.As<T3>(v3));
i++;
}
try
{
return new Span<(T, T1, T2, T3)>(array, 0, i);
}
finally
{
pool.Return(array);
_pool.Return(collection);
}
}
return list.ToArray();
// List<(T, T1, T2, T3)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3))
// list.Add((t, t1, t2, t3));
// }
// return list.ToArray();
public Span<(T, T1, T2, T3, T4)> QueryComponents<T, T1, T2, T3, T4>() where T : class
where T1 : class
where T2 : class
where T3 : class
where T4 : class
{
var pool = ArrayPool<(T, T1, T2, T3, T4)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var t4Set = _typeCaches.GetOrCreate(typeof(T4).GetHashCode());
var count = math.max(hashset.Count,
math.max(t1Set.Count, math.max(t2Set.Count, math.max(t3Set.Count, t4Set.Count))));
var array = pool.Rent(count);
var collection = _pool.Take();
collection.Clear();
collection.UnionWith(hashset);
collection.IntersectWith(t1Set);
collection.IntersectWith(t2Set);
collection.IntersectWith(t3Set);
collection.IntersectWith(t4Set);
var i = 0;
foreach (var id in collection)
{
var instances = _typeInstances.GetOrCreate(id);
var h0 = typeof(T).GetHashCode();
var h1 = typeof(T1).GetHashCode();
var h2 = typeof(T2).GetHashCode();
var h3 = typeof(T3).GetHashCode();
var h4 = typeof(T4).GetHashCode();
if (instances.TryGetValue(h0, out var v0) is false)
{
instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T>();
}
public (T, T1, T2, T3, T4)[] QueryComponents<T, T1, T2, T3, T4>()
if (instances.TryGetValue(h1, out var v1) is false)
{
var list = new List<(T, T1, T2, T3, T4)>();
foreach (var entity in Entities.Values)
instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T1>();
}
if (instances.TryGetValue(h2, out var v2) is false)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5)
instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T2>();
}
if (instances.TryGetValue(h3, out var v3) is false)
{
list.Add((t1, t2, t3, t4, t5));
instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T3>();
}
if (instances.TryGetValue(h4, out var v4) is false)
{
instances[h4] = v4 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T4>();
}
array[i] = (Unsafe.As<T>(v0), Unsafe.As<T1>(v1), Unsafe.As<T2>(v2), Unsafe.As<T3>(v3),
Unsafe.As<T4>(v4));
i++;
}
try
{
return new Span<(T, T1, T2, T3, T4)>(array, 0, i);
}
finally
{
pool.Return(array);
_pool.Return(collection);
}
}
return list.ToArray();
// List<(T, T1, T2, T3, T4)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3) && entity.TryGetComponent(out T4 t4))
// list.Add((t, t1, t2, t3, t4));
// }
// return list.ToArray();
public Span<(T, T1, T2, T3, T4, T5)> QueryComponents<T, T1, T2, T3, T4, T5>() where T : class
where T1 : class
where T2 : class
where T3 : class
where T4 : class
where T5 : class
{
var pool = ArrayPool<(T, T1, T2, T3, T4, T5)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var t4Set = _typeCaches.GetOrCreate(typeof(T4).GetHashCode());
var t5Set = _typeCaches.GetOrCreate(typeof(T5).GetHashCode());
var count = math.max(hashset.Count,
math.max(t1Set.Count,
math.max(t2Set.Count, math.max(t3Set.Count, math.max(t4Set.Count, t5Set.Count)))));
var array = pool.Rent(count);
var collection = _pool.Take();
collection.Clear();
collection.UnionWith(hashset);
collection.IntersectWith(t1Set);
collection.IntersectWith(t2Set);
collection.IntersectWith(t3Set);
collection.IntersectWith(t4Set);
collection.IntersectWith(t5Set);
var i = 0;
foreach (var id in collection)
{
var instances = _typeInstances.GetOrCreate(id);
var h0 = typeof(T).GetHashCode();
var h1 = typeof(T1).GetHashCode();
var h2 = typeof(T2).GetHashCode();
var h3 = typeof(T3).GetHashCode();
var h4 = typeof(T4).GetHashCode();
var h5 = typeof(T5).GetHashCode();
if (instances.TryGetValue(h0, out var v0) is false)
{
instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T>();
}
public (T, T1, T2, T3, T4, T5)[] QueryComponents<T, T1, T2, T3, T4, T5>()
if (instances.TryGetValue(h1, out var v1) is false)
{
var list = new List<(T, T1, T2, T3, T4, T5)>();
foreach (var entity in Entities.Values)
instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T1>();
}
if (instances.TryGetValue(h2, out var v2) is false)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5 &&
entity.ServiceProvider.GetService<T5>() is { } t6)
instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T2>();
}
if (instances.TryGetValue(h3, out var v3) is false)
{
list.Add((t1, t2, t3, t4, t5, t6));
instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T3>();
}
if (instances.TryGetValue(h4, out var v4) is false)
{
instances[h4] = v4 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T4>();
}
if (instances.TryGetValue(h5, out var v5) is false)
{
instances[h5] = v5 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T5>();
}
array[i] = (Unsafe.As<T>(v0), Unsafe.As<T1>(v1), Unsafe.As<T2>(v2), Unsafe.As<T3>(v3),
Unsafe.As<T4>(v4), Unsafe.As<T5>(v5));
i++;
}
try
{
return new Span<(T, T1, T2, T3, T4, T5)>(array, 0, i);
}
finally
{
pool.Return(array);
_pool.Return(collection);
}
}
return list.ToArray();
// List<(T, T1, T2, T3, T4, T5)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3) && entity.TryGetComponent(out T4 t4) && entity.TryGetComponent(out T5 t5))
// list.Add((t, t1, t2, t3, t4, t5));
// }
// return list.ToArray();
public Span<(T, T1, T2, T3, T4, T5, T6)> QueryComponents<T, T1, T2, T3, T4, T5, T6>() where T : class
where T1 : class
where T2 : class
where T3 : class
where T4 : class
where T5 : class
where T6 : class
{
var pool = ArrayPool<(T, T1, T2, T3, T4, T5, T6)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var t4Set = _typeCaches.GetOrCreate(typeof(T4).GetHashCode());
var t5Set = _typeCaches.GetOrCreate(typeof(T5).GetHashCode());
var t6Set = _typeCaches.GetOrCreate(typeof(T6).GetHashCode());
var count = math.max(hashset.Count,
math.max(t1Set.Count,
math.max(t2Set.Count,
math.max(t3Set.Count, math.max(t4Set.Count, math.max(t5Set.Count, t6Set.Count))))));
var array = pool.Rent(count);
var collection = _pool.Take();
collection.Clear();
collection.UnionWith(hashset);
collection.IntersectWith(t1Set);
collection.IntersectWith(t2Set);
collection.IntersectWith(t3Set);
collection.IntersectWith(t4Set);
collection.IntersectWith(t5Set);
collection.IntersectWith(t6Set);
var i = 0;
foreach (var id in collection)
{
var instances = _typeInstances.GetOrCreate(id);
var h0 = typeof(T).GetHashCode();
var h1 = typeof(T1).GetHashCode();
var h2 = typeof(T2).GetHashCode();
var h3 = typeof(T3).GetHashCode();
var h4 = typeof(T4).GetHashCode();
var h5 = typeof(T5).GetHashCode();
var h6 = typeof(T6).GetHashCode();
if (instances.TryGetValue(h0, out var v0) is false)
{
instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T>();
}
public (T, T1, T2, T3, T4, T5, T6)[] QueryComponents<T, T1, T2, T3, T4, T5, T6>()
if (instances.TryGetValue(h1, out var v1) is false)
{
var list = new List<(T, T1, T2, T3, T4, T5, T6)>();
foreach (var entity in Entities.Values)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5 &&
entity.ServiceProvider.GetService<T5>() is { } t6 &&
entity.ServiceProvider.GetService<T6>() is { } t7)
{
list.Add((t1, t2, t3, t4, t5, t6, t7));
}
instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T1>();
}
return list.ToArray();
// List<(T, T1, T2, T3, T4, T5, T6)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3) && entity.TryGetComponent(out T4 t4) && entity.TryGetComponent(out T5 t5) && entity.TryGetComponent(out T6 t6))
// list.Add((t, t1, t2, t3, t4, t5, t6));
// }
// return list.ToArray();
if (instances.TryGetValue(h2, out var v2) is false)
{
instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T2>();
}
public ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>[] QueryComponents<T, T1, T2, T3, T4, T5, T6, TRest>()
where TRest : struct
if (instances.TryGetValue(h3, out var v3) is false)
{
var list = new List<ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>>();
foreach (var entity in Entities.Values)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5 &&
entity.ServiceProvider.GetService<T5>() is { } t6 &&
entity.ServiceProvider.GetService<T6>() is { } t7 &&
entity.ServiceProvider.GetService<TRest>() is { } t8)
{
list.Add(new ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>(t1, t2, t3, t4, t5, t6, t7, t8));
instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T3>();
}
if (instances.TryGetValue(h4, out var v4) is false)
{
instances[h4] = v4 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T4>();
}
if (instances.TryGetValue(h5, out var v5) is false)
{
instances[h5] = v5 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T5>();
}
if (instances.TryGetValue(h6, out var v6) is false)
{
instances[h6] = v6 = _entitiesInternal[id].ServiceProvider.GetRequiredService<T6>();
}
array[i] = (Unsafe.As<T>(v0), Unsafe.As<T1>(v1), Unsafe.As<T2>(v2), Unsafe.As<T3>(v3),
Unsafe.As<T4>(v4), Unsafe.As<T5>(v5), Unsafe.As<T6>(v6));
i++;
}
try
{
return new Span<(T, T1, T2, T3, T4, T5, T6)>(array, 0, i);
}
finally
{
pool.Return(array);
_pool.Return(collection);
}
return list.ToArray();
}
public void Dispose()
@ -286,14 +625,14 @@ namespace BITKit.Entities
_count--;
if (_count <= 0)
{
Entities.Clear();
_entitiesInternal.Clear();
}
_logger.LogInformation($"已释放,还剩{_count}个实例");
_cancellationTokenSource?.Dispose();
_ticker.Remove(OnTick);
_ticker?.Remove(OnTick);
}
}
}

View File

@ -18,6 +18,7 @@ namespace BITKit.Entities
IServiceProvider ServiceProvider { get; }
IServiceCollection ServiceCollection { get; }
void Inject(object obj);
}
/// <summary>
/// 基本实体服务
@ -67,18 +68,12 @@ namespace BITKit.Entities
/// </summary>
IEntity GetOrAdd(int id,Func<int,IEntity> factory);
/// <summary>
/// 查询Entity,例如
/// </summary>
/// <para>var rotationEntities=EntitiesService.Query&lt;RotationComponent&gt;</para>
IEntity[] Query<T>();
/// <summary>
/// 查询1个组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T[] QueryComponents<T>();
Span<T> QueryComponents<T>() where T : class;
/// <summary>
/// 查询2个组件
@ -86,7 +81,7 @@ namespace BITKit.Entities
/// <typeparam name="T"></typeparam>
/// <typeparam name="T1"></typeparam>
/// <returns></returns>
ValueTuple<T, T1>[] QueryComponents<T, T1>();
Span<ValueTuple<T, T1>> QueryComponents<T, T1>() where T : class where T1 : class;
/// <summary>
/// 查询3个组件
@ -95,7 +90,7 @@ namespace BITKit.Entities
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2>[] QueryComponents<T, T1, T2>();
Span<ValueTuple<T, T1, T2>> QueryComponents<T, T1, T2>() where T : class where T1 : class where T2 : class;
/// <summary>
/// 查询4个组件
@ -105,7 +100,7 @@ namespace BITKit.Entities
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3>[] QueryComponents<T, T1, T2, T3>();
Span<ValueTuple<T, T1, T2, T3>> QueryComponents<T, T1, T2, T3>() where T : class where T1 : class where T2 : class where T3:class;
/// <summary>
/// 查询5个组件
@ -116,7 +111,7 @@ namespace BITKit.Entities
/// <typeparam name="T3"></typeparam>
/// <typeparam name="T4"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4>[] QueryComponents<T, T1, T2, T3, T4>();
Span<ValueTuple<T, T1, T2, T3, T4>> QueryComponents<T, T1, T2, T3, T4>()where T : class where T1 : class where T2 : class where T3:class where T4:class;
/// <summary>
/// 查询6个组件
/// </summary>
@ -127,7 +122,7 @@ namespace BITKit.Entities
/// <typeparam name="T4"></typeparam>
/// <typeparam name="T5"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4, T5>[] QueryComponents<T, T1, T2, T3, T4, T5>();
Span<ValueTuple<T, T1, T2, T3, T4, T5>> QueryComponents<T, T1, T2, T3, T4, T5>()where T : class where T1 : class where T2 : class where T3:class where T4:class where T5 : class;
/// <summary>
/// 查询7个组件
@ -140,7 +135,7 @@ namespace BITKit.Entities
/// <typeparam name="T5"></typeparam>
/// <typeparam name="T6"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4, T5, T6>[] QueryComponents<T, T1, T2, T3, T4, T5, T6>();
Span<ValueTuple<T, T1, T2, T3, T4, T5, T6>> QueryComponents<T, T1, T2, T3, T4, T5, T6>()where T : class where T1 : class where T2 : class where T3:class where T4:class where T5 : class where T6 :class;
/// <summary>
/// 查询8个组件
@ -154,6 +149,6 @@ namespace BITKit.Entities
/// <typeparam name="T6"></typeparam>
/// <typeparam name="TRest">剩余实例</typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>[] QueryComponents<T, T1, T2, T3, T4, T5, T6, TRest>() where TRest : struct;
//Span<ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>> QueryComponents<T, T1, T2, T3, T4, T5, T6, TRest>() where TRest : struct;
}
}

View File

@ -13,6 +13,7 @@ namespace BITKit.Entities
[DefaultExecutionOrder(-1)]
public class UnityEntity : MonoBehaviour,IEntity
{
[SerializeField] private bool debug;
private IEntitiesService _entitiesService;
private IEntity _entity;
@ -22,6 +23,11 @@ namespace BITKit.Entities
if (_entitiesService.Entities.ContainsKey(gameObject.GetInstanceID())) return;
if (debug)
{
}
var entity = new Entity()
{
Id = gameObject.GetInstanceID(),
@ -38,8 +44,11 @@ namespace BITKit.Entities
foreach (var component in GetComponents<Component>())
{
if(!component)continue;
var type = component.GetType();
entity.ServiceCollection.AddSingleton(type, component);
foreach (var x in type.GetInterfaces())
{
entity.ServiceCollection.AddSingleton(x, component);

View File

@ -122,44 +122,20 @@ namespace BITKit
value = default;
return default;
}
[Obsolete("Use TryGetElementAt instead")]
public static bool TryGet<T>(this IEnumerable<T> self, int index, out T value)=>TryGetElementAt(self, index, out value);
public static bool TryInsert<TKey, TValue>(this IDictionary<TKey, TValue> self, TKey key, TValue value)
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> self, TKey t) where TValue : new()
{
lock (self)
{
if (self.ContainsKey(key))
{
self[key] = value;
}
else
{
self.Add(key, value);
}
return true;
}
}
public static void Insert<TKey, TValue>(this IDictionary<TKey, TValue> self, TKey key, TValue value)
{
TryInsert(self, key, value);
}
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> self, TKey t)
{
lock (self)
{
if (self.TryGetValue(t, out TValue value))
if (self.TryGetValue(t, out var value))
{
}
else
{
value = Activator.CreateInstance<TValue>();
self.Add(t, value);
value = new TValue();
self.TryAdd(t, value);
}
return value;
}
}
public static bool TryRemove<T>(this IList<T> self, T t)
{
if (self.Contains(t))

View File

@ -1,9 +1,64 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.DependencyInjection;
using Unity.Mathematics;
namespace BITKit
{
public static partial class Extensions
{
private static readonly ConcurrentDictionary<int,ConcurrentDictionary<int,object>> Services = new();
public static bool QueryComponents<T>(this IServiceProvider self,out T t1) where T : class
{
var id = self.GetHashCode();
var typeId = typeof(T).GetHashCode();
if (Services.GetOrCreate(id).TryGetValue(typeId, out var value) is false)
{
value = self.GetService<T>();
Services.GetOrCreate(id).TryAdd(typeId,value);
}
if (value is null)
{
t1 = null;
return false;
}
t1 = Unsafe.As<T>(value);
return true;
}
public static bool QueryComponents<T,T1> (this IServiceProvider self,out T t1,out T1 t2) where T : class where T1 : class
{
t1 = null;
t2 = null;
if (QueryComponents(self,out t1) is false) return false;
if (QueryComponents(self,out t2) is false) return false;
return true;
}
public static bool QueryComponents<T,T1,T2> (this IServiceProvider self,out T t1,out T1 t2,out T2 t3) where T : class where T1 : class where T2 : class
{
t1 = null;
t2 = null;
t3 = null;
if (QueryComponents(self,out t1) is false) return false;
if (QueryComponents(self,out t2) is false) return false;
if (QueryComponents(self,out t3) is false) return false;
return true;
}
public static bool QueryComponents<T,T1,T2,T3> (this IServiceProvider self,out T t1,out T1 t2,out T2 t3,out T3 t4) where T : class where T1 : class where T2 : class where T3 : class
{
t1 = null;
t2 = null;
t3 = null;
t4 = null;
if (QueryComponents(self,out t1) is false) return false;
if (QueryComponents(self,out t2) is false) return false;
if (QueryComponents(self,out t3) is false) return false;
if (QueryComponents(self,out t4) is false) return false;
return true;
}
public static T IsNull<T>(this T t, Action action)
{
if (t is null)

View File

@ -1,139 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Cysharp.Threading.Tasks;
// ReSharper disable MethodHasAsyncOverload
namespace BITKit
{
public interface IEntryGroup
{
}
public interface IEntryElement
{
bool IsEntered { get; set; }
void Entry();
UniTask EntryAsync();
void Entered();
void Exit();
UniTask ExitAsync();
void Exited();
}
[System.Serializable]
public class EntryGroup<T> : IEntryGroup where T : IEntryElement
{
public int index = -1;
public List<T> list = new();
private int m_index = -1;
private bool completed=true;
public event Action<T> OnEntry;
public event Action<T> OnExit;
public void Entry(T t)
{
if (t is not null)
{
list.TryAdd(t);
}
index = list.IndexOf(t);
EnsureConfiguration();
}
public void Entry(int index)
{
if (index < list.Count)
{
this.index = index;
}
}
public void Entry(Func<T,bool> entryFactory)
{
var index = list.FindIndex(x => entryFactory.Invoke(x));
Entry(index);
}
public void Entry()
{
index = 0;
EnsureConfiguration();
}
public void Exit()
{
index = -1;
EnsureConfiguration();
}
public bool TryGetEntried(out T value)
{
EnsureConfiguration();
if (m_index is not -1)
{
value = list[m_index];
return true;
}
value = default;
return false;
}
private async void EnsureConfiguration()
{
try
{
if(completed is false) return;
completed = false;
if (index == m_index)
{
}
else
{
var currentIndex = m_index;
if (currentIndex is not -1 && list.TryGetElementAt(currentIndex, out var currentElement))
{
currentElement.Exit();
try
{
await currentElement.ExitAsync();
}
catch (OperationCanceledException)
{
return;
}
catch (Exception e)
{
BIT4Log.LogException(e);
}
currentElement.Exited();
currentElement.IsEntered = false;
OnExit?.Invoke(currentElement);
}
m_index = index;
if (index is not -1 && list.TryGetElementAt(index, out var nextElement))
{
nextElement.IsEntered = true;
OnEntry?.Invoke(nextElement);
nextElement.Entry();
try
{
await nextElement.EntryAsync();
nextElement.Entered();
}
catch (OperationCanceledException){}
}
}
completed = true;
}
catch (Exception e)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogException(e);
#else
BIT4Log.LogException(e);
#endif
}
}
}
}

View File

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

View File

@ -0,0 +1,92 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using BITKit;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Net.BITKit.Localization
{
public class CsvLocalizationService
{
public string Url { get; set; } =
@"http://server.bitfall.icu:21982/com.project.b/net.project.b.localization.csv";
private readonly ILogger<CsvLocalizationService> _logger;
private readonly ILocalizationService _localizationService;
public CsvLocalizationService(ILocalizationService localizationService, ILogger<CsvLocalizationService> logger)
{
_localizationService = localizationService;
_logger = logger;
_localizationService.OnLanguageChangeAsync += OnLanguageChangeAsync;
}
public static Dictionary<string, Dictionary<string, string>> ParseCsvToDictionary(string csvData)
{
var dict = new Dictionary<string, Dictionary<string, string>>();
using var reader = new StringReader(csvData);
var headerLine = reader.ReadLine();
if (headerLine == null) return dict;
var headers = headerLine.Split(',');
for (var i = 1; i < headers.Length; i++) // 跳过 "Key" 列
{
var header = headers[i];
if (dict.ContainsKey(header) is false)
{
dict.Add(header, new Dictionary<string, string>());
}
}
string? line;
while ((line = reader.ReadLine()) != null)
{
var columns = line.Split(',');
if (columns.Length < 2) continue;
var key = columns[0]; // 取 Key 值
for (var i = 1; i < columns.Length; i++)
{
if(i>headers.Length-1)continue;
var header = headers[i];
if (dict.TryGetValue(header, out var d) is false)
{
d = new Dictionary<string, string>();
dict.Add(header,d);
}
d.Set(key,columns[i]);
//dict[headers[i]][key] = columns[i]; // 填充语言数据
}
}
return dict;
}
private async UniTask OnLanguageChangeAsync(string arg1, string arg2)
{
var csv = await new HttpClient().GetStringAsync(Url+$"?{DateTime.Now.Ticks}");
_logger.LogInformation($"下载完成:\n{csv}");
foreach (var (lang, dictionary) in ParseCsvToDictionary(csv))
{
if (_localizationService.LocalizedStrings.TryGetValue(lang, out var currentDictionary) is false)
{
currentDictionary = new Dictionary<string, string>();
_localizationService.LocalizedStrings.Add(lang,currentDictionary);
}
foreach (var (key, value) in dictionary)
{
currentDictionary.Set(key, value);
}
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2bc2bf5dce5a1fa418a53e9616109677
guid: 0a0edc8f5d75b3646a7b405ef830a52d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,113 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using BITKit;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Net.BITKit.Localization
{
/// <summary>
/// 本地化
/// </summary>
public interface ILocalizationService
{
public string Prefix { get; }
/// <summary>
/// 当前语言
/// </summary>
public string CurrentLanguage { get; }
/// <summary>
/// 更改回调,通常在此检查并下载语言包
/// </summary>
public event Func<string,string,UniTask> OnLanguageChangeAsync;
/// <summary>
/// 语言更改完成回调,UI和World执行更新
/// </summary>
public event Action<string,string> OnLanguageChanged;
/// <summary>
/// 获取翻译文本,返回无复制的引用
/// </summary>
/// <param name="key"></param>
/// <param name="language">语言,默认为当前CultureInfo.Name</param>
/// <returns></returns>
public string GetLocalizedString(string key,string language=null);
public UniTask ChangeLanguageAsync(string newLanguage);
public IDictionary<string, IDictionary<string, string>> LocalizedStrings { get; }
}
public class LocalizationService : ILocalizationService,IDisposable
{
private static LocalizationService _singleton;
public string Prefix => "#";
private char _prefix = '#';
public string CurrentLanguage { get; private set; }
public event Func<string, string, UniTask> OnLanguageChangeAsync;
public event Action<string, string> OnLanguageChanged;
private readonly ILogger<LocalizationService> _logger;
public IDictionary<string, IDictionary<string, string>> LocalizedStrings { get; } =
new Dictionary<string, IDictionary<string, string>>();
private readonly ValidHandle _isBusy = new();
private readonly HashSet<string> _untranslatedKeys = new();
public LocalizationService(ILogger<LocalizationService> logger)
{
if (_singleton is not null)
{
logger.LogError("LocalizationService can only be one singleton");
return;
}
_logger = logger;
}
public string GetLocalizedString(string key, string language = null)
{
language ??= CurrentLanguage; // 默认使用当前语言
if (key[0] != _prefix)
{
key = _prefix + key;
}
if (LocalizedStrings.TryGetValue(language, out var langDict) && langDict.TryGetValue(key, out var value))
{
return value;
}
_untranslatedKeys.Add(key);
return key.Replace(Prefix,string.Empty).Replace("_"," "); // 如果找不到翻译,就返回 key 本身(常见策略)
}
public async UniTask ChangeLanguageAsync(string newLanguage)
{
if (CurrentLanguage == newLanguage) return;
var oldLanguage = CurrentLanguage;
CurrentLanguage = newLanguage;
await _isBusy;
using var _ = _isBusy.GetHandle();
#if UNITY_5_3_OR_NEWER
await UniTask.SwitchToMainThread();
#endif
await OnLanguageChangeAsync.UniTaskFunc(oldLanguage, newLanguage);
// 触发同步事件(例如更新 UI
OnLanguageChanged?.Invoke(oldLanguage, newLanguage);
}
public void Dispose()
{
_isBusy?.Dispose();
_singleton = null;
_logger.LogInformation("Untranslated keys:\n"+string.Join("\n", _untranslatedKeys));
_untranslatedKeys.Clear();
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 407c6cf54ae8abc42b77a090f9fa7616
guid: baab5c8590ad6ee44acbc258eeacdc8b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -19,6 +19,7 @@ namespace BITKit.Mod
{
list.Add((T)obj);
}
}
return list;
}

8
Src/Core/Quadtree.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c15a9900918e4324991bcf53015b006e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
{
"name": "Net.BITKit.Quadtree",
"rootNamespace": "",
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:e0cd26848372d4e5c891c569017e11f1",
"GUID:2665a8d13d1b3f18800f46e256720795"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -0,0 +1,305 @@
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Policy;
using System.Threading;
using System.Threading.Tasks;
using BITKit;
#if UNITY_5_3_OR_NEWER
using Unity.Burst;
#endif
using Unity.Mathematics;
namespace Net.BITKit.Quadtree
{
public class Quadtree
{
public QuadtreeNode Root { get; private set; }
public IDictionary<int, float2> Positions
{
get
{
Expansion();
return _positions;
}
}
public IDictionary<int, float2> Sizes => _sizes;
private readonly Dictionary<int, float2> _positions;
private readonly ConcurrentQueue<(int, float2)> _queue;
private readonly Dictionary<int, float2> _sizes;
public Quadtree(float2 center, float2 size)
{
_sizes = new Dictionary<int, float2>();
_positions = new Dictionary<int, float2>();
_queue = new ConcurrentQueue<(int, float2)>();
Root = new QuadtreeNode(this, center, size);
}
public void Insert(in int objectId, in float2 position, in float2 size = default)
{
_queue.Enqueue((objectId, position));
if (size.x is not 0)
{
_sizes.TryAdd(objectId, size);
}
else
{
var root = Root;
InsertRecursive(ref root, in objectId, in position);
}
}
private void InsertRecursive(ref QuadtreeNode node, in int objectId, in float2 position)
{
if (!node.Contains(position))
return;
if (node.Objects.Count < 4 || node.Size.x <= 1f || node.Depth > Root.Size.x / 8) // 假设最小节点大小为1
{
node.Objects.Add(objectId);
}
else
{
if (node.Children[0].Size.x == 0) // 如果子节点未初始化
{
node.Split();
}
for (var i = 0; i < 4; i++)
{
InsertRecursive(ref node.Children[i], objectId, position);
}
}
}
public Span<int> Query(float2 position, float radius)
{
Expansion();
var index = 0;
var root = Root;
var pool = ArrayPool<int>.Shared;
var array = pool.Rent(math.ceilpow2(_positions.Count * 2));
pool.Return(array);
QueryRecursive(in root, in position, in radius, array.AsSpan(), ref index);
foreach (var (key, size) in _sizes)
{
var pos = _positions[key];
if (IsCircleInRect(pos, radius, position, size))
{
array[index] = key;
index++;
}
}
try
{
return array.AsSpan()[..index];
}
finally
{
pool.Return(array);
}
}
#if UNITY_5_3_OR_NEWER
[BurstCompile]
#endif
private static bool IsCircleInRect(float2 point, float radius, float2 pos, float2 size)
{
var halfSize = size * 0.5f;
var min = pos - halfSize; // 矩形左下角
var max = pos + halfSize; // 矩形右上角
// 计算扩展后的包围盒
var expandedMin = min - radius;
var expandedMax = max + radius;
// 检查点是否在扩展的矩形内
return point.x >= expandedMin.x && point.x <= expandedMax.x &&
point.y >= expandedMin.y && point.y <= expandedMax.y;
}
private void Expansion()
{
while (_queue.TryDequeue(out var item))
{
_positions.TryAdd(item.Item1, item.Item2);
}
}
#if UNITY_5_3_OR_NEWER
[BurstCompile]
#endif
private void QueryRecursive(in QuadtreeNode node, in float2 position, in float radius, Span<int> result,
ref int index)
{
if (!Intersects(node.Center, node.Size, position, radius))
return;
foreach (var obj in node.Objects)
{
// 直接 TryGetValue避免 `Positions[obj]` 可能的重复哈希查找
if (!_positions.TryGetValue(obj, out var objPos)) continue;
// 计算平方距离,避免额外变量
if (math.dot(objPos - position, objPos - position) > radius * radius) continue;
// 直接写入 result
try
{
result[index] = obj;
}
catch (Exception)
{
#if UNITY_EDITOR
var unions = result.ToArray().Distinct();
var hashSet = new HashSet<int>();
For(Root);
void For(in QuadtreeNode node)
{
foreach (var child in node.Children)
{
For(child);
}
foreach (var x in node.Objects)
{
if (hashSet.Add(x) is false)
{
}
}
}
#endif
throw;
}
index++;
}
for (var i = 0; i < 4; i++)
{
if (node.Children[i].Size.x > 0)
{
QueryRecursive(in node.Children[i], in position, in radius, result, ref index);
}
}
}
#if UNITY_5_3_OR_NEWER
[BurstCompile]
#endif
private static bool Intersects(in float2 center, in float2 size, in float2 position, in float radius)
{
// 计算 AABB 的最小/最大点
var min = center - size * 0.5f;
var max = center + size * 0.5f;
// 找到圆心到 AABB 的最近点
var closest = math.clamp(position, min, max);
// 计算圆心到最近点的距离
var delta = position - closest;
var distanceSq = math.dot(delta, delta);
// 相交条件:如果距离平方 <= 半径平方
return distanceSq <= (radius * radius);
}
// 删除对象
public bool Remove(int objectId)
{
Expansion();
if (_positions.TryGetValue(objectId, out var position) is false) return false;
_sizes.TryRemove(objectId);
var root = Root;
if (RemoveRecursive(ref root, objectId, position) is false) return false;
_positions.Remove(objectId);
return true;
}
private static bool RemoveRecursive(ref QuadtreeNode node, int objectId, float2 position)
{
if (!node.Contains(position))
return false;
// 尝试从当前节点删除
bool removed = node.Objects.Remove(objectId);
// 如果当前节点是叶子节点,返回是否成功删除
if (node.IsLeaf())
{
return removed;
}
// 递归从子节点删除
for (var i = 0; i < 4; i++)
{
if (RemoveRecursive(ref node.Children[i], objectId, position))
{
removed = true;
}
}
// 如果对象被移除,尝试合并子节点
if (removed)
{
TryMerge(ref node);
}
return removed;
}
private static void TryMerge(ref QuadtreeNode node)
{
if (node.IsLeaf()) return;
// 检查是否可以合并子节点
var totalObjects = node.Objects.Count;
for (var i = 0; i < 4; i++)
{
totalObjects += node.Children[i].Objects.Count;
}
// 如果当前节点和所有子节点的对象数量小于等于阈值,则合并
if (totalObjects <= 4)
{
// 把所有子节点的对象都合并到当前节点
foreach (var child in node.Children)
{
node.Objects.UnionWith(child.Objects);
child.Objects.Clear(); // 清空子节点的对象集合
}
// 清空子节点,重新初始化
for (var i = 0; i < 4; i++)
{
node.Children[i] = new QuadtreeNode()
{
Depth = node.Depth + 1
};
}
}
}
}
}

View File

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

View File

@ -0,0 +1,84 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using Unity.Mathematics;
using System.Collections.Generic;
namespace Net.BITKit.Quadtree
{
public struct QuadtreeNode
{
public readonly Quadtree Root;
public int Depth;
public float2 Center; // 节点中心
public float2 Size; // 节点大小
public readonly QuadtreeNode[] Children; // 子节点
public readonly HashSet<int> Objects; // 存储的对象ID
public QuadtreeNode(Quadtree root,float2 center, float2 size)
{
Root = root;
Center = center;
Size = size;
Children = new QuadtreeNode[4];
Objects = new HashSet<int>();
Depth = 0;
}
public bool Contains(float2 point)
{
var min = Center - Size * 0.5f;
var max = Center + Size * 0.5f;
return math.all(point >= min & point <= max);
}
public void Split()
{
var childSize = Size / 2;
var offset = childSize / 2;
foreach (var node in Children)
{
foreach (var id in Objects)
{
var pos = Root.Positions[id];
if (node.Contains(pos))
{
node.Objects.Add(id);
}
}
}
Objects.Clear();
Children[0] = new QuadtreeNode(Root, Center + new float2(-offset.x, offset.y), childSize)
{
Depth = Depth + 1,
};
Children[1] = new QuadtreeNode(Root, Center + new float2(offset.x, offset.y), childSize)
{
Depth = Depth + 1,
};
Children[2] = new QuadtreeNode(Root, Center + new float2(-offset.x, -offset.y), childSize)
{
Depth = Depth + 1,
};
Children[3] = new QuadtreeNode(Root, Center + new float2(offset.x, -offset.y), childSize)
{
Depth = Depth + 1,
};
}
public bool IsLeaf()
{
return Children[0].Size.x == 0; // 如果子节点未初始化,说明是叶子节点
}
}
}

View File

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

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BITKit.Mod;
@ -37,6 +38,7 @@ namespace BITKit
}
public class CodeGenerator:ICodeGenerator
{
public virtual string Generate(Type type, string className = null)
{
if (type.IsInterface is false) throw new InvalidDataException("The type have to be interface");

View File

@ -35,7 +35,25 @@ namespace BITKit.Tween
return new TweenSequence();
}
public static async UniTask Lerp<T>(Action<T> setter,T from,T to,float duration, Func<T, T,float, T> lerp,CancellationToken cancellationToken = default)
public static async UniTask MoveToForward<T>(Action<T> setter, T from, T to, float delta,
Func<T, T, float, T> func, CancellationToken cancellationToken = default)
{
setter(from);
while (Equals(from,to) is false && cancellationToken.IsCancellationRequested is false)
{
from = func(from, to, delta*BITApp.Time.DeltaTime);
#if UNITY_5_3_OR_NEWER
await UniTask.NextFrame(cancellationToken);
#else
await UniTask.Yield();
#endif
setter(from);
}
if (cancellationToken.IsCancellationRequested is false)
setter(to);
}
public static async UniTask Lerp<T>(Action<T> setter,T from,T to,float duration, Func<T, T,float, T> func,CancellationToken cancellationToken = default)
{
var t = 0f;
var delta = 1f / duration;
@ -44,7 +62,7 @@ namespace BITKit.Tween
while (t < 1 && cancellationToken.IsCancellationRequested is false)
{
t = math.clamp(t + delta*BITApp.Time.DeltaTime, 0, 1);
var next = lerp(from, to, t);
var next = func(from, to, t);
#if UNITY_5_3_OR_NEWER
await UniTask.NextFrame(cancellationToken);
#else

View File

@ -8,6 +8,5 @@ namespace BITKit.UX
{
void Show(string content,string title = "Alert",Action confirmAction=null,Action<bool> onChoose=null);
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
// ReSharper disable UnassignedGetOnlyAutoProperty
@ -12,7 +14,7 @@ namespace BITKit.UX
/// <para>⭐异步打开与关闭</para>
/// <para>⭐当前可见状态</para>
/// <para>⭐基本UI导航回调</para>
public interface IUXPanel:IEntryElement
public interface IUXPanel:IStateAsync
{
/// <summary>
/// 是否为窗口,用于覆盖其他面板

View File

@ -79,9 +79,6 @@ namespace BITKit
var action = x as Action<T>;
action.Invoke(value);
});
if (value is not JToken)
Objects.TryInsert(key, value);
}
public static bool TryGetValue<T>(string key, out T value)
{

View File

@ -19,7 +19,10 @@ namespace BITKit
var type = typeof(T);
if (type == typeof(string))
{
_value = string.Empty.As<T>();
if (string.Empty is T t)
{
_value = t;
}
return;
}
if(type.IsAbstract || type.IsInterface)return;

View File

@ -33,7 +33,7 @@ namespace kcp2k
// return an element to the pool
public void Return(T item)
{
objectResetter(item);
objectResetter?.Invoke(item);
objects.Push(item);
}

8
Src/Unity/BitMask.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c72b222167e45bb4b91034232c2164b5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,19 @@
{
"name": "Net.Project.B.BitMask.Unity",
"rootNamespace": "",
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
"GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008",
"GUID:d525ad6bd40672747bde77962f1c401e",
"GUID:49b49c76ee64f6b41bf28ef951cb0e50"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4f23b1d01b5fa044784cf0a3dd9794eb
guid: 1f7ecae3bbd1640418dd25dd62e6a9d3
AssemblyDefinitionImporter:
externalObjects: {}
userData:

View File

@ -0,0 +1,17 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4bc860277386c244aac5bc8ec6cbb54b, type: 3}
m_Name: NewScriptableBitMask
m_EditorClassIdentifier:
implements: {fileID: 11400000, guid: 1e642bf42ffa8bd429cbfdeeefabaecd, type: 2}
dictionary:
_serializedList: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8411467f1adb3754ba46f890d7be5ff4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,38 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9fab0368c5976f24dafd43f829e91aab, type: 3}
m_Name: NewScriptableBitMaskSample
m_EditorClassIdentifier:
implements:
- rid: 7572707602352832517
- rid: 7572707602352832518
- rid: 7572707602352832519
- rid: 7572707602352832520
- rid: 7572707602352832521
references:
version: 2
RefIds:
- rid: 7572707602352832517
type: {class: SampleInterface1, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity}
data:
- rid: 7572707602352832518
type: {class: SampleInterface2, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity}
data:
- rid: 7572707602352832519
type: {class: SampleInterface3, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity}
data:
- rid: 7572707602352832520
type: {class: SampleInterface4, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity}
data:
- rid: 7572707602352832521
type: {class: SampleInterface5, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity}
data:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1e642bf42ffa8bd429cbfdeeefabaecd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,192 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using AYellowpaper.SerializedCollections;
using BITKit;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UIElements;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Net.BITKit.BitMask
{
public class ScriptableBitMask : ScriptableObject,ISerializationCallbackReceiver
{
[SerializeField] public ScriptableImplements implements;
internal readonly Dictionary<string, List<string>> Dictionary=new();
[SerializeField] public string yaml;
public Type Type => implements.Type;
public Type[] Types => implements ? implements.Types : Array.Empty<Type>();
private void OnEnable()
{
OnBeforeSerialize();
}
public void OnBeforeSerialize()
{
if(string.IsNullOrEmpty(yaml))return;
var deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance) // 驼峰命名
.Build();
Dictionary.Clear();
var newDictionary = deserializer.Deserialize<Dictionary<string, List<string>>>(yaml);
foreach (var pair in newDictionary)
{
Dictionary[pair.Key] = pair.Value;
}
}
public void OnAfterDeserialize()
{
if(Dictionary.Count is 0)return;
var serializer = new SerializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
yaml = serializer.Serialize(Dictionary);
}
public IDictionary<Type,HashSet<Type>> FlagMask
{
get
{
var typeDictionary = implements.Types.ToDictionary(x => x.Name, x => x);
var nextDictionary = new Dictionary<Type, HashSet<Type>>();
foreach (var (key, d) in Dictionary)
{
if (typeDictionary.TryGetValue(key, out var type) is false) continue;
var hashSet = new HashSet<Type>();
foreach (var x in d)
{
if(typeDictionary.TryGetValue(x,out var t1) is false)continue;
hashSet.Add(t1);
}
nextDictionary[type]=hashSet;
}
foreach (var type in Types)
{
if (nextDictionary.TryAdd(type, new HashSet<Type>())) ;
}
return nextDictionary;
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(ScriptableBitMask),true)]
public sealed class ScriptableBitMaskEditor:Editor
{
private const int CellSize = 24;
public override VisualElement CreateInspectorGUI()
{
if (serializedObject.targetObject is not ScriptableBitMask scriptableBitMask)
return base.CreateInspectorGUI();
var inspector = new VisualElement
{
style =
{
flexDirection = FlexDirection.Column
}
};
var defaultPropertyFields = inspector.Create<VisualElement>();
BITInspectorExtensions.FillDefaultInspector(defaultPropertyFields.Create<VisualElement>(),serializedObject,false);
var root = inspector.Create<VisualElement>();
root.style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
var allFlags = scriptableBitMask.Types;
var labelRow = root.Create<VisualElement>();
var toggleRot = root.Create<VisualElement>();
labelRow.style.alignSelf = new StyleEnum<Align>(Align.FlexEnd);
for (var x = -1; x < allFlags.Length; x++)
{
if (x is -1)
{
var container = toggleRot.Create<VisualElement>();
container.style.flexDirection = FlexDirection.Row;
foreach (var type in scriptableBitMask.Types.Reverse())
{
var text = container.Create<Label>();
text.style.width = CellSize;
text.text = string.Join("\n", type.Name.ToArray());
}
continue;
}
{
var xEnum = allFlags[x];
var label = labelRow.Create<Label>();
label.text = xEnum.Name;
label.style.height = CellSize;
label.style.unityTextAlign = TextAnchor.MiddleRight;
var toggles = toggleRot.Create<VisualElement>();
toggles.style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
for (var y = allFlags.Length-1; y >= 0+x; y--)
{
var yEnum = allFlags[y];
var toggle = toggles.Create<Toggle>();
toggle.tooltip = $"{xEnum.Name}/{yEnum.Name}";
toggle.style.width = toggle.style.height = CellSize;
toggle.style.marginBottom
= toggleRot.style.marginLeft
= toggleRot.style.marginRight
= toggleRot.style.marginTop
= toggleRot.style.paddingBottom
=toggleRot.style.paddingLeft
=toggleRot.style.paddingRight
=toggleRot.style.paddingTop
=-1;
toggle.SetValueWithoutNotify(scriptableBitMask.Dictionary.GetOrCreate(xEnum.Name).Contains(yEnum.Name));
toggle.RegisterValueChangedCallback(changeEvent =>
{
if (changeEvent.newValue)
{
scriptableBitMask.Dictionary.GetOrCreate(xEnum.Name).TryAdd(yEnum.Name);
scriptableBitMask.Dictionary.GetOrCreate(yEnum.Name).TryAdd(xEnum.Name);
}
else
{
scriptableBitMask.Dictionary.GetOrCreate(xEnum.Name).TryRemove(yEnum.Name);
scriptableBitMask.Dictionary.GetOrCreate(yEnum.Name).TryRemove(xEnum.Name);
}
scriptableBitMask.OnAfterDeserialize();
EditorUtility.SetDirty(serializedObject.targetObject);
});
}
}
}
return root;
}
}
#endif
}

View File

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

View File

@ -0,0 +1,30 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Net.BITKit.BitMask
{
public interface ISampleInterface
{
}
[Serializable]
public struct SampleInterface1:ISampleInterface{}
[Serializable]
public struct SampleInterface2:ISampleInterface{}
[Serializable]
public struct SampleInterface3:ISampleInterface{}
[Serializable]
public struct SampleInterface4:ISampleInterface{}
[Serializable]
public struct SampleInterface5:ISampleInterface{}
public class ScriptableBitMaskSample : ScriptableImplements<ISampleInterface>
{
}
}

View File

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

View File

@ -0,0 +1,20 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Net.BITKit.BitMask
{
public abstract class ScriptableImplements : ScriptableObject
{
public abstract Type[] Types { get; }
public abstract Type Type { get; }
}
public class ScriptableImplements<T> : ScriptableImplements
{
[SerializeReference,SubclassSelector] private T[] implements;
public override Type[] Types => Array.ConvertAll(implements, x => x.GetType());
public override Type Type => typeof(T);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +0,0 @@

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

View File

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

View File

@ -1,10 +0,0 @@

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c849275eb4fd266468253c3fb286f6c1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: bfa5a201809d352438b4d7ae50015303
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 1
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -89,6 +89,12 @@
},
{
"m_Id": "0a6f5506fb09487b863cc88af30ffd90"
},
{
"m_Id": "8b303c689afb416f899dde0fa22e4851"
},
{
"m_Id": "7adb268d18d14606b413352e1a3140c5"
}
],
"m_GroupDatas": [],
@ -139,9 +145,23 @@
{
"m_OutputSlot": {
"m_Node": {
"m_Id": "a4b3e91356646e85858189c1b05c46e5"
"m_Id": "7adb268d18d14606b413352e1a3140c5"
},
"m_SlotId": 2
"m_SlotId": 0
},
"m_InputSlot": {
"m_Node": {
"m_Id": "8b303c689afb416f899dde0fa22e4851"
},
"m_SlotId": -598364474
}
},
{
"m_OutputSlot": {
"m_Node": {
"m_Id": "8b303c689afb416f899dde0fa22e4851"
},
"m_SlotId": 1
},
"m_InputSlot": {
"m_Node": {
@ -516,6 +536,24 @@
}
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Texture2DInputMaterialSlot",
"m_ObjectId": "181877474f794a60bb9edb7cfb270535",
"m_Id": -598364474,
"m_DisplayName": "MainTex",
"m_SlotType": 0,
"m_Hidden": false,
"m_ShaderOutputName": "_MainTex",
"m_StageCapability": 2,
"m_BareResource": false,
"m_Texture": {
"m_SerializedTexture": "{\"texture\":{\"instanceID\":0}}",
"m_Guid": ""
},
"m_DefaultType": 0
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.SamplerStateMaterialSlot",
@ -924,6 +962,55 @@
}
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Texture2DMaterialSlot",
"m_ObjectId": "7195dfd20afe4eed9f2b1df2f19208e0",
"m_Id": 0,
"m_DisplayName": "_MainTex",
"m_SlotType": 1,
"m_Hidden": false,
"m_ShaderOutputName": "Out",
"m_StageCapability": 3,
"m_BareResource": false
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.PropertyNode",
"m_ObjectId": "7adb268d18d14606b413352e1a3140c5",
"m_Group": {
"m_Id": ""
},
"m_Name": "Property",
"m_DrawState": {
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": -452.6666564941406,
"y": -389.333251953125,
"width": 134.66671752929688,
"height": 36.0
}
},
"m_Slots": [
{
"m_Id": "7195dfd20afe4eed9f2b1df2f19208e0"
}
],
"synonyms": [],
"m_Precision": 0,
"m_PreviewExpanded": true,
"m_DismissedVersion": 0,
"m_PreviewMode": 0,
"m_CustomColors": {
"m_SerializableColors": []
},
"m_Property": {
"m_Id": "eed366d7a1e62f86a1437c023d6a951f"
}
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.CategoryData",
@ -1081,6 +1168,51 @@
"m_SerializedDescriptor": "VertexDescription.Position"
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.SubGraphNode",
"m_ObjectId": "8b303c689afb416f899dde0fa22e4851",
"m_Group": {
"m_Id": ""
},
"m_Name": "WorldSpace_SubGraph",
"m_DrawState": {
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": -204.0,
"y": -408.6666259765625,
"width": 242.6666259765625,
"height": 281.33331298828127
}
},
"m_Slots": [
{
"m_Id": "181877474f794a60bb9edb7cfb270535"
},
{
"m_Id": "ba6ba85468b34a51aa4403e01be10621"
}
],
"synonyms": [],
"m_Precision": 0,
"m_PreviewExpanded": true,
"m_DismissedVersion": 0,
"m_PreviewMode": 0,
"m_CustomColors": {
"m_SerializableColors": []
},
"m_SerializedSubGraph": "{\n \"subGraph\": {\n \"fileID\": -5475051401550479605,\n \"guid\": \"c29905eb4a526bd4ea3cdea7ab6cd2ce\",\n \"type\": 3\n }\n}",
"m_PropertyGuids": [
"5ab51d94-613e-4ffb-8452-e43de880fff4"
],
"m_PropertyIds": [
-598364474
],
"m_Dropdowns": [],
"m_DropdownSelectedEntries": []
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.BlockNode",
@ -1412,6 +1544,31 @@
"m_SerializedDescriptor": "SurfaceDescription.Alpha"
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector4MaterialSlot",
"m_ObjectId": "ba6ba85468b34a51aa4403e01be10621",
"m_Id": 1,
"m_DisplayName": "Out_Vector4",
"m_SlotType": 1,
"m_Hidden": false,
"m_ShaderOutputName": "OutVector4",
"m_StageCapability": 2,
"m_Value": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"w": 0.0
},
"m_DefaultValue": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"w": 0.0
},
"m_Labels": []
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector1MaterialSlot",
@ -1515,10 +1672,10 @@
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": -210.66665649414063,
"y": -218.0,
"width": 209.33338928222657,
"height": 304.00006103515627
"x": 73.33332061767578,
"y": -114.66669464111328,
"width": 209.3333740234375,
"height": 304.0
}
},
"m_Slots": [
@ -1840,10 +1997,10 @@
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": -365.3333435058594,
"y": -151.3333282470703,
"width": 132.00001525878907,
"height": 36.000022888183597
"x": -239.3333282470703,
"y": -49.333309173583987,
"width": 131.99998474121095,
"height": 35.999977111816409
}
},
"m_Slots": [

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c29905eb4a526bd4ea3cdea7ab6cd2ce
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9e346a267956eb3479c89010979a5a83
guid: d67461ea58a21334ea2488664d62f758
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,11 +1,10 @@
{
"name": "BITKit.Extensions.Polaris",
"name": "Net.BITKit.Polaris.Support",
"rootNamespace": "",
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
"GUID:9c184a1e5cc20654384071dfebc106ed",
"GUID:bdb069e155d2f944cb1bf28602b6d4c1",
"GUID:1193c2664d97cc049a6e4c486c6bce71"
"GUID:39db31f0562d9184c92881e43adea352",
"GUID:9c184a1e5cc20654384071dfebc106ed"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

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

View File

@ -0,0 +1,23 @@
using System.Collections;
using System.Collections.Generic;
using MonKey;
using Pinwheel.Griffin;
using UnityEditor;
using UnityEngine;
namespace Net.Project.B.Po
{
public static class PolarisCommand
{
[Command(nameof(Shrinkwrap), "Shrinkwrap Terrain By Scene Colliders"), MenuItem("Tools/Terrain/Snrinkwrap")]
public static void Shrinkwrap()
{
var terrain = Object.FindObjectOfType<GStylizedTerrain>();
if (terrain is null)
{
Debug.LogError("未找到地形");
return;
}
}
}
}

View File

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

View File

@ -1,59 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Pinwheel.Griffin;
using Quadtree;
using UnityEngine;
namespace BITKit.OpenWorld
{
public class PolarisTerrainChunkService : WorldChunkService<PolarisTerrainChunkService>
{
private class ChunkData:IWorldChunkObject
{
public Collider Collider { get; set; }
public Bounds GetBounds() => Bounds;
public Bounds Bounds { get; set; }
public Node<IWorldChunkObject> ParentNode { get; set; }
public void QuadTree_Root_Initialized(IQuadtreeRoot<IWorldChunkObject, Node<IWorldChunkObject>> root)
{
}
public int Id { get; set; }
public int Lod
{
get => lod;
set
{
Collider.enabled = value is 0;
lod = value;
}
}
private int lod=-1;
}
[SerializeField] private GStylizedTerrain[] terrains;
protected override void Start()
{
base.Start();
var reporter = new StringBuilder();
foreach (var terrain in terrains)
{
reporter.AppendLine($"正在注册地形 {terrain.name},尺寸:{terrain.TerrainData.Geometry.Width}x{terrain.TerrainData.Geometry.Length}");
foreach (var chunk in terrain.GetChunks())
{
var data =new ChunkData()
{
Collider = chunk.MeshColliderComponent,
Bounds = chunk.MeshColliderComponent.bounds
};
data.Collider.enabled = false;
Register(data);
reporter.AppendLine($"注册地形碰撞体 {chunk.name},尺寸:{data.Bounds.size}");
}
}
Debug.Log(reporter);
OnTick(Time.deltaTime);
}
}
}

View File

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