fix: 编队偏移持久化到航点 + yLanes=Quantity + 云Z展开
- DroneEntity: 偏移写入克隆航点List而非位置字段 - Planner: 车道数=无人机数, laneSpacingZ=LateralSpacing - 200m间距: 33发, 2/3击毁, 第三架hp=0.10边缘效应
This commit is contained in:
parent
b596b4a791
commit
070167f572
@ -133,15 +133,13 @@ namespace CounterDrone.Core.Algorithms
|
||||
// 单机需求
|
||||
int singleNeeded = CalcRoundsNeeded(threat, ammo, env, false);
|
||||
|
||||
// 横向编队:计算 Y 方向云带数
|
||||
// 横向编队:每种 Width = Quantity 个独立车道
|
||||
int yLanes = 1;
|
||||
float formationWidth = 0f;
|
||||
if (threat.Target.Quantity > 1 && threat.Route != null && (FormationMode)threat.Route.FormationMode == FormationMode.Formation)
|
||||
{
|
||||
yLanes = threat.Target.Quantity;
|
||||
formationWidth = (threat.Target.Quantity - 1) * (float)threat.Route.LateralSpacing;
|
||||
var (effR, _) = ComputeEffectiveRadius(threat, ammo, env);
|
||||
float cloudDiam = effR * 2f;
|
||||
yLanes = Math.Max(1, (int)Math.Ceiling(formationWidth / cloudDiam));
|
||||
}
|
||||
int totalRoundsNeeded = singleNeeded * yLanes;
|
||||
|
||||
@ -425,11 +423,10 @@ namespace CounterDrone.Core.Algorithms
|
||||
var (effectiveR, expansionTime) = ComputeEffectiveRadius(threat, ammo, env);
|
||||
|
||||
// 编队 Y 偏移:按车道分布
|
||||
float laneSpacingY = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f;
|
||||
float targetY = mid.Y + yLane * laneSpacingY;
|
||||
float laneSpacingZ = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f;
|
||||
float tz = mid.Z + yLane * laneSpacingZ;
|
||||
|
||||
float tx = mid.X + targetOffset;
|
||||
float tz = mid.Z;
|
||||
|
||||
// 按目标点位置计算无人机到达时间,非中点
|
||||
float droneSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
|
||||
@ -480,7 +477,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
FireTime = fireTime,
|
||||
PlatformIndex = 0,
|
||||
TargetX = tx,
|
||||
TargetY = targetY,
|
||||
TargetY = mid.Y,
|
||||
TargetZ = tz,
|
||||
MuzzleVelocity = unit.Type == PlatformType.AirBased ? 0f : unit.MuzzleVelocity,
|
||||
});
|
||||
|
||||
@ -35,16 +35,42 @@ namespace CounterDrone.Core.Simulation
|
||||
TypicalSpeed = (float)config.TypicalSpeed;
|
||||
Route = route;
|
||||
|
||||
if (route.Count > 0)
|
||||
// 编队偏移应用到航点上(每架独立航路)
|
||||
float latOffset = 0, longOffset = 0;
|
||||
switch (mode)
|
||||
{
|
||||
PosX = (float)route[0].PosX;
|
||||
PosY = (float)route[0].PosY;
|
||||
PosZ = (float)route[0].PosZ;
|
||||
case FormationMode.Formation:
|
||||
latOffset = formationIndex * lateralSpacing; // Z 横向
|
||||
longOffset = -longitudinalIndex * longitudinalSpacing; // X 纵向
|
||||
break;
|
||||
case FormationMode.Swarm:
|
||||
var rng = new Random((formationIndex + longitudinalIndex * 100) * 137);
|
||||
latOffset = (float)(rng.NextDouble() - 0.5) * lateralSpacing * 3;
|
||||
longOffset = (float)(rng.NextDouble() - 0.5) * lateralSpacing * 3;
|
||||
break;
|
||||
}
|
||||
|
||||
ApplyFormationOffset(formationIndex, lateralSpacing, longitudinalIndex, longitudinalSpacing, mode);
|
||||
PosX += FormationOffsetX;
|
||||
PosY += FormationOffsetY;
|
||||
// 克隆航点并加偏移
|
||||
var offsetRoute = new List<Waypoint>();
|
||||
foreach (var wp in route)
|
||||
{
|
||||
offsetRoute.Add(new Waypoint
|
||||
{
|
||||
PosX = wp.PosX + longOffset,
|
||||
PosY = wp.PosY,
|
||||
PosZ = wp.PosZ + latOffset,
|
||||
Altitude = wp.Altitude,
|
||||
Speed = wp.Speed,
|
||||
});
|
||||
}
|
||||
Route = offsetRoute;
|
||||
|
||||
if (offsetRoute.Count > 0)
|
||||
{
|
||||
PosX = (float)offsetRoute[0].PosX;
|
||||
PosY = (float)offsetRoute[0].PosY;
|
||||
PosZ = (float)offsetRoute[0].PosZ;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFormationOffset(int latIdx, float latSpacing, int longIdx, float longSpacing, FormationMode mode)
|
||||
|
||||
@ -24,8 +24,8 @@ namespace CounterDrone.Core.Tests
|
||||
{
|
||||
return new List<Waypoint>
|
||||
{
|
||||
new Waypoint { PosX = fromX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
||||
new Waypoint { PosX = toX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
||||
new() { PosX = fromX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
||||
new() { PosX = toX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
||||
};
|
||||
}
|
||||
|
||||
@ -64,7 +64,6 @@ namespace CounterDrone.Core.Tests
|
||||
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||||
drone.ApplyDamage(0.4f);
|
||||
Assert.Equal(0.6f, drone.Hp, 0.01f);
|
||||
Assert.Equal(DroneStatus.Flying, drone.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -84,24 +83,22 @@ namespace CounterDrone.Core.Tests
|
||||
CreateRoute(0, 1000, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||||
float xBefore = drone.PosX;
|
||||
drone.Update(1f, 20f, WindDirection.E);
|
||||
Assert.True(drone.PosX > xBefore + 15f, "东风 20m/s 应增加 X");
|
||||
Assert.True(drone.PosX > xBefore + 15f);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
// 编队位置
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void Formation_Lateral_YOffsetsDiffer()
|
||||
public void Formation_Lateral_ZOffsetsDiffer()
|
||||
{
|
||||
var route = CreateRoute(0, 100, 300, 60);
|
||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 0, FormationMode.Formation);
|
||||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 0, FormationMode.Formation);
|
||||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 2, 50, 0, 0, FormationMode.Formation);
|
||||
// 横向展开:Y 递增
|
||||
Assert.Equal(0, d0.FormationOffsetY);
|
||||
Assert.Equal(50, d1.FormationOffsetY);
|
||||
Assert.Equal(100, d2.FormationOffsetY);
|
||||
// 起始Z不同
|
||||
Assert.NotEqual(d0.PosZ, d1.PosZ);
|
||||
Assert.NotEqual(d1.PosZ, d2.PosZ);
|
||||
// 航点Z不同(偏移持久化到航点)
|
||||
Assert.NotEqual(d0.Route[0].PosZ, d1.Route[0].PosZ);
|
||||
Assert.NotEqual(d0.Route[1].PosZ, d1.Route[1].PosZ);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -110,40 +107,19 @@ namespace CounterDrone.Core.Tests
|
||||
var route = CreateRoute(0, 100, 300, 60);
|
||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 0, 0, 100, FormationMode.Formation);
|
||||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 0, 0, 1, 100, FormationMode.Formation);
|
||||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 0, 0, 2, 100, FormationMode.Formation);
|
||||
// 纵向串列:X 落后递增
|
||||
Assert.Equal(0, d0.FormationOffsetX);
|
||||
Assert.Equal(-100, d1.FormationOffsetX);
|
||||
Assert.Equal(-200, d2.FormationOffsetX);
|
||||
Assert.NotEqual(d0.PosX, d1.PosX);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Formation_Mixed_2x2()
|
||||
{
|
||||
var route = CreateRoute(0, 100, 300, 60);
|
||||
// 2横 × 2纵: index 0=(0,0), 1=(1,0), 2=(0,1), 3=(1,1)
|
||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route,
|
||||
0, 50, 0, 100, FormationMode.Formation); // lat=0, long=0
|
||||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route,
|
||||
1, 50, 0, 100, FormationMode.Formation); // lat=1, long=0
|
||||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route,
|
||||
0, 50, 1, 100, FormationMode.Formation); // lat=0, long=1
|
||||
var d3 = new DroneEntity("d3", "g1", CreateConfig(), route,
|
||||
1, 50, 1, 100, FormationMode.Formation); // lat=1, long=1
|
||||
|
||||
Assert.Equal(0, d0.FormationOffsetY); Assert.Equal(0, d0.FormationOffsetX);
|
||||
Assert.Equal(50, d1.FormationOffsetY); Assert.Equal(0, d1.FormationOffsetX);
|
||||
Assert.Equal(0, d2.FormationOffsetY); Assert.Equal(-100, d2.FormationOffsetX);
|
||||
Assert.Equal(50, d3.FormationOffsetY); Assert.Equal(-100, d3.FormationOffsetX);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Formation_Single_HasNoOffset()
|
||||
{
|
||||
var drone = new DroneEntity("d1", "g1", CreateConfig(),
|
||||
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||||
Assert.Equal(0, drone.FormationOffsetX);
|
||||
Assert.Equal(0, drone.FormationOffsetY);
|
||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 100, FormationMode.Formation);
|
||||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 100, FormationMode.Formation);
|
||||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 0, 50, 1, 100, FormationMode.Formation);
|
||||
var d3 = new DroneEntity("d3", "g1", CreateConfig(), route, 1, 50, 1, 100, FormationMode.Formation);
|
||||
Assert.NotEqual(d0.PosZ, d1.PosZ); // lateral
|
||||
Assert.NotEqual(d0.PosX, d2.PosX); // longitudinal
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -152,7 +128,7 @@ namespace CounterDrone.Core.Tests
|
||||
var route = CreateRoute(0, 100, 300, 60);
|
||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 30, 0, 0, FormationMode.Swarm);
|
||||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 30, 0, 0, FormationMode.Swarm);
|
||||
Assert.True(d0.PosX != d1.PosX || d0.PosY != d1.PosY, "蜂群应有位置差异");
|
||||
Assert.True(d0.PosX != d1.PosX || d0.PosZ != d1.PosZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,7 +208,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan
|
||||
{
|
||||
FormationMode = (int)FormationMode.Formation,
|
||||
LateralSpacing = 50,
|
||||
LateralSpacing = 200,
|
||||
LateralCount = 3,
|
||||
LongitudinalCount = 1,
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user