第1行: |
第1行: |
| ← [[模组:目录|目录]] | | ← [[模组:目录|目录]] |
| {{stub}} | | {{stub}} |
− | {{翻译}}
| |
| | | |
− | 此页面展示了制作SMAPI模组时常 见 的 任务 。在阅读时,请结合参考'''[[模组:创建 SMAPI 模组|模组制作入门]]'''和'''[[模组:制作指南/游戏基本架构|游戏基本架构]]'''。 | + | 此页面展示了制作SMAPI模组时常 用功能 的 构建方法 。在阅读时,请结合参考'''[[模组:创建 SMAPI 模组|模组制作入门]]'''和'''[[模组:制作指南/游戏基本架构|游戏基本架构]]'''。 |
| | | |
| ==基础技巧== | | ==基础技巧== |
第9行: |
第8行: |
| 在写模组时,你可能常常需要了解一个值的变化(什么时候变,变化前后的值分别是多少,等等)。如果该值没有包括在SMAPI内置的'''事件''' (event)中,那么你可以为该值创建一个私有变量,然后在SMAPI的update tick事件中刷新此变量,以达到追踪值变化的目的。 | | 在写模组时,你可能常常需要了解一个值的变化(什么时候变,变化前后的值分别是多少,等等)。如果该值没有包括在SMAPI内置的'''事件''' (event)中,那么你可以为该值创建一个私有变量,然后在SMAPI的update tick事件中刷新此变量,以达到追踪值变化的目的。 |
| | | |
− | 示例见:[模组:制作指南/APIs/Events#变化监控] | + | 示例见:[[模组:制作指南/APIs/Events#变化监控]]。 |
| | | |
| ==物品 (Items)== | | ==物品 (Items)== |
第87行: |
第86行: |
| | | |
| ==玩家 (Player)== | | ==玩家 (Player)== |
− | ===自定义 精灵 (Custom Sprite)=== | + | ===自定义 贴图 (Custom Sprite)=== |
| | | |
| ===位置 (Position)=== | | ===位置 (Position)=== |
| 角色(Character) 的位置(Position) 表示他在当前地点(Location) 的坐标。 | | 角色(Character) 的位置(Position) 表示他在当前地点(Location) 的坐标。 |
| ====相对于地图 (Map)==== | | ====相对于地图 (Map)==== |
− | 每个地点(location) 都有一个对应的xTile地图(map)。如果以像素(pixel) 为单位,地图左上角坐标代表''(0, 0)'',坐下角则代表<tt>(location.Map.DisplayWidth, location.Map.DisplayHeight)</tt>。 | + | 每个地点(location) 都有一个对应的xTile地图(map)。如果以像素(pixel) 为单位,地图左上角坐标代表''(0, 0)'',坐下角则代表<samp>(location.Map.DisplayWidth, location.Map.DisplayHeight)</samp>。 |
| 角色在当前地点的位置有两种表达方式: | | 角色在当前地点的位置有两种表达方式: |
| * 以像素(pixel) 为单位的绝对(absoulte) 坐标:<code>Position.X</code>与<code>Position.Y</code>。 | | * 以像素(pixel) 为单位的绝对(absoulte) 坐标:<code>Position.X</code>与<code>Position.Y</code>。 |
第126行: |
第125行: |
| | | |
| 格式: '''''具体操作''''':'''''(往)目标文件夹或文件名''''' | | 格式: '''''具体操作''''':'''''(往)目标文件夹或文件名''''' |
− | * 添加新文件:<tt>Characters\Dialogue\<文件名></tt> | + | * 添加新文件:<samp>Characters\Dialogue\<文件名></samp> |
− | * 添加新文件:<tt>Characters\schedules\<文件名></tt> | + | * 添加新文件:<samp>Characters\schedules\<文件名></samp> |
− | * 添加新文件:<tt>Portraits\<文件名></tt> | + | * 添加新文件:<samp>Portraits\<文件名></samp> |
− | * 添加新文件:<tt>Characters\<文件名></tt> | + | * 添加新文件:<samp>Characters\<文件名></samp> |
− | * 在已有文件中添加新的条目:<tt>Data\EngagementDialogue</tt>(可结婚NPC) | + | * 在已有文件中添加新的条目:<samp>Data\EngagementDialogue</samp>(可结婚NPC) |
− | * 在已有文件中添加新的条目:<tt>Data\NPCDispositions</tt> | + | * 在已有文件中添加新的条目:<samp>Data\NPCDispositions</samp> |
− | * 在已有文件中添加新的条目:<tt>Data\NPCGiftTastes</tt> | + | * 在已有文件中添加新的条目:<samp>Data\NPCGiftTastes</samp> |
− | * 在已有文件中添加新的条目:<tt>Characters\Dialogue\rainy</tt> | + | * 在已有文件中添加新的条目:<samp>Characters\Dialogue\rainy</samp> |
− | * 在已有文件中添加新的条目:<tt>Data\animationDescriptions</tt>(在行程(schedule) 中增加自定义的动画) | + | * 在已有文件中添加新的条目:<samp>Data\animationDescriptions</samp>(在行程(schedule) 中增加自定义的动画) |
| | | |
| 以上所有的操作都可以通过<code>IAssetLoaders</code>/<code>IAssetEditors</code>或者<code>Content Patcher</code>做到。 | | 以上所有的操作都可以通过<code>IAssetLoaders</code>/<code>IAssetEditors</code>或者<code>Content Patcher</code>做到。 |
第221行: |
第220行: |
| </syntaxhighlight> | | </syntaxhighlight> |
| | | |
− | 如果你想要自定义菜单,请继承自<tt>IClickableMenu</tt>,将对象分配给<tt>Game1.activeClickableMenu</tt>。 | + | 如果你想要自定义菜单,请继承自<samp>IClickableMenu</samp>,将对象分配给<samp>Game1.activeClickableMenu</samp>。 |
− | 一个菜单基本上重写了一些方法,如<tt>draw</tt>、<tt>receiveLeftClick </tt>等等。 | + | 一个菜单基本上重写了一些方法,如<samp>draw</samp>、<samp>receiveLeftClick </samp>等等。 |
− | <tt>draw</tt>方法绘制屏幕上的元素;<tt>receiveLeftClick </tt>方法处理左键点击事件。 | + | <samp>draw</samp>方法绘制屏幕上的元素;<samp>receiveLeftClick </samp>方法处理左键点击事件。 |
− | 你通常可以使用一些游戏封装好的类作为菜单元素,如<tt>ClickableTextureButton</tt>。 | + | 你通常可以使用一些游戏封装好的类作为菜单元素,如<samp>ClickableTextureButton</samp>。 |
| | | |
| 这里提供了一个[https://github.com/janavarro95/Stardew_Valley_Mods/blob/master/GeneralMods/HappyBirthday/Framework/BirthdayMenu.cs 简单的例子],这是[https://www.nexusmods.com/stardewvalley/mods/520 Birthday Mod]的菜单。 | | 这里提供了一个[https://github.com/janavarro95/Stardew_Valley_Mods/blob/master/GeneralMods/HappyBirthday/Framework/BirthdayMenu.cs 简单的例子],这是[https://www.nexusmods.com/stardewvalley/mods/520 Birthday Mod]的菜单。 |
第241行: |
第240行: |
| Game1.activeClickableMenu = new DialogueBox(message); | | Game1.activeClickableMenu = new DialogueBox(message); |
| </syntaxhighlight> | | </syntaxhighlight> |
| + | |
| + | ==邮件 (Mail)== |
| + | 如果您不熟悉 SMAPI 或一般对 Stardew Valley 进行改装,向玩家的邮箱发送一封简单的信件是开始学习之旅的好地方。 您将接触到一些简单易懂的代码和概念,并以有形的游戏内信件的形式获得一些即时的满足,您可以在实际操作中看到这些信件。 如果本节中的示例不足,有很多人可以在 Discord 频道上为您提供帮助。 |
| + | ===概述=== |
| + | 在您实际向播放器发送任何您自己的自定义邮件之前,您必须决定您的信件将如何撰写。 我的意思是,您的信件是静态的 - 总是相同的文本 - 还是动态的 - 文本会根据可变信息而变化? 显然,静态信件更容易实现,所以如果你刚刚开始,现在就走这条路。 然而,静态和动态方法都在下面解释。 |
| + | |
| + | 要发送静态或动态邮件,您首先必须让 Stardew Valley 知道您的内容,也称为Aeest。 对于邮件,您必须将添加内容注入邮件数据。 您可以通过 IAssetEditor 界面完成此操作。 您可以从 ModEntry 类实现 IAssetEditor,或者创建一个单独的类来实现 IAssetEditor 以将新邮件内容注入“Data\Mail.xnb”。 为了清楚起见、易于重用和封装,下面引用的示例使用后一种方法: |
| + | ===注入静态内容 (Inject static content)=== |
| + | 大多数情况下,一个静态的、预定义的信件就足够了,无论您是否包含附件(即对象、金钱等)。 “静态”只是意味着您不需要在发送信件之前输入文本后更改文本。 “静态”信件在游戏中始终可用(除非您将其从模组中移除或模组被玩家移除),这意味着如果玩家退出且您的信件仍在邮箱中,则该信件仍然可用稍后回来玩。这可能是“动态”字母的问题,如该部分更详细的解释,因此请尽可能使用“静态”内容。 |
| + | |
| + | 您可以使用“@”轻轻地引用播放器的名称,但其他可能在对话文本中起作用的替换代码,如 %pet 或 %farm,目前在静态邮件内容中不起作用。但是,您可以使用一些在字母中显示图标的特殊字符,例如“=”,将显示一个紫色的星星,“<”,将显示一个粉红色的心形,“$”,将显示替换为金币,“>”将显示向右箭头,“`”将显示向上箭头,“+”将显示骑着滑板的头(也许?)。可能还有其他尚未记录的特殊情况。 |
| + | |
| + | 下面的示例将 4 个字母添加到邮件数据集合中。请注意,下面的代码不会向玩家发送任何信件,而只是将它们提供给 Stardew Valley 游戏,以便它们可以发送。 |
| + | <syntaxhighlight lang="c#"> |
| + | using StardewModdingAPI; |
| + | |
| + | namespace MyMod |
| + | { |
| + | public class MyModMail : IAssetEditor |
| + | { |
| + | public MyModMail() |
| + | { |
| + | } |
| + | |
| + | public bool CanEdit<T>(IAssetInfo asset) |
| + | { |
| + | return asset.AssetNameEquals("Data\\mail"); |
| + | } |
| + | |
| + | public void Edit<T>(IAssetData asset) |
| + | { |
| + | var data = asset.AsDictionary<string, string>().Data; |
| + | |
| + | // "MyModMail1" is referred to as the mail Id. It is how you will uniquely identify and reference your mail. |
| + | // The @ will be replaced with the player's name. Other items do not seem to work (''i.e.,'' %pet or %farm) |
| + | // %item object 388 50 %% - this adds 50 pieces of wood when added to the end of a letter. |
| + | // %item money 250 601 %% - this sends a random amount of gold from 250 to 601 inclusive. |
| + | // %item cookingRecipe %% - this is for recipes (did not try myself) Not sure how it know which recipe. |
| + | data["MyModMail1"] = "Hello @... ^A single carat is a new line ^^Two carats will double space."; |
| + | data["MyModMail2"] = "This is how you send an existing item via email! %item object 388 50 %%"; |
| + | data["MyModMail3"] = "Coin $ Star = Heart < Dude + Right Arrow > Up Arrow `"; |
| + | data["MyWizardMail"] = "Include Wizard in the mail Id to use the special background on a letter"; |
| + | } |
| + | } |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | ===客户端调用静态内容 (Send a letter with static content)=== |
| + | 例如,要在您自己的项目中使用此类,从而使静态邮件数据可用,请挂入 OnGameLaunch 事件。 |
| + | |
| + | |
| + | <syntaxhighlight lang="c#"> |
| + | /// <summary> |
| + | /// Fires after game is launched, right before first update tick. Happens once per game session (unrelated to loading saves). |
| + | /// All mods are loaded and initialized at this point, so this is a good time to set up mod integrations. |
| + | /// </summary> |
| + | private void OnGameLaunched(object sender, GameLaunchedEventArgs e) |
| + | { |
| + | Helper.Content.AssetEditors.Add(new MyModMail()); |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | Now that you have your letter loaded, it's time to send it to the player. There are a couple different methods available to accomplish this as well, depending on your need. Two examples are shown below. The distinction between the two methods will be explained below: |
| + | |
| + | <syntaxhighlight lang="c#"> |
| + | Game1.player.mailbox.Add("MyModMail1"); |
| + | Game1.addMailForTomorrow("MyModMail2"); |
| + | </syntaxhighlight> |
| + | |
| + | 第一种方法(Game1.player.mailbox.Add)将信件直接添加到当天的邮箱中。例如,这可以在您的“DayStaring”事件代码中完成。即使在保存后,直接添加到邮箱的邮件也不会被“记住”为已发送。根据您的需要,这在某些情况下很有用。 |
| + | |
| + | 第二种方法(Game1.addMailForTomorrow),顾名思义,会在第二天将信件添加到玩家的邮箱中。此方法会记住发送的邮件 (Id),从而可以不一遍又一遍地发送相同的信件。这可以根据您的需要在“DayStaring”、“DayEnding”或其他事件中处理。 |
| + | |
| + | 您可以将信件直接放入邮箱,并使用 mailRecieved 集合记住它。如果您希望在使用直接添加到邮箱方法时记住它,您可以简单地手动添加您的 mailId。 |
| + | |
| + | 如果您希望 Stardew Valley 忘记已经发送了特定的信件,您可以将其从 mailReceived 集合中删除。如果您需要批量删除邮件,您也可以使用 foreach 遍历集合。 |
| + | |
| + | 这就是发送一封简单的信件的全部内容。 附上物品和通过信件寄钱很简单,但发送食谱更复杂,以后需要一些额外的解释。 |
| + | ===注入动态内容 (Inject dynamic content)=== |
| + | 如果您想发送一封包含需要根据情况更改的数据的信件,例如今天吃的紫色蘑菇的数量,那么您必须在每次计划发送时创建该信件内容,特别是如果您想要一个 最新值。 这就是我所说的“动态”字母。 |
| + | |
| + | 考虑以下源代码,它基本上是上述静态邮件类的增强版本,也将支持“动态”内容。 您当然可以始终使用此代码的增强版本,除非需要,否则不要使用动态内容。 代码被分开是为了说明差异。 |
| + | |
| + | <syntaxhighlight lang="c#"> |
| + | using StardewModdingAPI; |
| + | using System.Collections.Generic; |
| + | |
| + | namespace MyMail |
| + | { |
| + | public class MailData : IAssetEditor |
| + | { |
| + | // This collection holds any letters loaded after the initial load or last cache refresh |
| + | private Dictionary<string, string> dynamicMail = new Dictionary<string, string>(); |
| + | |
| + | public MailData() |
| + | { |
| + | } |
| + | |
| + | public bool CanEdit<T>(IAssetInfo asset) |
| + | { |
| + | return asset.AssetNameEquals("Data\\mail"); |
| + | } |
| + | |
| + | public void Edit<T>(IAssetData asset) |
| + | { |
| + | var data = asset.AsDictionary<string, string>().Data; |
| + | |
| + | // This is just an example |
| + | data["StaticMail"] = "If there were any letters with static content they could be placed here."; |
| + | |
| + | // Inject any mail that was added after the initial load. |
| + | foreach (var item in dynamicMail) |
| + | { |
| + | data.Add(item); |
| + | } |
| + | |
| + | dynamicMail.Clear(); // For the usage of this MOD the letters are cleared |
| + | } |
| + | |
| + | /// <summary> |
| + | /// Add a new mail asset into the collection so it can be injected by the next cache refresh. The letter will |
| + | /// not be available to send until the cache is invalidated in the code. |
| + | /// </summary> |
| + | /// <param name="mailId">The mail key</param> |
| + | /// <param name="mailText">The mail text</param> |
| + | public void Add(string mailId, string mailText) |
| + | { |
| + | if (!string.IsNullOrEmpty(mailId)) |
| + | { |
| + | if (dynamicMail.ContainsKey(mailId)) |
| + | dynamicMail[mailId] = mailText; |
| + | else |
| + | dynamicMail.Add(mailId, mailText); |
| + | } |
| + | } |
| + | } |
| + | } |
| + | |
| + | </syntaxhighlight> |
| + | |
| + | 您会注意到用于静态邮件和动态邮件的代码实际上几乎没有区别。支持动态邮件的类有一个私有字典集合,用于保存等待注入的任何邮件内容。本来可以公开以允许将邮件直接添加到集合中,但这不是一个好的做法。相反,提供了一个公共 Add 方法,以便可以将邮件发送到集合。此代码适用于特定的 MOD,而不是健壮的框架,因此不会过分关注错误处理。您可以根据自己的需要进行改进。 |
| + | |
| + | 请注意 Edit 方法中的附加代码,其中 dynamicMail 集合中的任何邮件都被注入到 Stardew Valley 的内容中。第一次加载MOD时(在这种情况下),dynamicMail集合中将没有邮件。如果在原始加载后添加邮件,则必须通过使缓存无效来重新加载内容。更多细节请参考[[Modding:Modder_Guide/APIs/Content#Cache invalidation|Cache invalidation]]。 |
| + | |
| + | ===客户端调用动态内容 (Send a letter with dynamic content)=== |
| + | 例如,要在您自己的项目中使用此类,从而使动态邮件可用,请挂入 OnGameLaunch 事件。 |
| + | |
| + | <syntaxhighlight lang="c#"> |
| + | // Make this available to other methods in the class to access |
| + | private MailData mailData = new MailData(); |
| + | |
| + | /// <summary> |
| + | /// Fires after game is launched, right before first update tick. Happens once per game session (unrelated to loading saves). |
| + | /// All mods are loaded and initialized at this point, so this is a good time to set up mod integrations. |
| + | /// </summary> |
| + | private void OnGameLaunched(object sender, GameLaunchedEventArgs e) |
| + | { |
| + | Helper.Content.AssetEditors.Add(mailData); |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | 您可以连接到其他事件,例如“开始日期”或“结束日期”以生成要发送的信件。 考虑这个简单的例子,它仅用于说明目的。 |
| + | |
| + | <syntaxhighlight lang="c#"> |
| + | private void OnDayStarting(object sender, DayStartedEventArgs e) |
| + | { |
| + | string mailMessage = $"@, you have gathered {Game1.stats.rabbitWoolProduced} units of rabbit wool!"; |
| + | |
| + | mailData.Add("MyModMailWool", mailMessage); // Add this new letter to the mail collection (for next refresh). |
| + | |
| + | Game1.mailbox.Add("MyModMailWool"); // Add to mailbox and we don't need to track it |
| + | |
| + | modHelper.Content.InvalidateCache("Data\\mail"); // (modHelper was assigned in ModEntry for use throughout the class) |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | 此示例格式化一封信,显示最新的兔毛计数,使其可用于邮件收集,将该信放入邮箱,然后使缓存无效,以便在缓存刷新期间注入此新信.在这种情况下,不需要记住 mailId,因为每次需要发送信件时都会重新创建该信件,在本例中是每天。同样,此代码仅用于说明该概念。 |
| + | |
| + | 在以这种简单的方式注入邮件时,需要了解一个重要的警告。可用的各种邮件框架处理这个问题,本节将被扩展以解释如何解决这个问题,但在此处进行介绍是为了确保您完全了解 MOD 如何与 Stardew Valley 和 SMAPI 一起工作。 |
| + | |
| + | 如果添加动态信件并在Day Ending 的内容中注入它,显然您必须添加明天显示的邮件。这意味着游戏将通过对邮箱中待处理的动态信件(在本例中为“MyMailModWool”)的引用来保存。如果玩家此时退出游戏并稍后返回继续玩,则该动态字母不可用,从而导致“幻影字母”。邮箱将显示一封可用的信件,但单击时不会显示任何内容。这可以通过多种方式处理,包括保存自定义字母并在玩家继续时加载它们,但同样,此示例代码尚未涵盖这一点。这就是该示例使用 On Day Start 并立即使信件可用的原因。 |
| + | |
| + | ==其他 (Other)== |
| + | ===简单动画(animation) 的添加=== |
| + | <syntaxhighlight lang='c#'> |
| + | location.temporarySprites.Add(new TemporaryAnimatedSprite(...)); |
| + | </syntaxhighlight> |
| + | 详见<samp>TemporaryAnimatedSprite</samp>类。 |
| + | |
| + | ===播放一段声音=== |
| + | <syntaxhighlight lang='c#'> |
| + | location.playSound("SOUND"); // "SOUND"为声音的名字 |
| + | </syntaxhighlight> |
| + | 详见[https://docs.google.com/spreadsheets/d/1CpDrw23peQiq-C7F2FjYOMePaYe0Rc9BwQsj3h6sjyo/edit#gid=239695361 声音名字一览]。 |
| + | |
| + | ==开源 (Open Source)== |
| + | 详见[[模组:模组兼容性|模组大全]]中'''源代码(source)'''一栏。 |
| | | |
| [[en:Modding:Common tasks]] | | [[en:Modding:Common tasks]] |
| [[ru:Модификации:Основные возможности]] | | [[ru:Модификации:Основные возможности]] |