模组:常用方法

来自Stardew Valley Wiki
Horizon98讨论 | 贡献2021年6月24日 (四) 15:57的版本 (更新common tasks的翻译)
跳到导航 跳到搜索

目录

Robin building.png
“我这里还有很多事情需要处理。”
— 罗宾

不完整的翻译

本文或部分尚未完全翻译成中文。 欢迎您通过编辑帮助其建设。
最后编辑Horizon98于2021-06-24 15:57:39.

此页面展示了制作SMAPI模组时常用功能的构建方法。在阅读时,请结合参考模组制作入门游戏基本架构

基础技巧

追踪一个值的变化

在写模组时,你可能常常需要了解一个值的变化(什么时候变,变化前后的值分别是多少,等等)。如果该值没有包括在SMAPI内置的事件 (event)中,那么你可以为该值创建一个私有变量,然后在SMAPI的update tick事件中刷新此变量,以达到追踪值变化的目的。

示例见:模组:制作指南/APIs/Events#变化监控

物品 (Items)

物品 代表那些能够放在背包里的东西,比如说工具、农作物等等。

创建一个物品 (Object) 的实例

Object中所有的构造函数:

 public Object(Vector2 tileLocation, int parentSheetIndex, int initialStack);
 public Object(Vector2 tileLocation, int parentSheetIndex, bool isRecipe = false);
 public Object(int parentSheetIndex, int initialStack, bool isRecipe = false, int price = -1, int quality = 0);
 public Object(Vector2 tileLocation, int parentSheetIndex, string Givenname, bool canBeSetDown, bool canBeGrabbed, bool isHoedirt, bool isSpawnedObject);

参数parentSheetIndex表示该物品的ID(储存在 ObjectInformation.xnb 文件中)。

在地上生成物品

public virtual bool dropObject(Object obj, Vector2 dropLocation, xTile.Dimensions.Rectangle viewport, bool initialPlacement, Farmer who = null);

 // 调用:
 Game1.getLocationFromName("Farm").dropObject(new StardewValley.Object(itemId, 1, false, -1, 0), new Vector2(x, y) * 64f, Game1.viewport, true, (Farmer)null);

添加物品到背包 (Inventory)

// You can add items found in ObjectInformation using:
    Game1.player.addItemByMenuIfNecessary((Item)new StardewValley.Object(int parentSheetIndex, int initialStack, [bool isRecipe = false], [int price = -1], [int quality = 0]));

例2:

// Add a weapon directly into player's inventory
    const int WEAP_ID = 19;                  // Shadow Dagger -- see Data/weapons
    Item weapon = new MeleeWeapon(WEAP_ID);  // MeleeWeapon is a class in StardewValley.Tools
    Game1.player.addItemByMenuIfNecessary(weapon);

    // Note: This code WORKS.

从背包移除物品

取决于你背包的具体情况。很少有情况需要你亲自来调用,因为相关的方法在Farmer类中已经有了。

在大多数情况下,仅需调用 .removeItemFromInventory(Item) 方法。

地点 (Locations)

游戏基本架构#地点

获取所有地点

Game1.locations属性中虽然储存着主要的地点,但是不包括建筑的室内(constructed building interiors)。以下这个方法提供了主玩家的所有地点。

/// <summary>Get all game locations.</summary>
public static IEnumerable<GameLocation> GetLocations()
{
    return Game1.locations
        .Concat(
            from location in Game1.locations.OfType<BuildableGameLocation>()
            from building in location.buildings
            where building.indoors.Value != null
            select building.indoors.Value
        );
}

遍历:

foreach (GameLocation location in this.GetLocations())
{
   // ...
}

注意:在联机模式中,客机是拿不到上述所有地点的。要解决这一问题,见获取有效的地点

编辑地图

模组:地图数据

玩家 (Player)

自定义贴图 (Custom Sprite)

位置 (Position)

角色(Character) 的位置(Position) 表示他在当前地点(Location) 的坐标。

相对于地图 (Map)

每个地点(location) 都有一个对应的xTile地图(map)。如果以像素(pixel) 为单位,地图左上角坐标代表(0, 0),坐下角则代表(location.Map.DisplayWidth, location.Map.DisplayHeight)。 角色在当前地点的位置有两种表达方式:

  • 以像素(pixel) 为单位的绝对(absoulte) 坐标:Position.XPosition.Y
  • 以图块(tile) 为单位的图块(tile) 坐标:getTileX()getTileY()

常量Game1.tileSize规定,游戏内每个图块(tile) 大小为64x64像素。于是有以下单位换算:

// 绝对坐标 → 图块坐标
Math.Floor(Game1.player.Position.X / Game1.tileSize)
Math.Floor(Game1.player.Position.Y / Game1.tileSize)

// 图块坐标 → 绝对坐标
Game1.player.getTileX() * Game1.tileSize
Game1.player.getTileY() * Game1.tileSize

// 地图大小(以图块为单位)
Math.Floor(Game1.player.currentLocation.Map.DisplayWidth / Game1.tileSize)
Math.Floor(Game1.player.currentLocation.Map.DisplayHeight / Game1.tileSize)

相对于视野 (Viewport)

视野、视口、视窗(Viewport) 代表在当前屏幕上的区域。若以像素计算,其宽高应该与游戏的屏幕分辨率相等,分别为Game1.viewport.WidthGame1.viewport.Height

玩家相对于视野的位置(像素)可表示为:

Game1.player.Position.X - Game1.viewport.X
Game1.player.Position.Y - Game1.viewport.Y

NPC

自定义NPC

想要自定义NPC,你得修改或添加以下文件:

格式: 具体操作(往)目标文件夹或文件名

  • 添加新文件:Characters\Dialogue\<文件名>
  • 添加新文件:Characters\schedules\<文件名>
  • 添加新文件:Portraits\<文件名>
  • 添加新文件:Characters\<文件名>
  • 在已有文件中添加新的条目:Data\EngagementDialogue(可结婚NPC)
  • 在已有文件中添加新的条目:Data\NPCDispositions
  • 在已有文件中添加新的条目:Data\NPCGiftTastes
  • 在已有文件中添加新的条目:Characters\Dialogue\rainy
  • 在已有文件中添加新的条目:Data\animationDescriptions(在行程(schedule) 中增加自定义的动画)

以上所有的操作都可以通过IAssetLoaders/IAssetEditors或者Content Patcher做到。

最后,你需要调用NPC的构造器来创建实例。

 public NPC(AnimatedSprite sprite, Vector2 position, int facingDir, string name, LocalizedContentManager content = null);
 public NPC(AnimatedSprite sprite, Vector2 position, string defaultMap, int facingDir, string name, Dictionary<int, int[]> schedule, Texture2D portrait, bool eventActor);
 public NPC(AnimatedSprite sprite, Vector2 position, string defaultMap, int facingDirection, string name, bool datable, Dictionary<int, int[]> schedule, Texture2D portrait);

 // 调用:
 Game1.getLocationFromName("Town").addCharacter(npc);

用户界面 (UI)

用户界面(User-interface、UI) 是指一系列有关界面元素(如按钮、列表框、下拉框等等)以及它们组合起来呈现的画面(如菜单、HUD等等)。

//TODO:欢迎补充UI方面的内容。

HUD消息

HUD消息是指你屏幕左下角时常弹出来的消息框。以下是它的构造函数(不包括一部分无关的):

 public HUDMessage(string message);
 public HUDMessage(string message, int whatType);
 public HUDMessage(string type, int number, bool add, Color color, Item messageSubject = null);
 public HUDMessage(string message, string leaveMeNull)
 public HUDMessage(string message, Color color, float timeLeft, bool fadeIn)
样式一览

可选的样式(type):

  1. 成就 HUDMessage.achievement_type
  2. 新任务 HUDMessage.newQuest_type
  3. 错误 HUDMessage.error_type
  4. 体力值 HUDMessage.stamina_type
  5. 生命值 HUDMessage.health_type

颜色(color):

第1、2个构造器并没有给出表示颜色的参数,此时颜色默认为Color.OrangeRed。 若使用第4个构造器,颜色则与游戏内文字颜色一样。

特别:

  • public HUDMessage(string type, int number, bool add, Color color, Item messageSubject = null); 支持消息内容扩展。常用于金钱相关。
  • public HUDMessage(string message, string leaveMeNull); 左侧没有图标框。
  • public HUDMessage(string message, Color color, float timeLeft, bool fadeIn); 文字渐入效果。


示例1: 弹出一个带有Error-image-ingame.png的消息框。

 Game1.addHUDMessage(new HUDMessage("MESSAGE", 3));

示例2: 弹出一个纯文字的消息框。

 Game1.addHUDMessage(new HUDMessage("MESSAGE", ""));  // second parameter is the 'leaveMeNull' parameter

菜单 (Active clickable menu)

菜单指绘制于最顶层的UI,能够接受用户输入。比方说,当你按下ESCB键时呈现的GameMenu就是一个菜单。 菜单的值储存在Game1.activeClickableMenu,当该字段不为null时,其值便能呈现出一个菜单了。

每个菜单不尽相同,请阅读代码来了解其运作方式。你可能经常需要了解GameMenu的当前栏目 (tab),这是一个示例:

if (Game1.activeClickableMenu is GameMenu menu)
{
  // 获取栏目页
  IList<IClickableMenu> pages = this.Helper.Reflection.GetField<List<IClickableMenu>>(menu, "pages").GetValue();

  // 方法1:比较栏目的ID
  if (menu.currentTab == GameMenu.mapTab)
  {
     ...
  }

  // 方法2:比较菜单类型
  switch (pages[menu.currentTab])
  {
    case MapPage mapPage:
       ...
       break;
  }
}

如果你想要自定义菜单,请继承自IClickableMenu,将对象分配给Game1.activeClickableMenu。 一个菜单基本上重写了一些方法,如drawreceiveLeftClick 等等。 draw方法绘制屏幕上的元素;receiveLeftClick 方法处理左键点击事件。 你通常可以使用一些游戏封装好的类作为菜单元素,如ClickableTextureButton

这里提供了一个简单的例子,这是Birthday Mod的菜单。

对话框 (DialogueBox)

不带选项的对话框示例。

对话框有许多变种,比如有种对话框能够选择想要的对话内容。

如果想换行,请输入"^"。

下面是一种不带选项的对话框示例:

using StardewValley.Menus;  // 引用DialogueBox类的命名空间

string message = "This looks like a typewriter ... ^But it's not ...^It's a computer.^";
Game1.activeClickableMenu = new DialogueBox(message);

邮件 (Mail)

// 未翻译

概述

// 未翻译

注入静态内容 (Inject static content)

// 未翻译

客户端调用静态内容 (Send a letter with static content)

// 未翻译

注入动态内容 (Inject dynamic content)

// 未翻译

客户端调用动态内容 (Send a letter with dynamic content)

// 未翻译

其他 (Other)

简单动画(animation) 的添加

location.temporarySprites.Add(new TemporaryAnimatedSprite(...));

详见TemporaryAnimatedSprite类。

播放一段声音

location.playSound("SOUND");  // "SOUND"为声音的名字

详见声音名字一览

开源 (Open Source)

详见模组大全源代码(source)一栏。