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);
|
int singleNeeded = CalcRoundsNeeded(threat, ammo, env, false);
|
||||||
|
|
||||||
// 横向编队:计算 Y 方向云带数
|
// 横向编队:每种 Width = Quantity 个独立车道
|
||||||
int yLanes = 1;
|
int yLanes = 1;
|
||||||
float formationWidth = 0f;
|
float formationWidth = 0f;
|
||||||
if (threat.Target.Quantity > 1 && threat.Route != null && (FormationMode)threat.Route.FormationMode == FormationMode.Formation)
|
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;
|
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;
|
int totalRoundsNeeded = singleNeeded * yLanes;
|
||||||
|
|
||||||
@ -425,11 +423,10 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
var (effectiveR, expansionTime) = ComputeEffectiveRadius(threat, ammo, env);
|
var (effectiveR, expansionTime) = ComputeEffectiveRadius(threat, ammo, env);
|
||||||
|
|
||||||
// 编队 Y 偏移:按车道分布
|
// 编队 Y 偏移:按车道分布
|
||||||
float laneSpacingY = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f;
|
float laneSpacingZ = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f;
|
||||||
float targetY = mid.Y + yLane * laneSpacingY;
|
float tz = mid.Z + yLane * laneSpacingZ;
|
||||||
|
|
||||||
float tx = mid.X + targetOffset;
|
float tx = mid.X + targetOffset;
|
||||||
float tz = mid.Z;
|
|
||||||
|
|
||||||
// 按目标点位置计算无人机到达时间,非中点
|
// 按目标点位置计算无人机到达时间,非中点
|
||||||
float droneSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
|
float droneSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
|
||||||
@ -480,7 +477,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
FireTime = fireTime,
|
FireTime = fireTime,
|
||||||
PlatformIndex = 0,
|
PlatformIndex = 0,
|
||||||
TargetX = tx,
|
TargetX = tx,
|
||||||
TargetY = targetY,
|
TargetY = mid.Y,
|
||||||
TargetZ = tz,
|
TargetZ = tz,
|
||||||
MuzzleVelocity = unit.Type == PlatformType.AirBased ? 0f : unit.MuzzleVelocity,
|
MuzzleVelocity = unit.Type == PlatformType.AirBased ? 0f : unit.MuzzleVelocity,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -35,16 +35,42 @@ namespace CounterDrone.Core.Simulation
|
|||||||
TypicalSpeed = (float)config.TypicalSpeed;
|
TypicalSpeed = (float)config.TypicalSpeed;
|
||||||
Route = route;
|
Route = route;
|
||||||
|
|
||||||
if (route.Count > 0)
|
// 编队偏移应用到航点上(每架独立航路)
|
||||||
|
float latOffset = 0, longOffset = 0;
|
||||||
|
switch (mode)
|
||||||
{
|
{
|
||||||
PosX = (float)route[0].PosX;
|
case FormationMode.Formation:
|
||||||
PosY = (float)route[0].PosY;
|
latOffset = formationIndex * lateralSpacing; // Z 横向
|
||||||
PosZ = (float)route[0].PosZ;
|
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;
|
var offsetRoute = new List<Waypoint>();
|
||||||
PosY += FormationOffsetY;
|
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)
|
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>
|
return new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = fromX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
new() { PosX = fromX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
||||||
new Waypoint { PosX = toX, 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);
|
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||||||
drone.ApplyDamage(0.4f);
|
drone.ApplyDamage(0.4f);
|
||||||
Assert.Equal(0.6f, drone.Hp, 0.01f);
|
Assert.Equal(0.6f, drone.Hp, 0.01f);
|
||||||
Assert.Equal(DroneStatus.Flying, drone.Status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -84,24 +83,22 @@ namespace CounterDrone.Core.Tests
|
|||||||
CreateRoute(0, 1000, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
CreateRoute(0, 1000, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||||||
float xBefore = drone.PosX;
|
float xBefore = drone.PosX;
|
||||||
drone.Update(1f, 20f, WindDirection.E);
|
drone.Update(1f, 20f, WindDirection.E);
|
||||||
Assert.True(drone.PosX > xBefore + 15f, "东风 20m/s 应增加 X");
|
Assert.True(drone.PosX > xBefore + 15f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════
|
|
||||||
// 编队位置
|
|
||||||
// ═══════════════════════════════════════
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Formation_Lateral_YOffsetsDiffer()
|
public void Formation_Lateral_ZOffsetsDiffer()
|
||||||
{
|
{
|
||||||
var route = CreateRoute(0, 100, 300, 60);
|
var route = CreateRoute(0, 100, 300, 60);
|
||||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 0, FormationMode.Formation);
|
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 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);
|
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 2, 50, 0, 0, FormationMode.Formation);
|
||||||
// 横向展开:Y 递增
|
// 起始Z不同
|
||||||
Assert.Equal(0, d0.FormationOffsetY);
|
Assert.NotEqual(d0.PosZ, d1.PosZ);
|
||||||
Assert.Equal(50, d1.FormationOffsetY);
|
Assert.NotEqual(d1.PosZ, d2.PosZ);
|
||||||
Assert.Equal(100, d2.FormationOffsetY);
|
// 航点Z不同(偏移持久化到航点)
|
||||||
|
Assert.NotEqual(d0.Route[0].PosZ, d1.Route[0].PosZ);
|
||||||
|
Assert.NotEqual(d0.Route[1].PosZ, d1.Route[1].PosZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -110,40 +107,19 @@ namespace CounterDrone.Core.Tests
|
|||||||
var route = CreateRoute(0, 100, 300, 60);
|
var route = CreateRoute(0, 100, 300, 60);
|
||||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 0, 0, 100, FormationMode.Formation);
|
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 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);
|
Assert.NotEqual(d0.PosX, d1.PosX);
|
||||||
// 纵向串列:X 落后递增
|
|
||||||
Assert.Equal(0, d0.FormationOffsetX);
|
|
||||||
Assert.Equal(-100, d1.FormationOffsetX);
|
|
||||||
Assert.Equal(-200, d2.FormationOffsetX);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Formation_Mixed_2x2()
|
public void Formation_Mixed_2x2()
|
||||||
{
|
{
|
||||||
var route = CreateRoute(0, 100, 300, 60);
|
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);
|
||||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route,
|
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 100, FormationMode.Formation);
|
||||||
0, 50, 0, 100, FormationMode.Formation); // lat=0, long=0
|
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 0, 50, 1, 100, FormationMode.Formation);
|
||||||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route,
|
var d3 = new DroneEntity("d3", "g1", CreateConfig(), route, 1, 50, 1, 100, FormationMode.Formation);
|
||||||
1, 50, 0, 100, FormationMode.Formation); // lat=1, long=0
|
Assert.NotEqual(d0.PosZ, d1.PosZ); // lateral
|
||||||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route,
|
Assert.NotEqual(d0.PosX, d2.PosX); // longitudinal
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -152,7 +128,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
var route = CreateRoute(0, 100, 300, 60);
|
var route = CreateRoute(0, 100, 300, 60);
|
||||||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 30, 0, 0, FormationMode.Swarm);
|
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);
|
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
|
_scenario.SaveRoute(_taskId, "default", new RoutePlan
|
||||||
{
|
{
|
||||||
FormationMode = (int)FormationMode.Formation,
|
FormationMode = (int)FormationMode.Formation,
|
||||||
LateralSpacing = 50,
|
LateralSpacing = 200,
|
||||||
LateralCount = 3,
|
LateralCount = 3,
|
||||||
LongitudinalCount = 1,
|
LongitudinalCount = 1,
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user