This commit is contained in:
parent
18239a5ae4
commit
9845d20f7f
|
@ -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" />
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RotationComponent></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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f97e02abd61aa274683a80be45fec110
|
||||
guid: c9f4b5601bd64124a9efdee9cf9c7226
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2bc2bf5dce5a1fa418a53e9616109677
|
||||
guid: 0a0edc8f5d75b3646a7b405ef830a52d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 407c6cf54ae8abc42b77a090f9fa7616
|
||||
guid: baab5c8590ad6ee44acbc258eeacdc8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
|
@ -19,6 +19,7 @@ namespace BITKit.Mod
|
|||
{
|
||||
list.Add((T)obj);
|
||||
}
|
||||
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c15a9900918e4324991bcf53015b006e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 097957ac1375f8141a80aa94f32030db
|
||||
guid: b421b3302ecaaab4bab9eebf970e2bb6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
|
@ -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; // 如果子节点未初始化,说明是叶子节点
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 625588063be34ab419d783b87c474a44
|
||||
guid: 837e58f0e4d63c2438b1037a76a5a6ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,6 +8,5 @@ namespace BITKit.UX
|
|||
{
|
||||
void Show(string content,string title = "Alert",Action confirmAction=null,Action<bool> onChoose=null);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
/// 是否为窗口,用于覆盖其他面板
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c72b222167e45bb4b91034232c2164b5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4f23b1d01b5fa044784cf0a3dd9794eb
|
||||
guid: 1f7ecae3bbd1640418dd25dd62e6a9d3
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
|
@ -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: []
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8411467f1adb3754ba46f890d7be5ff4
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1e642bf42ffa8bd429cbfdeeefabaecd
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4bc860277386c244aac5bc8ec6cbb54b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9fab0368c5976f24dafd43f829e91aab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9277a745f0b4ebc4c9d0759e9e56de77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -57,6 +57,8 @@ namespace BITKit
|
|||
}
|
||||
public class BITAppForUnity : MonoBehaviour
|
||||
{
|
||||
public static event Action OnDrawGizmo;
|
||||
public static event Action OnDrawGizmoSelected;
|
||||
[Serializable]
|
||||
public class OpenUrl
|
||||
{
|
||||
|
@ -224,5 +226,15 @@ namespace BITKit
|
|||
{
|
||||
return BITApp.State;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
OnDrawGizmo?.Invoke();
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
OnDrawGizmoSelected?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ using UnityEngine;
|
|||
|
||||
namespace Net.BITKit.Impact
|
||||
{
|
||||
|
||||
public class ScriptableImpact : ScriptableObject,ITag
|
||||
{
|
||||
[SerializeReference, SubclassSelector] private IReference[] tags;
|
||||
|
|
|
@ -6,6 +6,28 @@ using UnityEngine;
|
|||
|
||||
namespace BITKit.Physics
|
||||
{
|
||||
public static class PhysicsExtensions
|
||||
{
|
||||
public static bool TryGetClosestPointFromCollider(this Collider collider,Vector3 point,out Vector3 closestPoint)
|
||||
{
|
||||
closestPoint = default;
|
||||
switch (collider)
|
||||
{
|
||||
case BoxCollider:
|
||||
case SphereCollider:
|
||||
case CapsuleCollider:
|
||||
case MeshCollider { convex: true }:
|
||||
case TerrainCollider:
|
||||
closestPoint = collider.ClosestPoint(point);
|
||||
return true;
|
||||
case MeshCollider { convex: false } meshCollider when new GetClosestPointFromMesh(meshCollider.sharedMesh,point).TryGetValue(out closestPoint,out _):
|
||||
closestPoint = collider.transform.TransformPoint(closestPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public readonly struct GetClosestPointFromMesh:IClosePointProvider
|
||||
{
|
||||
private readonly Vector3 _position;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c15a9900918e4324991bcf53015b006e
|
||||
guid: 8a606fb5272e8a84dac37bae47771409
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
using Quadtree.Items;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Spatial partitioning/Quadtree/Root node (for GameObjects)")]
|
||||
public class GameObjectQuadtreeRoot : QuadtreeMonoRoot<GameObjectItem, Node<GameObjectItem>>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
using Quadtree.Items;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Mandatory interface of any single quadtree node.
|
||||
/// </summary>
|
||||
public interface INode<TItem, TNode>
|
||||
where TItem : IItem<TItem, TNode>
|
||||
where TNode : INode<TItem, TNode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Bounds of this tree node.
|
||||
/// </summary>
|
||||
Bounds Bounds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Root of the whole tree.
|
||||
/// </summary>
|
||||
IQuadtreeRoot<TItem, TNode> TreeRoot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to parent tree node.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is <c>null</c> for root node of the tree.
|
||||
/// </remarks>
|
||||
TNode ParentNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Child nodes of this node.
|
||||
/// </summary>
|
||||
IList<TNode> SubNodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Verifies whether provided boundaries (<paramref name="bounds"/>) are fully contained within the boundaries of the node.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="bounds">Boundaries of an object</param>
|
||||
/// <returns><c>True</c> if object is fully contained within the node, <c>False</c> otherwise</returns>
|
||||
bool Contains(Bounds bounds);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates relative internal position of the provided bounds (<paramref name="bounds"/>) within the node.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method expects the boundaries to be fully contained within the node.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <param name="bounds">Boundaries contained within the node</param>
|
||||
/// <returns>Relative internal position</returns>
|
||||
IntraLocation Location(Bounds bounds);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts item (<paramref name="item"/>) into the smallest node possible in the subtree.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method expects item boundaries to be fully contained within the node.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <param name="item">Item to be inserted</param>
|
||||
void Insert(TItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided item (<paramref name="item"/>) from the node and its subtree.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="item">Item to be removed from the tree</param>
|
||||
void Remove(TItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the node and recursively all its subnodes are empty.
|
||||
/// </summary>
|
||||
///
|
||||
/// <returns><c>True</c> if node and all its subnodes are empty, <c>False</c> otherwise</returns>
|
||||
bool IsEmpty();
|
||||
|
||||
/// <summary>
|
||||
/// Updates provided item's (<paramref name="item"/>) location within the tree.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="item">Item which's location is to be updated</param>
|
||||
/// <param name="forceInsertionEvaluation"><c>True</c> forces tree to re-insert the item</param>
|
||||
/// <param name="hasOriginallyContainedItem"><c>True</c> only for the first called node</param>
|
||||
void Update(TItem item, bool forceInsertionEvaluation = true, bool hasOriginallyContainedItem = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds items (<paramref name="items"/>) located within provided boundaries (<paramref name="bounds"/>).
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="bounds">Boundaries to look for items within</param>
|
||||
/// <param name="items">Output list for found items</param>
|
||||
void FindAndAddItems(Bounds bounds, ref IList<TItem> items);
|
||||
|
||||
/// <summary>
|
||||
/// Adds all items of this node and its sub-nodes to the provided list of items (<paramref name="items"/>).
|
||||
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="items">Output list for found items</param>
|
||||
/// <param name="bounds">Boundaries to look for items within</param>
|
||||
void AddItems(ref IList<TItem> items, Bounds? bounds = null);
|
||||
|
||||
/// <summary>
|
||||
/// Removes any existing items from the node and removes all of its sub-nodes.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Displays boundaries of this node and all its sub-nodes and optinally a current number of contained items if <paramref name="displayNumberOfItems"/> is <c>True</c>.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="displayNumberOfItems"><c>True</c> if number of node's items should be displayed</param>
|
||||
void DrawBounds(bool displayNumberOfItems = false);
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
using Quadtree.Items;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class of the Quadtree structure - it represents the root of the tree.
|
||||
/// </summary>
|
||||
public interface IQuadtreeRoot<TItem, TNode>
|
||||
where TItem : IItem<TItem, TNode>
|
||||
where TNode : INode<TItem, TNode>
|
||||
{
|
||||
/// <summary>
|
||||
/// The tree has been initialized and is ready to be used.
|
||||
/// </summary>
|
||||
bool Initialized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Node currently acting as a root of the tree.
|
||||
/// </summary>
|
||||
TNode CurrentRootNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum possible size of any of the nodes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Must always be a positive number or zero for no size limit.
|
||||
/// </remarks>
|
||||
float MinimumPossibleNodeSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether or not should number of items in nodes be displayed in gizmos.
|
||||
/// </summary>
|
||||
bool DisplayNumberOfItemsInGizmos { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Inserts item to the tree structure.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="item">Item to be inserted</param>
|
||||
void Insert(TItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Expands size of root node.
|
||||
/// New root node is created and current root node is assigned as its sub-node.
|
||||
/// </summary>
|
||||
void Expand();
|
||||
|
||||
/// <summary>
|
||||
/// Finds items located within provided boundaries.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="bounds">Boundaries to look for items within</param>
|
||||
/// <returns>List of items found within provided boundaries</returns>
|
||||
List<TItem> Find(Bounds bounds);
|
||||
|
||||
/// <summary>
|
||||
/// Removes provided item from the tree.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="item">Item to be removed from the tree</param>
|
||||
void Remove(TItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Clears and resets the whole tree.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes relative local position in respect to the current node.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Integer values of <c>UPPER_LEFT</c>, <c>UPPER_RIGHT</c>, <c>LOWER_RIGHT</c>, <c>LOWER_LEFT</c> do correspond with the indices of the sub-nodes.
|
||||
/// </remarks>
|
||||
public enum IntraLocation
|
||||
{
|
||||
UPPER_LEFT,
|
||||
UPPER_RIGHT,
|
||||
LOWER_RIGHT,
|
||||
LOWER_LEFT,
|
||||
SPANNING_LEFT,
|
||||
SPANNING_RIGHT,
|
||||
SPANNING_UPPER,
|
||||
SPANNING_LOWER,
|
||||
SPANNING
|
||||
};
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6ab96e72850290b47b83ba45e5f1d02e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree.Items
|
||||
{
|
||||
public abstract class GameObjectItem : GameObjectItemBase<GameObjectItem, Node<GameObjectItem>>
|
||||
{
|
||||
protected override GameObjectItem This() => this;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3bb93ae6561edaa49a599f7ccf090da1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,152 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Quadtree.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom item interface for GameObject quadtree items.
|
||||
/// </summary>
|
||||
public abstract class GameObjectItemBase<TItem, TNode> : MonoBehaviour, IItem<TItem, TNode>
|
||||
where TItem : IItem<TItem, TNode>
|
||||
where TNode : INode<TItem, TNode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Game object's bounds from last update call.
|
||||
/// </summary>
|
||||
private Bounds _lastBounds;
|
||||
|
||||
/// <summary>
|
||||
/// Game object's bounds from last update call.
|
||||
/// </summary>
|
||||
private Bounds _safeBounds;
|
||||
|
||||
//==========================================================================dd==
|
||||
// MonoBehaviour METHODS
|
||||
//==========================================================================dd==
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Root = null;
|
||||
ItemInitialized = false;
|
||||
ParentNode.Remove(This());
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
var currentBounds = GetBounds();
|
||||
if (currentBounds != _lastBounds)
|
||||
{
|
||||
// the object has moved or changed size
|
||||
var forceInsertionEvaluation = false;
|
||||
if (!currentBounds.Intersects(_safeBounds)
|
||||
|| (currentBounds.size - _lastBounds.size).magnitude > 0)
|
||||
{
|
||||
// ...far enough to force re-insertion
|
||||
forceInsertionEvaluation = true;
|
||||
_safeBounds = currentBounds;
|
||||
}
|
||||
|
||||
// current object bounds are not the same as last update
|
||||
// initiate tree update from currently
|
||||
ParentNode?.Update(This(), forceInsertionEvaluation);
|
||||
_lastBounds = currentBounds;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================dd==
|
||||
// CORE TREE ITEM METHODS
|
||||
//==========================================================================dd==
|
||||
|
||||
/// <summary>
|
||||
/// <c>True</c> if the item has been initialized.
|
||||
/// </summary>
|
||||
protected internal bool ItemInitialized = false;
|
||||
|
||||
public IQuadtreeRoot<TItem, TNode> Root { get; set; }
|
||||
|
||||
public TNode ParentNode { get; set; }
|
||||
|
||||
public abstract Bounds GetBounds();
|
||||
|
||||
public void QuadTree_Root_Initialized(IQuadtreeRoot<TItem, TNode> root)
|
||||
{
|
||||
Root = root;
|
||||
|
||||
if (ItemInitialized)
|
||||
{
|
||||
// the item has been initialized before the tree root
|
||||
root.Insert(This());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns reference to corresponding game object.
|
||||
/// </summary>
|
||||
///
|
||||
/// <returns>Game object instance.</returns>
|
||||
public abstract GameObject GetGameObject();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the item instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may be called either before or after the initialization of the tree root.
|
||||
/// </remarks>
|
||||
protected virtual void Init()
|
||||
{
|
||||
// designate item as initialized
|
||||
ItemInitialized = true;
|
||||
|
||||
// set initial last bounds
|
||||
_lastBounds = GetBounds();
|
||||
// set initial safe bounds
|
||||
_safeBounds = _lastBounds;
|
||||
|
||||
if (Root == null)
|
||||
{
|
||||
if (TryGetComponent(out GameObjectQuadtreeRoot quadtreeRoot) && quadtreeRoot.Initialized)
|
||||
{
|
||||
Root = (IQuadtreeRoot<TItem, TNode>)quadtreeRoot;
|
||||
}
|
||||
}
|
||||
|
||||
if (Root != null)
|
||||
{
|
||||
// the tree root has been initialized before the item
|
||||
Root.Insert(This());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overloaded in sub-classes to return correct instance of the item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is necessary due to generic typechecking -- <c>this</c> in context of the abstract generic class does not reference TItem itself.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <returns>Instance of the item</returns>
|
||||
protected abstract TItem This();
|
||||
|
||||
/// <summary>
|
||||
/// Returns unique identifier of the item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is extremely important to override this method because nodes are using HashSets to store items and the items are changing during the updates and so are the hash codes which can result in program not working properly.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <returns>Unique identifier</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return GetInstanceID();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 34e0c8684cc1c224a91b831b8c99c968
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,29 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Quadtree.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Mandatory interface of any quadtree item.
|
||||
/// </summary>
|
||||
public interface IItem<TItem, TNode>
|
||||
where TItem : IItem<TItem, TNode>
|
||||
where TNode : INode<TItem, TNode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns object bounds.
|
||||
/// </summary>
|
||||
///
|
||||
/// <returns>Object box bounds.</returns>
|
||||
Bounds GetBounds();
|
||||
|
||||
/// <summary>
|
||||
/// Node which currently contains the item.
|
||||
/// </summary>
|
||||
TNode ParentNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Receiver method for broadcasted tree initialization message.
|
||||
/// </summary>
|
||||
void QuadTree_Root_Initialized(IQuadtreeRoot<TItem, TNode> root);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1e6be24bac4fd4a409194655d1cf0665
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,46 +0,0 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Quadtree.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Boundaries of this quadtree item are determined by present <c>UnityEngine.Renderer</c> component.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Renderer))]
|
||||
[AddComponentMenu("Spatial partitioning/Quadtree/Items/Renderer-based Item")]
|
||||
public class RendererItem : GameObjectItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the item should be automatically inserted into the tree upon its initialization.
|
||||
/// </summary>
|
||||
///
|
||||
/// <seealso cref="QuadtreeMonoRoot{TItem}.Insert(TItem)"/>
|
||||
[SerializeField]
|
||||
protected bool InsertOnInitialization = true;
|
||||
|
||||
private Renderer _renderer;
|
||||
|
||||
//==========================================================================dd==
|
||||
// Quadtree ITEM METHODS
|
||||
//==========================================================================dd==
|
||||
|
||||
/// <summary>
|
||||
/// Finds and locally stores this <c>GameObject</c>'s <c>Renderer</c> component instance.
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
// load game object renderer component
|
||||
_renderer = GetComponent<Renderer>();
|
||||
|
||||
base.Init();
|
||||
}
|
||||
|
||||
public override Bounds GetBounds()
|
||||
{
|
||||
return _renderer.bounds;
|
||||
}
|
||||
|
||||
public override GameObject GetGameObject() => gameObject;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b92b133d7eeb95b4d95b27cf8ef679b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6f51bcded3e1e574187f641567d12ff8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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,
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c39447ec3de56774087b5dc77d510181
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,12 +0,0 @@
|
|||
using Quadtree.Items;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Single quadtree node.
|
||||
/// </summary>
|
||||
public class Node<TItem> : NodeBase<TItem, Node<TItem>>
|
||||
where TItem : IItem<TItem, Node<TItem>>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 836cada8514e07d4d958f972ccf9a0e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,420 +0,0 @@
|
|||
using Quadtree.Items;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Base quadtree node implementation.
|
||||
/// </summary>
|
||||
public abstract class NodeBase<TItem, TNode> : INode<TItem, TNode>
|
||||
where TItem : IItem<TItem, TNode>
|
||||
where TNode : NodeBase<TItem, TNode>, new()
|
||||
{
|
||||
public Bounds Bounds { get; set; }
|
||||
|
||||
public TNode ParentNode { get; set; }
|
||||
|
||||
public IList<TNode> SubNodes { get; set; }
|
||||
|
||||
public IQuadtreeRoot<TItem, TNode> TreeRoot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of inserted items.
|
||||
/// </summary>
|
||||
private readonly HashSet<TItem> _items;
|
||||
|
||||
public NodeBase()
|
||||
{
|
||||
SubNodes = new List<TNode>(4);
|
||||
_items = new HashSet<TItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies whether provided boundaries (<paramref name="bounds"/>) are fully contained within the boundaries of the node.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="bounds">Boundaries of an object</param>
|
||||
/// <returns><c>True</c> if object is fully contained within the node, <c>False</c> otherwise</returns>
|
||||
public bool Contains(Bounds bounds) =>
|
||||
bounds.min.x >= Bounds.min.x
|
||||
&& bounds.min.z >= Bounds.min.z
|
||||
&& bounds.max.x < Bounds.max.x
|
||||
&& bounds.max.z < Bounds.max.z;
|
||||
|
||||
public IntraLocation Location(Bounds bounds)
|
||||
{
|
||||
if (bounds.min.z >= Bounds.center.z)
|
||||
{
|
||||
// items are located in top sub-nodes
|
||||
if (bounds.max.x < Bounds.center.x)
|
||||
{
|
||||
// items are located in top left sub-node
|
||||
return IntraLocation.UPPER_LEFT;
|
||||
}
|
||||
else if (bounds.min.x >= Bounds.center.x)
|
||||
{
|
||||
// items are located in top right sub-node
|
||||
return IntraLocation.UPPER_RIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// item does not fit to either one, but is top
|
||||
// (max.x is right, min.x is left)
|
||||
return IntraLocation.SPANNING_UPPER;
|
||||
}
|
||||
}
|
||||
else if (bounds.max.z < Bounds.center.z)
|
||||
{
|
||||
// items are located in bottom sub-nodes
|
||||
if (bounds.max.x < Bounds.center.x)
|
||||
{
|
||||
// items are located in bottom left sub-node
|
||||
return IntraLocation.LOWER_LEFT;
|
||||
}
|
||||
else if (bounds.min.x >= Bounds.center.x)
|
||||
{
|
||||
// items are located in bottom right sub-node
|
||||
return IntraLocation.LOWER_RIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// item does not fit to either one, but is bottom
|
||||
// (max.x is right, min.x is left)
|
||||
return IntraLocation.SPANNING_LOWER;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// item does not fit to any sub-node
|
||||
// (max.z is top, min.z is bottom)
|
||||
if (bounds.min.x >= Bounds.center.x)
|
||||
{
|
||||
// bounds span over top right and bottom right nodes
|
||||
return IntraLocation.SPANNING_RIGHT;
|
||||
}
|
||||
else if (bounds.max.x < Bounds.center.x)
|
||||
{
|
||||
// bounds span over top left and bottom left nodes
|
||||
return IntraLocation.SPANNING_LEFT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// bounds span over all sub-nodes
|
||||
return IntraLocation.SPANNING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(TItem item)
|
||||
{
|
||||
// create new sub-nodes
|
||||
if (SubNodes.Count == 0)
|
||||
CreateSubNodes();
|
||||
|
||||
// sub-nodes can not be created anymore
|
||||
if (SubNodes.Count == 0)
|
||||
{
|
||||
// insert item into this node
|
||||
_items.Add(item);
|
||||
// and designate this node its parent
|
||||
item.ParentNode = (TNode)this;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var itemBounds = item.GetBounds();
|
||||
var itemBoundsLocation = Location(itemBounds);
|
||||
switch (itemBoundsLocation)
|
||||
{
|
||||
// boundaries are contained within one of the subnodes
|
||||
case IntraLocation.UPPER_LEFT:
|
||||
case IntraLocation.UPPER_RIGHT:
|
||||
case IntraLocation.LOWER_RIGHT:
|
||||
case IntraLocation.LOWER_LEFT:
|
||||
SubNodes[(int)itemBoundsLocation].Insert(item);
|
||||
break;
|
||||
|
||||
// boundaries are spanning over 2 or more subnodes
|
||||
default:
|
||||
_items.Add(item);
|
||||
item.ParentNode = (TNode)this;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(TItem item)
|
||||
{
|
||||
var itemBounds = item.GetBounds();
|
||||
var itemBoundsLocation = Location(itemBounds);
|
||||
switch (itemBoundsLocation)
|
||||
{
|
||||
// boundaries are contained within one of the subnodes
|
||||
case IntraLocation.UPPER_LEFT:
|
||||
case IntraLocation.UPPER_RIGHT:
|
||||
case IntraLocation.LOWER_RIGHT:
|
||||
case IntraLocation.LOWER_LEFT:
|
||||
SubNodes[(int)itemBoundsLocation].Remove(item);
|
||||
break;
|
||||
|
||||
// boundaries are spanning over 2 or more subnodes
|
||||
default:
|
||||
RemoveOwnItem(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes provided item (<paramref name="item"/>) from the node.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="item">Item to be removed from the node</param>
|
||||
///
|
||||
/// <seealso cref="INode{TItem, TNode}.Clear"/>
|
||||
protected internal void RemoveOwnItem(TItem item)
|
||||
{
|
||||
// remove the item from the node
|
||||
_items.Remove(item);
|
||||
// update its parent node
|
||||
item.ParentNode = null;
|
||||
|
||||
if (IsEmpty())
|
||||
{
|
||||
// remove subnodes if subtree of this node is empty
|
||||
SubNodes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
if (_items.Count > 0)
|
||||
return false;
|
||||
|
||||
foreach (var subNode in SubNodes)
|
||||
if (!subNode.IsEmpty())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Update(TItem item, bool forceInsertionEvaluation = true, bool hasOriginallyContainedItem = true)
|
||||
{
|
||||
if (Contains(item.GetBounds()))
|
||||
{
|
||||
// item is contained by this node
|
||||
if (hasOriginallyContainedItem)
|
||||
{
|
||||
// ...and this node has originally contained the item
|
||||
if (forceInsertionEvaluation)
|
||||
{
|
||||
// ...and insertion evaluation is forced
|
||||
// this checks whether the item hasn't moved into any of the subnodes
|
||||
RemoveOwnItem(item);
|
||||
Insert(item);
|
||||
}
|
||||
|
||||
// item is still contained by its original node, no action necessary
|
||||
return;
|
||||
}
|
||||
|
||||
// ...but this node is not its original container
|
||||
// insert item either to this node or any of its children
|
||||
Insert(item);
|
||||
|
||||
// update has been successful
|
||||
return;
|
||||
}
|
||||
|
||||
// the item is not contained by this node
|
||||
if (ParentNode == null)
|
||||
{
|
||||
// ...and this node does not have any parent - the tree must be expanded
|
||||
TreeRoot.Expand();
|
||||
if (ParentNode == null)
|
||||
{
|
||||
// the expansion has failed for some reason
|
||||
Debug.LogError("Tree root expansion failed for item " + item.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// the item is not contained by this node
|
||||
if (hasOriginallyContainedItem)
|
||||
{
|
||||
// ...and this node has originally contained the item - it must be removed
|
||||
RemoveOwnItem(item);
|
||||
}
|
||||
|
||||
// parent is (now) available
|
||||
ParentNode.Update(item, forceInsertionEvaluation, false);
|
||||
// the item is now contained by another node, update has been successful
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates sub-nodes for the node.
|
||||
/// </summary>
|
||||
protected internal void CreateSubNodes()
|
||||
{
|
||||
var subBoundsSize = Bounds.size * .5f;
|
||||
if (subBoundsSize.x < TreeRoot.MinimumPossibleNodeSize
|
||||
|| subBoundsSize.z < TreeRoot.MinimumPossibleNodeSize)
|
||||
{
|
||||
// new sub-node bounds are too small
|
||||
return;
|
||||
}
|
||||
|
||||
var centerOffset = subBoundsSize * .5f;
|
||||
|
||||
// top left node [-x +z]
|
||||
centerOffset.x *= -1f;
|
||||
SubNodes.Insert((int)IntraLocation.UPPER_LEFT, new TNode()
|
||||
{
|
||||
TreeRoot = TreeRoot,
|
||||
ParentNode = (TNode)this,
|
||||
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
|
||||
});
|
||||
|
||||
// top right node [+x +z]
|
||||
centerOffset.x *= -1f;
|
||||
SubNodes.Insert((int)IntraLocation.UPPER_RIGHT, new TNode()
|
||||
{
|
||||
TreeRoot = TreeRoot,
|
||||
ParentNode = (TNode)this,
|
||||
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
|
||||
});
|
||||
|
||||
// bottom right node [+x -z]
|
||||
centerOffset.z *= -1f;
|
||||
SubNodes.Insert((int)IntraLocation.LOWER_RIGHT, new TNode()
|
||||
{
|
||||
TreeRoot = TreeRoot,
|
||||
ParentNode = (TNode)this,
|
||||
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
|
||||
});
|
||||
|
||||
// bottom left node [-x -z]
|
||||
centerOffset.x *= -1f;
|
||||
SubNodes.Insert((int)IntraLocation.LOWER_LEFT, new TNode()
|
||||
{
|
||||
TreeRoot = TreeRoot,
|
||||
ParentNode = (TNode)this,
|
||||
Bounds = new Bounds(Bounds.center + centerOffset, subBoundsSize),
|
||||
});
|
||||
}
|
||||
|
||||
public void FindAndAddItems(Bounds bounds, ref IList<TItem> items)
|
||||
{
|
||||
if (SubNodes.Count == 0)
|
||||
{
|
||||
// no sub-nodes exist
|
||||
AddOwnItems(ref items);
|
||||
return;
|
||||
}
|
||||
|
||||
// always add any items in this node intersecting with the boundaries
|
||||
AddOwnItems(ref items, bounds);
|
||||
|
||||
var boundsLocation = Location(bounds);
|
||||
switch (boundsLocation)
|
||||
{
|
||||
// boundaries are contained within one of the subnodes
|
||||
case IntraLocation.UPPER_LEFT:
|
||||
case IntraLocation.UPPER_RIGHT:
|
||||
case IntraLocation.LOWER_RIGHT:
|
||||
case IntraLocation.LOWER_LEFT:
|
||||
SubNodes[(int)boundsLocation].FindAndAddItems(bounds, ref items);
|
||||
break;
|
||||
|
||||
// boundaries are spanning over left subnodes
|
||||
case IntraLocation.SPANNING_LEFT:
|
||||
SubNodes[(int)IntraLocation.UPPER_LEFT].AddItems(ref items, bounds);
|
||||
SubNodes[(int)IntraLocation.LOWER_LEFT].AddItems(ref items, bounds);
|
||||
break;
|
||||
|
||||
// boundaries are spanning over right subnodes
|
||||
case IntraLocation.SPANNING_RIGHT:
|
||||
SubNodes[(int)IntraLocation.UPPER_RIGHT].AddItems(ref items, bounds);
|
||||
SubNodes[(int)IntraLocation.LOWER_RIGHT].AddItems(ref items, bounds);
|
||||
break;
|
||||
|
||||
// boundaries are spanning over upper subnodes
|
||||
case IntraLocation.SPANNING_UPPER:
|
||||
SubNodes[(int)IntraLocation.UPPER_LEFT].AddItems(ref items, bounds);
|
||||
SubNodes[(int)IntraLocation.UPPER_RIGHT].AddItems(ref items, bounds);
|
||||
break;
|
||||
|
||||
// boundaries are spanning over lower subnodes
|
||||
case IntraLocation.SPANNING_LOWER:
|
||||
SubNodes[(int)IntraLocation.LOWER_LEFT].AddItems(ref items, bounds);
|
||||
SubNodes[(int)IntraLocation.LOWER_RIGHT].AddItems(ref items, bounds);
|
||||
break;
|
||||
|
||||
// boundaries are spanning over all subnodes
|
||||
case IntraLocation.SPANNING:
|
||||
default:
|
||||
AddSubNodeItems(ref items, bounds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddItems(ref IList<TItem> items, Bounds? bounds = null)
|
||||
{
|
||||
AddOwnItems(ref items, bounds);
|
||||
AddSubNodeItems(ref items, bounds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all items belonging to this node (ignoring sub-nodes) to the provided list of items (<paramref name="items"/>).
|
||||
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="items">Output list for found items</param>
|
||||
/// <param name="bounds">Boundaries to look for items within</param>
|
||||
protected internal void AddOwnItems(ref IList<TItem> items, Bounds? bounds = null)
|
||||
{
|
||||
var itemSource = bounds != null
|
||||
? _items.Where(item => item.GetBounds().Intersects((Bounds)bounds))
|
||||
: _items;
|
||||
|
||||
foreach (var item in itemSource)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all items belonging to sub-nodes (ignoring own items) to the provided list of items (<paramref name="items"/>).
|
||||
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="items">Output list for found items</param>
|
||||
/// <param name="bounds">Boundaries to look for items within</param>
|
||||
protected internal void AddSubNodeItems(ref IList<TItem> items, Bounds? bounds = null)
|
||||
{
|
||||
foreach (var subNode in SubNodes)
|
||||
subNode.AddItems(ref items, bounds);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_items.Clear();
|
||||
SubNodes.Clear();
|
||||
}
|
||||
|
||||
public void DrawBounds(bool displayNumberOfItems = false)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (displayNumberOfItems)
|
||||
Handles.Label(Bounds.center, _items.Count.ToString());
|
||||
|
||||
Gizmos.DrawWireCube(Bounds.center, Bounds.size);
|
||||
#endif
|
||||
foreach (var subNode in SubNodes)
|
||||
subNode.DrawBounds(displayNumberOfItems);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a2bca5a4f0f3b554ea3c3f9ba067918b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7865070e56eaeb1439c92757b6ee00b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,102 +0,0 @@
|
|||
using Quadtree.Items;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class of the Quadtree structure - it represents the root of the tree.
|
||||
/// </summary>
|
||||
public abstract class QuadtreeMonoRoot<TItem, TNode> : MonoBehaviour, IQuadtreeRoot<TItem, TNode>
|
||||
where TItem : IItem<TItem, TNode>
|
||||
where TNode : INode<TItem, TNode>, new()
|
||||
{
|
||||
//==========================================================================dd==
|
||||
// MonoBehaviour METHODS
|
||||
//==========================================================================dd==
|
||||
|
||||
protected void Start()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
protected void OnDrawGizmos()
|
||||
{
|
||||
DrawBounds();
|
||||
}
|
||||
|
||||
//==========================================================================dd==
|
||||
// CORE ROOT NODE METHODS
|
||||
//==========================================================================dd==
|
||||
|
||||
/// <summary>
|
||||
/// Root node containing all items and sub-nodes.
|
||||
/// </summary>
|
||||
protected QuadtreeRoot<TItem, TNode> TreeRoot = null;
|
||||
|
||||
public bool Initialized => TreeRoot != null && TreeRoot.Initialized;
|
||||
|
||||
public TNode CurrentRootNode => TreeRoot.CurrentRootNode;
|
||||
|
||||
[SerializeField]
|
||||
protected Vector3 DefaultRootNodeSize = new Vector3(64f, 0f, 64f);
|
||||
|
||||
/// <inheritdoc cref="IQuadtreeRoot{TItem, TNode}.MinimumPossibleNodeSize"/>
|
||||
[SerializeField]
|
||||
protected float MinimumPossibleNodeSize = 1f;
|
||||
|
||||
float IQuadtreeRoot<TItem, TNode>.MinimumPossibleNodeSize => MinimumPossibleNodeSize;
|
||||
|
||||
/// <inheritdoc cref="IQuadtreeRoot{TItem, TNode}.DisplayNumberOfItemsInGizmos"/>
|
||||
[SerializeField]
|
||||
private bool DisplayNumberOfItemsInGizmos = false;
|
||||
|
||||
bool IQuadtreeRoot<TItem, TNode>.DisplayNumberOfItemsInGizmos => DisplayNumberOfItemsInGizmos;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes Quadtree - creates initial root node and builds the tree (if allowed).
|
||||
/// </summary>
|
||||
///
|
||||
/// <seealso cref="IItem{TItem, TNode}.QuadTree_Root_Initialized(IQuadtreeRoot{TItem, TNode})"/>
|
||||
protected void Init()
|
||||
{
|
||||
if (TreeRoot == null)
|
||||
{
|
||||
TreeRoot = new QuadtreeRoot<TItem, TNode>(transform.position, DefaultRootNodeSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// root node has already been initialized, clear the tree
|
||||
TreeRoot.Clear();
|
||||
}
|
||||
|
||||
// send a message to children that the tree root has been initialized
|
||||
BroadcastMessage("QuadTree_Root_Initialized", this, SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays Quadtree node boundaries.
|
||||
/// </summary>
|
||||
///
|
||||
/// <seealso cref="INode{TItem, TNode}.DrawBounds(bool)"/>
|
||||
protected void DrawBounds()
|
||||
{
|
||||
TreeRoot?.CurrentRootNode.DrawBounds(DisplayNumberOfItemsInGizmos);
|
||||
}
|
||||
|
||||
public void Insert(TItem item) => TreeRoot.Insert(item);
|
||||
|
||||
public void Expand() => TreeRoot.Expand();
|
||||
|
||||
public List<TItem> Find(Bounds bounds) => TreeRoot.Find(bounds);
|
||||
|
||||
public void Remove(TItem item) => TreeRoot.Remove(item);
|
||||
|
||||
public void Clear() => TreeRoot.Clear();
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 016ed1871ab14284db00aa21f490908c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,148 +0,0 @@
|
|||
using Quadtree.Items;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Quadtree
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class of the Quadtree structure - it represents the root of the tree.
|
||||
/// </summary>
|
||||
public class QuadtreeRoot<TItem, TNode> : IQuadtreeRoot<TItem, TNode>
|
||||
where TItem : IItem<TItem, TNode>
|
||||
where TNode : INode<TItem, TNode>, new()
|
||||
{
|
||||
public bool Initialized { get; set; }
|
||||
|
||||
public TNode CurrentRootNode { get; internal set; }
|
||||
|
||||
public float MinimumPossibleNodeSize => _minimumPossibleNodeSize;
|
||||
|
||||
/// <inheritdoc cref="IQuadtreeRoot{TItem}.MinimumPossibleNodeSize"/>
|
||||
protected float _minimumPossibleNodeSize = 1f;
|
||||
|
||||
public bool DisplayNumberOfItemsInGizmos => _displayNumberOfItemsInGizmos;
|
||||
|
||||
/// <inheritdoc cref="IQuadtreeRoot{TItem}.DisplayNumberOfItemsInGizmos"/>
|
||||
protected bool _displayNumberOfItemsInGizmos = false;
|
||||
|
||||
/// <summary>
|
||||
/// Determines side of root node expansion if necessary.
|
||||
/// </summary>
|
||||
protected bool ExpansionRight = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes Quadtree - creates initial root node and builds the tree (if allowed).
|
||||
/// </summary>
|
||||
public QuadtreeRoot(Vector3 center, Vector3 size)
|
||||
{
|
||||
CurrentRootNode = new TNode
|
||||
{
|
||||
TreeRoot = this,
|
||||
ParentNode = default,
|
||||
Bounds = new Bounds(center, size),
|
||||
};
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
public void Insert(TItem item)
|
||||
{
|
||||
// get item bounds
|
||||
var itemBounds = item.GetBounds();
|
||||
|
||||
// expand root node if necessary
|
||||
while (!CurrentRootNode.Contains(itemBounds))
|
||||
Expand();
|
||||
|
||||
// insert item into the tree
|
||||
CurrentRootNode.Insert(item);
|
||||
}
|
||||
|
||||
public void Expand()
|
||||
{
|
||||
// the subnodes will be of the same size as current root node
|
||||
var subBoundsSize = CurrentRootNode.Bounds.size;
|
||||
var centerOffset = subBoundsSize * .5f;
|
||||
|
||||
// center if expanding to left
|
||||
var center = CurrentRootNode.Bounds.min;
|
||||
if (ExpansionRight)
|
||||
{
|
||||
// center if expanding to right
|
||||
center = CurrentRootNode.Bounds.max;
|
||||
}
|
||||
|
||||
var subNodes = new List<TNode>(4);
|
||||
var newRootNode = new TNode
|
||||
{
|
||||
TreeRoot = this,
|
||||
ParentNode = default,
|
||||
Bounds = new Bounds(center, subBoundsSize * 2f),
|
||||
SubNodes = subNodes,
|
||||
};
|
||||
CurrentRootNode.ParentNode = newRootNode;
|
||||
|
||||
// top left node [-x +y]
|
||||
centerOffset.x *= -1f;
|
||||
subNodes.Insert((int)IntraLocation.UPPER_LEFT, new TNode
|
||||
{
|
||||
TreeRoot = this,
|
||||
ParentNode = newRootNode,
|
||||
Bounds = new Bounds(center + centerOffset, subBoundsSize),
|
||||
});
|
||||
|
||||
// top right node [+x +y]
|
||||
centerOffset.x *= -1f;
|
||||
subNodes.Insert((int)IntraLocation.UPPER_RIGHT, !ExpansionRight
|
||||
? CurrentRootNode
|
||||
: new TNode
|
||||
{
|
||||
TreeRoot = this,
|
||||
ParentNode = newRootNode,
|
||||
Bounds = new Bounds(center + centerOffset, subBoundsSize),
|
||||
});
|
||||
|
||||
// bottom right node [+x -y]
|
||||
centerOffset.z *= -1f;
|
||||
subNodes.Insert((int)IntraLocation.LOWER_RIGHT, new TNode
|
||||
{
|
||||
TreeRoot = this,
|
||||
ParentNode = newRootNode,
|
||||
Bounds = new Bounds(center + centerOffset, subBoundsSize),
|
||||
});
|
||||
|
||||
// bottom left node [-x -y]
|
||||
centerOffset.x *= -1f;
|
||||
subNodes.Insert((int)IntraLocation.LOWER_LEFT, ExpansionRight
|
||||
? CurrentRootNode
|
||||
: new TNode
|
||||
{
|
||||
TreeRoot = this,
|
||||
ParentNode = newRootNode,
|
||||
Bounds = new Bounds(center + centerOffset, subBoundsSize),
|
||||
});
|
||||
|
||||
// assign new root node
|
||||
CurrentRootNode = newRootNode;
|
||||
// toggle expansion side for next expansion
|
||||
ExpansionRight = !ExpansionRight;
|
||||
}
|
||||
|
||||
public List<TItem> Find(Bounds bounds)
|
||||
{
|
||||
IList<TItem> itemList = new List<TItem>();
|
||||
CurrentRootNode.FindAndAddItems(bounds, ref itemList);
|
||||
|
||||
return (List<TItem>)itemList;
|
||||
}
|
||||
|
||||
public void Remove(TItem item)
|
||||
{
|
||||
CurrentRootNode.Remove(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
CurrentRootNode.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 244873571ee2c644b9ee21f4848b63b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -43,6 +43,7 @@ namespace BITKit
|
|||
|
||||
public string Get()
|
||||
{
|
||||
|
||||
if (DictionaryReferenceScriptableObject.Dictionary.TryGetValue(index, out var value))
|
||||
{
|
||||
return value;
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITKit;
|
||||
using UnityEngine;
|
||||
|
||||
public class UnityAutoDestroyOnStart : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private bool inActive;
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
if (inActive)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class NewBehaviourScript : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c2048d435d8387842b5b0c9bf5498a1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -8,6 +8,7 @@ using Cysharp.Threading.Tasks;
|
|||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.LowLevel;
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
|
@ -58,15 +59,18 @@ namespace BITKit
|
|||
}
|
||||
|
||||
[SerializeField] private int tickRate = 32;
|
||||
[SerializeField] private bool isMainThread;
|
||||
[SerializeField] private bool isConcurrent;
|
||||
[SerializeField] private string lastTickTime="0";
|
||||
private readonly Timer _timer = new();
|
||||
private float _deltaTime;
|
||||
private double _lastTime;
|
||||
|
||||
private PlayerLoopSystem _playerLoop;
|
||||
|
||||
private int _update;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_playerLoop = PlayerLoop.GetCurrentPlayerLoop();
|
||||
tickRate = Mathf.Clamp(tickRate, 1, 128);
|
||||
|
||||
_deltaTime = 1f / tickRate;
|
||||
|
@ -81,32 +85,34 @@ namespace BITKit
|
|||
_timer.Dispose();
|
||||
});
|
||||
}
|
||||
private async void Tick(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c849275eb4fd266468253c3fb286f6c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 01963dbed2bec23468d2a4b617bdcfc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using BITKit.Mod;
|
||||
using BITKit.StateMachine;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
@ -18,7 +19,7 @@ using Debug = UnityEngine.Debug;
|
|||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public abstract class UIToolKitPanel : IUXPanel,IDisposable
|
||||
public abstract class UIToolKitPanel :StateAsync, IUXPanel,IDisposable
|
||||
{
|
||||
public const string USSEntry = "transition_entry";
|
||||
public const string USSEntryAsync = "transition_entry_async";
|
||||
|
@ -115,7 +116,7 @@ namespace BITKit.UX
|
|||
//WaitUtilTransitionCompleted = new();
|
||||
}
|
||||
|
||||
protected static readonly InputActionGroup InputActionGroup = new()
|
||||
protected readonly InputActionGroup InputActionGroup = new()
|
||||
{
|
||||
allowGlobalActivation = false,
|
||||
Source = nameof(UIToolKitPanel)
|
||||
|
@ -137,13 +138,12 @@ namespace BITKit.UX
|
|||
}
|
||||
protected virtual void OnPanelEntry(){}
|
||||
protected virtual void OnPanelExit(){}
|
||||
|
||||
void IEntryElement.Entry()
|
||||
public override void OnStateEntry(IState old)
|
||||
{
|
||||
InputActionGroup.allowInput.AddElement(this);
|
||||
OnEntry?.Invoke();
|
||||
}
|
||||
async UniTask IEntryElement.EntryAsync()
|
||||
public override async UniTask OnStateEntryAsync(IState old)
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
|
@ -193,6 +193,12 @@ namespace BITKit.UX
|
|||
await UniTask.SwitchToMainThread();
|
||||
|
||||
RootVisualElement.AddToClassList(USSEntered);
|
||||
|
||||
OnPanelEntry();
|
||||
OnEntryCompleted?.Invoke();
|
||||
|
||||
RootVisualElement.RemoveFromClassList(USSEntry);
|
||||
RootVisualElement.RemoveFromClassList(USSEntryAsync);
|
||||
}
|
||||
private void OnTransitionEnd<TChangeEvent>(TChangeEvent evt)
|
||||
{
|
||||
|
@ -203,22 +209,15 @@ namespace BITKit.UX
|
|||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
void IEntryElement.Entered()
|
||||
{
|
||||
OnPanelEntry();
|
||||
OnEntryCompleted?.Invoke();
|
||||
|
||||
RootVisualElement.RemoveFromClassList(USSEntry);
|
||||
RootVisualElement.RemoveFromClassList(USSEntryAsync);
|
||||
}
|
||||
|
||||
void IEntryElement.Exit()
|
||||
public override void OnStateExit(IState old, IState newState)
|
||||
{
|
||||
OnPanelExit();
|
||||
InputActionGroup.allowInput.RemoveElement(this);
|
||||
OnExit?.Invoke();
|
||||
}
|
||||
async UniTask IEntryElement.ExitAsync()
|
||||
|
||||
public override async UniTask OnStateExitAsync(IState old, IState newState)
|
||||
{
|
||||
RootVisualElement?.RemoveFromClassList(USSEntered);
|
||||
await UniTask.NextFrame();
|
||||
|
@ -244,9 +243,6 @@ namespace BITKit.UX
|
|||
{
|
||||
}
|
||||
|
||||
}
|
||||
void IEntryElement.Exited()
|
||||
{
|
||||
RootVisualElement?.RemoveFromClassList(USSExit);
|
||||
RootVisualElement?.RemoveFromClassList(USSExitAsync);
|
||||
RootVisualElement?.AddToClassList(USSExited);
|
||||
|
@ -275,6 +271,7 @@ namespace BITKit.UX
|
|||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
InputActionGroup.Dispose();
|
||||
RootVisualElement?.RemoveFromHierarchy();
|
||||
IsDisposed = true;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITKit.StateMachine;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using UnityEngine;
|
||||
|
@ -67,43 +68,6 @@ namespace BITKit.UX
|
|||
{ UXUtils.Inject(this,Panel.Root as VisualElement);
|
||||
|
||||
}
|
||||
|
||||
bool IEntryElement.IsEntered
|
||||
{
|
||||
get => Panel.IsEntered;
|
||||
set => Panel.IsEntered = value;
|
||||
}
|
||||
|
||||
void IEntryElement.Entry()
|
||||
{
|
||||
Panel.Entry();
|
||||
}
|
||||
|
||||
UniTask IEntryElement.EntryAsync()
|
||||
{
|
||||
return Panel.EntryAsync();
|
||||
}
|
||||
|
||||
void IEntryElement.Entered()
|
||||
{
|
||||
Panel.Entered();
|
||||
}
|
||||
|
||||
void IEntryElement.Exit()
|
||||
{
|
||||
Panel.Exit();
|
||||
}
|
||||
|
||||
UniTask IEntryElement.ExitAsync()
|
||||
{
|
||||
return Panel.ExitAsync();
|
||||
}
|
||||
|
||||
void IEntryElement.Exited()
|
||||
{
|
||||
Panel.Exited();
|
||||
}
|
||||
|
||||
bool IUXPanel.IsWindow => Panel.IsWindow;
|
||||
|
||||
string IUXPanel.Index => Panel.Index;
|
||||
|
@ -136,6 +100,58 @@ namespace BITKit.UX
|
|||
{
|
||||
Panel.OnTick(deltaTime);
|
||||
}
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => Panel.Enabled;
|
||||
set => Panel.Enabled = value;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Panel.Initialize();
|
||||
}
|
||||
|
||||
public void OnStateEntry(IState old)
|
||||
{
|
||||
Panel.OnStateEntry(old);
|
||||
}
|
||||
|
||||
public void OnStateUpdate(float deltaTime)
|
||||
{
|
||||
Panel.OnStateUpdate(deltaTime);
|
||||
}
|
||||
|
||||
public void OnStateExit(IState old, IState newState)
|
||||
{
|
||||
Panel.OnStateExit(old, newState);
|
||||
}
|
||||
|
||||
public int Identifier
|
||||
{
|
||||
get => Panel.Identifier;
|
||||
set => Panel.Identifier = value;
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
return Panel.InitializeAsync();
|
||||
}
|
||||
|
||||
public UniTask OnStateEntryAsync(IState old)
|
||||
{
|
||||
return Panel.OnStateEntryAsync(old);
|
||||
}
|
||||
|
||||
public UniTask OnStateUpdateAsync(float deltaTime)
|
||||
{
|
||||
return Panel.OnStateUpdateAsync(deltaTime);
|
||||
}
|
||||
|
||||
public UniTask OnStateExitAsync(IState old, IState newState)
|
||||
{
|
||||
return Panel.OnStateExitAsync(old, newState);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using BITKit.Mod;
|
||||
using BITKit.StateMachine;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Unity.Mathematics;
|
||||
|
@ -27,10 +28,9 @@ namespace BITKit.UX
|
|||
_ticker = ticker;
|
||||
_serviceProvider = serviceProvider;
|
||||
_cancellationTokenSource = cancellationTokenSource;
|
||||
_entryGroup.OnEntry += OnEntry;
|
||||
_entryGroup.OnStateChanged += OnEntry;
|
||||
|
||||
_windowEntryGroup.OnEntry += OnWindowEntry;
|
||||
_windowEntryGroup.OnExit += OnWindowExit;
|
||||
_windowEntryGroup.OnStateChanged += OnWindowEntry;
|
||||
_ticker.Add(OnTick);
|
||||
}
|
||||
|
||||
|
@ -41,15 +41,15 @@ namespace BITKit.UX
|
|||
BITInputSystem.AllowInput.RemoveDisableElements(_windowEntryGroup);
|
||||
}
|
||||
|
||||
private void OnWindowEntry(IUXPanel obj)
|
||||
private void OnWindowEntry(IUXPanel prev, IUXPanel next)
|
||||
{
|
||||
BITAppForUnity.AllowCursor.SetElements(_windowEntryGroup, obj.AllowCursor);
|
||||
if (obj.AllowInput is false)
|
||||
BITInputSystem.AllowInput.AddDisableElements(_windowEntryGroup);
|
||||
BITAppForUnity.AllowCursor.SetElements(_windowEntryGroup, next is { AllowCursor: true });
|
||||
|
||||
BITInputSystem.AllowInput.SetDisableElements(_windowEntryGroup, next is { AllowInput: false });
|
||||
}
|
||||
|
||||
private readonly EntryGroup<IUXPanel> _entryGroup = new();
|
||||
private readonly EntryGroup<IUXPanel> _windowEntryGroup = new();
|
||||
private readonly AsyncStateMachine<IUXPanel> _entryGroup = new();
|
||||
private readonly AsyncStateMachine<IUXPanel> _windowEntryGroup = new();
|
||||
/// <summary>
|
||||
/// 内部注册面板队列
|
||||
/// </summary>
|
||||
|
@ -163,9 +163,9 @@ Object.Destroy(gameObject);
|
|||
|
||||
public void Return()
|
||||
{
|
||||
if(_windowEntryGroup.TryGetEntried(out _))
|
||||
if (_windowEntryGroup.CurrentState is not null)
|
||||
{
|
||||
_windowEntryGroup.Entry(-1);
|
||||
_windowEntryGroup.DisposeState();
|
||||
return;
|
||||
}
|
||||
if (History.TryPop(out var returnPanel))
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Net.BITKit.VFX
|
|||
public VFXService(IEntitiesService entitiesService)
|
||||
{
|
||||
_entitiesService = entitiesService;
|
||||
_scriptableImpacts = _entitiesService.QueryComponents<ScriptableImpact>();
|
||||
_scriptableImpacts = _entitiesService.QueryComponents<ScriptableImpact>().ToArray();
|
||||
}
|
||||
|
||||
public AudioClip GetAudioClip(IReadOnlyCollection<string> tags)
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"defineConstraints": [
|
||||
"Disabled"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -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
|
@ -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
|
@ -0,0 +1,10 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c29905eb4a526bd4ea3cdea7ab6cd2ce
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9e346a267956eb3479c89010979a5a83
|
||||
guid: d67461ea58a21334ea2488664d62f758
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
|
@ -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": [],
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c05a2a2c7bf2c74c8639d3841c493d6
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 11baeb3f9cbfb344b9b855f9a79e1368
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 196335990c4123f46b440b909e1536d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue