Merge branch '冲撞2'
This commit is contained in:
Binary file not shown.
53
1.6/1.6/Defs/AbilityDefs/Abilities_TrackingCharge.xml
Normal file
53
1.6/1.6/Defs/AbilityDefs/Abilities_TrackingCharge.xml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<Defs>
|
||||||
|
|
||||||
|
<!-- 1. Definition for the PawnFlyer itself -->
|
||||||
|
<ThingDef ParentName="PawnFlyerBase">
|
||||||
|
<defName>ARA_Flyer_TrackingCharge</defName>
|
||||||
|
<thingClass>ArachnaeSwarm.PawnFlyer_TrackingCharge</thingClass>
|
||||||
|
<pawnFlyer>
|
||||||
|
<flightSpeed>0.5</flightSpeed>
|
||||||
|
<heightFactor>0</heightFactor>
|
||||||
|
</pawnFlyer>
|
||||||
|
</ThingDef>
|
||||||
|
|
||||||
|
<!-- 2. The final Ability Definition -->
|
||||||
|
<AbilityDef>
|
||||||
|
<defName>ARA_Ability_TrackingCharge</defName>
|
||||||
|
<label>追踪冲撞</label>
|
||||||
|
<description>阿拉克涅盾头种对目标发起蓄势冲撞,对路径上的一切造成伤害。飞行的距离越远,伤害越高。</description>
|
||||||
|
<iconPath>UI/Commands/WarTrumpet</iconPath> <!-- Placeholder Icon -->
|
||||||
|
<cooldownTicksRange>600</cooldownTicksRange>
|
||||||
|
<verbProperties>
|
||||||
|
<verbClass>ArachnaeSwarm.Verb_CastAbilityTrackingCharge</verbClass>
|
||||||
|
<label>tracking charge</label>
|
||||||
|
<targetParams>
|
||||||
|
<canTargetPawns>true</canTargetPawns>
|
||||||
|
<canTargetBuildings>false</canTargetBuildings>
|
||||||
|
<canTargetAnimals>true</canTargetAnimals>
|
||||||
|
<canTargetMechs>true</canTargetMechs>
|
||||||
|
<canTargetSelf>false</canTargetSelf>
|
||||||
|
</targetParams>
|
||||||
|
<range>60</range>
|
||||||
|
<warmupTime>1.0</warmupTime>
|
||||||
|
</verbProperties>
|
||||||
|
<comps>
|
||||||
|
<li Class="ArachnaeSwarm.CompProperties_TrackingCharge">
|
||||||
|
<homingSpeed>1.5</homingSpeed>
|
||||||
|
<initialDamage>15</initialDamage>
|
||||||
|
<damagePerTile>2</damagePerTile>
|
||||||
|
<inertiaDistance>6</inertiaDistance>
|
||||||
|
<collisionDamageDef>Blunt</collisionDamageDef>
|
||||||
|
<flyerDef>ARA_Flyer_TrackingCharge</flyerDef>
|
||||||
|
<collisionRadius>1.5</collisionRadius> <!-- Larger collision radius -->
|
||||||
|
<impactSound>Pawn_Melee_BigBash_HitPawn</impactSound>
|
||||||
|
<damageHostileOnly>true</damageHostileOnly> <!-- Set to false to damage everyone in the path -->
|
||||||
|
</li>
|
||||||
|
<li Class="CompProperties_AbilityEffecterOnCaster">
|
||||||
|
<effecterDef>WarTrumpet</effecterDef>
|
||||||
|
<maintainTicks>20</maintainTicks> <!-- Long enough for the "2nd wave" to spawn -->
|
||||||
|
</li>
|
||||||
|
</comps>
|
||||||
|
</AbilityDef>
|
||||||
|
|
||||||
|
</Defs>
|
||||||
@@ -147,6 +147,9 @@
|
|||||||
<apparelTags>
|
<apparelTags>
|
||||||
</apparelTags>
|
</apparelTags>
|
||||||
<apparelMoney>0</apparelMoney>
|
<apparelMoney>0</apparelMoney>
|
||||||
|
<abilities>
|
||||||
|
<li>ARA_Ability_TrackingCharge</li>
|
||||||
|
</abilities>
|
||||||
</PawnKindDef>
|
</PawnKindDef>
|
||||||
<PawnKindDef ParentName="ArachnaeNodeABasePawnKind">
|
<PawnKindDef ParentName="ArachnaeNodeABasePawnKind">
|
||||||
<defName>ArachnaeNode_Race_WeaponSmith</defName>
|
<defName>ArachnaeNode_Race_WeaponSmith</defName>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class CompAbilityEffect_TrackingCharge : CompAbilityEffect
|
||||||
|
{
|
||||||
|
public new CompProperties_TrackingCharge Props => (CompProperties_TrackingCharge)this.props;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class CompProperties_TrackingCharge : CompProperties_AbilityEffect
|
||||||
|
{
|
||||||
|
public float homingSpeed = 1.0f;
|
||||||
|
public float initialDamage = 10f;
|
||||||
|
public float damagePerTile = 2f;
|
||||||
|
public float inertiaDistance = 3f;
|
||||||
|
public DamageDef collisionDamageDef;
|
||||||
|
public ThingDef flyerDef;
|
||||||
|
public float collisionRadius = 1.5f;
|
||||||
|
public SoundDef impactSound;
|
||||||
|
public bool damageHostileOnly = true;
|
||||||
|
|
||||||
|
public CompProperties_TrackingCharge()
|
||||||
|
{
|
||||||
|
this.compClass = typeof(CompAbilityEffect_TrackingCharge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
157
Source/ArachnaeSwarm/Abilities/PawnFlyer_TrackingCharge.cs
Normal file
157
Source/ArachnaeSwarm/Abilities/PawnFlyer_TrackingCharge.cs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
using Verse.AI;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Verse.Sound;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class PawnFlyer_TrackingCharge : PawnFlyer
|
||||||
|
{
|
||||||
|
// --- Public fields to be set by the Verb ---
|
||||||
|
public float homingSpeed;
|
||||||
|
public float initialDamage;
|
||||||
|
public float damagePerTile;
|
||||||
|
public float inertiaDistance;
|
||||||
|
public DamageDef collisionDamageDef;
|
||||||
|
public LocalTargetInfo primaryTarget;
|
||||||
|
public float collisionRadius;
|
||||||
|
public SoundDef impactSound;
|
||||||
|
public bool damageHostileOnly;
|
||||||
|
public int maxFlightTicks;
|
||||||
|
|
||||||
|
// --- Internal state ---
|
||||||
|
private bool homing = true;
|
||||||
|
private bool hasHitPrimaryTarget = false;
|
||||||
|
private Vector3 exactPosition;
|
||||||
|
|
||||||
|
// --- Reflection Fields ---
|
||||||
|
private static FieldInfo TicksFlyingInfo;
|
||||||
|
private static FieldInfo TicksFlightTimeInfo;
|
||||||
|
private static FieldInfo StartVecInfo;
|
||||||
|
private static FieldInfo DestCellInfo;
|
||||||
|
private static FieldInfo PawnWasDraftedInfo;
|
||||||
|
private static FieldInfo PawnCanFireAtWillInfo;
|
||||||
|
private static FieldInfo JobQueueInfo;
|
||||||
|
private static FieldInfo InnerContainerInfo;
|
||||||
|
|
||||||
|
static PawnFlyer_TrackingCharge()
|
||||||
|
{
|
||||||
|
TicksFlyingInfo = typeof(PawnFlyer).GetField("ticksFlying", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
TicksFlightTimeInfo = typeof(PawnFlyer).GetField("ticksFlightTime", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
StartVecInfo = typeof(PawnFlyer).GetField("startVec", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
DestCellInfo = typeof(PawnFlyer).GetField("destCell", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
PawnWasDraftedInfo = typeof(PawnFlyer).GetField("pawnWasDrafted", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
PawnCanFireAtWillInfo = typeof(PawnFlyer).GetField("pawnCanFireAtWill", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
JobQueueInfo = typeof(PawnFlyer).GetField("jobQueue", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
InnerContainerInfo = typeof(PawnFlyer).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom initializer called by the Verb
|
||||||
|
public void StartFlight(Pawn pawn, IntVec3 finalDest)
|
||||||
|
{
|
||||||
|
var innerContainer = (ThingOwner)InnerContainerInfo.GetValue(this);
|
||||||
|
|
||||||
|
StartVecInfo.SetValue(this, pawn.TrueCenter());
|
||||||
|
DestCellInfo.SetValue(this, finalDest);
|
||||||
|
PawnWasDraftedInfo.SetValue(this, pawn.Drafted);
|
||||||
|
if (pawn.drafter != null) PawnCanFireAtWillInfo.SetValue(this, pawn.drafter.FireAtWill);
|
||||||
|
if (pawn.CurJob != null) pawn.jobs.SuspendCurrentJob(JobCondition.InterruptForced);
|
||||||
|
JobQueueInfo.SetValue(this, pawn.jobs.CaptureAndClearJobQueue());
|
||||||
|
|
||||||
|
if (pawn.Spawned) pawn.DeSpawn(DestroyMode.WillReplace);
|
||||||
|
if (!innerContainer.TryAdd(pawn))
|
||||||
|
{
|
||||||
|
Log.Error("Could not add pawn to tracking flyer.");
|
||||||
|
pawn.Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||||||
|
{
|
||||||
|
base.SpawnSetup(map, respawningAfterLoad);
|
||||||
|
if (!respawningAfterLoad)
|
||||||
|
{
|
||||||
|
this.exactPosition = base.DrawPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Tick()
|
||||||
|
{
|
||||||
|
// --- THE CORRECT APPROACH ---
|
||||||
|
// Let the base class handle all flight mechanics (position, timing, etc.)
|
||||||
|
// We only intervene to do two things:
|
||||||
|
// 1. Continuously update the destination to "steer" the flyer.
|
||||||
|
// 2. Perform our own collision checks (for primary target and AOE).
|
||||||
|
|
||||||
|
if (homing && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
||||||
|
{
|
||||||
|
// Steer the flyer by constantly updating its destination cell.
|
||||||
|
DestCellInfo.SetValue(this, primaryTarget.Thing.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Primary Target Collision Check ---
|
||||||
|
if (!hasHitPrimaryTarget && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
||||||
|
{
|
||||||
|
if ((this.DrawPos - primaryTarget.Thing.DrawPos).sqrMagnitude < this.collisionRadius * this.collisionRadius)
|
||||||
|
{
|
||||||
|
// --- Impact! ---
|
||||||
|
if (this.impactSound != null)
|
||||||
|
{
|
||||||
|
SoundStarter.PlayOneShot(this.impactSound, new TargetInfo(this.Position, this.Map));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 startPosition = (Vector3)StartVecInfo.GetValue(this);
|
||||||
|
float distance = (this.DrawPos - startPosition).magnitude;
|
||||||
|
float calculatedDamage = this.initialDamage + (distance * this.damagePerTile);
|
||||||
|
var dinfo = new DamageInfo(this.collisionDamageDef, calculatedDamage, 1f, -1, this.FlyingPawn);
|
||||||
|
|
||||||
|
primaryTarget.Thing.TakeDamage(dinfo);
|
||||||
|
hasHitPrimaryTarget = true;
|
||||||
|
|
||||||
|
homing = false;
|
||||||
|
|
||||||
|
Vector3 direction = (this.DrawPos - startPosition).normalized;
|
||||||
|
IntVec3 inertiaEndPos = (this.DrawPos + (direction * this.inertiaDistance)).ToIntVec3();
|
||||||
|
DestCellInfo.SetValue(this, inertiaEndPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- AOE Damage Logic ---
|
||||||
|
float distanceTravelled = ((Vector3)StartVecInfo.GetValue(this) - this.DrawPos).magnitude;
|
||||||
|
float currentAOEDamage = this.initialDamage + (distanceTravelled * this.damagePerTile);
|
||||||
|
|
||||||
|
foreach (var thing in GenRadial.RadialDistinctThingsAround(this.Position, this.Map, this.collisionRadius, false))
|
||||||
|
{
|
||||||
|
if (thing != this.FlyingPawn && thing != this && thing != primaryTarget.Thing)
|
||||||
|
{
|
||||||
|
if (thing is Pawn pawn && !pawn.Downed)
|
||||||
|
{
|
||||||
|
if (!this.damageHostileOnly || pawn.HostileTo(this.FlyingPawn))
|
||||||
|
{
|
||||||
|
var aoeDinfo = new DamageInfo(this.collisionDamageDef, currentAOEDamage, 1f, -1, this.FlyingPawn);
|
||||||
|
pawn.TakeDamage(aoeDinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (thing.def.destroyable && thing.def.building != null)
|
||||||
|
{
|
||||||
|
var aoeDinfo = new DamageInfo(this.collisionDamageDef, currentAOEDamage, 1f, -1, this.FlyingPawn);
|
||||||
|
thing.TakeDamage(aoeDinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the base class do its thing. This is crucial.
|
||||||
|
base.Tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void RespawnPawn()
|
||||||
|
{
|
||||||
|
// This is the correct place to call the base method.
|
||||||
|
base.RespawnPawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace ArachnaeSwarm
|
||||||
|
{
|
||||||
|
public class Verb_CastAbilityTrackingCharge : Verb_CastAbility
|
||||||
|
{
|
||||||
|
protected override bool TryCastShot()
|
||||||
|
{
|
||||||
|
var props = this.ability.def.comps?.OfType<CompProperties_TrackingCharge>().FirstOrDefault();
|
||||||
|
if (props == null)
|
||||||
|
{
|
||||||
|
Log.Error("Verb_CastAbilityTrackingCharge requires CompProperties_TrackingCharge on the ability def.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.flyerDef == null)
|
||||||
|
{
|
||||||
|
Log.Error("CompProperties_TrackingCharge requires a flyerDef.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Best Practice: Cache Map and Position FIRST ---
|
||||||
|
// Per MCP analysis, Caster.Map is the most reliable source.
|
||||||
|
// Cache this before ANY other logic.
|
||||||
|
Map map = this.Caster.Map;
|
||||||
|
if (map == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Verb_CastAbilityTrackingCharge: Caster {this.Caster.LabelCap} has a null map. Cannot cast.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.CasterPawn == null || !this.CasterPawn.Spawned)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- This is now a fully custom Thing, so we spawn it directly ---
|
||||||
|
var trackingCharge = (PawnFlyer_TrackingCharge)ThingMaker.MakeThing(props.flyerDef);
|
||||||
|
|
||||||
|
// Inject properties
|
||||||
|
trackingCharge.homingSpeed = props.homingSpeed;
|
||||||
|
trackingCharge.initialDamage = props.initialDamage;
|
||||||
|
trackingCharge.damagePerTile = props.damagePerTile;
|
||||||
|
trackingCharge.inertiaDistance = props.inertiaDistance;
|
||||||
|
trackingCharge.collisionDamageDef = props.collisionDamageDef;
|
||||||
|
trackingCharge.primaryTarget = this.currentTarget;
|
||||||
|
trackingCharge.collisionRadius = props.collisionRadius;
|
||||||
|
trackingCharge.impactSound = props.impactSound;
|
||||||
|
trackingCharge.damageHostileOnly = props.damageHostileOnly;
|
||||||
|
|
||||||
|
// Setup and spawn
|
||||||
|
trackingCharge.StartFlight(this.CasterPawn, this.currentTarget.Cell);
|
||||||
|
GenSpawn.Spawn(trackingCharge, this.CasterPawn.Position, map); // Use the cached map
|
||||||
|
|
||||||
|
// --- FIX for Comp Effects ---
|
||||||
|
// --- The Standard Pattern to trigger Comps like EffecterOnCaster ---
|
||||||
|
// After our custom verb logic (spawning the flyer) is done,
|
||||||
|
// we call the ability's Activate method with invalid targets.
|
||||||
|
// This triggers the standard Comp cycle without re-casting the verb.
|
||||||
|
this.ability.Activate(LocalTargetInfo.Invalid, LocalTargetInfo.Invalid);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,6 +133,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="HediffComp_Temperature.cs" />
|
<Compile Include="HediffComp_Temperature.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Abilities\CompAbilityEffect_TrackingCharge.cs" />
|
||||||
|
<Compile Include="Abilities\CompProperties_TrackingCharge.cs" />
|
||||||
|
<Compile Include="Abilities\PawnFlyer_TrackingCharge.cs" />
|
||||||
|
<Compile Include="Abilities\Verb_CastAbilityTrackingCharge.cs" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- 自定义清理任务,删除obj文件夹中的临时文件 -->
|
<!-- 自定义清理任务,删除obj文件夹中的临时文件 -->
|
||||||
<Target Name="CleanDebugFiles" AfterTargets="Build">
|
<Target Name="CleanDebugFiles" AfterTargets="Build">
|
||||||
|
|||||||
97
Source/Documents/design_doc_tracking_charge.md
Normal file
97
Source/Documents/design_doc_tracking_charge.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# 设计文档:跟踪冲撞技能 (Tracking Charge)
|
||||||
|
|
||||||
|
**版本:** 0.1
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
本文档旨在详细说明一个新的 RimWorld 技能:“跟踪冲撞”。该技能允许一个 Pawn(施法者)像制导导弹一样冲向一个目标 Pawn。在飞行过程中,它会对路径上接触到的所有敌对单位和可破坏建筑造成伤害。伤害值会随着飞行距离的增加而累积。当撞击到主目标后,施法者会因惯性继续向前滑行一小段距离。
|
||||||
|
|
||||||
|
## 2. 核心功能需求
|
||||||
|
|
||||||
|
* **技能类型**: 主动施放的 targeted ability。
|
||||||
|
* **移动方式**: 动态追踪曲线移动,而非直线或抛物线。
|
||||||
|
* **伤害机制**:
|
||||||
|
* **路径伤害**: 对飞行路径上碰撞到的所有有效目标(敌方、中立、可破坏建筑)造成伤害。
|
||||||
|
* **累积伤害**: 总伤害 = 基础伤害 + (飞行距离 * 每米伤害增量)。
|
||||||
|
* **主目标伤害**: 与路径伤害计算方式相同。
|
||||||
|
* **最终效果**: 撞击主目标后,追踪停止,施法者沿最后的方向继续滑行一小段距离后停下。
|
||||||
|
|
||||||
|
## 3. 技术架构
|
||||||
|
|
||||||
|
我们将采用基于自定义 `PawnFlyer` 的核心架构。这种方法将移动和效果逻辑封装在一个临时的 `Thing` 中,而施法者 Pawn 本身在技能持续期间会从地图上暂时移除。
|
||||||
|
|
||||||
|
### 3.1. 核心组件
|
||||||
|
|
||||||
|
| 组件名称 (C# Class) | 类型 | 职责 |
|
||||||
|
| ---------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `Verb_CastAbilityTrackingCharge` | `Verb` | **技能启动器**: 验证目标,从 `CompProperties` 读取配置,创建并初始化 `PawnFlyer_TrackingCharge`。 |
|
||||||
|
| `PawnFlyer_TrackingCharge` | `PawnFlyer` | **核心逻辑处理器**: 接收来自 `Verb` 的参数,实现追踪、碰撞、伤害、惯性等所有动态逻辑。 |
|
||||||
|
| `CompProperties_TrackingCharge` | `CompProperties_AbilityEffect` | **XML配置接口**: 在 `AbilityDef` 的 `<comps>` 中定义技能的所有可调参数,如伤害、速度、惯性等。 |
|
||||||
|
| `CompAbilityEffect_TrackingCharge` | `CompAbilityEffect` | **数据容器**: 作为一个轻量级的组件,其主要作用是让 `CompProperties_TrackingCharge` 能被游戏正确加载。它本身不执行复杂逻辑。 |
|
||||||
|
|
||||||
|
### 3.2. 设计决策:配置的分层与传递
|
||||||
|
|
||||||
|
* **采纳的方案 (混合模式)**: 我们将采用一种既符合直觉又技术稳健的混合模式。
|
||||||
|
1. **配置集中在 `AbilityDef`**: 使用 `CompProperties_TrackingCharge` (继承自 `CompProperties_AbilityEffect`) 来存放所有技能参数。这使得 Mod 用户可以在 `AbilityDef` 的 `<comps>` 节点中方便地调整一切,符合 RimWorld 的标准实践。
|
||||||
|
2. **`Verb` 作为数据中介**: `Verb_CastAbilityTrackingCharge` 在施法时,会从 `this.ability.def` 中轻松获取到 `CompProperties_TrackingCharge` 的实例,读取所有配置参数。
|
||||||
|
3. **`PawnFlyer` 接收参数**: `Verb` 在创建 `PawnFlyer_TrackingCharge` 实例后,会通过一个自定义的初始化方法(例如 `Initialize(...)`),将所有读取到的参数“注入”到 `PawnFlyer` 实例中。
|
||||||
|
* **优点**: 这个方案完美地解决了您的顾虑。配置的**易用性**(在 `AbilityDef` 中)和逻辑的**清晰性**(`PawnFlyer` 负责执行)都得到了保证。
|
||||||
|
|
||||||
|
### 3.3. 数据流与交互
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph Player Action
|
||||||
|
A[玩家选择目标并施放技能] --> B(AbilityDef);
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph XML Definitions
|
||||||
|
B -- contains --> E[CompProperties_TrackingCharge];
|
||||||
|
B -- uses --> C(VerbDef);
|
||||||
|
C -- verbClass --> D[Verb_CastAbilityTrackingCharge];
|
||||||
|
F(ThingDef for Flyer) -- thingClass --> G[PawnFlyer_TrackingCharge];
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph C# Logic
|
||||||
|
D -- 1. Reads Params from --> E;
|
||||||
|
D -- 2. Creates --> G;
|
||||||
|
D -- 3. Injects Params into --> G;
|
||||||
|
G -- 4. Executes Tick Logic --> H{追踪 & 碰撞};
|
||||||
|
H -- 5. Applies Damage to --> I[Things on Path];
|
||||||
|
G -- 6. Handles Inertia & Self-Destructs --> J[Final Position];
|
||||||
|
end
|
||||||
|
|
||||||
|
PlayerInput([施法者 Pawn]) -- temporarily removed --> G;
|
||||||
|
G -- places back --> PlayerInput;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 详细实现步骤
|
||||||
|
|
||||||
|
### 步骤 1: 创建 C# 类骨架 (最终版)
|
||||||
|
|
||||||
|
* **文件**: `CompProperties_TrackingCharge.cs`
|
||||||
|
* **继承**: `Verse.CompProperties_AbilityEffect`
|
||||||
|
* **职责**: 定义所有可在 XML 中配置的技能参数。
|
||||||
|
* **示例字段**: `public float homingSpeed;`, `public float initialDamage;`, `public float damagePerTile;`, `public float inertiaDistance;`, `public DamageDef collisionDamageDef;`, `public ThingDef flyerDef;` (用于指定飞行器的ThingDef)。
|
||||||
|
|
||||||
|
* **文件**: `CompAbilityEffect_TrackingCharge.cs`
|
||||||
|
* **继承**: `Verse.CompAbilityEffect`
|
||||||
|
* **职责**: 轻量级组件,其存在是为了让 `CompProperties_TrackingCharge` 能被游戏正确加载和访问。
|
||||||
|
|
||||||
|
* **文件**: `Verb_CastAbilityTrackingCharge.cs`
|
||||||
|
* **继承**: `Verse.Verb_CastAbility`
|
||||||
|
* **职责**:
|
||||||
|
1. 在 `TryCastShot()` 中,从 `this.ability.GetComp<CompAbilityEffect_TrackingCharge>().Props` 获取配置。
|
||||||
|
2. 调用 `PawnFlyer.MakeFlyer(Props.flyerDef, ...)` 创建 `PawnFlyer_TrackingCharge` 实例。
|
||||||
|
3. **将配置参数设置到 `PawnFlyer` 的公共字段上**。
|
||||||
|
4. 调用 `flyer.Launch()` 启动。
|
||||||
|
|
||||||
|
* **文件**: `PawnFlyer_TrackingCharge.cs`
|
||||||
|
* **继承**: `Verse.PawnFlyer`
|
||||||
|
* **职责**:
|
||||||
|
1. 定义一系列**公共字段** (`public float homingSpeed;` 等) 来接收来自 `Verb` 的参数。
|
||||||
|
2. 在 `Tick()` 中实现所有核心的追踪、碰撞和伤害逻辑。
|
||||||
|
* **注意**: **不**创建自定义的 `Initialize()` 方法,也**不**重写 `Launch()` 来接收参数,以保持设计的简洁和标准。
|
||||||
|
|
||||||
|
---
|
||||||
|
> **下一步**: 设计文档已最终确定。我将开始实施 **步骤 1**,为您创建这四个 C# 类的代码框架。
|
||||||
Reference in New Issue
Block a user