🚩release-2025.12.5
GPU动画工具新增一些新手友好的提示。
例如:
1. prefab中不存在AnimationClip会用中文提示。
2. Mesh或Material资源丢失时,工具无法转换,会给出中文提示。
3. 存在重复的节点路径会给出中文警告Log。
4. 以及其它因资源存在问题,无法继续转换的提示和引导。
🚩release-2025.12.1
1. 深度优化Jobs调度, 10w同屏移动、索敌、战斗demo,帧数提升至100+。
2. 优化WebGL平台渲染性能。
🚩release-2025.11.29
应多数用户反馈RVO移动流动性较高,缺乏真实感。改为取消流动,有利有弊,取消流动会大大增强真实感,但也会加重"阻塞感"。"流动"与"阻塞"是相悖的物理现象。
1. RVO移动取消流动性,增强真实感。(不会过于频繁流动、转向)
2. RVO索敌性能优化、新增更多索敌接口。
3. 优化RVO、索敌、渲染同步之间的多线程Jobs调度,避免竞争卡顿。
4. 重写简易版红蓝对抗demo, i7-13代 + 3070配置下 10w个单位索敌+战斗帧数提升到90+帧。
5. 更新WebGL版demo。
6. 其它性能优化, bug修复, 稳定性测试。
🚩release-2025.11.21
大幅重构, 已有项目谨慎升级, 新项目强烈推荐使用
https://www.bilibili.com/video/BV1v9ygBAEND/
1. ECSGraphics正式支持WebGL(2.0), 支持微信/抖音等小游戏平台大幅提升性能.
2. RVO和ECSGraphics各实现一套独立的索敌/目标查找/碰撞检测系统。更高性能、更多的接口、更易用、更强大的目标筛选控制。精心针对密集小战场或稀疏大世界量身定制不同搜索算法,通过宏定义无感切换。支持最近、范围内、射线、球体射线等搜索方法,更完善的索敌过滤功能,更简单的用法。支持NativeArrayPool,RVO版索敌,沿用KDTree构建。 ECSGraphics版索敌支持Grid、Loose Octree两种算法,全部Jobs多线程索敌接口,性能微秒必争。
3. 优化RVO与ECSGraphics渲染同步性能。使用NativeArray缓存池,自动扩容、缩容。减少开销和内存占用。
4. 优化GPU动画帧事件触发检测性能。
5. GPU动画转换工具新增动画速度缩放设置。
6. 若干bug修复、体验优化。
一、RVO版索敌系统
1. Raycast射线检测/SphereCast检测, 提供多个接口重载,支持以实体或点进行批量目标搜索:
//通过点的方式,批量射线检测。A、B两点确定一条射线,适用于技能伤害等场景
1. 提供了最大索敌个数预测接口, 可以快速计算出空间内最多容纳多少单位
int maxAttackCount = RVOComponent.CalculateMaxTargetingCountRaycast(pointsA[0], pointsB[0], 0, 0.5f);
2. Raycast接口重载,以点的方式射线检测。 TargetingData是目标筛选过滤数据,支持根据视野、攻击范围、阵营、Layer、优先级等,精准控制过滤。
var ids = RVOComponent.Instance.Raycast(pointsA, pointsB, pointsA.Length, new TargetingData(TargetingLayer.NONE, TargetingLayer.NONE, TargetingLayer.NONE, attackRadius, maxAttackCount), out int idsLength);
3. 提供了索敌结果遍历接口,更简单易用。接口自动遍历批量索敌结果,返回索敌者和被锁敌者信息。
RVOComponent.Instance.ForeachMultiTargetingResults(ids, pointsA.Length, maxAttackCount, (queryIndex, targetAgentId, targetIndex) =>
{
//遍历每个被索住的目标,处理攻击、伤害等AI逻辑
if (RVOComponent.Instance.TryGetAgent(targetAgentId, out var targetAgent))
ECSGraphicsComponent.Instance.SetMainColor(targetAgent.ecsEntity, debugColor);
});
2. GetNearest查找最近目标:
var ids = RVOComponent.Instance.GetNearest(pointsA, pointsA.Length, new TargetingData(TargetingLayer.NONE, TargetingLayer.NONE, TargetingLayer.NONE, 0, 0), out int idsLength);
RVOComponent.Instance.ForeachSingleTargetingResults(ids, idsLength, (queryIndex, targetAgentId) =>
{
if (RVOComponent.Instance.TryGetAgent(targetAgentId, out var targetAgent))
ECSGraphicsComponent.Instance.SetMainColor(targetAgent.ecsEntity, debugColor);
});
3. GetWithin查找范围内N个目标:
int maxAttackCount = RVOComponent.CalculateMaxTargetingCountWithin(debugTargetingRadius, 0.5f);
var ids = RVOComponent.Instance.GetWithin(pointsA, pointsA.Length, new TargetingData(TargetingLayer.NONE, TargetingLayer.NONE, TargetingLayer.NONE, debugTargetingRadius, maxAttackCount, 360), math.forward(), out int idsLength);
RVOComponent.Instance.ForeachMultiTargetingResults(ids, pointsA.Length, maxAttackCount, (queryIndex, targetAgentId, targetIndex) =>
{
if (RVOComponent.Instance.TryGetAgent(targetAgentId, out var targetAgent))
{
ECSGraphicsComponent.Instance.SetMainColor(targetAgent.ecsEntity, debugColor);
}
});
用法示例,简易版AI逻辑:
1. 批量查询每个战斗单位最近的敌方单位,并得到这个最近敌人是否在攻击范围内
var nearestIds = rvoCom.GetNearestCheckAtkRange(agents, agentsLength, out int idsLength);
2. 遍历查询结果
rvoCom.ForeachSingleTargetingResults(agents, nearestIds, idsLength, (queryIndex, queryAgent, result) =>
{
if (!rvoCom.TryGetAgent(result.Id, out var targetAgent)) return;
if (m_Players.TryGetValue(queryAgent.ecsEntity, out var player) && m_Players.TryGetValue(targetAgent.ecsEntity, out var target))
{
3. 如果最近的敌人在自己的攻击范围内,则在CD已冷却的情况下发动攻击
if (result.WithinRadius)
{
Debug.DrawLine(player.Position, target.Position, Color.red, 0.25f);
if (player.CheckAttackCD())
player.AttackImmediate(target);
}
else
{
4. 如果最近的敌人不在攻击范围内,向最近的敌人移动
player.SetMovePoint(target.Position);
Debug.DrawLine(player.Position, target.Position, Color.gray, 0.25f);
}
}
});
二、ECSGraphics版索敌系统
创建实体时,添加一个ECSTargetingData(索敌筛选配置)即可参与索敌:
var entity = ECSGraphicsComponent.Instance.Add(0, pos, Quaternion.identity); ECSGraphicsComponent.Instance.AddComponentData<ECSTargetingData>(entity, new ECSTargetingData(ECSTargetingLayer.L1, ECSTargetingLayer.L1, ECSTargetingLayer.NONE, 0.5f, 5f, 100, 360));
1. Raycast / SphereCast射线检测,支持以实体查找、以点查找:
NativeArray<Unity.Entities.Entity> targets;
if (Input.GetKey(KeyCode.Q))
{
// 射线检测
targets = ECSGraphicsComponent.Instance.Raycast(m_QueryPoints, m_QueryPointsB, m_PointsTargetingData);
}
else
{
// SphereCast 球体抛射检测
targets = ECSGraphicsComponent.Instance.SphereRaycast(m_QueryPoints, m_QueryPointsB, m_PointsTargetingData);
}
// 遍历检测目标
if (targets.IsCreated)
{
foreach (var tag in targets)
{
if (tag == Unity.Entities.Entity.Null)
{
break;
}
ECSGraphicsComponent.Instance.SetMainColor(tag, new Unity.Mathematics.float4(1, 0, 0, 1));
}
targets.Dispose();
}
2. GetNearest查找最近实体, 支持以实体查找、以点查找:
targets = ECSGraphicsComponent.Instance.GetNearest(m_QueryPoints, m_PointsTargetingData);
3. GetWithin查找范围内实体,支持以实体查找、以点查找:
1. 批量查询每个实体 攻击范围内的敌人
var targets = ECSGraphicsComponent.Instance.GetWithin(m_Player, m_Player.Length);
if (targets.IsCreated)
{
int resultIndexOffset = 0;
for (int i = 0; i < m_Player.Length; i++)
{
var player = m_Player[i];
2. 根据每个实体配置的最大攻击个数遍历被攻击对象
var maxTargetCount = ECSGraphicsComponent.Instance.GetComponentData<ECSTargetingData>(player).MaxAttackCount;
var playerColor = ECSGraphicsComponent.Instance.GetComponentData<MaterialBaseColor>(player).Value;
for (int j = 0; j < maxTargetCount; j++)
{
var target = targets[resultIndexOffset + j];
3. 遇到Null说明后面已经无对象
if (target == Unity.Entities.Entity.Null)
{
break;
}
4. 对目标进行伤害
//TODO Something...
player.Attack(target);
}
resultIndexOffset += maxTargetCount;
}
5. 用完结果后释放内存
targets.Dispose();
}