6.7 KiB
6.7 KiB
RimWorld Modding: 利用Hediff存储Pawn的深度解析
在RimWorld的Mod开发中,有时需要将一个Pawn(人物、动物等)从游戏世界中临时移除,并将其数据完整保存起来,之后再释放回游戏中。一个非常精妙且强大的实现方式就是让Hediff(健康效果)扮演一个“容器”的角色。
本文档将以HediffAbility_PaintedSkin为例,深入剖析其实现Pawn存储的核心机制。
核心概念
该功能主要依赖于RimWorld框架中的两个核心组件:
IThingHolder接口: 一个对象(如建筑、Hediff、Pawn的装备栏等)如果实现了这个接口,就等于向游戏声明:“我是一个可以容纳其他物品(Thing)的容器”。ThingOwner类: 这是实现存储功能的“袋子”。它是一个专门用于管理一组Thing对象的集合,并负责处理这些物品的保存、加载和所有权关系。
案例分析: HediffAbility_PaintedSkin
以下是HediffAbility_PaintedSkin的完整源代码,它完美地展示了如何利用Hediff来存储一个Pawn。
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功能的基石。