伴飞系统,技能飞行物垂直入场

This commit is contained in:
2025-10-28 17:31:39 +08:00
parent f55c0df25a
commit 8c4924144a
12 changed files with 864 additions and 196 deletions

Binary file not shown.

View File

@@ -34,8 +34,9 @@
<useHitPoints>false</useHitPoints>
<selectable>false</selectable>
<alwaysHaulable>false</alwaysHaulable>
<altitudeLayer>LightingOverlay</altitudeLayer>
<altitudeLayer>FogOfWar</altitudeLayer>
<comps>
<!-- 入场信封信息 -->
<li Class="ArachnaeSwarm.CompProperties_SendLetterAfterTicks">
<ticksDelay>60</ticksDelay> <!-- 2秒后发送 -->
<letterLabel>虫巢母舰</letterLabel>
@@ -45,6 +46,7 @@
<requireOnMap>true</requireOnMap>
<destroyAfterSending>false</destroyAfterSending> <!-- 发送后销毁flyover -->
</li>
<!-- 空投 -->
<li Class="ArachnaeSwarm.CompProperties_FlyOverDropPods">
<!-- <dropProgress>0.5</dropProgress> -->
<useCyclicDrops>true</useCyclicDrops>
@@ -102,13 +104,13 @@
<joinPlayer>false</joinPlayer>
<makePrisoners>false</makePrisoners>
</li>
<!-- 炮击支援 -->
<li Class="ArachnaeSwarm.CompProperties_ShipArtillery">
<ticksBetweenAttacks>600</ticksBetweenAttacks>
<attackDurationTicks>600</attackDurationTicks>
<warmupTicks>60</warmupTicks>
<attackRadius>60</attackRadius>
<shellsPerVolley>3</shellsPerVolley>
<scatterRadius>60</scatterRadius>
<ignoreProtectionChance>0.1</ignoreProtectionChance>
<skyfallerDef>ARA_HiveShip_Fire_Incoming</skyfallerDef>
@@ -120,13 +122,78 @@
<avoidPlayerAssets>true</avoidPlayerAssets>
<playerAssetAvoidanceRadius>10</playerAssetAvoidanceRadius>
<sendAttackLetter>true</sendAttackLetter>
<sendAttackLetter>false</sendAttackLetter>
<customLetterLabel>战舰炮击警告</customLetterLabel>
<customLetterText>一艘敌方战舰正在对殖民地进行炮击!立即寻找掩护!</customLetterText>
<letterDef>ThreatBig</letterDef>
</li>
<!-- 伴飞 -->
<li Class="ArachnaeSwarm.CompProperties_FlyOverEscort">
<escortFlyOverDef>ARA_HiveCorvette</escortFlyOverDef>
<!-- 生成配置 -->
<spawnIntervalTicks>250</spawnIntervalTicks> <!-- 5秒 -->
<maxEscorts>60</maxEscorts>
<spawnCount>1</spawnCount>
<!-- 位置配置 -->
<spawnDistance>15</spawnDistance>
<lateralOffset>200</lateralOffset>
<verticalOffset>5</verticalOffset>
<useRandomOffset>true</useRandomOffset>
<!-- 飞行配置 -->
<escortSpeedMultiplier>20</escortSpeedMultiplier> <!-- 比主舰稍快 -->
<escortAltitudeOffset>10</escortAltitudeOffset> <!-- 比主舰稍高 -->
<mirrorMovement>false</mirrorMovement>
<!-- 行为配置 -->
<spawnOnStart>true</spawnOnStart>
<destroyWithParent>false</destroyWithParent>
<continuousSpawning>true</continuousSpawning>
<!-- 外观配置 -->
<escortScale>0.6</escortScale>
<useParentRotation>true</useParentRotation>
</li>
</comps>
</ThingDef>
<ThingDef Parent="EtherealThingBase">
<defName>ARA_HiveCorvette</defName>
<label>天巫虫巢舰</label>
<thingClass>ArachnaeSwarm.FlyOver</thingClass>
<tickerType>Normal</tickerType>
<drawerType>RealtimeOnly</drawerType>
<graphicData>
<!-- <texPath>ArachnaeSwarm/Weapon/ARA_Weapon_Empty</texPath> -->
<texPath>ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow</texPath>
<graphicClass>Graphic_Single</graphicClass>
<shaderType>TransparentPostLight</shaderType>
<drawSize>(20,30)</drawSize>
<color>(195,195,195,45)</color>
</graphicData>
<skyfaller>
<shadowSize>(0, 0)</shadowSize>
<motesPerCell>0</motesPerCell>
<floatingSound>FlyOver/Flying</floatingSound>
<impactSound>FlyOver/Landing</impactSound>
</skyfaller>
<modExtensions>
<li Class="ArachnaeSwarm.FlyOverShadowExtension">
<customShadowPath>ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow</customShadowPath>
<useCustomShadow>false</useCustomShadow>
<!-- <shadowIntensity>0.8</shadowIntensity>
<minShadowAlpha>0</minShadowAlpha>
<maxShadowAlpha>0</maxShadowAlpha>
<minShadowScale>15</minShadowScale>
<maxShadowScale>15</maxShadowScale> -->
</li>
</modExtensions>
<useHitPoints>false</useHitPoints>
<selectable>false</selectable>
<alwaysHaulable>false</alwaysHaulable>
<altitudeLayer>FogOfWar</altitudeLayer>
</ThingDef>
<ThingDef ParentName="SkyfallerBase">
<defName>ARA_HiveShip_Fire_Incoming</defName>
@@ -140,6 +207,12 @@
<explosionDamage>ARA_AcidBurn</explosionDamage>
<explosionDamageFactor>0.5</explosionDamageFactor>
<cameraShake>1</cameraShake>
<angleCurve>
<points>
<li>(0,0)</li>
<li>(1,-1)</li>
</points>
</angleCurve>
</skyfaller>
</ThingDef>
</Defs>

View File

@@ -1,7 +1,28 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [],
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\flyover\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\flyover\\ara_flyoverescort\\compflyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_flyoverescort\\compflyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\flyover\\ara_flyoverescort\\compproperties_flyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_flyoverescort\\compproperties_flyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\flyover\\ara_shipartillery\\compshipartillery.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_shipartillery\\compshipartillery.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\flyover\\ara_shipartillery\\compproperties_shipartillery.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_shipartillery\\compproperties_shipartillery.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
@@ -9,11 +30,76 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": -1,
"SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "ThingclassFlyOver.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs",
"RelativeDocumentMoniker": "Flyover\\ThingclassFlyOver.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs",
"RelativeToolTip": "Flyover\\ThingclassFlyOver.cs",
"ViewState": "AgIAAAMCAAAAAAAAAAAAABcCAAAeAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-28T09:09:22.03Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "CompFlyOverEscort.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
"RelativeToolTip": "Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAMAAAAMAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-28T07:30:55.268Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "CompProperties_FlyOverEscort.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"RelativeToolTip": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
"ViewState": "AgIAAAMAAAAAAAAAAAAAABUAAAAjAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-28T07:30:47.27Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "CompProperties_ShipArtillery.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
"RelativeToolTip": "Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAA8AAAAhAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-28T06:21:06.271Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "CompShipArtillery.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
"RelativeDocumentMoniker": "Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
"RelativeToolTip": "Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
"ViewState": "AgIAALEAAAAAAAAAAAAywNEAAABcAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-28T06:21:04.222Z",
"EditorCaption": ""
}
]
}

View File

@@ -138,6 +138,8 @@
<Compile Include="EventSystem\EventVariableManager.cs" />
<Compile Include="EventSystem\Letter_EventChoice.cs" />
<Compile Include="EventSystem\QuestNode_Root_EventLetter.cs" />
<Compile Include="Flyover\ARA_FlyOverEscort\CompFlyOverEscort.cs" />
<Compile Include="Flyover\ARA_FlyOverEscort\CompProperties_FlyOverEscort.cs" />
<Compile Include="Flyover\ARA_SendLetterAfterTicks\CompProperties_SendLetterAfterTicks.cs" />
<Compile Include="Flyover\ARA_SendLetterAfterTicks\CompSendLetterAfterTicks.cs" />
<Compile Include="Flyover\ARA_ShipArtillery\CompProperties_ShipArtillery.cs" />

View File

@@ -0,0 +1,338 @@
using RimWorld;
using System.Collections.Generic;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class CompFlyOverEscort : ThingComp
{
public CompProperties_FlyOverEscort Props => (CompProperties_FlyOverEscort)props;
// 状态变量
private float ticksUntilNextSpawn = 0f;
private List<FlyOver> activeEscorts = new List<FlyOver>();
private bool hasInitialized = false;
public override void Initialize(CompProperties props)
{
base.Initialize(props);
if (Props.spawnOnStart)
{
ticksUntilNextSpawn = 0f;
}
else
{
ticksUntilNextSpawn = Props.spawnIntervalTicks;
}
Log.Message($"FlyOver Escort initialized: {Props.spawnIntervalTicks} ticks interval, max {Props.maxEscorts} escorts");
}
public override void CompTick()
{
base.CompTick();
if (parent is not FlyOver mainFlyOver || !mainFlyOver.Spawned || mainFlyOver.Map == null)
return;
// 初始化检查
if (!hasInitialized && mainFlyOver.hasStarted)
{
hasInitialized = true;
Log.Message($"FlyOver Escort: Main FlyOver started at {mainFlyOver.startPosition}");
}
// 清理已销毁的伴飞
activeEscorts.RemoveAll(escort => escort == null || escort.Destroyed || !escort.Spawned);
// 检查是否应该生成新伴飞
if (ShouldSpawnEscort(mainFlyOver))
{
ticksUntilNextSpawn -= 1f;
if (ticksUntilNextSpawn <= 0f)
{
SpawnEscorts(mainFlyOver);
ticksUntilNextSpawn = Props.spawnIntervalTicks;
}
}
// 更新现有伴飞的位置(如果需要)
UpdateEscortPositions(mainFlyOver);
}
private bool ShouldSpawnEscort(FlyOver mainFlyOver)
{
if (!mainFlyOver.hasStarted || mainFlyOver.hasCompleted)
return false;
if (!Props.continuousSpawning && activeEscorts.Count >= Props.spawnCount)
return false;
if (activeEscorts.Count >= Props.maxEscorts)
return false;
return true;
}
private void SpawnEscorts(FlyOver mainFlyOver)
{
int escortsToSpawn = Mathf.Min(Props.spawnCount, Props.maxEscorts - activeEscorts.Count);
for (int i = 0; i < escortsToSpawn; i++)
{
FlyOver escort = CreateEscort(mainFlyOver);
if (escort != null)
{
activeEscorts.Add(escort);
Log.Message($"Spawned escort #{i+1} for FlyOver at {mainFlyOver.DrawPos}");
}
}
}
private FlyOver CreateEscort(FlyOver mainFlyOver)
{
try
{
// 选择伴飞定义
ThingDef escortDef = SelectEscortDef();
if (escortDef == null)
{
Log.Error("FlyOver Escort: No valid escort def found");
return null;
}
// 计算伴飞的起点和终点
IntVec3 escortStart = CalculateEscortStart(mainFlyOver);
IntVec3 escortEnd = CalculateEscortEnd(mainFlyOver, escortStart);
if (!escortStart.InBounds(mainFlyOver.Map) || !escortEnd.InBounds(mainFlyOver.Map))
{
Log.Warning("FlyOver Escort: Escort start or end position out of bounds");
return null;
}
// 计算伴飞参数
float escortSpeed = mainFlyOver.flightSpeed * Props.escortSpeedMultiplier;
float escortAltitude = mainFlyOver.altitude + Props.escortAltitudeOffset;
// 创建伴飞
FlyOver escort = FlyOver.MakeFlyOver(
escortDef,
escortStart,
escortEnd,
mainFlyOver.Map,
escortSpeed,
escortAltitude,
null, // 没有内容物
mainFlyOver.fadeInDuration
);
// 设置伴飞属性
SetupEscortProperties(escort, mainFlyOver);
Log.Message($"Created escort: {escortStart} -> {escortEnd}, speed: {escortSpeed}, altitude: {escortAltitude}");
return escort;
}
catch (System.Exception ex)
{
Log.Error($"Error creating FlyOver escort: {ex}");
return null;
}
}
private ThingDef SelectEscortDef()
{
if (Props.escortFlyOverDefs != null && Props.escortFlyOverDefs.Count > 0)
{
return Props.escortFlyOverDefs.RandomElement();
}
return Props.escortFlyOverDef;
}
private IntVec3 CalculateEscortStart(FlyOver mainFlyOver)
{
Vector3 mainDirection = mainFlyOver.MovementDirection;
Vector3 mainPosition = mainFlyOver.DrawPos;
// 计算横向偏移方向(垂直于飞行方向)
Vector3 lateralDirection = GetLateralOffsetDirection(mainDirection);
// 计算偏移量
float lateralOffset = Props.useRandomOffset ?
Rand.Range(-Props.lateralOffset, Props.lateralOffset) :
Props.lateralOffset;
float spawnDistance = Props.useRandomOffset ?
Rand.Range(Props.spawnDistance * 0.5f, Props.spawnDistance * 1.5f) :
Props.spawnDistance;
// 计算起点位置从主FlyOver后方偏移
Vector3 offset = (-mainDirection * spawnDistance) + (lateralDirection * lateralOffset);
Vector3 escortStartPos = mainPosition + offset;
// 确保位置在地图边界内
IntVec3 escortStart = escortStartPos.ToIntVec3();
if (!escortStart.InBounds(mainFlyOver.Map))
{
// 如果超出边界,调整到边界内
escortStart = ClampToMap(escortStart, mainFlyOver.Map);
}
return escortStart;
}
private IntVec3 CalculateEscortEnd(FlyOver mainFlyOver, IntVec3 escortStart)
{
Vector3 mainDirection = mainFlyOver.MovementDirection;
Vector3 mainEndPos = mainFlyOver.endPosition.ToVector3();
// 如果镜像移动,使用相反方向
if (Props.mirrorMovement)
{
mainDirection = -mainDirection;
}
// 计算从起点沿飞行方向延伸的终点
float flightDistance = mainFlyOver.startPosition.DistanceTo(mainFlyOver.endPosition);
Vector3 escortEndPos = escortStart.ToVector3() + (mainDirection * flightDistance);
// 确保终点在地图边界内
IntVec3 escortEnd = escortEndPos.ToIntVec3();
if (!escortEnd.InBounds(mainFlyOver.Map))
{
escortEnd = ClampToMap(escortEnd, mainFlyOver.Map);
}
return escortEnd;
}
private Vector3 GetLateralOffsetDirection(Vector3 mainDirection)
{
// 获取垂直于飞行方向的向量(随机选择左侧或右侧)
Vector3 lateral = new Vector3(-mainDirection.z, 0f, mainDirection.x);
// 随机选择方向
if (Rand.Value > 0.5f)
{
lateral = -lateral;
}
return lateral.normalized;
}
private IntVec3 ClampToMap(IntVec3 position, Map map)
{
CellRect mapRect = CellRect.WholeMap(map);
return new IntVec3(
Mathf.Clamp(position.x, mapRect.minX, mapRect.maxX),
0,
Mathf.Clamp(position.z, mapRect.minZ, mapRect.maxZ)
);
}
private void SetupEscortProperties(FlyOver escort, FlyOver mainFlyOver)
{
// 设置缩放
if (Props.escortScale != 1f)
{
// 这里可能需要通过ModExtension或其他方式设置缩放
}
// 禁用阴影(如果需要)
if (!mainFlyOver.createShadow)
{
escort.createShadow = false;
}
// 禁用音效(如果需要)
if (!mainFlyOver.playFlyOverSound)
{
escort.playFlyOverSound = false;
}
}
private void UpdateEscortPositions(FlyOver mainFlyOver)
{
// 如果需要实时更新伴飞位置,可以在这里实现
// 目前伴飞会按照自己的路径飞行
}
public override void PostDestroy(DestroyMode mode, Map previousMap)
{
base.PostDestroy(mode, previousMap);
// 销毁所有伴飞
if (Props.destroyWithParent)
{
foreach (FlyOver escort in activeEscorts)
{
if (escort != null && escort.Spawned)
{
escort.Destroy();
}
}
activeEscorts.Clear();
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref ticksUntilNextSpawn, "ticksUntilNextSpawn", 0f);
Scribe_Collections.Look(ref activeEscorts, "activeEscorts", LookMode.Reference);
Scribe_Values.Look(ref hasInitialized, "hasInitialized", false);
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
if (DebugSettings.ShowDevGizmos && parent is FlyOver)
{
yield return new Command_Action
{
defaultLabel = "Dev: Spawn Escort",
action = () => SpawnEscorts(parent as FlyOver)
};
yield return new Command_Action
{
defaultLabel = $"Dev: Escort Status - Active: {activeEscorts.Count}/{Props.maxEscorts}",
action = () => {}
};
yield return new Command_Action
{
defaultLabel = "Dev: Clear All Escorts",
action = () =>
{
foreach (var escort in activeEscorts)
{
if (escort != null && escort.Spawned)
escort.Destroy();
}
activeEscorts.Clear();
}
};
}
}
// 公共方法:强制生成伴飞
public void SpawnEscortNow()
{
if (parent is FlyOver flyOver)
{
SpawnEscorts(flyOver);
}
}
// 公共方法:获取活跃伴飞数量
public int GetActiveEscortCount()
{
return activeEscorts.Count;
}
}
}

View File

@@ -0,0 +1,43 @@
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_FlyOverEscort : CompProperties
{
// 伴飞配置
public ThingDef escortFlyOverDef; // 伴飞FlyOver定义
public List<ThingDef> escortFlyOverDefs; // 多个伴飞定义(随机选择)
// 生成配置
public float spawnIntervalTicks = 600f; // 生成间隔tick
public int maxEscorts = 3; // 最大伴飞数量
public int spawnCount = 1; // 每次生成的伴飞数量
// 位置配置
public float spawnDistance = 10f; // 生成距离从主FlyOver
public float lateralOffset = 5f; // 横向偏移量
public float verticalOffset = 2f; // 垂直偏移量(高度差)
public bool useRandomOffset = true; // 是否使用随机偏移
// 飞行配置
public float escortSpeedMultiplier = 1f; // 速度乘数相对于主FlyOver
public float escortAltitudeOffset = 0f; // 高度偏移
public bool mirrorMovement = false; // 是否镜像移动(相反方向)
// 行为配置
public bool spawnOnStart = true; // 开始时立即生成
public bool continuousSpawning = true; // 是否持续生成
public bool destroyWithParent = true; // 是否随父级销毁
// 外观配置
public float escortScale = 1f; // 缩放比例
public bool useParentRotation = true; // 使用父级旋转
public CompProperties_FlyOverEscort()
{
compClass = typeof(CompFlyOverEscort);
}
}
}

View File

@@ -26,7 +26,6 @@ namespace ArachnaeSwarm
public ThingDef skyfallerDef; // 使用的 Skyfaller 定义
public List<ThingDef> skyfallerDefs; // 多个 Skyfaller 定义(随机选择)
public int shellsPerVolley = 1; // 每轮齐射的炮弹数量
public float scatterRadius = 3f; // 散布半径
public bool useDifferentShells = false; // 是否使用不同类型的炮弹
// 音效配置

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace ArachnaeSwarm
{
@@ -29,7 +30,7 @@ namespace ArachnaeSwarm
ticksUntilNextAttack = Props.ticksBetweenAttacks;
Log.Message($"Ship Artillery initialized: {Props.ticksBetweenAttacks} ticks between attacks, {Props.attackRadius} radius, Scatter: {Props.scatterRadius}");
Log.Message($"Ship Artillery initialized: {Props.ticksBetweenAttacks} ticks between attacks, {Props.attackRadius} radius");
}
public override void CompTick()
@@ -132,7 +133,7 @@ namespace ArachnaeSwarm
attackEffecter = Props.attackEffect.Spawn();
}
Log.Message($"Ship Artillery started firing at area {currentTarget} (scatter radius: {Props.scatterRadius})");
Log.Message($"Ship Artillery started firing at area {currentTarget}");
// 发送攻击通知
if (Props.sendAttackLetter)
@@ -194,17 +195,20 @@ namespace ArachnaeSwarm
return;
}
// 计算散布位置 - 使用新的散射逻辑
IntVec3 shellTarget = GetScatteredTarget(flyOver);
// 直接选择随机目标
IntVec3 shellTarget = SelectRandomTarget(flyOver);
// 创建 Skyfaller
Skyfaller shell = (Skyfaller)ThingMaker.MakeThing(shellDef);
// 生成炮弹
GenSpawn.Spawn(shell, GetLaunchPosition(flyOver), flyOver.Map);
// 关键修复:使用 SkyfallerMaker 创建并立即生成 Skyfaller
SkyfallerMaker.SpawnSkyfaller(shellDef, shellTarget, flyOver.Map);
float distanceFromCenter = shellTarget.DistanceTo(currentTarget);
Log.Message($"Ship Artillery fired shell at {shellTarget} (distance from center: {distanceFromCenter:F1}, scatter radius: {Props.scatterRadius})");
Log.Message($"Ship Artillery fired shell at {shellTarget} (distance from center: {distanceFromCenter:F1})");
// 播放音效
if (Props.attackSound != null)
{
Props.attackSound.PlayOneShot(new TargetInfo(shellTarget, flyOver.Map));
}
}
catch (System.Exception ex)
{
@@ -243,45 +247,11 @@ namespace ArachnaeSwarm
return launchPos;
}
// 新的散射逻辑 - 基于 FlyOverDropPods 的实现方式
private IntVec3 GetScatteredTarget(FlyOver flyOver)
// 简化的目标选择 - 每次直接随机选择目标
private IntVec3 SelectRandomTarget(FlyOver flyOver)
{
// 基础目标(攻击区域中心)
IntVec3 baseTarget = currentTarget;
// 如果散射半径为0直接返回基础目标
if (Props.scatterRadius <= 0)
return baseTarget;
// 使用类似 FlyOverDropPods 的散射逻辑
for (int i = 0; i < 20; i++) // 尝试20次找到有效位置
{
// 在散射半径内随机选择位置
IntVec3 scatteredPos = baseTarget;
// 使用圆形散射而不是方形
float angle = Rand.Range(0f, 360f);
float distance = Rand.Range(0f, Props.scatterRadius);
scatteredPos.x += Mathf.RoundToInt(Mathf.Cos(angle * Mathf.Deg2Rad) * distance);
scatteredPos.z += Mathf.RoundToInt(Mathf.Sin(angle * Mathf.Deg2Rad) * distance);
// 检查是否无视保护机制
bool ignoreProtectionThisShot = Rand.Value < Props.ignoreProtectionChance;
if (scatteredPos.InBounds(flyOver.Map) && IsValidTarget(scatteredPos, flyOver.Map, ignoreProtectionThisShot))
{
if (ignoreProtectionThisShot)
{
Log.Warning($"Ship Artillery: Protection ignored! Shell may hit player assets at {scatteredPos}");
}
return scatteredPos;
}
}
// 如果找不到有效位置,使用基础目标
Log.Warning($"Ship Artillery: Could not find valid scattered target after 20 attempts, using base target");
return baseTarget;
IntVec3 center = GetFlyOverPosition(flyOver) + Props.targetOffset;
return FindRandomTargetInRadius(center, flyOver.Map, Props.attackRadius);
}
private IntVec3 SelectTarget(FlyOver flyOver)
@@ -316,7 +286,7 @@ namespace ArachnaeSwarm
return result;
}
// 新的目标查找逻辑 - 基于攻击半径
// 目标查找逻辑 - 基于攻击半径
private IntVec3 FindRandomTargetInRadius(IntVec3 center, Map map, float radius)
{
Log.Message($"Finding target around {center} with radius {radius}");

View File

@@ -22,8 +22,18 @@ namespace ArachnaeSwarm
Log.Message($"Target cell: {target.Cell}, Dest: {dest.Cell}");
// 计算起始和结束位置
IntVec3 startPos = CalculateStartPosition(target);
IntVec3 endPos = CalculateEndPosition(target, startPos);
IntVec3 startPos, endPos;
// 根据进场类型选择不同的计算方法
if (Props.approachType == ApproachType.Perpendicular)
{
CalculatePerpendicularPath(target, out startPos, out endPos);
}
else
{
startPos = CalculateStartPosition(target);
endPos = CalculateEndPosition(target, startPos);
}
Log.Message($"Final positions - Start: {startPos}, End: {endPos}");
@@ -40,6 +50,18 @@ namespace ArachnaeSwarm
endPos = parent.pawn.Map.Center;
}
// 确保起点和终点不同
if (startPos == endPos)
{
Log.Warning($"FlyOver start and end positions are the same: {startPos}. Adjusting end position.");
IntVec3 randomOffset = new IntVec3(Rand.Range(-10, 11), 0, Rand.Range(-10, 11));
endPos += randomOffset;
if (!endPos.InBounds(parent.pawn.Map))
{
endPos = parent.pawn.Map.Center;
}
}
// 根据类型创建不同的飞越物体
switch (Props.flyOverType)
{
@@ -58,6 +80,78 @@ namespace ArachnaeSwarm
}
}
// 新增:计算垂直线进场路径
private void CalculatePerpendicularPath(LocalTargetInfo target, out IntVec3 startPos, out IntVec3 endPos)
{
Map map = parent.pawn.Map;
IntVec3 casterPos = parent.pawn.Position;
IntVec3 targetPos = target.Cell;
Log.Message($"Calculating perpendicular path: Caster={casterPos}, Target={targetPos}");
// 计算施法者到目标的方向向量
Vector3 directionToTarget = (targetPos.ToVector3() - casterPos.ToVector3()).normalized;
// 如果方向为零向量,使用随机方向
if (directionToTarget == Vector3.zero)
{
directionToTarget = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
Log.Message($"Using random direction: {directionToTarget}");
}
// 计算垂直于施法者-目标连线的方向旋转90度
Vector3 perpendicularDirection = new Vector3(-directionToTarget.z, 0, directionToTarget.x).normalized;
Log.Message($"Perpendicular direction: {perpendicularDirection}");
// 从目标点出发,向垂直方向的两侧延伸找到地图边缘
IntVec3 edge1 = FindMapEdgeInDirection(map, targetPos, perpendicularDirection);
IntVec3 edge2 = FindMapEdgeInDirection(map, targetPos, -perpendicularDirection);
// 随机选择起点和终点(确保目标点在路径上)
if (Rand.Value < 0.5f)
{
startPos = edge1;
endPos = edge2;
}
else
{
startPos = edge2;
endPos = edge1;
}
Log.Message($"Perpendicular path: {startPos} -> {targetPos} -> {endPos}");
}
// 新增:在指定方向上找到地图边缘
private IntVec3 FindMapEdgeInDirection(Map map, IntVec3 fromPos, Vector3 direction)
{
// 计算最大搜索距离(地图对角线的一半)
float maxDistance = Mathf.Sqrt(map.Size.x * map.Size.x + map.Size.z * map.Size.z) * 0.6f;
// 沿着方向逐步搜索,直到找到地图边界
for (float distance = 10f; distance <= maxDistance; distance += 5f)
{
IntVec3 testPos = fromPos + new IntVec3(
Mathf.RoundToInt(direction.x * distance),
0,
Mathf.RoundToInt(direction.z * distance));
if (!testPos.InBounds(map))
{
// 找到边界,返回最近的有效位置
IntVec3 edgePos = FindClosestValidPosition(testPos, map);
Log.Message($"Found map edge at {edgePos} (direction: {direction}, distance: {distance})");
return edgePos;
}
}
// 如果没找到边界,使用随机边缘位置
Log.Warning($"Could not find map edge in direction {direction}, using random edge");
return GetRandomMapEdgePosition(map);
}
// 原有的位置计算方法
private IntVec3 CalculateStartPosition(LocalTargetInfo target)
{
Map map = parent.pawn.Map;
@@ -114,60 +208,33 @@ namespace ArachnaeSwarm
break;
}
// 关键修复:确保起点和终点不同
if (startPos == endPos)
{
Log.Warning($"FlyOver start and end positions are the same: {startPos}. Adjusting end position.");
// 如果相同,将终点向随机方向移动
IntVec3 randomOffset = new IntVec3(Rand.Range(-10, 11), 0, Rand.Range(-10, 11));
endPos += randomOffset;
// 确保新位置在地图内
if (!endPos.InBounds(map))
{
endPos = map.Center;
}
}
Log.Message($"Calculated positions - Start: {startPos}, End: {endPos}, Distance: {startPos.DistanceTo(endPos)}");
return endPos;
}
// 修复的 OppositeMapEdge 逻辑:确保穿过地图中心
// 原有的辅助方法保持不变
private IntVec3 GetOppositeMapEdgeThroughCenter(Map map, IntVec3 startPos)
{
IntVec3 center = map.Center;
// 计算从起点到中心的方向
Vector3 toCenter = (center.ToVector3() - startPos.ToVector3()).normalized;
// 如果方向为零向量,使用随机方向
if (toCenter == Vector3.zero)
{
toCenter = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
Log.Message($"Using random direction to center: {toCenter}");
}
// 计算从中心到对面边缘的方向(与起点到中心的方向相同)
Vector3 fromCenter = toCenter;
Log.Message($"Calculating opposite edge: Start={startPos}, Center={center}, Direction={fromCenter}");
// 从中心出发,沿着相同方向找到对面的地图边缘
IntVec3 oppositeEdge = GetMapEdgePositionFromCenter(map, fromCenter);
Log.Message($"Found opposite edge through center: {oppositeEdge}");
return oppositeEdge;
}
// 从地图中心出发找到指定方向的地图边缘
private IntVec3 GetMapEdgePositionFromCenter(Map map, Vector3 direction)
{
IntVec3 center = map.Center;
float maxDist = Mathf.Max(map.Size.x, map.Size.z) * 0.6f;
// 从中心向指定方向延伸
for (int i = 1; i <= maxDist; i++)
{
IntVec3 testPos = center + new IntVec3(
@@ -177,14 +244,12 @@ namespace ArachnaeSwarm
if (!testPos.InBounds(map))
{
// 找到边界位置
IntVec3 edgePos = FindClosestValidPosition(testPos, map);
Log.Message($"Found map edge from center: {edgePos} (direction: {direction}, distance: {i})");
return edgePos;
}
}
// 如果没找到边界,使用随机边缘位置
Log.Warning("Could not find map edge from center, using random edge");
return GetRandomMapEdgePosition(map);
}
@@ -200,7 +265,6 @@ namespace ArachnaeSwarm
IntVec3 center = map.Center;
float maxDist = Mathf.Max(map.Size.x, map.Size.z) * 0.6f;
// 从中心向指定方向延伸
for (int i = 1; i <= maxDist; i++)
{
IntVec3 testPos = center + new IntVec3(
@@ -210,21 +274,18 @@ namespace ArachnaeSwarm
if (!testPos.InBounds(map))
{
// 找到边界位置
IntVec3 edgePos = FindClosestValidPosition(testPos, map);
Log.Message($"Found map edge position: {edgePos} (direction: {direction}, distance: {i})");
return edgePos;
}
}
// 如果没找到边界,使用随机边缘位置
Log.Warning("Could not find map edge in direction, using random edge");
return GetRandomMapEdgePosition(map);
}
private IntVec3 FindClosestValidPosition(IntVec3 invalidPos, Map map)
{
// 在无效位置周围寻找有效的边界位置
for (int radius = 1; radius <= 5; radius++)
{
foreach (IntVec3 pos in GenRadial.RadialPatternInRadius(radius))
@@ -240,26 +301,8 @@ namespace ArachnaeSwarm
return map.Center;
}
// 旧的 OppositeMapEdge 逻辑(保留作为参考)
private IntVec3 GetOppositeMapEdgePosition(Map map, IntVec3 startPos)
{
// 计算从起点指向地图中心的方向,然后取反
Vector3 toCenter = (map.Center.ToVector3() - startPos.ToVector3()).normalized;
Vector3 oppositeDirection = -toCenter;
// 如果方向为零向量,使用随机方向
if (oppositeDirection == Vector3.zero)
{
oppositeDirection = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
}
Log.Message($"Opposite direction from {startPos}: {oppositeDirection}");
return GetMapEdgePosition(map, oppositeDirection);
}
private IntVec3 GetRandomMapEdgePosition(Map map)
{
// 随机选择一个地图边缘位置
int edge = Rand.Range(0, 4);
int x, z;
@@ -305,7 +348,6 @@ namespace ArachnaeSwarm
{
Vector3 direction = (target.Cell.ToVector3() - parent.pawn.Position.ToVector3()).normalized;
// 如果方向为零向量,使用随机方向
if (direction == Vector3.zero)
{
direction = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
@@ -317,7 +359,6 @@ namespace ArachnaeSwarm
private void CreateStandardFlyOver(IntVec3 startPos, IntVec3 endPos)
{
// 确保有默认的飞越定义
ThingDef flyOverDef = Props.flyOverDef ?? DefDatabase<ThingDef>.GetNamedSilentFail("ARA_HiveShip");
if (flyOverDef == null)
{
@@ -334,14 +375,12 @@ namespace ArachnaeSwarm
Props.altitude
);
// 设置属性
flyOver.spawnContentsOnImpact = Props.dropContentsOnImpact;
flyOver.playFlyOverSound = Props.playFlyOverSound;
// 应用自定义音效
if (Props.customSound != null)
{
// 这里需要更复杂的逻辑来替换音效
// 自定义音效逻辑
}
Log.Message($"Standard FlyOver created: {flyOver} from {startPos} to {endPos}");

View File

@@ -8,6 +8,7 @@ namespace ArachnaeSwarm
{
public ThingDef flyOverDef; // 飞越物体的 ThingDef
public FlyOverType flyOverType = FlyOverType.Standard; // 飞越类型
public ApproachType approachType = ApproachType.Standard; // 进场类型
public float flightSpeed = 1f; // 飞行速度
public float altitude = 15f; // 飞行高度
public bool spawnContents = false; // 是否生成内容物
@@ -16,11 +17,11 @@ namespace ArachnaeSwarm
public SoundDef customSound; // 自定义音效
public bool playFlyOverSound = true; // 是否播放飞越音效
// 起始位置选项
// 起始位置选项当approachType为Standard时使用
public StartPosition startPosition = StartPosition.Caster;
public IntVec3 customStartOffset = IntVec3.Zero;
// 终点位置选项
// 终点位置选项当approachType为Standard时使用
public EndPosition endPosition = EndPosition.TargetCell;
public IntVec3 customEndOffset = IntVec3.Zero;
public int flyOverDistance = 30; // 飞越距离(当终点为自定义时)
@@ -41,6 +42,13 @@ namespace ArachnaeSwarm
Reconnaissance // 侦察飞越
}
// 新增:进场类型枚举
public enum ApproachType
{
Standard, // 标准进场(使用原有的位置计算)
Perpendicular // 垂直线进场(垂直于施法者-目标连线)
}
// 起始位置枚举
public enum StartPosition
{
@@ -56,7 +64,7 @@ namespace ArachnaeSwarm
TargetCell, // 目标单元格
OppositeMapEdge, // 对面地图边缘
CustomOffset, // 自定义偏移
FixedDistance, // 固定距离
FixedDistance, // 固定距离
RandomMapEdge
}
}

View File

@@ -22,6 +22,14 @@ namespace ArachnaeSwarm
public float currentFadeInTime = 0f; // 当前淡入时间
public bool fadeInCompleted = false; // 淡入是否完成
// 淡出效果相关
public float fadeOutDuration = 0f; // 动态计算的淡出持续时间
public float currentFadeOutTime = 0f; // 当前淡出时间
public bool fadeOutStarted = false; // 淡出是否开始
public bool fadeOutCompleted = false; // 淡出是否完成
public float fadeOutStartProgress = 0.7f; // 开始淡出的进度阈值0-1
public float defaultFadeOutDuration = 1.5f; // 默认淡出持续时间(仅用于销毁)
// 状态标志
public bool hasStarted = false;
public bool hasCompleted = false;
@@ -110,6 +118,55 @@ namespace ArachnaeSwarm
}
}
// 淡出透明度0-1
public float FadeOutAlpha
{
get
{
if (!fadeOutStarted) return 1f;
if (fadeOutCompleted) return 0f;
return Mathf.Clamp01(1f - (currentFadeOutTime / fadeOutDuration));
}
}
// 总体透明度(淡入 * 淡出)
public float OverallAlpha
{
get
{
return FadeInAlpha * FadeOutAlpha;
}
}
// 新增:计算剩余飞行时间(秒)
public float RemainingFlightTime
{
get
{
float remainingProgress = 1f - currentProgress;
return remainingProgress / (flightSpeed * 0.001f) * (1f / 60f);
}
}
// 新增:计算基于剩余距离的淡出持续时间
private float CalculateDynamicFadeOutDuration()
{
// 获取 ModExtension 配置
var shadowExtension = def.GetModExtension<FlyOverShadowExtension>();
float minFadeOutDuration = shadowExtension?.minFadeOutDuration ?? 0.5f;
float maxFadeOutDuration = shadowExtension?.maxFadeOutDuration ?? 3f;
float fadeOutDistanceFactor = shadowExtension?.fadeOutDistanceFactor ?? 0.3f;
// 计算剩余飞行时间
float remainingTime = RemainingFlightTime;
// 使用剩余时间的一部分作为淡出持续时间
float dynamicDuration = remainingTime * fadeOutDistanceFactor;
// 限制在最小和最大范围内
return Mathf.Clamp(dynamicDuration, minFadeOutDuration, maxFadeOutDuration);
}
public FlyOver()
{
innerContainer = new ThingOwner<Thing>(this);
@@ -130,6 +187,14 @@ namespace ArachnaeSwarm
Scribe_Values.Look(ref fadeInDuration, "fadeInDuration", 1.5f);
Scribe_Values.Look(ref currentFadeInTime, "currentFadeInTime", 0f);
Scribe_Values.Look(ref fadeInCompleted, "fadeInCompleted", false);
// 淡出效果数据保存
Scribe_Values.Look(ref fadeOutDuration, "fadeOutDuration", 0f);
Scribe_Values.Look(ref currentFadeOutTime, "currentFadeOutTime", 0f);
Scribe_Values.Look(ref fadeOutStarted, "fadeOutStarted", false);
Scribe_Values.Look(ref fadeOutCompleted, "fadeOutCompleted", false);
Scribe_Values.Look(ref fadeOutStartProgress, "fadeOutStartProgress", 0.7f);
Scribe_Values.Look(ref defaultFadeOutDuration, "defaultFadeOutDuration", 1.5f);
}
public override void SpawnSetup(Map map, bool respawningAfterLoad)
@@ -148,6 +213,12 @@ namespace ArachnaeSwarm
// 重置淡入状态
currentFadeInTime = 0f;
fadeInCompleted = false;
// 重置淡出状态
currentFadeOutTime = 0f;
fadeOutStarted = false;
fadeOutCompleted = false;
fadeOutDuration = 0f;
// 开始飞行音效
if (playFlyOverSound && def.skyfaller?.floatingSound != null)
@@ -169,7 +240,7 @@ namespace ArachnaeSwarm
// 更新淡入效果
if (!fadeInCompleted)
{
currentFadeInTime += 1f / 60f; // 假设 60 FPS
currentFadeInTime += 1f / 60f;
if (currentFadeInTime >= fadeInDuration)
{
fadeInCompleted = true;
@@ -180,11 +251,29 @@ namespace ArachnaeSwarm
// 更新飞行进度
currentProgress += flightSpeed * 0.001f;
// 更新当前位置(用于碰撞检测等
// 检查是否应该开始淡出(基于剩余距离动态计算
if (!fadeOutStarted && currentProgress >= fadeOutStartProgress)
{
StartFadeOut();
}
// 更新淡出效果
if (fadeOutStarted && !fadeOutCompleted)
{
currentFadeOutTime += 1f / 60f;
if (currentFadeOutTime >= fadeOutDuration)
{
fadeOutCompleted = true;
currentFadeOutTime = fadeOutDuration;
Log.Message("FlyOver fade out completed");
}
}
// 更新当前位置
UpdatePosition();
// 维持飞行音效
flightSoundPlaying?.Maintain();
// 维持飞行音效(在淡出时逐渐降低音量)
UpdateFlightSound();
// 检查是否到达终点
if (currentProgress >= 1f)
@@ -192,16 +281,23 @@ namespace ArachnaeSwarm
CompleteFlyOver();
}
// 可选:生成飞行轨迹特效
if (Rand.MTBEventOccurs(0.5f, 1f, 1f) && def.skyfaller?.motesPerCell > 0)
{
CreateFlightEffects();
}
// 生成飞行轨迹特效(在淡出时减少特效)
CreateFlightEffects();
}
// 新增:开始淡出效果
private void StartFadeOut()
{
fadeOutStarted = true;
// 基于剩余距离动态计算淡出持续时间
fadeOutDuration = CalculateDynamicFadeOutDuration();
Log.Message($"FlyOver started fade out at progress {currentProgress:F2}, duration: {fadeOutDuration:F2}s, remaining time: {RemainingFlightTime:F2}s");
}
private void UpdatePosition()
{
// 更新物体的网格位置(用于碰撞检测等)
Vector3 currentWorldPos = Vector3.Lerp(startPosition.ToVector3(), endPosition.ToVector3(), currentProgress);
IntVec3 newPos = currentWorldPos.ToIntVec3();
@@ -211,6 +307,19 @@ namespace ArachnaeSwarm
}
}
private void UpdateFlightSound()
{
if (flightSoundPlaying != null)
{
if (fadeOutStarted)
{
// 淡出时逐渐降低音效音量
flightSoundPlaying.externalParams["VolumeFactor"] = FadeOutAlpha;
}
flightSoundPlaying?.Maintain();
}
}
private void CompleteFlyOver()
{
hasCompleted = true;
@@ -235,6 +344,21 @@ namespace ArachnaeSwarm
Destroy();
}
// 新增:紧急销毁方法(使用默认淡出时间)
public void EmergencyDestroy()
{
if (!fadeOutStarted)
{
// 如果还没有开始淡出,使用默认淡出时间
fadeOutStarted = true;
fadeOutDuration = defaultFadeOutDuration;
Log.Message($"FlyOver emergency destroy with default fade out: {defaultFadeOutDuration}s");
}
// 设置标记,下一帧会处理淡出
hasCompleted = true;
}
private void SpawnContents()
{
foreach (Thing thing in innerContainer)
@@ -250,33 +374,36 @@ namespace ArachnaeSwarm
private void CreateFlightEffects()
{
// 在飞行轨迹上生成粒子效果
Vector3 effectPos = DrawPos;
effectPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
FleckMaker.ThrowSmoke(effectPos, base.Map, 1f);
// 可选:根据速度生成更多效果
if (flightSpeed > 2f)
if (Rand.MTBEventOccurs(0.5f, 1f, 1f) && def.skyfaller?.motesPerCell > 0 && !fadeOutCompleted)
{
FleckMaker.ThrowAirPuffUp(effectPos, base.Map);
Vector3 effectPos = DrawPos;
effectPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
// 淡出时减少粒子效果强度
float effectIntensity = fadeOutStarted ? FadeOutAlpha : 1f;
FleckMaker.ThrowSmoke(effectPos, base.Map, 1f * effectIntensity);
// 可选:根据速度生成更多效果
if (flightSpeed > 2f && !fadeOutStarted)
{
FleckMaker.ThrowAirPuffUp(effectPos, base.Map);
}
}
}
// 关键修改:添加淡入效果的绘制方法
// 绘制方法保持不变使用OverallAlpha
protected override void DrawAt(Vector3 drawLoc, bool flip = false)
{
Vector3 finalDrawPos = drawLoc;
// 绘制阴影
if (createShadow)
{
DrawFlightShadow();
}
// 绘制主体 - 使用带淡入效果的绘制方法
DrawFlyOverWithFade(finalDrawPos);
}
// 带淡入效果的主体绘制方法
protected virtual void DrawFlyOverWithFade(Vector3 drawPos)
{
Thing thingForGraphic = GetThingForGraphic();
@@ -285,41 +412,41 @@ namespace ArachnaeSwarm
if (graphic == null)
return;
// 获取原始材质
Material material = graphic.MatSingle;
if (material == null)
return;
// 计算淡入透明度
float alpha = FadeInAlpha;
float alpha = OverallAlpha;
// 如果已经完全淡入,使用原版绘制方法以获得最佳性能
if (alpha >= 0.999f)
if (alpha <= 0.001f)
return;
if (fadeInCompleted && !fadeOutStarted && alpha >= 0.999f)
{
graphic.Draw(drawPos, Rot4.North, thingForGraphic, ExactRotation.eulerAngles.y);
Vector3 highAltitudePos = drawPos;
highAltitudePos.y = AltitudeLayer.MetaOverlays.AltitudeFor();
graphic.Draw(highAltitudePos, Rot4.North, thingForGraphic, ExactRotation.eulerAngles.y);
return;
}
// 创建带透明度的材质属性块
fadePropertyBlock.SetColor(ShaderPropertyIDs.Color,
fadePropertyBlock.SetColor(ShaderPropertyIDs.Color,
new Color(graphic.Color.r, graphic.Color.g, graphic.Color.b, graphic.Color.a * alpha));
// 计算缩放
Vector3 scale = Vector3.one;
if (def.graphicData != null)
{
scale = new Vector3(def.graphicData.drawSize.x, 1f, def.graphicData.drawSize.y);
}
// 使用自定义绘制实现淡入效果
Matrix4x4 matrix = Matrix4x4.TRS(drawPos, ExactRotation, scale);
Vector3 highPos = drawPos;
highPos.y = AltitudeLayer.MetaOverlays.AltitudeFor();
Matrix4x4 matrix = Matrix4x4.TRS(highPos, ExactRotation, scale);
Graphics.DrawMesh(MeshPool.plane10, matrix, material, 0, null, 0, fadePropertyBlock);
}
// 简化的阴影绘制方法 - 完全移除旋转(模仿原版)
protected virtual void DrawFlightShadow()
{
// 检查是否有 ModExtension
var shadowExtension = def.GetModExtension<FlyOverShadowExtension>();
Material shadowMaterial;
@@ -338,7 +465,6 @@ namespace ArachnaeSwarm
Vector3 shadowPos = DrawPos;
shadowPos.y = AltitudeLayer.Shadows.AltitudeFor();
// 使用 ModExtension 参数或默认值
float shadowIntensity = shadowExtension?.shadowIntensity ?? 1f;
float minAlpha = shadowExtension?.minShadowAlpha ?? 0.3f;
float maxAlpha = shadowExtension?.maxShadowAlpha ?? 1f;
@@ -348,10 +474,11 @@ namespace ArachnaeSwarm
float shadowAlpha = Mathf.Lerp(minAlpha, maxAlpha, currentProgress) * shadowIntensity;
float shadowScale = Mathf.Lerp(minScale, maxScale, currentProgress);
// 阴影也应用淡入效果
shadowAlpha *= FadeInAlpha;
shadowAlpha *= OverallAlpha;
if (shadowAlpha <= 0.001f)
return;
// 完全移除阴影旋转 - 始终使用默认朝向(模仿原版 Projectile
Vector3 s = new Vector3(shadowScale, 1f, shadowScale);
Vector3 vector = new Vector3(0f, -0.01f, 0f);
Matrix4x4 matrix = Matrix4x4.TRS(shadowPos + vector, Quaternion.identity, s);
@@ -359,7 +486,7 @@ namespace ArachnaeSwarm
Graphics.DrawMesh(MeshPool.plane10, matrix, shadowMaterial, 0);
}
// IThingHolder 接口实现
// IThingHolder 接口实现和其他方法保持不变
public ThingOwner GetDirectlyHeldThings()
{
return innerContainer;
@@ -381,7 +508,8 @@ namespace ArachnaeSwarm
// 工具方法:创建飞越物体
public static FlyOver MakeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
float speed = 1f, float height = 10f, ThingOwner contents = null, float fadeInDuration = 1.5f)
float speed = 1f, float height = 10f, ThingOwner contents = null,
float fadeInDuration = 1.5f, float defaultFadeOutDuration = 1.5f)
{
FlyOver flyOver = (FlyOver)ThingMaker.MakeThing(flyOverDef);
flyOver.startPosition = start;
@@ -389,6 +517,7 @@ namespace ArachnaeSwarm
flyOver.flightSpeed = speed;
flyOver.altitude = height;
flyOver.fadeInDuration = fadeInDuration;
flyOver.defaultFadeOutDuration = defaultFadeOutDuration;
if (contents != null)
{
@@ -397,51 +526,32 @@ namespace ArachnaeSwarm
GenSpawn.Spawn(flyOver, start, map);
Log.Message($"FlyOver created: {flyOver} from {start} to {end} at altitude {height} with {fadeInDuration}s fade-in");
Log.Message($"FlyOver created: {flyOver} from {start} to {end} at altitude {height}");
return flyOver;
}
// 高空版本的工具方法
public static FlyOver MakeHighAltitudeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
float speed = 2f, float height = 50f, ThingOwner contents = null, float fadeInDuration = 1.5f)
{
FlyOver flyOver = MakeFlyOver(flyOverDef, start, end, map, speed, height, contents, fadeInDuration);
// 高空特有的设置
flyOver.playFlyOverSound = false;
flyOver.createShadow = true;
flyOver.spawnContentsOnImpact = false;
Log.Message($"HighAltitude FlyOver created: {flyOver} from {start} to {end} at altitude {height}");
return flyOver;
}
// 快速淡入版本的工具方法
public static FlyOver MakeQuickFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
float speed = 1f, float height = 10f, ThingOwner contents = null)
{
return MakeFlyOver(flyOverDef, start, end, map, speed, height, contents, 0.5f);
}
// 慢速淡入版本的工具方法(适合非常大的模型)
public static FlyOver MakeSlowFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
float speed = 1f, float height = 10f, ThingOwner contents = null)
{
return MakeFlyOver(flyOverDef, start, end, map, speed, height, contents, 3f);
}
// 其他工具方法...
}
// 更新的 ModExtension添加淡配置
// 更新的 ModExtension添加淡配置
public class FlyOverShadowExtension : DefModExtension
{
public string customShadowPath; // 自定义阴影材质路径
public float shadowIntensity = 0.6f; // 阴影强度
public bool useCustomShadow = false; // 是否使用自定义阴影
public float minShadowAlpha = 0.05f; // 最小阴影透明度
public float maxShadowAlpha = 0.2f; // 最大阴影透明度
public float minShadowScale = 0.5f; // 最小阴影大小
public float maxShadowScale = 1.0f; // 最大阴影大小
public float defaultFadeInDuration = 1.5f; // 默认淡入持续时间
public float ActuallyHeight = 150f; // 实际高度
public string customShadowPath;
public float shadowIntensity = 0.6f;
public bool useCustomShadow = false;
public float minShadowAlpha = 0.05f;
public float maxShadowAlpha = 0.2f;
public float minShadowScale = 0.5f;
public float maxShadowScale = 1.0f;
public float defaultFadeInDuration = 1.5f;
public float defaultFadeOutDuration = 1.5f; // 默认淡出持续时间(用于紧急销毁)
public float fadeOutStartProgress = 0.98f;
// 新增:动态淡出配置
public float minFadeOutDuration = 0.5f; // 最小淡出持续时间
public float maxFadeOutDuration = 3f; // 最大淡出持续时间
public float fadeOutDistanceFactor = 0.3f; // 淡出距离因子(剩余时间的百分比)
public float ActuallyHeight = 150f;
}
}