模组:制作指南/APIs/Events

来自Stardew Valley Wiki
跳到导航 跳到搜索

制作SMAPI模组 SMAPI mascot.png


模组:目录

SMAPI 提供了几个 C#事件,这些事件使模组在某些事情发生时(例如,玩家放置一个对象时)做出响应,或者定期运行代码(例如,每个更新周期一次)

常见问题

什么是事件(events) ?

事件使你可以在发生某些事情时运行代码。可以在引发“事件”(发生的情况)时添加任意数量的“事件处理程序”(调用方法)。 可以将事件和处理程序视为“when...then”语句:

存档已加载              <-- 事件
然后运行我的代码        <-- 事件处理程序

有关详细信息,请参阅C# 编程指南中的事件中的“事件”。

如何使用它们?

通常将事件处理程序添加到 Entry 方法中,可以随时添加和删除它们。例如,在每天开始时打印一条消息。首先,从下面的列表中选择适当的事件 (GameLoop.DayStarted), 然后添加一个事件处理程序,并在方法代码中执行以下操作:

/// <summary>模组的主要入口点。</summary>
public class ModEntry : Mod
{
    /**********
    ** 公共方法
    *********/
    /// <summary>模组入口点,加载模组后自动调用</summary>
    /// <param name="helper">提供用于编写模组的简化API</param>
    public override void Entry(IModHelper helper)
    {
        // 事件 += 方法
        helper.Events.GameLoop.DayStarted += this.OnDayStarted;
    }
    
    /**********
    ** 私有方法
    *********/
    /// <summary>在新的一天开始后调用的方法</summary>
    /// <param name="sender">事件对象</param>
    /// <param name="e">事件参数</param>
    private void OnDayStarted(object sender, DayStartedEventArgs e)
    {
       this.Monitor.Log("新的一天到来了!");
    }
}

提示:不需要记住方法参数。在 Visual Studio 中,输入 helper.Events.GameLoop.SaveLoaded += 然后按 TAB 来自动生成方法

事件如何呈现到游戏中?

每次游戏计时(游戏更新其状态并呈现到屏幕时)都会引发事件,每秒60次。 一个事件可能会引发多次(例如,如果玩家同时按下两个键),但是大多数事件不会每秒引发60次(例如,玩家不太可能每秒按下60个按钮)

事件处理程序是“同步”运行的:游戏暂停时模组的代码不会运行,因此没有更改冲突的风险。由于代码运行非常迅速,因此除非你的代码异常缓慢,否则玩家不会注意到任何延迟。就是说,当使用诸如 UpdateTicked 或者 Rendered 应该缓存繁重的操作(例如加载资源),而不是在每个刻度中重复执行这些操作,以免影响性能。

如果模组更改了事件的发起?

事件根据游戏状态的快照引发,这通常是“但不一定”是当前游戏状态

例如,考虑这种情况:

  1. 菜单 GameMenu 打开了
  2. SMAPI 引发 MenuChanged 事件,并且模组 A 和 B 正在监听
  3. 模组 A 接收了事件并关闭了菜单
  4. 模组 B 接收了事件

每个模组仍在处理菜单打开的 MenuChanged 事件,即使第一个模组已将菜单关闭。SMAPI 将在下一个刻度时为关闭的菜单引发一个新的 MenuChanged 事件

这很少会影响模组,但是如果你需要当前状态,则需要牢记 (例如考虑用 Game1.activeClickableMenu 代替 e.NewMenu)

事件

可用的事件记录在下面

内容

this.Helper.Events.Content具有关于从内容管线加载的素材的事件。

事件 概述
#AssetRequested 当一个素材通过内容管线被请求时,触发此事件。此素材无需已加载(例如,游戏会自动检查其存在性)。
可使用如下事件参数以注册欲应用的更改;素材被真正加载后会自动应用这些更改。参见内容API以获取更多信息。

若在同一时刻素材被请求多次(例如,其中一次请求检查存在性,另一次请求用于加载素材),则SMAPI可能仅调用该事件一次,并在余下的次数中使用缓存结果。

事件参数:

参数 类型 描述
e.Name IAssetName 请求的素材文件的名称,包含本地化代码(例如Data/Bundles.fr-FR中的.fr-FR)。此参数包括一些解析素材值的工具方法,例如 e.Name.IsEquivalentTo("Portraits/Abigail")(此方法会自动处理各种格式差异)。
e.NameWithoutLocale IAssetName 相当于e.Name,但不加本地化代码。例如,若e.NameData/Bundles.fr-FR,则此字段为Data/Bundles
e.LoadFrom(...) 方法 调用此方法会返回素材的内部实例,而不是尝试从Content载入此素材。例如:
e.LoadFrom(() => this.Helper.Content.Load<Texture2D>("assets/portraits.png"), AssetLoadPriority.Medium);

使用说明:

  • 指定素材无需位于Content目录。若加载某模组的素材,游戏也会自动将其视作存在的素材,就像其真的存在于Content目录一样。
  • 每个素材逻辑上只能拥有一个初始实例。若同时加载多个,SMAPI会报错并忽略所有实例。若您正在尝试修改而非替换已经存在的素材,则应使用Edit方法以避免数量限制和提高模组兼容性。
e.LoadFromModFile<T>(...) 方法 调用此方法可以返回素材的初始实例,也就是从模组文件夹中加载素材所得的那个实例。例如:
e.LoadFromModFile<Texture2D>("assets/portraits.png", AssetLoadPriority.Medium);
参见e.LoadFrom的使用说明。
e.Edit(...) 方法 调用此方法可以修改已加载的素材。例如:
e.Edit(asset =>
{
    Texture2D ribbon = this.Helper.Content.Load<Texture2D>("assets/ribbon.png");
    asset.AsImage().PatchImage(source: overlay, patchMode: PatchMode.Overlay);
});

使用说明:

  • 编辑不存在的素材是无效的。此命令所作的修改,须在素材加载完成后方可奏效,也就是需要先从Content目录、其他模组的LoadFrom方法或LoadFromModFile方法处加载素材后方可奏效。
  • 可以编辑素材任意多次。每次编辑会叠加在前一编辑的基础上(即前一编辑的输出是后一编辑的输入)。
#AssetsInvalidated 模组可以使缓存中某些素材失效,以便在下次请求这些素材时重新加载它们。此事件正是在素材失效后触发。 若素材会被自动重新加载(包括asset propagation[1]),则在其前触发此事件。

待到下次加载素材(包括asset propagation),此AssetRequested事件会被再次触发。

事件参数:

参数 类型 描述
e.Names IReadOnlySet<IAssetName> 使失效素材的名称,包含本地化代码(例如Data/Bundles.fr-FR中的.fr-FR)。此参数包括一些解析素材值的工具方法,例如 e.Name.IsEquivalentTo("Portraits/Abigail")(此方法会自动处理各种格式差异)。
e.NamesWithoutLocale IReadOnlySet<IAssetName> 相当于e.Name,但不加本地化代码。例如,若e.NameData/Bundles.fr-FR,则此字段为Data/Bundles
#AssetReady 在素材文件被内容管线加载后、在应用了任何由AssetRequested给出的模组编辑后触发。

此事件仅在某些东西通过内容管线请求了素材的情况下触发。使内容缓存中的某个素材失效并不意味着会自动重新加载此素材。

事件参数:

参数 类型 描述
e.Name IAssetName 被加载的素材的名称,包括本地化代码(若有),例如Data/Bundles.fr-FR的本地化代码为.fr-FR。此参数包括一些用于解析素材值的工具方法,例如e.Name.IsEquivalentTo("Portraits/Abigail")(此方法会自动处理各种格式差异)。
e.NameWithoutLocale IAssetName 相当于e.Name,但不加本地化代码。例如,若e.NameData/Bundles.fr-FR,则此字段为Data/Bundles
#LocaleChanged 在游戏语言更改后触发。对于非英语玩家,在游戏切换到先前选择的语言时,此事件可能会在启动时触发。

事件参数:

参数 类型 描述
e.OldLanguage LanguageCode 游戏语言枚举类型的先前取值。对于自定义语言,此为LanguageCode.mod
e.OldLocale string 先前的本地化代码,即,素材名称中出现的本地化代码值,例如Maps/springobjects.fr-FRfr-FR。注意英语的本地化代码为空字符串。
e.NewLanguage LanguageCode 游戏语言枚举类型的新取值。参见OldLanguage的说明。
e.NewLocale string 本地化代码的新取值。参见OldLocale的说明。
  1. Asset propagation 是SMAPI的一个功能,用于在模组修改素材数据后自动将修改同步到游戏。

显示

this.Helper.Events.Display具有关于UI和关于在屏幕上绘图的事件。

事件 概述
#MenuChanged 在打开/关闭/替换游戏菜单时触发。

事件参数:

参数 类型 描述
e.NewMenu IClickableMenu 新的菜单实例(如果没有,则为null)。
e.OldMenu IClickableMenu 旧的菜单实例(如果没有,则为null)。
#Rendering 游戏在绘制新的一帧时触发此事件。确切地说,只要开启SpriteBatch即会触发此事件。调用此事件后,仍然可能开启和关闭SpriteBatch多次,但每帧只会调用一次此事件。此事件并不用于在屏幕上绘图,因为此事件绘制的所有图形会在随后绘制此帧时被覆盖。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch中。
#Rendered 游戏每帧绘制各SpriteBatch后触发此事件。确切地说,是在最后一个SpriteBatch即将渲染到屏幕上时触发此事件。由于游戏可能在同一帧内开启/关闭SpriteBatch多次,因此事件参数给出的SpriteBatch可能不会完全包含此帧的图形或已渲染的图形。而所有绘制到参数e.SpriteBatch的图形会置于所有原版内容(包括菜单、UI、光标)顶层。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
#RenderingWorld 在游戏世界即将绘制到屏幕时触发此事件。此事件并不用于在屏幕上绘图,因为游戏绘图会覆盖它的绘图。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
#RenderedWorld 在游戏世界被写入SpriteBatch后、但尚未绘制到屏幕时触发此事件。事件参数e.SpriteBatch中的图形将画在世界上层,但会画在菜单、用户界面图标或光标下层。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
#RenderingActiveMenu 在打开菜单后(即Game1.activeClickableMenu != null)、但尚未将此菜单绘制到屏幕时触发此事件。包括游戏的内部菜单,例如标题界面。事件参数e.SpriteBatch中的图形将被画在菜单下层。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
#RenderedActiveMenu 若打开一个菜单(即Game1.activeClickableMenu != null),则在此菜单加入SpriteBatch后、但尚未渲染到屏幕时触发此事件。事件参数e.SpriteBatch中的图形会出现在菜单和菜单光标上层。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
#RenderingHud 在将玩家UI(物品栏、时钟等)元素绘制到屏幕前触发此事件。此时原版游戏的UI可能被隐藏(例如,打开菜单)。事件参数e.SpriteBatch中的图形可能会出现在HUD下层。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
#RenderedHud 在在将玩家UI(物品栏、时钟等)元素绘制到SpriteBatch后、但尚未渲染到屏幕时触发此事件。此时原版游戏的UI可能被隐藏(例如,打开菜单)。事件参数e.SpriteBatch中的图形会出现在HUD上层。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
#WindowResized 更改游戏窗口尺寸时触发此事件。

事件参数:

参数 类型 描述
e.OldSize Point 先前的窗口宽度(e.OldSize.X)和高度(e.OldSize.Y)。
e.NewSize Point 新的窗口宽度(e.NewSize.X)和高度(e.NewSize.Y)。
#RenderingStep (专用) 在游戏渲染周期中的特定步骤之前触发。这提供了对渲染逻辑的更精细控制,但更容易受到游戏更新中变化的影响。如果可能的话,请考虑使用其他渲染事件之一。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
e.Step RenderSteps 该周期中待渲染的步骤。
#RenderedStep 专用 在游戏渲染周期中的特定步骤之后触发。这提供了对渲染逻辑的更精细控制,但更容易受到游戏更新中变化的影响。如果可能的话,请考虑使用其他渲染事件之一。

事件参数:

参数 类型 描述
e.SpriteBatch SpriteBatch 待绘制的SpriteBatch。您希望绘制在屏幕上的任何内容都可以被添加到此SpriteBatch。
e.Step RenderSteps 该周期中待渲染的步骤。

游戏循环

this.Helper.Events.GameLoop提供了与游戏的更新循环相关的事件。游戏的更新循环约运行60次/秒(即每秒经历60个时刻),以运行状态更改、动作处理等游戏逻辑。这些事件往往很实用,但应当考虑Input等语义事件适用的场景。

事件 概述
#GameLaunched 在游戏启动后触发。即,正好在第一个更新时刻前。此事件对于每个游戏会话仅触发1次(与加载存档无关)。所有的模组都会在该时间节点被加载和初始化,因此这是设置模组整合的好时机。
#UpdateTicking
UpdateTicked
在游戏状态更新前/后触发(1秒约为60个更新时刻)。

事件参数:

参数 类型 描述
e.Ticks int 自游戏开始经历的时刻数,包括当前时刻。
e.IsOneSecond bool 经历的时刻数e.TicksElapsed是否为60倍数,注意每秒约经历60个游戏时刻。
e.IsMultipleOf(int number) 返回bool方法 经历的时刻数e.TicksElapsed是否为给定数字的倍数。主要用于间歇地运行逻辑(例如e.IsMultipleOf(30)用于每半秒触发一次)。
#OneSecondUpdateTicking
OneSecondUpdateTicked
在游戏状态更新前/后触发,但每秒仅1次。

事件参数:

参数 类型 描述
e.Ticks int 自游戏开始经历的时刻数,包括当前时刻。
e.IsMultipleOf(int number) 返回bool方法 e.TicksElapsed是否为指定数字的倍数。主要用于间歇地运行罗逻辑(e.IsMultipleOf(120)用于每2秒触发一次)。
#SaveCreating
SaveCreated
在游戏创建存档文件前/后触发(在新存档的介绍部分之后)。在所有模组都处理完此事件之前,存档不会被写入。此事件在某种意义上是专用的,因为在此时间节点世界并没有完全初始化;在大多数情况下,您应当转而使用DayStartedSavingSaved事件。
#Saving
Saved
在游戏向存档文件写入数据前/后触发(除了存档初创时)。在所有模组都处理完此事件之前,存档不会被写入。此事件也会对多人游戏中的农场帮手触发。
#SaveLoaded 在加载存档后(包括创建新存档后的第一天),或连接到多人游戏后触发。此事件正好发生于DayStarted之前;在此时间节点存档文件会被读取,且Context.IsWorldReady为 true。

此事件在保存存档后并不会触发;若您希望在每日开始时做些什么,请另行参阅DayStarted

#DayStarted 在游戏中新的一天开始后触发,或在连接到多人游戏后触发。在此时间节点,所有事情都已初始化完毕。(欲在新的一天初始化之前运行代码,请另行参阅DayEnding instead。)
#DayEnding 在游戏结束当天前触发。具体而言,是发生在初始化次日和Saving之前。
#TimeChanged 在游戏内的时间变化时触发。即,每经过游戏中的10分钟即触发一次。

事件参数:

参数 类型 描述
e.OldTime int 26小时制的前一时间(例如1600就是16:00,而2600代表次日凌晨2时)。
e.NewTime int 26小时制的当前时间(例如1600就是16:00,而2600代表次日凌晨2时)。
#ReturnedToTitle 在游戏返回到标题界面后触发。

输入

this.Helper.Events.Input具有在玩家使用手柄、键盘或鼠标时触发的事件。可与Input API搭配使用以获取更多信息或抑制输入。 to access more info or suppress input.

事件 概述
#ButtonsChanged 在玩家按下/松开键盘、鼠标或手柄上的任意按钮时触发。这包括鼠标点击。若玩家同时按下/松开多个键,则仅触发一次。

事件参数:

参数 类型 描述
e.Pressed SButton[] 自前一时刻按下的按钮。
e.Held SButton[] 自前一时刻按住不放的按钮。
e.Released SButton[] 自前一时刻松开的按钮。
e.Cursor ICursorPosition 光标位置和抓取的地块。 注意: 模组不会接收聊天框的输入。
#ButtonPressed
ButtonReleased
在玩家按下/松开键盘、鼠标或手柄上的任意按钮时触发。这包括鼠标点击。若玩家同时按下/松开多个键,则仅触发一次。

事件参数:

参数 类型 描述
e.Button SButton 按下或松开的按钮。
e.Cursor ICursorPosition 光标位置和抓取的地块。
e.IsDown 返回bool方法 一个指示当前是否按下了指定按钮的方法。
e.IsSuppressed 返回bool方法 一个指示指定按钮是否被其他模组抑制的方法(若按钮被抑制,则游戏本身会忽略此按钮)。 注意: 模组不会接收聊天框的输入。
#CursorMoved 在玩家移动游戏内的光标时触发。

事件参数:

参数 类型 描述
e.OldPosition ICursorPosition 先前的光标位置和抓取地块。
e.NewPosition ICursorPosition 当前的光标位置和抓取地块。
#MouseWheelScrolled 在玩家滚动鼠标滚轮后触发。

事件参数:

参数 类型 描述
e.Position ICursorPosition 当前的鼠标位置和抓取地块。
e.Delta int 自上一更新后鼠标滚轮的滚动量。
e.OldValue
e.NewValue
int 先前的和当前的鼠标滚轮值,自游戏开始后累计。模组一般应当使用e.Delta参数而非此参数。

多人

this.Helper.Events.Multiplayer具有与多人消息和连接相关的事件。

事件 概述
#PeerContextReceived 在玩家接收到模组上下文后触发。此事件会对于包括房主和农场帮手在内的所有玩家触发,即使联机玩家未安装SMAPI。这也是消息能够通过SMAPI发送给玩家的最早时间点。

此事件会在游戏允许连接前立即触发,此时玩家尚未存在于游戏中。当连接到房主时,Game1.IsMasterGameContext.IsMultiplayer等上下文字段可能尚未设置;您可以检查e.Peer.IsHost以获悉当前玩家是否为农场帮手,因为连接时会最先收到此上下文。假设另外的模组不会阻止连接,此连接将在下一个时刻被允许。

事件参数:

参数 类型 描述
e.Peer IMultiplayerPeer 接收到其上下文的玩家(参见Multiplayer#Get connected player info)。
#PeerConnected 在游戏准许来自另一玩家的连接后触发。此事件会对于包括房主和农场帮手在内的所有玩家触发。此事件发生在PeerContextReceived之后。

在此时间点,玩家已连接到游戏,因此可以使用Game1.server.kick之类的方法。

事件参数:

参数 类型 描述
e.Peer IMultiplayerPeer 连接的玩家(参见Multiplayer#Get connected player info)。
#ModMessageReceived 在某个模组消息 通过网络被接受后触发。

事件参数:

参数 类型 描述
e.FromPlayerID long 发送此消息的玩家ID。
e.FromModID string 发送此消息的模组ID。
e.Type string 消息类型,用于判断此消息是否为欲处理的消息。消息类型不是全局唯一的,因此应当同时检查FromModID字段。
e.ReadAs<TModel>() 返回TModel方法 将消息数据读入给定的模型类型(例如e.ReadAs<MyMessageClass>()e.ReadAs<string>())。每次会返回一个新实例。
#PeerDisconnected 在与玩家的连接断开后触发。

事件参数:

参数 类型 描述
e.Peer IMultiplayerPeer 断开连接的玩家(参见Multiplayer#Get connected player info)。

玩家

this.Helper.Events.Player具有当玩家数据更改时触发的事件。

目前,这些事件仅对当前玩家触发。在未来的版本中,可能会修改此特性。因此,若仅希望处理当前玩家,请先检查e.IsLocalPlayer

事件 概述
#InventoryChanged 玩家背包增减物品时触发。

事件参数:

参数 类型 描述
e.Player Farmer 背包发生变化的玩家。
e.Added IEnumerable<Item> 新增的物品。
e.Removed IEnumerable<Item> 移除的物品。
e.QuantityChanged IEnumerable<ItemStackSizeChange> 堆叠数量改变的物品。每个ItemStackSizeChange实例包括 Item(受影响的物品)、OldSize(先前的堆叠数量)、NewSize(新的堆叠数量)。
e.IsLocalPlayer bool 受影响玩家是否为本地玩家。
#LevelChanged 在一个玩家技能等级发生变化后触发. 当玩家技能等级提升的第一时间立马触发 (不是在玩家睡觉后游戏提示时触发).

事件参数:

参数 类型 描述
e.Player Farmer 技能等级发生变化的玩家.
e.Skill SkillType 等级发生变化的技能. 像 SkillType.Combat (战斗技能)这种能调用 (int)e.Skill 转化成内部id的技能.
e.OldLevel int 变化之前的技能等级.
e.NewLevel int 变化之后的等级.
e.IsLocalPlayer bool 受影响玩家是否为本地玩家。
#Warped 当玩家移动到新地点的时候触发(农场到车站,玩家所在矿井层数发生变化也算).

事件参数:

参数 类型 描述
e.Player Farmer 地点发生变化的玩家.
e.OldLocation GameLocation 玩家移动之前所在的地点.
e.NewLocation GameLocation 玩家移动之后到达的地点.
e.IsLocalPlayer bool 受影响玩家是否为本地玩家。

世界

this.Helper.Events.World具有在游戏世界发生变化时触发的事件。

事件 概述
#LocationListChanged 在添加或移除地点后触发(包括建筑内部)。

事件参数:

参数 类型 描述
e.Added IEnumerable<GameLocation> 自上一更新时刻起添加的地点的列表。
e.Removed IEnumerable<GameLocation> 自上一更新时刻起移除的地点的列表。
#BuildingListChanged 在任何地点添加/移除建筑后触发。

对于和所在地点一并添加进游戏的建筑,此事件并不触发。若需处理此类建筑,请使用LocationListChanged并检查e.Added.OfType<BuildableGameLocation>()buildings

事件参数:

参数 类型 描述
e.Location GameLocation 发生变化的地点。
e.Added IEnumerable<Building> 自上一更新时刻起添加的建筑的列表。
e.Removed IEnumerable<Building> 自上一更新时刻起移除的建筑的列表。
e.IsCurrentLocation bool e.Location是否包含本地玩家。
#ChestInventoryChanged 在将物品加入/拿出箱子时触发。

事件参数:

参数 类型 描述
e.Chest Chest 发生变化的箱子。
e.Location Location 箱子所在地点。
e.Added IEnumerable<Item> 添加的物品。
e.Removed IEnumerable<Item> 移除的物品。
e.QuantityChanged IEnumerable<ItemStackSizeChange> 堆叠数量改变的物体。每个ItemStackSizeChange实例包括Item(受影响的物品)、OldSize(先前的堆叠数量)和NewSize(新的堆叠数量)。
#DebrisListChanged 在任何地点添加/移除掉落物后触发(包括掉落或生成的漂浮物)。

对于和所在地点一并添加进游戏的掉落物,此事件并不触发。若需处理此类掉落物,请使用LocationListChanged并检查e.Addeddebris

事件参数:

参数 类型 描述
e.Location GameLocation 发生变化的地点。
e.Added IEnumerable<Debris> 自上一更新时刻起,添加的掉落物的列表。
e.Removed IEnumerable<Debris> 自上一更新时刻起,移除的掉落物的列表。
e.IsCurrentLocation bool e.Location是否包含本地玩家。
#LargeTerrainFeatureListChanged 在任何地点添加/移除大型地形特征(例如灌木)后触发。

对于和所在地点一并添加进游戏的大型地形特征,此事件并不触发。若需处理此类地形特征,请使用LocationListChanged并检查e.AddedlargeTerrainFeatures

事件参数:

参数 类型 描述
e.Location GameLocation 发生变化的地点。
e.Added IEnumerable<LargeTerrainFeature> 自上一更新时刻起,添加的大型地形特征的列表。
e.Removed IEnumerable<LargeTerrainFeature> 自上一更新时刻起,移除的大型地形特征的列表。A list of large terrain features removed since the last update tick.
e.IsCurrentLocation bool e.Location是否包含本地玩家。
#NpcListChanged 在任何地点添加/移除NPC后触发(包括村民、马、祝尼魔、怪物和宠物)。

对于和所在地点一并添加进游戏的NPC,此事件并不触发。若需处理此类NPC,请使用LocationListChanged并检查e.Addedcharacters

事件参数:

参数 类型 描述
e.Location GameLocation 发生变化的地点。
e.Added IEnumerable<NPC> 自上一更新时刻起添加的村民的列表。
e.Removed IEnumerable<NPC> 自上一更新时刻起移除的村民的列表。
e.IsCurrentLocation bool e.Location是否包含本地玩家。
#ObjectListChanged 在任意地点添加/移除物体后触发(包括机器、家具、围栏等)。对于漂浮物,请另见DebrisListChanged

对于和所在地点一并添加进游戏的物体,此事件并不触发。若需处理此类物体,请使用LocationListChanged并检查e.Addedobjects

事件参数:

参数 类型 描述
e.Location GameLocation 发生变化的地点。
e.Added IEnumerable<KeyValuePair<Vector2, Object>> 自上一更新时刻起,“地块坐标 + 添加的物体”参数对的列表。
e.Removed IEnumerable<KeyValuePair<Vector2, Object>> 自上一更新时刻起,“地块坐标 + 移除的物体”参数对的列表。
e.IsCurrentLocation bool e.Location是否包含本地玩家。
#TerrainFeatureListChanged 在任何地点添加/移除地形特征后触发(包括树、耕地、地板等)。对于灌木,另请参阅LargeTerrainFeatureListChanged

对于和所在地点一并添加进游戏的地形特征,此事件并不触发。若需处理此类地形特征,请使用LocationListChanged并检查e.AddedterrainFeatures

事件参数:

参数 类型 描述
e.Location GameLocation 发生变化的地点。
e.Added IEnumerable<KeyValuePair<Vector2, TerrainFeature>> 自上一更新时刻起,“地块坐标 + 添加的地形特征”参数对的列表。
e.Removed IEnumerable<KeyValuePair<Vector2, TerrainFeature>> 自上一更新时刻起,“地块坐标 + 移除的地形特征”参数对的列表。
e.IsCurrentLocation bool e.Location是否包含本地玩家。

特殊

this.Helper.Events.Specialised 针对特殊情况的事件. 大多数模组不应使用这些功能

事件 概述
#LoadStageChanged 当游戏加载过程中低级阶段发生变化时,会触发此事件,以供需要在加载过程特定时间点运行代码的模组使用。未来版本中,可用的阶段或它们发生的时间可能会在没有警告的情况下发生变化(例如,由于游戏加载过程的更改),因此使用此事件的模组更容易出错或产生漏洞。大多数模组应使用游戏循环事件代替。

事件参数:

参数 类型 描述
e.NewStage LoadStage 新的加载阶段。包括:
  • None:完全未加载存档(例如,玩家在标题界面)。
  • 创建新存档:
    • CreatedBasicInfo:游戏初始化了基本存档信息。
    • CreatedInitialLocations:游戏添加了地点实例,但尚未充分初始化它们。
    • CreatedLocations:游戏初始化了游戏内地点。
    • CreatedSaveFile:游戏创建了物理存档。
  • 加载存档:
    • SaveParsed:游戏已将原始存档数据读入StardewValley.SaveGame.loaded。不适用于多人游戏中连接到房主的情形。等价于StardewValley.SaveGame.getLoadEnumerator的值为20。
    • SaveLoadedBasicInfo:游戏应用了基础存档数据(包括玩家数据)。不适用于多人游戏中连接到房主的情形。注意此时间节点上,某些基础信息(例如每日运气)尚未初始化。等价于StardewValley.SaveGame.getLoadEnumerator的值为36。
    • SaveAddedLocations:游戏将地点实例添加到游戏中,但尚未还原其存档数据。
    • SaveLoadedLocations:游戏已经还原了地点数据。不适用于多人游戏中连接到房主的情形。等价于StardewValley.SaveGame.getLoadEnumerator的值为50。
  • Preloaded:已从存档文件中加载最后的元数据。这发生在游戏应用问题修复、检查成就、播放音乐等之前。不适用于多人游戏中连接到房主的情形。
  • Loaded:存档被完全加载,但此时世界可能尚未完全初始化。
  • Ready:存档被完全加载,世界已被初始化,且Context.IsWorldReady当前为true。
  • ReturningToTitle:游戏正在退出当前存档并返回到标题界面。发生在返回空标题界面前;已返回标题界面时对应的游戏阶段为None
e.OldStage LoadStage 前一加载阶段。参见e.NewStage的注释。
#UnvalidatedUpdateTicking
UnvalidatedUpdateTicked
该事件在游戏状态更新前/后被触发(大约每秒60次),且不受SMAPI正常验证的限制。此事件不是线程安全的,可能会在游戏逻辑异步运行时被调用。在此方法中更改游戏状态可能会导致游戏崩溃或损坏正在进行中的存档。除非您完全了解您的代码将在何种上下文中运行,否则不要使用此事件。使用此事件将在SMAPI控制台中触发警告。

事件参数:

参数 类型 描述
e.Ticks int 自游戏启动时经历的时刻数,包括当前时刻。
e.IsOneSecond bool e.TicksElapsed是否为60的倍数。注意60时刻约等于1秒。
e.IsMultipleOf(int number) method returns bool e.TicksElapsed是否为指定数字的倍数。主要用于间歇地运行逻辑(例如e.IsMultipleOf(30)约每半秒触发一次)。

进阶

变化监控

您可能希望处理一个没有对应事件的变化(例如,一个游戏内事件结束,一封信被添加到信箱中等)。通常,您可以通过处理一个通用事件来实现这一点(如UpdateTicked),并检测您关注的值何时发生了变化。例如,下面是一个完整的模组,它会在游戏内事件结束时记录一条消息:

/// <summary>The main entry point for the mod.</summary>
public class ModEntry : Mod
{
    /*********
    ** Fields
    *********/
    /// <summary>The in-game event detected on the last update tick.</summary>
    private Event LastEvent;


    /*********
    ** Public methods
    *********/
    /// <summary>The mod entry point, called after the mod is first loaded.</summary>
    /// <param name="helper">Provides simplified APIs for writing mods.</param>
    public override void Entry(IModHelper helper)
    {
        helper.Events.GameLoop.UpdateTicked += this.OnUpdateTicked;
    }


    /*********
    ** Private methods
    *********/
    /// <summary>The method invoked when the game updates its state.</summary>
    /// <param name="sender">The event sender.</param>
    /// <param name="e">The event arguments.</param>
    private void OnUpdateTicked(object sender, EventArgs e)
    {
        if (this.LastEvent != null && Game1.CurrentEvent == null)
            this.Monitor.Log($"Event {this.LastEvent.id} just ended!");

        this.LastEvent = Game1.CurrentEvent;
    }
}

自定义优先级

SMAPI 默认按照事件处理器被注册的顺序调用它们,因此每次都会先调用最先注册的事件处理器。然而,这并不总是可预测的,因为它取决于模组的加载顺序以及每个模组注册其处理器的时间。此外,这个顺序也是“实现细节”的一部分,因此并不保证必然如此。

若需控制顺序,可以使用[EventPriority]属性来指定事件优先级,包括:Low(后于大部分处理器)、DefaultHigh (先于大部分处理器)或自定义值(例如High + 1优先于High)。请仅在必需时使用;不同模组间的处理器顺序是不可靠的(例如,其他模组也可能改变自己的优先级)。

/// <summary>The main entry point for the mod.</summary>
public class ModEntry : Mod
{
    /*********
    ** Public methods
    *********/
    /// <summary>The mod entry point, called after the mod is first loaded.</summary>
    /// <param name="helper">Provides simplified APIs for writing mods.</param>
    public override void Entry(IModHelper helper)
    {
        helper.Events.GameLoop.UpdateTicked += this.OnUpdateTicked;
    }


    /*********
    ** Private methods
    *********/
    /// <summary>The method invoked when the game updates its state.</summary>
    /// <param name="sender">The event sender.</param>
    /// <param name="e">The event arguments.</param>
    [EventPriority(EventPriority.High)]
    private void OnUpdateTicked(object sender, EventArgs e)
    {
        this.Monitor.Log("Update!");
    }
}