1. MaxTargets 现在严格生效(包含玩家候选)
  - 玩家先被加入候选后,会计入 selectedCount,达到上限后不再继续查敌人桶。
2. UseSimulationMovement/UseJobSimulation 战斗中不生效
  - 在 SimulationWorld 的 setter 中增加 Battle 状态保护:战斗中调用会被忽略并打 warning。
  - 在 ProcedureGame 暴露当前状态类型,供 SimulationWorld 判断。
3. AOE 结算不再依赖结算时刻的 Weapon.IsAttacking
  - 在 AOE query 入队时快照 source 活跃状态(武器用 IsAttacking)。
  - Query 数据结构增加 SourceWasActiveAtQueryTime。
  - 结算时使用该快照判定,并通过 AIUtility.PerformCollision(..., ignoreRuntimeState: true) 避免被实时状态误伤。
This commit is contained in:
SepComet 2026-02-23 11:48:05 +08:00
parent 688fefe848
commit 76094b711b
5 changed files with 80 additions and 9 deletions

View File

@ -35,6 +35,7 @@ namespace Procedure
public Player Player; public Player Player;
public GameStateBase CurrentGameState => _gameStates[_currentGameState]; public GameStateBase CurrentGameState => _gameStates[_currentGameState];
public GameStateType CurrentGameStateType => _currentGameState;
private void InitGameState() private void InitGameState()
{ {

View File

@ -75,6 +75,7 @@ namespace Simulation
public int SourceType; public int SourceType;
public int SourceEntityId; public int SourceEntityId;
public int SourceOwnerEntityId; public int SourceOwnerEntityId;
public bool SourceWasActiveAtQueryTime;
public float3 Position; public float3 Position;
public float Radius; public float Radius;
public int MaxTargets; public int MaxTargets;
@ -98,6 +99,7 @@ namespace Simulation
{ {
public int SourceEntityId; public int SourceEntityId;
public int SourceOwnerEntityId; public int SourceOwnerEntityId;
public bool SourceWasActiveAtQueryTime;
public Vector3 Center; public Vector3 Center;
public float Radius; public float Radius;
public int MaxTargets; public int MaxTargets;
@ -474,6 +476,7 @@ namespace Simulation
SourceType = CollisionSourceTypeProjectile, SourceType = CollisionSourceTypeProjectile,
SourceEntityId = projectile.EntityId, SourceEntityId = projectile.EntityId,
SourceOwnerEntityId = projectile.OwnerEntityId, SourceOwnerEntityId = projectile.OwnerEntityId,
SourceWasActiveAtQueryTime = true,
Position = projectile.Position, Position = projectile.Position,
Radius = radius, Radius = radius,
MaxTargets = math.max(1, maxTargets), MaxTargets = math.max(1, maxTargets),
@ -483,8 +486,9 @@ namespace Simulation
}); });
} }
private void AddAreaCollisionQuery(int queryId, int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, private void AddAreaCollisionQuery(int queryId, int sourceEntityId, int sourceOwnerEntityId,
float radius, int maxTargets, int shapeType, in Vector3 direction, float halfAngleDeg) bool sourceWasActiveAtQueryTime, in Vector3 center, float radius, int maxTargets, int shapeType,
in Vector3 direction, float halfAngleDeg)
{ {
if (!_collisionQueryInputs.IsCreated || radius <= 0f) if (!_collisionQueryInputs.IsCreated || radius <= 0f)
{ {
@ -508,6 +512,7 @@ namespace Simulation
SourceType = CollisionSourceTypeArea, SourceType = CollisionSourceTypeArea,
SourceEntityId = sourceEntityId, SourceEntityId = sourceEntityId,
SourceOwnerEntityId = sourceOwnerEntityId, SourceOwnerEntityId = sourceOwnerEntityId,
SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime,
Position = new float3(center.x, center.y, center.z), Position = new float3(center.x, center.y, center.z),
Radius = radius, Radius = radius,
MaxTargets = math.max(1, maxTargets), MaxTargets = math.max(1, maxTargets),

View File

@ -106,6 +106,7 @@ namespace Simulation
} }
int resolvedOwnerEntityId = sourceOwnerEntityId != 0 ? sourceOwnerEntityId : sourceEntityId; int resolvedOwnerEntityId = sourceOwnerEntityId != 0 ? sourceOwnerEntityId : sourceEntityId;
bool sourceWasActiveAtQueryTime = IsCollisionSourceActiveAtQueryTime(sourceEntityId);
Vector3 normalizedDirection = direction; Vector3 normalizedDirection = direction;
normalizedDirection.y = 0f; normalizedDirection.y = 0f;
if (normalizedDirection.sqrMagnitude <= Mathf.Epsilon) if (normalizedDirection.sqrMagnitude <= Mathf.Epsilon)
@ -121,6 +122,7 @@ namespace Simulation
{ {
SourceEntityId = sourceEntityId, SourceEntityId = sourceEntityId,
SourceOwnerEntityId = resolvedOwnerEntityId, SourceOwnerEntityId = resolvedOwnerEntityId,
SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime,
Center = center, Center = center,
Radius = Mathf.Max(0.01f, radius), Radius = Mathf.Max(0.01f, radius),
MaxTargets = Mathf.Max(1, maxTargets), MaxTargets = Mathf.Max(1, maxTargets),
@ -238,8 +240,9 @@ namespace Simulation
for (int i = 0; i < areaQueryCount; i++) for (int i = 0; i < areaQueryCount; i++)
{ {
AreaCollisionRequestData request = _areaCollisionRequests[i]; AreaCollisionRequestData request = _areaCollisionRequests[i];
AddAreaCollisionQuery(queryId, request.SourceEntityId, request.SourceOwnerEntityId, in request.Center, AddAreaCollisionQuery(queryId, request.SourceEntityId, request.SourceOwnerEntityId,
request.Radius, request.MaxTargets, request.ShapeType, in request.Direction, request.HalfAngleDeg); request.SourceWasActiveAtQueryTime, in request.Center, request.Radius, request.MaxTargets,
request.ShapeType, in request.Direction, request.HalfAngleDeg);
queryId++; queryId++;
builtAreaQueryCount++; builtAreaQueryCount++;
if (request.Radius > maxQueryRadius) if (request.Radius > maxQueryRadius)
@ -628,6 +631,11 @@ namespace Simulation
continue; continue;
} }
if (!query.SourceWasActiveAtQueryTime)
{
continue;
}
EntityBase sourceEntity = TryGetEntityById(hitEvent.SourceEntityId); EntityBase sourceEntity = TryGetEntityById(hitEvent.SourceEntityId);
if (sourceEntity == null || !sourceEntity.Available) if (sourceEntity == null || !sourceEntity.Available)
{ {
@ -644,7 +652,7 @@ namespace Simulation
continue; continue;
} }
AIUtility.PerformCollision(target, sourceEntity); AIUtility.PerformCollision(target, sourceEntity, true);
resolvedHitCount++; resolvedHitCount++;
} }
@ -787,10 +795,16 @@ namespace Simulation
{ {
areaCandidateCount++; areaCandidateCount++;
} }
selectedCount++;
if (selectedCount >= query.MaxTargets)
{
reachedLimit = true;
}
} }
} }
if (!hasEnemyTargets) if (!hasEnemyTargets || reachedLimit)
{ {
continue; continue;
} }
@ -893,6 +907,27 @@ namespace Simulation
return projectile.LifeTime > 0f && projectile.Age >= projectile.LifeTime; return projectile.LifeTime > 0f && projectile.Age >= projectile.LifeTime;
} }
private static bool IsCollisionSourceActiveAtQueryTime(int sourceEntityId)
{
EntityBase sourceEntity = TryGetEntityById(sourceEntityId);
if (sourceEntity == null || !sourceEntity.Available)
{
return false;
}
if (sourceEntity is WeaponBase weapon)
{
return weapon.IsAttacking;
}
if (sourceEntity is EnemyProjectile projectile)
{
return projectile.IsActive;
}
return true;
}
private static void ExecuteProjectileMovement(int index, NativeArray<ProjectileJobInputData> inputs, private static void ExecuteProjectileMovement(int index, NativeArray<ProjectileJobInputData> inputs,
NativeArray<ProjectileJobOutputData> outputs, float deltaTime, float3 playerPosition, NativeArray<ProjectileJobOutputData> outputs, float deltaTime, float3 playerPosition,
float maxSqrDistanceFromPlayer, float maxVerticalOffsetFromPlayer) float maxSqrDistanceFromPlayer, float maxVerticalOffsetFromPlayer)

View File

@ -4,6 +4,7 @@ using CustomDebugger;
using CustomUtility; using CustomUtility;
using Entity; using Entity;
using Entity.EntityData; using Entity.EntityData;
using Procedure;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@ -69,11 +70,23 @@ namespace Simulation
public void SetUseSimulationMovement(bool enabled) public void SetUseSimulationMovement(bool enabled)
{ {
if (IsBattleStateActive())
{
Log.Warning("SetUseSimulationMovement is ignored during Battle. Change this switch outside Battle.");
return;
}
_useSimulationMovement = enabled; _useSimulationMovement = enabled;
} }
public void SetUseJobSimulation(bool enabled) public void SetUseJobSimulation(bool enabled)
{ {
if (IsBattleStateActive())
{
Log.Warning("SetUseJobSimulation is ignored during Battle. Change this switch outside Battle.");
return;
}
_useJobSimulation = enabled; _useJobSimulation = enabled;
} }
@ -82,6 +95,18 @@ namespace Simulation
_useBurstJobs = enabled; _useBurstJobs = enabled;
} }
private static bool IsBattleStateActive()
{
var procedureComponent = GameEntry.Procedure;
if (procedureComponent == null ||
procedureComponent.CurrentProcedure is not ProcedureGame procedureGame)
{
return false;
}
return procedureGame.CurrentGameStateType == GameStateType.Battle;
}
protected override void Awake() protected override void Awake()
{ {
base.Awake(); base.Awake();

View File

@ -159,6 +159,11 @@ namespace CustomUtility
} }
public static void PerformCollision(TargetableObject entity, EntityBase other) public static void PerformCollision(TargetableObject entity, EntityBase other)
{
PerformCollision(entity, other, false);
}
public static void PerformCollision(TargetableObject entity, EntityBase other, bool ignoreRuntimeState)
{ {
if (entity == null || other == null) if (entity == null || other == null)
{ {
@ -204,7 +209,7 @@ namespace CustomUtility
EnemyProjectile enemyProjectile = other as EnemyProjectile; EnemyProjectile enemyProjectile = other as EnemyProjectile;
if (enemyProjectile != null) if (enemyProjectile != null)
{ {
if (!enemyProjectile.IsActive) return; if (!ignoreRuntimeState && !enemyProjectile.IsActive) return;
ImpactData entityImpactData = entity.GetImpactData(); ImpactData entityImpactData = entity.GetImpactData();
ImpactData projectileImpactData = enemyProjectile.GetImpactData(); ImpactData projectileImpactData = enemyProjectile.GetImpactData();
@ -224,7 +229,7 @@ namespace CustomUtility
WeaponBase weapon = other as WeaponBase; WeaponBase weapon = other as WeaponBase;
if (weapon != null) if (weapon != null)
{ {
if (!weapon.IsAttacking) return; if (!ignoreRuntimeState && !weapon.IsAttacking) return;
ImpactData entityImpactData = entity.GetImpactData(); ImpactData entityImpactData = entity.GetImpactData();
ImpactData weaponImpactData = weapon.GetImpactData(); ImpactData weaponImpactData = weapon.GetImpactData();
if (GetRelation(entityImpactData.Camp, weaponImpactData.Camp) == RelationType.Friendly) if (GetRelation(entityImpactData.Camp, weaponImpactData.Camp) == RelationType.Friendly)