User:Dem1se
Hey! I've made some mods, and you can find me on Discord at Dem1se # 7 9 9 0
Getting Started with Making UIs
Intro
What is this article?
This is a compilation of things that I've learnt making UI for my mods. This is just to help beginners to understand how making UIs works in Stardew Valley, since not everyone can read through codebases or find relatively simple ones to read in the first place.
This is about adding a new menu that the user can interact with in game, similar to the inventory menu, as opposed to modifying existing ones, but the basic ideas are the same.
How does adding UI work?
All the UIs in Stardew Valley are classes that derive from the IClickableMenu
abstract class. So making your own UI is a matter of making your own class that derives from IClickableMenu
, overriding the required methods and doing some other layout stuff.
After defining the UI, it is a matter of assigning an instance of that class to Game1.activeClickableMenu
. We'll look at how to do both in detail below.
Creating UIs
Define your UI
The general steps you want to take are as follows:
- Create a new class (preferably in a new .cs file), that implements
IClickableMenu
- Create constants / readonly fields that define the:-
- X, Y positions of the UI.
- Width and Height of the UI dialogue box.
- Declare all the UI elements that you going to be a part of your UI.
- Usually this means creating fields like, e.g., OkButton or Title, etc. of types
ClickableTextureComponent
andClickableComponent
(both of which are under theStartdewValley.Menus
namespace).
- Usually this means creating fields like, e.g., OkButton or Title, etc. of types
- Initialize all the declared UI elements, either in constructor or in a method called by the constructor.
- Override methods in the
IClickableMenu
abstract class, to add desired behaviour to the UI.- Behaviour like handling clicks on the UI, reacting to cursor hovering over any element. Both of those are handled by the methods
void receiveLeftClick(...)
andvoid performHoverAction(...)
.
- Behaviour like handling clicks on the UI, reacting to cursor hovering over any element. Both of those are handled by the methods
- Override the
void draw(...)
method to draw all the UI and UI elements you declared and set up, to the screen.- This method is called by Stardew Valley's draw code every render tick whenever your UI is actively displayed.
- The latter statements draw over the earlier statement's output. So things like the cursor should always be drawn at last so they are on top of everything else.
General Code Structure
// 1. Create your new UI class
public class MyUserInterface : IClickableMenu
{
// 2. Some of the constants you'll use to relatively lay out all the UI elements
int UIWidth = 632;
int UIHeight = 600;
int XPos = (Game1.viewport.Width / 2) - (UIWidth / 2);
int YPos = (Game1.viewport.Height / 2) - UIHeight;
// 3. Declare all the UI elements
ClickableComponent TitleLabel;
...
public MyUserInterface()
{
base.initialize(XPos, YPos, UIWidth, UIHeight);
// 4. initialize and lay out all the declared UI components
TitleLabel = new ClickableComponent(new Rectangle(XPos + 200, YPos + 96, UIwidth - 400, 64), "Some Title");
...
}
// 5. The method invoked when the player left-clicks on the menu.
public override void receiveLeftClick(int x, int y, bool playSound = true)
{
if (TitleLabel.containsPoint(x, y))
{
// handle user clicking on the title. More practical use-case would be with buttons
...
}
}
...
// 6. Render the UI that has been set up to the screen.
// Gets called automatically every render tick when this UI is active
public override void draw(SpriteBatch b)
{
//draw screen fade
b.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * 0.75f);
// draw menu dialogue box
Game1.drawDialogueBox(XPos, YPos, UIWidth, UIHeight, false, true);
// draw the TitleLabel
Utility.drawTextWithShadow(b, TitleLabel.name, Game1.dialogueFont, new Vector2(TitleLabel.bounds.X, TitleLabel.bounds.Y), Color.Black);
...
// draw cursor at last
drawMouse(b);
}
}
Displaying your UI
Game1.activeClickableMenu
stores the currently active UI, and has the value null
when no UI is active. It also can be assigned to, to display any IClickableMenu
UI that you want.
- Choose what event you want to activate your UI.
- This can be a simple button press on most cases, or a specfic game state like a time of the day, etc.
- Add the activation logic in your
Mod
subclass, where you bind to events and define handlers.
public class ModEntry : Mod
{
public override void Entry(IModHelper helper)
{
// bind the event handler
helper.Events.Input.ButtonPressed += OnButtonPressed;
...
}
void OnButtonPressed(object sender, ButtonPressedEventArgs ev)
{
// Don't process button presses if player hasn't loaded a save,
// is in another menu, or isn't free. I'd recommended you ignore these cases too.
if (!Context.IsWorldReady) return;
if (Game1.activeClickableMenu != null || (!Context.IsPlayerFree)) return;
// Display our UI if user presses F10
if (ev.Button == SButton.F10)
Game1.activeClickableMenu = new MyUserInterface();
...
}
}
Important Info
The Screen Positions
The number literals used in the UI element initializations and to define the UI dialogue's constants are pixel values that represent the UI's positions in the screen.
The positions are describing where to place the top left corner (anchor) of the UI element, dialogue boxes, etc.
The top-left corner of the screen is (0, 0) and increases across to the right and downwards.
Accounting for Scaling
Stardew valley video settings has an option to increase or decrease the UI scale which causes all the UI in the game to appear larger or smaller. This needs to be accounted for if you want your UI to be centered properly on the screen without being offset to either side.
This can be done by dividing with the UI Scale option.
// Scaling accounted for
int XPos = (int)(Game1.viewport.Width * Game1.options.zoomLevel * (1 / Game1.options.uiScale)) / 2 - (UIWidth / 2);
// Scaling unaccounted
int XPos = (Game1.viewport.Width / 2) - (UIWidth / 2);
Decompiling the Game
Use dotPeek (free) or ILSpy to decompile the game and view what are the available UI/Graphics classes in the game, to either modify or use within your UI. You'll have to look inside of StardewValley.Menus
namespace mostly.
Available UI Components
The best way to find the complete list of all the UI / graphics components you can use in your UI is to decompile Stardew Valley yourself and looking at what's available. I'll still list a few fundamental types that every UI will use, and might even find to be enough usually:
Component Name | Uses | Namespace |
---|---|---|
ClickableTextureComponent | Can be used to display any texture asset, including any image. Commonly used for buttons, images, etc. | StardewValley.Menus |
ClickableComponent | Commonly used to draw text labels. Base of ClickableTextureComponent. | StardewValley.Menus |
TextBox | Typical textbox. Used to get text inputs from users | StardewValley.Menus |
SpriteText | While not a drawable component by itself, contains useful methods to draw TextScrolls to screen like #drawStringWithScrollCenteredAt()
|
StardewValley.BellsAndWhistles |
Available IClickableMenu Methods to Override
Just like for UI components, the best way to find the complete list of all the UI / graphics components you can use in your UI is to decompile Stardew Valley yourself and looking at what's available. I'll still list a few fundamental methods that every UI will use, and might even find to be enough usually:
Method Name | Purpose |
---|---|
recieveLeftClick | Used to respond to clicks on the UI. |
performHoverAction | Used to perform actions when mouse hovers over the UI |
draw | Used to specify how to draw/render the UI to screen |
gameWindowSizeChanged | Used to rescale the UI to fit the screen again, when the resolution of the window changes. |
Using Custom Image Assets for UI
Since you'll want to load custom pixel art for buttons and other UI elements, follow this wiki article section on how to read assets.
Using Game Textures for UI
You can use sprites from the game's sprite sheets, this has some advantages to it; Using vanilla game textures makes your UIs feel a lot more cohesive with the game and give a vanilla feel to it.
To understand what textures are available, you need to unpack the game's content packs (.XNB files). See this wiki article on how to unpack the game files.
Once you've taken a look at the textures that are available, you can use this method to get the Texture2D output of that sprite from the sheet:
// returns an OkButton
Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 46)
// returns a cancel button
Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 47)