188 lines
6.7 KiB
Markdown
188 lines
6.7 KiB
Markdown
# 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功能的基石。 |