Line 139: |
Line 139: |
| [[Multiplayer|Split-screen multiplayer]] swaps the entire game state between each player, so each mod runs across every player. For example, with four local players the [[Modding:Modder Guide/APIs/Events#GameLoop.UpdateTicked|<tt>UpdateTicked</tt>]] event would be raised four times per tick. The game manages its own state automatically (e.g. <tt>Game1.activeClickableMenu</tt>), but if your mod stores info in its own fields you may need to handle those yourself. | | [[Multiplayer|Split-screen multiplayer]] swaps the entire game state between each player, so each mod runs across every player. For example, with four local players the [[Modding:Modder Guide/APIs/Events#GameLoop.UpdateTicked|<tt>UpdateTicked</tt>]] event would be raised four times per tick. The game manages its own state automatically (e.g. <tt>Game1.activeClickableMenu</tt>), but if your mod stores info in its own fields you may need to handle those yourself. |
| | | |
− | That can be tricky, so SMAPI provides <code>PerScreen<T></code> to take care of it. A <code>PerScreen<T></code> field manages a separate value for each local screen.
| + | SMAPI provides <code>PerScreen<T></code> to make that easy. You use it by creating '''readonly''' fields for your values (which can be any value from <tt>int</tt> to entire class instances): |
| + | <source lang="C#"> |
| + | 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 |
| + | </source> |
| | | |
− | For example, this mod would keep track of the last button pressed by each player: | + | Then you can just use its properties and methods: |
| + | |
| + | {| class="wikitable" |
| + | |- |
| + | ! member |
| + | ! description |
| + | |- |
| + | | <code>Value</code> |
| + | | Get or set the value for the current screen. If needed, this creates the value automatically. |
| + | |- |
| + | | <code>GetActiveValues()</code> |
| + | | Get the screen IDs and values for every active screen which has a value. |
| + | |- |
| + | | <code>GetValueForScreen(screenId)</code><br /><code>SetValueForScreen(screenId, value)</code> |
| + | | Get or set the value for a specific screen ID, creating it if needed. |
| + | |- |
| + | | <code>ResetAllScreens</code> |
| + | | Clear the values saved for every screen. |
| + | |} |
| + | |
| + | ===Example=== |
| + | For example, this mod plays a simple game: the main player presses a key, and farmhands need to guess which key was pressed. |
| | | |
| <source lang="C#"> | | <source lang="C#"> |
Line 147: |
Line 172: |
| { | | { |
| private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>(); | | private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>(); |
− | | + | private readonly PerScreen<int> Score = new PerScreen<int>(); |
| | | |
| public override void Entry(IModHelper helper) | | public override void Entry(IModHelper helper) |
Line 156: |
Line 181: |
| private void OnButtonPressed(object sender, ButtonPressedEventArgs e) | | private void OnButtonPressed(object sender, ButtonPressedEventArgs e) |
| { | | { |
− | this.LastButtonPressed.Value = e.Button; | + | // 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); |
| + | } |
| + | } |
| } | | } |
| } | | } |
− | </source>
| |
− |
| |
− | If you haven't set a value for the current player, <code>PerScreen<T></code> will return the default value for the type (e.g. 0 for <tt>int</tt>). You can change that by specifying your own default-value logic:
| |
− |
| |
− | <source lang="C#">
| |
− | private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>(createNewState: () => SButton.None);
| |
| </source> | | </source> |
| | | |