User:Dem1se

From Stardew Valley Wiki
Revision as of 07:15, 12 November 2021 by Dem1se (talk | contribs) (Still fixing that same error -_-)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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:

  1. Create a new class (preferably in a new .cs file), that implements IClickableMenu
  2. Create constants / readonly fields that define the:-
    1. X, Y positions of the UI.
    2. Width and Height of the UI dialogue box.
  3. Declare all the UI elements that you going to be a part of your UI.
    1. Usually this means creating fields like, e.g., OkButton or Title, etc. of types ClickableTextureComponent and ClickableComponent (both of which are under the StartdewValley.Menus namespace).
  4. Initialize all the declared UI elements, either in constructor or in a method called by the constructor.
  5. Override methods in the IClickableMenu abstract class, to add desired behaviour to the UI.
    1. Behaviour like handling clicks on the UI, reacting to cursor hovering over any element. Both of those are handled by the methods void receiveLeftClick(...) and void performHoverAction(...).
  6. Override the void draw(...) method to draw all the UI and UI elements you declared and set up, to the screen.
    1. This method is called by Stardew Valley's draw code every render tick whenever your UI is actively displayed.
    2. 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.

  1. Choose what event you want to activate your UI.
    1. This can be a simple button press on most cases, or a specfic game state like a time of the day, etc.
  2. 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)