Difference between revisions of "Modding:Modder Guide/APIs/Input"

From Stardew Valley Wiki
Jump to navigation Jump to search
(update for SMAPI 2.6 release)
(ZH link)
 
(25 intermediate revisions by 4 users not shown)
Line 2: Line 2:
  
 
The input API lets you check and suppress controller/keyboard/mouse state.
 
The input API lets you check and suppress controller/keyboard/mouse state.
 +
 +
==APIs==
 +
===Check button state===
 +
<dl>
 +
<dt><samp>IsDown</samp></dt>
 +
<dd>
 +
You can check if any [[#SButton|controller/keyboard/mouse button]] is currently pressed by calling the <samp>IsDown(button)</samp> method. For example:
 +
<syntaxhighlight lang="c#">
 +
bool isShiftPressed = this.Helper.Input.IsDown(SButton.LeftShift) || this.Helper.Input.IsDown(SButton.RightShift);
 +
</syntaxhighlight>
 +
</dd>
 +
 +
<dt><samp>GetState</samp></dt>
 +
<dd>
 +
For more finetuned control, you can check the [[#SButton|button]] state relative to the previous game tick:
 +
<syntaxhighlight lang="c#">
 +
SButtonState state = this.Helper.Input.GetState(SButton.LeftShift);
 +
bool isDown = (state == SButtonState.Pressed || state == SButtonState.Held);
 +
</syntaxhighlight>
 +
Available button states:
 +
{| class="wikitable"
 +
|-
 +
! previous tick
 +
! current tick
 +
! resulting state
 +
|-
 +
| up
 +
| up
 +
| <samp>None</samp>
 +
|-
 +
| up
 +
| down
 +
| <samp>Pressed</samp>
 +
|-
 +
| down
 +
| down
 +
| <samp>Held</samp>
 +
|-
 +
| down
 +
| up
 +
| <samp>Released</samp>
 +
|}
 +
</dd>
 +
</dl>
 +
 +
===Check cursor position===
 +
The <samp>GetCursorPosition()</samp> method provides the [[#ICursorPosition|cursor position in three coordinate systems]].
 +
 +
For example:
 +
<syntaxhighlight lang="c#">
 +
// draw text at the cursor position
 +
ICursorPosition cursorPos = this.Helper.Input.GetCursorPosition();
 +
Game1.spriteBatch.DrawString(Game1.smallFont, "some text", cursorPos.ScreenPixels, Color.Black);
 +
</syntaxhighlight>
 +
 +
===Suppress input===
 +
You can prevent the game from handling any [[#SButton|controller/keyboard/mouse button press]] (including clicks) by ''suppressing'' it. This suppression will remain in effect until the player releases the button. This won't prevent other mods from handling it.
 +
 +
{| class="wikitable"
 +
|-
 +
! method
 +
! effect
 +
|-
 +
| <code>Suppress</code>
 +
| Suppress the specified [[#SButton|<samp>SButton</samp>]] value.
 +
|-
 +
| <code>SuppressActiveKeybinds</code>
 +
| For the given [[#KeybindList|<samp>KeybindList</samp>]], suppress every button that's part of an activated keybind.
 +
|-
 +
| <code>IsSuppressed</code>
 +
| Get whether the specified [[#SButton|<samp>SButton</samp>]] value is currently suppressed.
 +
|}
 +
 +
For example:
 +
<syntaxhighlight lang="c#">
 +
// prevent game from seeing that LeftShift is pressed
 +
this.Helper.Input.Suppress(SButton.LeftShift);
 +
 +
// that works for clicks too:
 +
this.Helper.Input.Suppress(SButton.MouseLeft);
 +
 +
// check if a button is being suppressed:
 +
bool suppressed = this.Helper.Input.IsSuppressed(SButton.LeftShift);
 +
</syntaxhighlight>
 +
 +
Side-effects:
 +
<ul>
 +
<li>The [[Modding:Modder Guide/APIs/Events#Input.ButtonReleased|<samp>ButtonReleased</samp> event]] will be raised on the next tick for the suppressed input.</li>
 +
<li>Methods like <samp>helper.Input.IsDown(button)</samp> and <samp>helper.Input.GetState(button)</samp> will show the button as released for the duration of the suppression, even if it's physically still pressed. You can use <samp>helper.Input.IsSuppressed(button)</samp> to check if that's the case (it will only be true until the button is physically released):
 +
<syntaxhighlight lang="c#">
 +
bool isPhysicallyDown = helper.Input.IsDown(button) || helper.Input.IsSuppressed(button);
 +
</syntaxhighlight></li>
 +
</ul>
  
 
==Data structures==
 
==Data structures==
 
===SButton===
 
===SButton===
SMAPI's <tt>SButton</tt> constants unify the [https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.buttons.aspx <tt>Buttons</tt>], [https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.keys.aspx <tt>Keys</tt>], [https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.mousestate.aspx <tt>MouseState</tt>], and <tt>InputButton</tt> constants. SMAPI events use this to let you handle controller, keyboard, and mouse input without needing separate code for each. See [[Modding:Key bindings]] for a list of values.
+
SMAPI's <samp>SButton</samp> is a constant which includes every [https://docs.microsoft.com/en-us/previous-versions/windows/xna/bb975202(v%3dxnagamestudio.40) controller], [https://docs.microsoft.com/en-us/previous-versions/windows/xna/bb197781(v%3dxnagamestudio.40) keyboard], and [https://docs.microsoft.com/en-us/previous-versions/windows/xna/bb198097(v%3dxnagamestudio.40) mouse] button. SMAPI events use this to let you handle button presses without needing separate code for each. See [[Modding:Player Guide/Key Bindings]] for a list of values.
  
SMAPI provides extensions to convert any of the other constants to <tt>SButton</tt>:
+
SMAPI provides extensions to convert any of the other constants to <samp>SButton</samp>:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
SButton key = Keys.A.ToSButton(); // SButton.A
 
SButton key = Keys.A.ToSButton(); // SButton.A
 
SButton button = Buttons.A.ToSButton(); // SButton.ControllerA
 
SButton button = Buttons.A.ToSButton(); // SButton.ControllerA
 
SButton input = new InputButton(true).ToSButton(); // SButton.MouseLeft
 
SButton input = new InputButton(true).ToSButton(); // SButton.MouseLeft
</source>
+
</syntaxhighlight>
  
You can also convert <tt>SButton</tt> to the other constants. This uses a <tt>TryGet</tt> approach since <tt>SButton</tt> is a superset of the others (e.g. you can't convert <tt>SButton.ControllerA</tt> to a keyboard value):
+
You can also convert <samp>SButton</samp> to the other constants. This uses a <samp>TryGet</samp> approach since <samp>SButton</samp> is a superset of the others (''e.g.,'' you can't convert <samp>SButton.ControllerA</samp> to a keyboard value):
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
SButton value = SButton.A;
 
SButton value = SButton.A;
 
if (value.TryGetKeyboard(out Keys key))
 
if (value.TryGetKeyboard(out Keys key))
Line 23: Line 116:
 
if (value.TryGetStardewInput(out InputButton input))
 
if (value.TryGetStardewInput(out InputButton input))
 
   ...;
 
   ...;
</source>
+
</syntaxhighlight>
  
 
Two last extensions let you check how the button is mapped in the game:
 
Two last extensions let you check how the button is mapped in the game:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
SButton button = SButton.MouseLeft;
 
SButton button = SButton.MouseLeft;
 
if (button.IsUseToolButton())
 
if (button.IsUseToolButton())
Line 32: Line 125:
 
else if (button.IsActionButton())
 
else if (button.IsActionButton())
 
   // perform action
 
   // perform action
</source>
+
</syntaxhighlight>
 +
 
 +
You can use <samp>SButton</samp> values directly in your [[../Config|config model]], but <samp>[[#KeybindList|KeybindList]]</samp> is recommended instead in most cases.
 +
 
 +
===KeybindList===
 +
SMAPI's <samp>KeybindList</samp> utility lets you manage an arbitrary set of keybindings. A ''keybind list'' has any number of ''keybinds'', each of which has any number of [[#SButton|button codes]]. For example, the keybind list <code>"F2, LeftShift + S"</code> would be pressed if (a) <samp>F2</samp> is pressed, ''or'' (b) both <samp>LeftShift</samp> and <samp>S</samp> are pressed.
 +
 
 +
You can use a <samp>KeybindList</samp> directly in [[../Config|your <samp>config.json</samp> model]]:
  
You can use <tt>SButton</tt> values directly in your [[../Config|config model]], and they'll be represented by their names:
 
 
{| class="wikitable"
 
{| class="wikitable"
| <source lang="c#">
+
|-
internal class ModConfig
+
! C# model
 +
! &nbsp;
 +
! JSON file
 +
|-
 +
| <syntaxhighlight lang="c#">
 +
class ModConfig
 
{
 
{
   public SButton DoThingButton { get; set; } = SButton.LeftControl;
+
   public KeybindList ToggleKey { get; set; } = KeybindList.Parse("LeftShift + F2");
 
}
 
}
</source>
+
</syntaxhighlight>
 
| &rarr;
 
| &rarr;
| <source lang="json">
+
| <syntaxhighlight lang="json">
 
{
 
{
   "DoThingButton": "LeftControl"
+
   "ToggleKey": "LeftShift + F2"
 
}
 
}
</source>
+
</syntaxhighlight>
 
|}
 
|}
  
===ICursorPosition===
+
And you can then check whether it's pressed directly in your code. For example, in a [[../Events|<samp>ButtonsChanged</samp> event handler]]:
SMAPI's <tt>ICursorPosition</tt> provides the cursor position in four coordinate systems:
 
* <tt>AbsolutePixels</tt> is the pixel position relative to the top-left corner of the in-game map.
 
* <tt>ScreenPixels</tt> is the pixel position relative to the top-left corner of the visible screen, adjusted for game zoom.
 
* <tt>Tile</tt> is the [[Modding:Modder Guide/Game Fundamentals#Tiles|tile position]] under the cursor.
 
* <tt>GrabTile</tt> is the tile position that the game considers under the cursor for the purposes of clicking actions. This automatically accounts for controller mode. This may be different than <tt>Tile</tt> if that's too far from the player.
 
  
This is returned by the <tt>this.Helper.Input.GetCursorPosition()</tt> method and in the event args for some input events.
+
<syntaxhighlight lang="c#">
 +
private void OnButtonsChanged(object sender, ButtonsChangedEventArgs e)
 +
{
 +
  if (this.Config.ToggleKey.JustPressed())
 +
  {
 +
      // perform desired action
 +
  }
 +
}
 +
</syntaxhighlight>
  
 +
The <samp>KeybindList</samp> provides a number of methods depending on your use case:
  
==APIs==
+
{| class="wikitable"
===Check button state===
+
|-
You can check if any controller/keyboard/mouse button is currently pressed by calling the <tt>IsDown(button)</tt> method. For example:
+
! member
<source lang="c#">
+
! description
bool isShiftPressed = this.Helper.Input.IsDown(SButton.LeftShift) || this.Helper.Input.IsDown(SButton.RightShift);
+
|-
</source>
+
| <code>KeybindList.Parse(…)</code><br /><code>KeybindList.TryParse()</code>
 +
| Parse a keybind string like <code>"F2, LeftShift + S"</code> into a keybind list.
 +
|-
 +
| <code>IsBound</code>
 +
| Whether the keybind list has any keys bound. For example, this would be false for the strings <code>"None"</code> or <code>""</code>.
 +
|-
 +
| <code>Keybinds</code>
 +
| The individual keybinds. In most cases you shouldn't use these directly.
 +
|-
 +
| <code>GetState()</code>
 +
| Get the overall [[#Check button state|keybind state relative to the previous tick]]. This state is transitive across keybinds; ''e.g.,'' if the player releases one keybind and immediately presses another within the list, the overall state is <samp>Held</samp>.
 +
|-
 +
| <code>IsDown()</code>
 +
| Get whether any keybind in the list is currently down (''i.e.,'' the player pressed or is holding down the keys).
 +
|-
 +
| <code>JustPressed()</code>
 +
| Get whether the player just activated the keybind during the current tick (''i.e.,'' <code>GetState()</code> returns <samp>Pressed</samp> instead of <samp>Held</samp>).
 +
|-
 +
| <code>GetKeybindCurrentlyDown()</code>
 +
| Get the individual <samp>Keybind</samp> in the list which is currently down, if any. If there are multiple keybinds down, the first one is returned.
 +
|-
 +
| <code>ToString()</code>
 +
| Get a string representation of the input binding (''e.g.,'' <code>"F2, LeftShift + S"</code>).
 +
|}
  
===Check cursor position===
+
Caveats:
The <tt>GetCursorPosition()</tt> method provides the cursor position in three coordinate systems; see [[#ICursorPosition|ICursorPosition]].
+
* '''Don't use the <samp>ButtonPressed</samp> [[../Events|event]] to check keybinds''', since it's raised once for each button pressed. If the player presses two keys at once, your keybind would be activated twice. Use <samp>ButtonsChanged</samp> instead.
  
For example:
+
===ICursorPosition===
<source lang="c#">
+
SMAPI's <samp>ICursorPosition</samp> provides the cursor position in four coordinate systems:
// draw text at the cursor position
+
* <samp>AbsolutePixels</samp> is the pixel position relative to the top-left corner of the in-game map, adjusted for [[Modding:Modder Guide/Game Fundamentals#Zoom level|zoom]] but not [[Modding:Modder Guide/Game Fundamentals#UI scaling|UI scaling]].
ICursorPosition cursorPos = this.Helper.Input.GetCursorPosition();
+
* <samp>ScreenPixels</samp> is the pixel position relative to the top-left corner of the visible screen, adjusted for [[Modding:Modder Guide/Game Fundamentals#Zoom level|zoom]] but not [[Modding:Modder Guide/Game Fundamentals#UI scaling|UI scaling]].
Game1.spriteBatch.DrawString(Game1.smallFont, "some text", cursorPos.ScreenPixels, Color.Black);
+
* <samp>Tile</samp> is the [[Modding:Modder Guide/Game Fundamentals#Tiles|tile position]] under the cursor.
</source>
+
* <samp>GrabTile</samp> is the tile position that the game considers under the cursor for the purposes of clicking actions. This automatically accounts for controller mode. This may be different than <samp>Tile</samp> if that's too far from the player.
 
 
===Suppress input===
 
You can prevent the game from handling any controller/keyboard/mouse button press (including clicks) by ''suppressing'' it. This suppression will remain in effect until the player releases the button. This won't prevent other mods from handling it.
 
 
 
For example, this will prevent the game from seeing that the <tt>LeftShift</tt> button is pressed:
 
<source lang="c#">
 
this.Helper.Input.Suppress(SButton.LeftShift);
 
</source>
 
  
That works for clicks too:
+
This is returned by the <samp>this.Helper.Input.GetCursorPosition()</samp> method and in the event args for some input events.
<source lang="c#">
 
this.Helper.Input.Suppress(SButton.MouseLeft);
 
</source>
 
  
You can also check if a button is being suppressed:
+
'''The pixel positions are ''not'' adjusted for [[Modding:Modder Guide/Game Fundamentals#UI scaling|UI scaling]]''' (''i.e.,'' they're non-UI mode). Whether you need UI or non-UI positions depends how you're using them, so you can use <samp>cursorPos.GetScaledAbsolutePixels()</samp> or <samp>cursorPos.GetScaledScreenPixels()</samp> to adjust them automatically for the current mode or <samp>Utility.ModifyCoordinatesForUIScale</samp> to always get UI mode coordinates.
<source lang="c#">
 
if (this.Helper.Input.IsSuppressed(SButton.LeftShift))
 
  // left shift button is being suppressed
 
</source>
 
  
 
==See also==
 
==See also==
 
* [[../Events#Input|Input events]]
 
* [[../Events#Input|Input events]]
* [[Modding:Key bindings]] for a list of valid <tt>SButton</tt> values
+
* [[Modding:Player Guide/Key Bindings]] for a list of valid <samp>SButton</samp> values
  
{{modding guide footer
+
[[zh:模组:制作指南/APIs/Input]]
|prev = [[Modding:Modder Guide/APIs|SMAPI reference]]
 
|next =
 
}}
 

Latest revision as of 18:19, 10 July 2023

Creating SMAPI mods SMAPI mascot.png


Modding:Index

The input API lets you check and suppress controller/keyboard/mouse state.

APIs

Check button state

IsDown
You can check if any controller/keyboard/mouse button is currently pressed by calling the IsDown(button) method. For example:
bool isShiftPressed = this.Helper.Input.IsDown(SButton.LeftShift) || this.Helper.Input.IsDown(SButton.RightShift);
GetState
For more finetuned control, you can check the button state relative to the previous game tick:
SButtonState state = this.Helper.Input.GetState(SButton.LeftShift);
bool isDown = (state == SButtonState.Pressed || state == SButtonState.Held);

Available button states:

previous tick current tick resulting state
up up None
up down Pressed
down down Held
down up Released

Check cursor position

The GetCursorPosition() method provides the cursor position in three coordinate systems.

For example:

// draw text at the cursor position
ICursorPosition cursorPos = this.Helper.Input.GetCursorPosition();
Game1.spriteBatch.DrawString(Game1.smallFont, "some text", cursorPos.ScreenPixels, Color.Black);

Suppress input

You can prevent the game from handling any controller/keyboard/mouse button press (including clicks) by suppressing it. This suppression will remain in effect until the player releases the button. This won't prevent other mods from handling it.

method effect
Suppress Suppress the specified SButton value.
SuppressActiveKeybinds For the given KeybindList, suppress every button that's part of an activated keybind.
IsSuppressed Get whether the specified SButton value is currently suppressed.

For example:

// prevent game from seeing that LeftShift is pressed
this.Helper.Input.Suppress(SButton.LeftShift);

// that works for clicks too:
this.Helper.Input.Suppress(SButton.MouseLeft);

// check if a button is being suppressed:
bool suppressed = this.Helper.Input.IsSuppressed(SButton.LeftShift);

Side-effects:

  • The ButtonReleased event will be raised on the next tick for the suppressed input.
  • Methods like helper.Input.IsDown(button) and helper.Input.GetState(button) will show the button as released for the duration of the suppression, even if it's physically still pressed. You can use helper.Input.IsSuppressed(button) to check if that's the case (it will only be true until the button is physically released):
    bool isPhysicallyDown = helper.Input.IsDown(button) || helper.Input.IsSuppressed(button);
    

Data structures

SButton

SMAPI's SButton is a constant which includes every controller, keyboard, and mouse button. SMAPI events use this to let you handle button presses without needing separate code for each. See Modding:Player Guide/Key Bindings for a list of values.

SMAPI provides extensions to convert any of the other constants to SButton:

SButton key = Keys.A.ToSButton(); // SButton.A
SButton button = Buttons.A.ToSButton(); // SButton.ControllerA
SButton input = new InputButton(true).ToSButton(); // SButton.MouseLeft

You can also convert SButton to the other constants. This uses a TryGet approach since SButton is a superset of the others (e.g., you can't convert SButton.ControllerA to a keyboard value):

SButton value = SButton.A;
if (value.TryGetKeyboard(out Keys key))
   ...;
if (value.TryGetController(out Buttons button))
   ...;
if (value.TryGetStardewInput(out InputButton input))
   ...;

Two last extensions let you check how the button is mapped in the game:

SButton button = SButton.MouseLeft;
if (button.IsUseToolButton())
   // use tool
else if (button.IsActionButton())
   // perform action

You can use SButton values directly in your config model, but KeybindList is recommended instead in most cases.

KeybindList

SMAPI's KeybindList utility lets you manage an arbitrary set of keybindings. A keybind list has any number of keybinds, each of which has any number of button codes. For example, the keybind list "F2, LeftShift + S" would be pressed if (a) F2 is pressed, or (b) both LeftShift and S are pressed.

You can use a KeybindList directly in your config.json model:

C# model   JSON file
class ModConfig
{
   public KeybindList ToggleKey { get; set; } = KeybindList.Parse("LeftShift + F2");
}
{
   "ToggleKey": "LeftShift + F2"
}

And you can then check whether it's pressed directly in your code. For example, in a ButtonsChanged event handler:

private void OnButtonsChanged(object sender, ButtonsChangedEventArgs e)
{
   if (this.Config.ToggleKey.JustPressed())
   {
      // perform desired action
   }
}

The KeybindList provides a number of methods depending on your use case:

member description
KeybindList.Parse(…)
KeybindList.TryParse(…)
Parse a keybind string like "F2, LeftShift + S" into a keybind list.
IsBound Whether the keybind list has any keys bound. For example, this would be false for the strings "None" or "".
Keybinds The individual keybinds. In most cases you shouldn't use these directly.
GetState() Get the overall keybind state relative to the previous tick. This state is transitive across keybinds; e.g., if the player releases one keybind and immediately presses another within the list, the overall state is Held.
IsDown() Get whether any keybind in the list is currently down (i.e., the player pressed or is holding down the keys).
JustPressed() Get whether the player just activated the keybind during the current tick (i.e., GetState() returns Pressed instead of Held).
GetKeybindCurrentlyDown() Get the individual Keybind in the list which is currently down, if any. If there are multiple keybinds down, the first one is returned.
ToString() Get a string representation of the input binding (e.g., "F2, LeftShift + S").

Caveats:

  • Don't use the ButtonPressed event to check keybinds, since it's raised once for each button pressed. If the player presses two keys at once, your keybind would be activated twice. Use ButtonsChanged instead.

ICursorPosition

SMAPI's ICursorPosition provides the cursor position in four coordinate systems:

  • AbsolutePixels is the pixel position relative to the top-left corner of the in-game map, adjusted for zoom but not UI scaling.
  • ScreenPixels is the pixel position relative to the top-left corner of the visible screen, adjusted for zoom but not UI scaling.
  • Tile is the tile position under the cursor.
  • GrabTile is the tile position that the game considers under the cursor for the purposes of clicking actions. This automatically accounts for controller mode. This may be different than Tile if that's too far from the player.

This is returned by the this.Helper.Input.GetCursorPosition() method and in the event args for some input events.

The pixel positions are not adjusted for UI scaling (i.e., they're non-UI mode). Whether you need UI or non-UI positions depends how you're using them, so you can use cursorPos.GetScaledAbsolutePixels() or cursorPos.GetScaledScreenPixels() to adjust them automatically for the current mode or Utility.ModifyCoordinatesForUIScale to always get UI mode coordinates.

See also