Godot 3: How to disable a button and tint its sprite icon a darker color

In this tutorial: disabling buttons and tinting their icons (sprites) a darker color to indicate that they cannot be interacted with by the player.

What we’re making

Here’s my game’s item vault. It displays a grid of buttons, each one representing an inventory “slot” and each button can be used to display an item the player actually owns. There’s an icon and a slot label to help the player understand what each item is (ie: a sword, a robe, a pair of boots, etc.)

This is great for viewing all the items, but sometimes the player wants to fill a specific slot on their character. Here’s what the character page looks like:

When the player goes to select, for example, a “jewelry” item for their character to wear, I want to make it easy to spot which items in the inventory vault are actually jewelry.

To do this, I will write script that figures out which items are not jewelry, disable their buttons, and tint their icons a darker color. The end result should look something like this:

(I could have just hidden all the non-jewelry items, but I think having items “move around” on the player would be confusing. Tinting the non-jewelry items seemed, to me, anyway, like the better user experience.)

Setting it up in script

The initial creation of the inventory button instances takes place in my game’s “vault” scene. Below is a simplified version of my game’s code that (hopefully) demonstrates how the grid of buttons is created and populated with icons and labels. (PS: I have a separate tutorial specific to making a grid of buttons just like this.)

Thematically, the vault represents a shared inventory across all of the player’s characters in my RPG-style game. The shared inventory is represented as an array named global.guildItems.

Each inventory “slot” is represented as an itemButton instance (itemButton is a scene I created, we’ll look at it more closely in a bit).

The first method, _position_vault_buttons(), creates the blank buttons and places them in a grid, the second method, _draw_vault_items(), “populates” them with actual items from the inventory.

This way, we always get X number of buttons, where X is the total capacity of the player’s inventory, and only some of them are used to “hold” the items the player actually owns.

(A lengthier explanation of how I know which item slot to filter by is outside the scope of this tutorial, but it’s done with a global flag set by the heroPage and you can see evidence of it in this code sample below about 6 lines from the end. It’s the one that goes “if (global.guildItems[i].slot != global.browsingForSlot):”)

#vault.gd

func _ready():
  #display inventory size and capacity
  _position_vault_buttons()

func _position_vault_buttons():
  #this method handles the STRUCTURE of the buttons
  #it places the empty buttons in the grid
  #use _draw_vault_items() to put icons and data into buttons
  for i in range(global.vaultSpace):
    var itemButton = preload("res://menus/itemButton.tscn").instance()
    itemButton._set_vault_index(i)
    itemButton.connect("updateSourceButtonArt", self, "_draw_vault_items")
    $centerContainer/grid.add_child(itemButton)
    buttonArray.append(itemButton)
    _draw_vault_items()

func _draw_vault_items():
  #now we pair each physical button with data from global.guildItems array 
  #grab each button from buttonArray and "decorate" it 
  var currentButton = null
  for i in range(buttonArray.size()):
    currentButton = buttonArray[i]
    if (global.guildItems[i]):
      currentButton._set_label(global.guildItems[i].slot)
      currentButton._set_icon(global.guildItems[i].icon)
      currentButton._set_data(global.guildItems[i])
      if (global.currentMenu == "vaultViaHeroPage"):
        if (global.guildItems[i].slot != global.browsingForSlot):
          currentButton._set_disabled() #script I wrote in itemButton.gd 
        else:
          currentButton._clear_label()
          currentButton._clear_icon()
          currentButton._clear_data()

About 4 lines up from the bottom is this line of code:

currentButton._set_disabled()

That _set_disabled() method is a method I wrote on the itemButton’s script, it’s not something built into Godot.

Let’s take a look at the itemButton scene and see how it works.

itemButton scene and script

An itemButton, in my game, is made up of a Button node along with a couple labels and a sprite.

Here is the scene’s structure:

Here is the script that disables the button and dims the sprite over the button:

func _set_disabled():
  $Button.disabled = true
  $Button.modulate = Color(0.5,0.5,0.5,1)
  $Button/sprite_itemIcon.modulate = Color(0.25,0.25,0.25)

The tricky part here, for me, was figuring out how modulate works. The Godot docs are thin on examples and it took some Googling just to figure out that the key word here was “modulate”, not “tint” or “color” or other terms I was initially guessing.

Some things to note about “modulate”:

  • Modulate is a property, so you can set it using “dot” notation.
  • It uses Color, which is built into Godot.
  • set_modulate and get_modulate are the old way of working with modulate (if you see reference to them the guide you’re reading/watching was probably made prior to Godot 3)

There’s a bunch of ways to work with Color, my example is just one of them. Mine is Color(R, G, B, alpha). Here are some more examples of working with Color in Godot.

This method could be expanded to a near-infinite number of uses, my example is just one tiny, specific use case.

References and more reading:

Godot 3: Centering a grid of evenly-spaced buttons on screen

In this tutorial: The scene structure and settings I used in the Godot 3 engine to achieve this amazing centered grid of evenly-spaced buttons. The buttons themselves are all instances of the same scene and are placed in the grid programmatically.

This is my “finished” result (pardon the placeholder assets). It’s a simple hero inventory page for an RPG. The buttons that make up the hero inventory are evenly spaced in a grid, and that grid is centered horizontally on the screen:

This guide will show you the code and the scene structure used to achieve this centered-grid-of-centered-buttons, but it assumes some familiarity with the Godot engine and will skip over some of the basics.

Scene hierarchy and container settings

This “Hero Page” is its own scene. This scene is named heroPage.tscn (and its script is in heroPage.gd). We’ll come back to its script at the end of the tutorial.  For now, we’re working in the scene’s hierarchy.

The parent-most node is a Node2D, and the important bits here for the centered grid of inventory items are:

  • a CenterContainer (which I renamed to centerContainer)
  • a GridContainer (which I renamed to grid)

(You could make this stuff programatically if you prefer but I like to see a certain amount of my UI in the viewport – at least the outlines of containers, where possible.)

I positioned my CenterContainer where I wanted it on the Y axis and gave it a width equal to that of my screen and left the rest alone.

Doesn’t look like much in the viewport yet – just a thin horizontal line.

That’s okay – the buttons themselves will bulk it out.

The next step is to adjust the grid’s settings. I changed the number of columns to 4 and under Custom Constants, I changed Vseparation and Hseparation to 24 (to put some space between each of the buttons).

The button instances

The square buttons are all instances of the same “itemButton” scene (that I made myself). That scene looks like this in the 2D viewport and scene hierarchy:

Unlike the grid or center container, I gave the button’s Control parent a size and a minimum size.

Generating the buttons with script

Finally, the code that adds the buttons to the grid is in the heroPage.gd file itself. This code goes in the _ready() method.

for i in range(heroEquipmentSlots.size()):
   slot = heroEquipmentSlots[i]

   var inventoryBtn = preload("res://menus/itemButton.tscn").instance()
   inventoryBtn._set_label(heroEquipmentSlotNames[i])
  
   #only set icon if the hero actually has an item in this slot
   #otherwise leave it empty
   if (global.selectedHero["equipment"][slot] != null):
      inventoryBtn._set_data(global.selectedHero["equipment"][slot])

   $centerContainer/grid.add_child(inventoryBtn)

I simplified some of the above code from my actual project for readability. The important parts are where it generates an instance of each inventory button and pushes the label and data into that instance, then adds the button to the grid.

The end result

The hero’s eight inventory slots are now in a grid and neatly centered. (I can’t say the same for the rest of the elements on this page yet, but it’s a start!)

pytest CLI tips, tricks, and additional settings

There are several Python unit testing frameworks, but pytest is both the most popular and the easiest to use. It requires no boilerplate, no imports, no API. Just name your files, classes, or methods starting with “test”, type py.test into the command line, and observe the results.

Simple, right? And we want it that way. Pytest allows our tests to be uncluttered and easy to read. But as far as the output of those tests goes… we can do better! This article will show you how to master the pytest CLI.

To start off, make sure you have pytest installed:

pip install pytest

For those of you new to pytest, note that the package is named “pytest”, while the command to run it is “py.test”. The dot remains for historical reasons: pytest used to be part of the “py” library.

If you already have pytest installed, double-check your version number:

py.test --version

This guide is written with pytest version 2.7.0 or greater in mind. If you have an older version, you can upgrade using pip:

pip install --upgrade pytest

All good? Let’s get started!

Useful Switches

-x : Stop After First Failure

When you’re trying to track down a bug, it can help to run all of your tests, so that you can draw conclusions based on what groups of tests are failing.

That said, sometimes you just want to work through failing tests one by one, or there are so many tests in your project that running them all every single time (not to mention sifting through all the output) would be too time-consuming. There’s a nice simple switch to take care of this:

py.test -x

This will stop pytest after the first failure is encountered, saving you oodles of time. Is one just not enough? How about after two failures? No problem!

py.test --maxfail=2

-k : Run Tests Matching Keywords

What if you know some group of tests is going to fail, but you just don’t want to think about them right now? You could go through your test files and comment them out… but there’s a much better way! Let’s say you only want to run tests with the word “log” in the names:

py.test -k “log”

Easy… but wait! This also ran the tests with “logout” in the names. Let’s say we don’t want those:

py.test -k “log and not logout”

That’s right! Instead of just a simple string match, you can feed -k a full expression to use.

Controlling Verbosity

Of course, if you want to run all your tests, but just make them run more quietly, you can tone down their output like this:

py.test -q

The opposite is increasing the output, which will put all the names of your tests in a list, along with their pass and fail states:

py.test -v

This can be useful if you’re logging your tests for someone else to read. This will only control the state output, however. For the rest, see the next section!

Changing Defaults

Changing Traceback Printing

When a test fails, pytest will attempt to do a traceback to give you some information on what went wrong. And when I say “some”, I mean a lot… often too much. What if you could shorten that output, so that you didn’t have to scroll for pages to see the results of all your tests? Well, try this:

py.test --tb=short

Using –tb will change pytest’s traceback formatting to the format you specify. If “short” is still not short enough, you can do it one better:

py.test --tb=line

Or if you’re looking for something a little more familiar, you can set pytest to use the Python standard traceback formatting, like so:

py.test --tb=native

Invoke pdb On Failure

Oftentimes, when debugging a problem, you’ll stick the following statement on the line before the test failed to set a pdb breakpoint:

# code here
import pdb; pdb.set_trace()
# more code here

But why go through that hassle, when you can just do this when you run pytest?

py.test -x --pdb

This will invoke pdb immediately upon failing a test. Notice that I combined it with -x to stop pytest after the first failure. This combination is quite useful, as you don’t want to have to go through pdb for every single failing test.

Watching Files

All of these tricks will help you save time and energy while running tests. But all of them have one thing in common: you still have to head back into the command line and type them out. Well, we can take care of that, too. Enter pytest-xdist. This handy little package (in addition to other things) can keep an eye on your project files and re-run your tests every time it detects something has changed. Just install and run with the -f option:

pip install pytest-xdist
py.test -f

This will immediately run all of your tests, then sit and wait for any file to be changed. Even better, if any tests failed in the previous run, it will re-run ONLY those tests until they pass.

Alternatively, you can use a package called pytest-watch:

pip install pytest-watch
ptw

The downside to pytest-watch is that it’s a separate executable, and therefore you can’t use any of the tips in the rest of this article. On the other hand, it has some neat options, like configuring actions to occur on test failure. If you’re on OSX, try this out to have your computer actually vocalize how your tests are doing:

ptw --onpass=”say passed” --onfail=”say failed”

Final Thoughts

These are some of my favorite pytest options, but it’s really only the beginning. pytest has many options for reporting, collection, and debugging, and that’s not even getting into the rich assortment of plugins and packages to extend its capabilities. You’re going to be spending a lot of time running tests, so take the time to poke around and find the configuration that works best for you. It’ll be worth the investment!

How to fork your own repo on Github

Forking one of your own Github repositories ought to be easy, right? After all, forking somebody else’s repo is as simple as clicking a single button! Surely you can just press that same button on your own repo?

fork

NOPE!

If you press the Fork button on your own repo, the page will refresh and… that’s it. No error message, no suggested course of action, nothing. It turns out forking your own repo on Github is impossible, but don’t worry: following the steps below will get you the next best thing.

1. Create a New Repo On Github

new_repo

First, go to github.com and create a new repository. This will contain our fork when we’re done. I’ll refer to this repo as “fork-repo”, with the original being “orig-repo”.

Make sure you don’t check the box for “Initialize this repository with a README”. You’ll see why in Step 4!

2. Clone the New Repo Locally

Next, make a local copy of the blank repo we just made. In Terminal, cd to the base directory you want to keep the fork in and then type the following:

git clone https://github.com/YourUsernameHere/fork-repo.git

3. Add an Upstream Remote

We’ll now add an upstream remote pointing at the original repo. This will allow us to pull files from the original repo, both now and in the future if we wish. Make sure you navigate to the directory you cloned the fork repo into first!

cd fork-repo
git remote add upstream https://github.com/YourUsernameHere/orig-repo.git

4. Pull From the Original Repo

Now we can pull all the files from our original repo into the fork, like so:

git pull upstream master

Your fork directory should now be identical to your original repo!

Note that if you made a README.md for the new repo (or added any other file) you may have some merge conflicts to resolve before you can go to the next step. Make the necessary changes to your files and commit to resolve the conflict.

5. Push!

You’re done! Well, locally at least. All that’s left is to push your new fork repo back up to Github:

git push origin master