暂存
This commit is contained in:
188
Source/Documents/PawnStorageInHediff_Analysis.md
Normal file
188
Source/Documents/PawnStorageInHediff_Analysis.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# 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功能的基石。
|
||||
Reference in New Issue
Block a user