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

6.7 KiB
Raw Blame History

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

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)

通过在类声明中加入 IThingHolderHediffAbility_PaintedSkin 就获得了“容器”的资格。这要求它必须实现接口定义的属性和方法,如 ParentHolderGetDirectlyHeldThings()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.DeepThingOwner 容器,我们可以将一个 Hediff 转变为一个功能强大的、能够随宿主移动的“Pawn胶囊”。这个“胶囊”可以安全地携带一个Pawn穿越存档的海洋,确保其数据的完整性和一致性。

这项技术是实现诸如吞噬、俘获、传送、特殊休眠仓等高级Mod功能的基石。