BWIKI 全站将于 9 月 3 日(全天)进行维护,期间无法编辑任何页面或发布新的评论。
模组:制作指南/APIs/Multiplayer
← 模组:目录
多人接口(multiplayer API)使模组可以支持多人游戏。虽然现在此接口的功能仍然很少,但在 SMAPI 的后续版本中会陆续添加更多功能。
方法
获取新的ID
某些情况下,游戏需要一个“多人ID”值,后者是一个唯一的 64 位数字。此 ID 主要用于数据同步,例如创建农场动物:
int animalID = this.Helper.Multiplayer.GetNewID();
var animal = new FarmAnimal("Cow", animalID, ownerID);
获取活跃地点
在合作模式下,游戏并不会把所有地点都同步给其他玩家。每个农场助手会接收他们当前地点、农场、农舍、农场建筑的信息。您可以按如下方式获取所有已同步地点的列表:
foreach (GameLocation location in this.Helper.Multiplayer.GetActiveLocations())
{
// do stuff
}
获取联机玩家信息
您可以利用 GetConnectedPlayer(playerID) 和 GetConnectedPlayers() 方法查看连接到同一个服务器的其他玩家的基本信息。玩家连接到服务器后就可以立即调用这些命令,甚至不需要等游戏批准连接。大多数信息仅适用于也使用 SMAPI 的玩家。您可以访问如下字段:
字段 | 类型 | 描述 |
---|---|---|
PlayerID | long | 玩家的唯一多人游戏ID。您可以将此ID传入游戏方法或需要唯一玩家ID的字段。 |
IsHost | bool | 若此玩家为房主则为 true;若为农场帮手则为 false。 |
IsSplitScreen | bool | 此玩家是否正在使用分屏模式在同一台电脑上玩多人游戏。 |
HasSmapi | bool | 此玩家是否安装了 SMAPI。 |
Platform | GamePlatform | (需要 SMAPI)玩家的操作系统,为 GamePlatform 枚举类型(取 Linux、Mac 或 Windows 之一)。 |
GameVersion | ISemanticVersion | (需要 SMAPI)玩家安装的游戏版本(形如 1.6.15)。 |
ApiVersion | ISemanticVersion | (需要 SMAPI)玩家安装的 SMAPI 版本(例如 2.11)。 |
Mods | IEnumerable<IMultiplayerPeerMod> | (需要 SMAPI) 玩家安装的模组。每个模组包括名称、唯一ID和版本。对于那些未安装 SMAPI 或有 SMAPI 但没有安装任何模组的玩家,此字段为空。 |
ScreenID | int? | 如果 IsSplitScreen 为 true,则此字段为玩家的屏幕ID。 |
GetMod(id) | 此方法返回 IMultiplayerPeerMod | (需要 SMAPI) 使用和 SMAPI 相同的大小写不敏感规则,返回指定模组ID对应的模组(若可用)。例如,此方法可用于检查是否安装了指定模组:if (peer.GetMod("Pathoschild.ContentPatcher") != null) 。
|
下面的例子演示了如何记录当前联机玩家的信息:
foreach (IMultiplayerPeer peer in this.Helper.Multiplayer.GetConnectedPlayers())
{
if (peer.HasSmapi)
{
// prints something like: "Found player -1634555469947451666 running Stardew Valley 1.5.2 and SMAPI 3.9.0 on Windows with 41 mods."
this.Monitor.Log($"Found player {peer.PlayerID} running Stardew Valley {peer.GameVersion} and SMAPI {peer.ApiVersion} on {peer.Platform} with {peer.Mods.Count()} mods.");
}
else
{
// most info not available if they don't have SMAPI
this.Monitor.Log($"Found player {peer.PlayerID} running Stardew Valley without SMAPI.");
}
}
注意: 尚未被游戏批准连接的玩家也会被返回。尽管您可以通过 SMAPI 的接口发送和接收消息,但游戏本体可能并不知晓这些消息。如果您希望仅获取完全连接的玩家,可以按如下方式操作:
foreach (Farmer farmer in Game1.getOnlineFarmers())
{
IMultiplayerPeer peer = this.Helper.Multiplayer.GetConnectedPlayer(farmer.UniqueMultiplayerID);
}
发送消息
您可以利用 SendMessage 方法向所有联机计算机(包括本地)的所有模组发送信息。接收方可以很精确(例如,某台计算机上的某个模组),也可以很泛泛(所有计算机上的所有模组)。尽管某台计算机上的某个模组不能给自己发消息,但可以给其他计算机上的同一模组发消息。
当您发送消息时,必须指定三件事:
- 希望发送的数据。可以为一个单纯的数值(例如数字或字符串),或类实例。当您希望发送具有自定义构造函数的类实例时,请确保它同时具有默认构造函数。
- 消息类型,接收方模组由此知道这是何种消息。因为模组一般会检查发送此消息的模组,所以不同模组可以有重名的消息类型。
- 消息接收方。可以指定为玩家ID和/或模组ID的组合。默认发送给所有玩家和所有模组。
例如:
// broadcast a message to all mods on all computers
MyMessage message = new MyMessage(...); // create your own class with the data to send
this.Helper.Multiplayer.SendMessage(message, "MyMessageType");
// send a message to a specific mod on all computers
MyMessage message = new MyMessage(...);
this.Helper.Multiplayer.SendMessage(message, "MyMessageType", modIDs: new[] { this.ModManifest.UniqueID });
接收消息
您可以通过监听 helper.Events.Multiplayer.ModMessageReceived 事件 来接收消息。此事件参数会给出发送消息方,并使您能够将消息转换为对应的数据模型。
例如:
public override void Entry(IModHelper helper)
{
helper.Events.Multiplayer.ModMessageReceived += this.OnModMessageReceived;
}
public void OnModMessageReceived(object sender, ModMessageReceivedEventArgs e)
{
if (e.FromModID == this.ModManifest.UniqueID && e.Type == "ExampleMessageType")
{
MyMessageClass message = e.ReadAs<MyMessageClass>();
// handle message fields here
}
}
分屏数据
分屏多人模式中不同玩家共享整个游戏状态,因此同一个模组实例需要服务所有玩家(而不是每个玩家有单独的模组实例)。例如,在有 4 名玩家的情况下,UpdateTicked 事件在每次刷新时会被触发 4 次。游戏会自动管理其状态(例如 Game1.activeClickableMenu),但如果您的模组在其自定义字段中存储信息,您就需要手动处理这些逻辑了。
SMAPI 提供了 PerScreen<T>
以便操作。假设您需要存储某个值(其类型既可以像 int 这样,也可以为类实例),您可以创建对应的 只读 字段:
private readonly PerScreen<int> LastPlayerId = new PerScreen<int>(); // defaults to 0
private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>(createNewState: () => SButton.None); // defaults to the given value
然后您就可以使用其字段和属性了:
成员 | 描述 |
---|---|
Value
|
获取/设置(get/set)当前分屏玩家的相应值。如有需要,此字段会自动创建值。 |
GetActiveValues()
|
获取分屏玩家ID和相应值的列表(玩家需处于活跃状态且拥有对应值) |
GetValueForScreen(screenId) SetValueForScreen(screenId, value)
|
获取/设置指定分屏玩家ID的相应值,如有需要,创建相应值。 |
ResetAllScreens
|
清除所有分屏玩家的相应值。 |
示例
下面给出一个示例模组。此模组会做一个简单的游戏:房主按下一个键,农场帮手猜按下了哪个键:
internal class ModEntry : Mod
{
private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>();
private readonly PerScreen<int> Score = new PerScreen<int>();
public override void Entry(IModHelper helper)
{
helper.Events.Input.ButtonPressed += this.OnButtonPressed;
}
private void OnButtonPressed(object sender, ButtonPressedEventArgs e)
{
// main player changes key
if (Context.IsMainPlayer)
{
this.LastButtonPressed.Value = e.Button;
this.Monitor.Log("The main player changed the key. Can you guess it?", LogLevel.Info);
}
// farmhands try to guess the key
else
{
SButton correctButton = this.LastButtonPressed.GetValueForScreen(0);
if (correctButton != SButton.None)
{
if (e.Button == correctButton)
{
this.Score.Value++;
this.LastButtonPressed.SetValueForScreen(0, SButton.None);
this.Monitor.Log($"Player #{Context.ScreenId + 1} correctly guessed the key: {e.Button}! Their score is now {this.Score.Value}.", LogLevel.Info);
}
else
this.Monitor.Log($"Player #{Context.ScreenId + 1} guessed incorrectly: {e.Button}.", LogLevel.Debug);
}
}
}
}
提示: 您几乎总是应该将一个分屏字段标记为 readonly
。重写整个字段(而非设置对应的 Value 属性)会同时清除所有玩家的信息,而非设置当前玩家的值。
另请参阅