Files
ArachnaeSwarm/Source/Documents/PawnStorageInHediff_Analysis.md
2025-09-04 20:40:32 +08:00

188 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RimWorld Modding: 利用Hediff存储Pawn的深度解析
在RimWorld的Mod开发中有时需要将一个`Pawn`(人物、动物等)从游戏世界中临时移除,并将其数据完整保存起来,之后再释放回游戏中。一个非常精妙且强大的实现方式就是让`Hediff`(健康效果)扮演一个“容器”的角色。
本文档将以`HediffAbility_PaintedSkin`为例,深入剖析其实现`Pawn`存储的核心机制。
## 核心概念
该功能主要依赖于RimWorld框架中的两个核心组件
1. **`IThingHolder`接口**: 一个对象如建筑、Hediff、Pawn的装备栏等如果实现了这个接口就等于向游戏声明“我是一个可以容纳其他物品`Thing`)的容器”。
2. **`ThingOwner`类**: 这是实现存储功能的“袋子”。它是一个专门用于管理一组`Thing`对象的集合,并负责处理这些物品的保存、加载和所有权关系。
## 案例分析: `HediffAbility_PaintedSkin`
以下是`HediffAbility_PaintedSkin`的完整源代码,它完美地展示了如何利用`Hediff`来存储一个`Pawn`
```csharp
using System;
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
using Verse.Sound;
namespace RigorMortis
{
public class HediffAbility_PaintedSkin : HediffWithComps, IHediffAbility, IThingHolder
{
// 1. 核心存储容器
protected ThingOwner innerContainer;
private CompYinAndMalevolent compYin;
public Pawn victim;
// 构造函数:初始化容器
public HediffAbility_PaintedSkin()
{
// 'this'表示容器的所有者是当前Hediff实例
// 'LookMode.Deep'是关键确保能完整保存Pawn的所有数据
this.innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep, true);
}
// --- IThingHolder 接口实现 ---
public IThingHolder ParentHolder
{
get
{
// 对于Hediff来说它的父容器就是持有它的Pawn
return this.pawn;
}
}
public void GetChildHolders(List<IThingHolder> outChildren)
{
ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, this.GetDirectlyHeldThings());
}
public ThingOwner GetDirectlyHeldThings()
{
return this.innerContainer;
}
// --- 容器内容访问 ---
public Thing ContainedThing
{
get
{
return this.innerContainer.Count > 0 ? this.innerContainer[0] : null;
}
}
public Pawn Zombie
{
get
{
// 提供一个便捷的属性来访问被存储的Pawn
return this.ContainedThing as Pawn;
}
}
public bool HasAnyContents
{
get
{
return this.innerContainer.Count > 0;
}
}
// --- 存入/取出逻辑 ---
public virtual bool Accepts(Thing thing)
{
return this.innerContainer.CanAcceptAnyOf(thing, true);
}
public virtual bool TryAcceptThing(Thing thing, bool allowSpecialEffects = true)
{
if (!this.Accepts(thing))
{
return false;
}
bool flag;
if (thing.holdingOwner != null)
{
// 将Pawn从当前持有者通常是地图转移到我们的容器中
thing.holdingOwner.TryTransferToContainer(thing, this.innerContainer, thing.stackCount, true);
flag = true;
}
else
{
// 如果Pawn没有持有者例如是新生成的直接添加
flag = this.innerContainer.TryAdd(thing, true);
}
return flag;
}
public virtual void EjectContents()
{
// 决定在何处释放Pawn
Map map = this.pawn.MapHeld ?? Find.AnyPlayerHomeMap;
IntVec3 cell = (this.pawn.Spawned || (this.pawn.Corpse != null && this.pawn.Corpse.Spawned)) ? this.pawn.PositionHeld : ((this.pawn.CarriedBy != null) ? this.pawn.CarriedBy.PositionHeld : map.Center);
// 将容器内的所有东西即被存储的Pawn扔到地图上
this.innerContainer.TryDropAll(cell, map, ThingPlaceMode.Direct, null, null, true);
}
// --- 存档/读档 ---
public override void ExposeData()
{
base.ExposeData();
Scribe_References.Look<Pawn>(ref this.victim, "victim", false);
// 2. 深度保存容器内容
// 'Scribe_Deep.Look' 会序列化容器内的Pawn的所有数据
Scribe_Deep.Look<ThingOwner>(ref this.innerContainer, "innerContainer", new object[]
{
this
});
// 兼容性处理:确保旧存档在加载后也能正确初始化容器
if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
if (this.innerContainer == null)
{
this.innerContainer = new ThingOwner<Thing>(this, false, LookMode.Deep, true);
}
}
}
// --- 其他逻辑 ---
// (为了简洁此处省略了PostTick, End, AbsolutelyKill等与存储机制非直接相关的代码)
// ...
}
}
```
## 机制剖析
### 1. 声明容器身份 (`IThingHolder`)
通过在类声明中加入 `IThingHolder``HediffAbility_PaintedSkin` 就获得了“容器”的资格。这要求它必须实现接口定义的属性和方法,如 `ParentHolder``GetDirectlyHeldThings()``GetDirectlyHeldThings()` 方法必须返回真正的存储实例,也就是我们的 `innerContainer`
### 2. 初始化存储核心 (`ThingOwner`)
在构造函数中,我们创建了一个 `ThingOwner` 实例。这里的关键在于 `LookMode.Deep` 参数。
* `LookMode.Value`: 只保存简单值类型如int, float, string
* `LookMode.Reference`: 只保存一个对物体的引用ID。加载时游戏会尝试在世界中找到这个ID对应的物体。如果物体已被销毁引用会丢失。**这不适用于存储Pawn**因为Pawn在被存入容器时已经从世界中移除了。
* **`LookMode.Deep`**: 这才是我们的选择。它告诉序列化系统:“请将这个物体(`Pawn`的所有数据——健康、技能、装备、Hediff、人际关系、思想等等——完完整整地打包保存起来。” 当游戏加载时,它会用这些数据重建一个一模一样的`Pawn`实例。
### 3. 序列化 (`ExposeData`)
`ExposeData` 方法是RimWorld存档机制的核心。
* `Scribe_Deep.Look<ThingOwner>(ref this.innerContainer, ...)`: 这行代码是魔法发生的地方。当游戏保存时,`Scribe_Deep` 会深入到 `innerContainer` 内部,并因为我们之前设置了 `LookMode.Deep`,它会对容器里的每一个 `Pawn` 进行递归式的深度保存。
* 当游戏加载时,`Scribe_Deep` 会读取存档中的数据,重建 `innerContainer`,并利用深度保存的数据重建一个与存入时状态完全一致的 `Pawn`
## 总结
通过实现 `IThingHolder` 接口并利用一个配置为 `LookMode.Deep``ThingOwner` 容器,我们可以将一个 `Hediff` 转变为一个功能强大的、能够随宿主移动的“Pawn胶囊”。这个“胶囊”可以安全地携带一个`Pawn`穿越存档的海洋,确保其数据的完整性和一致性。
这项技术是实现诸如吞噬、俘获、传送、特殊休眠仓等高级Mod功能的基石。