Pygame3
In this article we will create a small inventory that can hold up to 8 items. We will also discuss how to blit text to the display surface.
For this tutorial you will need to download the 'scroll.jpg' image from the backgrounds folder in the Pygame3 folder here. Save this image in your own backgrounds folder in the same directory as your 'MainGame.py' file. You will also need to download the 'map.png' image here, save it in your items folder. Download the entire Pygame3 folder for everything discussed in this article.
Displaying Text.
Before we create an inventory for the player it is important to know how to create a Font object and blit it to the display surface.
To create a Font object we use pygame.font.Font(filename, size). The first argument is a string filename like what we use to load images and sounds. The second argument is the size of the font.
On the line above where you assigned the charImgs variable, add the following:
(1 line) codebit 1
inventoryFont = pygame.font.Font(None, 32)
For the first argument of this Font object we used the None value. This will make this object the default Pygame font.
If you want to use a saved font that is not loaded with Pygame, it is quite simple. Say you downloaded a font called 'Ayuthaya.ttf' and you wanted to use it in your game. You could save the 'Ayuthaya.ttf' file in the same directory as your 'MainGame.py' file and then use pygame.font.Font('Ayuthaya.ttf', 70) to load a Font object, of size 70, from 'Ayuthaya.ttf'. Or you could save it in a fonts folder and use 'fonts/Ayuthaya.ttf' just like we did for our images and sounds.
You can create a Font object using system fonts also. This is done with pygame pygame.font.SysFont(name, size). For this you can use any system fonts as the name. For example, to use comic sans as your font object you would put 'comicsansms' as your name argument. A list of Windows system fonts can be found here.
Now we need to render the font, and tell it what to say.
After where you created the inventoryFont object, type this:
(2 lines) codebit 2
someText=inventoryFont.render('This is just a bunch of practice text!', True, BLUE)
This is an example of the render method, which creates a text image that can be drawn on a surface. The render method of the Font class takes 3 mandatory arguments; a string of text that will be drawn, a boolean value for antialiasing (if true the characters will have smooth edges), and the color of the text. There is also a fourth optional argument for the background color of the text (no background will appear if a color is not passed for this argument).
Finally, to display the text image we use the blit method of the surface we want the text to be blitted to. Just like how we blitted the background image, we now blit the someText text image.
On the line following DISPLAYSURF.blit(background, (0,0)), add this:
(1 line) codebit 3
DISPLAYSURF.blit(someText, (500,200))
This blits someText at the coordinates (500,200).
If you run the game you should have the text display on screen like this.
If you come across errors, make sure you are using a valid font or that you are loading the font file from the correct place!
The last thing worth mentioning is how to underline, bold, and italicize Font objects. To do this just call the set_underline, set_bold, or set_italic Font method after you create the Font object. You do not have to create another font object when doing this.
To underline inventoryFont, add the following right after you created the inventoryFont object:
(1 line) codebit 4
inventoryFont.set_underline(True)
Make sure you call the set_underline method before the render method.
Now when you run the game someText will be underlined! Try displaying more text to the screen with different fonts, colors, and messages!
For more on Pygame Font objects go to the documentation.
You can delete codebits 2, 3, and 4 from your game now, they were just for instructional purposes.
Adding to the Inventory.
Next we will add an inventory so the player can view the items he or she has picked up.
There are many ways to create an inventory system, but to keep things simple we will just use a dictionary. Keys will represent the item names and values represent the item quantities.
Add this to the Player class's __init__ method:
(1 line) codebit 5
self.inventory = {}
This is an empty dictionary, we will add every item the player picks up to it.
Now we need a way to add items to the inventory! For this we will add the updateInventory method to the Player class:
(6 lines) codebit 6
def updateInventory(self, item): for key in item.keys(): if key in self.inventory: self.inventory[key] += item[key] else: self.inventory = dict(self.inventory.items() + item.items())
This method takes one argument, a dictionary with key value pairs ({'item_name':quantity}). For every item_name key the method then checks to see if the item is already in the player's inventory. If the item already exists, the item's quantity is added to the inventory item's quantity. If the item does not exist, the item and its quantity are added to the inventory.
Uncomment the line that says #self.updateInventory(item) in the itemsCollision method. Now every item the player sprite collides with will be added to the inventory!
Try creating different items with different itemTypes and they all should be added to the player's inventory when a collision is detected. You can put print self.inventory after the line you just uncommented if you don't believe me.

Now we can add as many items as we want to the inventory, but the only way we can view the inventory is with a print statement... Let's fix that.
Taking Inventory.
Add this method to the Player class:
(2 lines) codebit 7
def get_inventory(self): return self.inventory
This method just returns the player's inventory. We will use it for the inventory menu we are about to create.
First let's create a message that displays if the player has an empty inventory. After the definition of the level function add this:
(19 lines) codebit 8
def emptyInvMessage(): HeaderText = inventoryFont.render('--Inventory--', True, BLACK) EmptyText = inventoryFont.render('Inventory is Empty... Go find some stuff!!', True, BLACK) xcoord = 212 ycoord = 150 Open = True while Open: for event in pygame.event.get(): if event.type==pygame.QUIT: pygame.quit() sys.exit() if event.type==pygame.KEYDOWN: if event.key==pygame.K_e: Open = False DISPLAYSURF.blit(scroll, (scrollX,scrollY)) DISPLAYSURF.blit(HeaderText, (xcoord,ycoord)) DISPLAYSURF.blit(EmptyText, (xcoord,ycoord+50)) FPSCLOCK.tick(FPS) pygame.display.update()
If you look closely at this function you will probably notice that it creates another loop inside of our existing game loop. We will use this loop-in-a-loop idea for menus, levels, and messages in our game!
This function starts off by rendering the HeaderText and EmptyText text images from the inventoryFont object we created earlier. It then defines the x coordinate and starting y coordinate at which the text will be blitted to the screen. It also says Open is equal to true, this causes the loop to run.
Inside the loop we have a new event handling loop. This event loop is much simpiler than our original. It just checks to see if quit or the 'e' button on the keyboard has been pressed. If 'e' is pressed, Open is set equal to false, which exits the while loop and returns us to the original game loop.
If Open continues to be True, we blit the scroll image, HeaderText, and EmptyText (in that order) to the DISPLAYSURF. We then update the time clock and display.
You probably are wondering WHAT SCROLL IMAGE?!?! Well just calm down and add this right before codebit 8:
(4 lines) codebit 9
scroll = pygame.image.load("backgrounds/scroll.png").convert_alpha() scroll = pygame.transform.scale(scroll, (844, 543))#scroll (width, height) scrollX = 90#90 default scrollY = 70#70 default
This loads the scroll image, scales it, and sets the coordinates of the scroll.
Adjust KEYDOWN section of the original event loop so it looks like this:
(11 lines) codebit 10
if event.type==pygame.KEYDOWN: if event.key==pygame.K_a: moveX = -5 if event.key==pygame.K_d: moveX = 5 if event.key==pygame.K_w: moveY = -5 if event.key==pygame.K_s: moveY = 5 if event.key==pygame.K_e: inventoryMenu()
With this adjustment, the event loop now checks if the 'e' key is being pressed. If 'e' is pressed we call the inventoryMenu function, which is defined right before the emptyInvMessage function like this:
(5 lines) codebit 11
def inventoryMenu(): playerInv = player.get_inventory() invKeys = [] if playerInv == {}: emptyInvMessage()
All this function does right now is get the player inventory, assign an empty invKeys variable, and run emptyInvMessage if the inventory is empty.
You should now be able to run the game and press 'e' on the keyboard to see an empty inventory message pop up! However, once you pick up an item, if you press 'e' nothing happens.
Let's create a different menu that displays when the player has items in the inventory. Define this function after emptyInvMessage:
(38 lines) codebit 12
def smallInvMenu(playerInv, invKeys): HeaderText = inventoryFont.render('--Inventory--', True, BLACK) xcoord = 212 Open = True selectNum = 0 while Open: ycoord = 150 for event in pygame.event.get(): if event.type==pygame.QUIT: pygame.quit() sys.exit() if event.type==pygame.KEYDOWN: if event.key==pygame.K_e: Open = False if event.key==pygame.K_w: if selectNum > 0: selectNum -= 1 else: selectNum = len(invKeys)-1 if event.key==pygame.K_s: if selectNum < len(invKeys)-1: selectNum += 1 else: selectNum = 0 DISPLAYSURF.blit(scroll, (scrollX,scrollY)) DISPLAYSURF.blit(HeaderText, (xcoord,ycoord)) ycoord += 50 for key in invKeys: if invKeys[selectNum] == key: itemtext = selectFont.render(key + '---' + str(playerInv[key]), True, BLACK) else: itemtext = inventoryFont.render(key + '---' + str(playerInv[key]), True, BLACK) DISPLAYSURF.blit(itemtext, (xcoord,ycoord)) ycoord += 40 FPSCLOCK.tick(FPS) pygame.display.update()
This function creates a loop that lets the player view the items in the inventory, and lets the player scroll through items by changing the selectNum variable when the player presses 'w' or 's' on the keyboard.
The selectNum variable is changed in the event loop. When the 'w' key is pressed, if selectNum is greater than zero, selectNum is subtracted by one. If selectNum is equal to zero, selectNum is set equal to the length of invKeys minus one.
invKeys is a list of all of the key (item_names) in the player inventory. We will add it to the invetnoryMenu function after we discuss this function.
When the 's' key is pressed, if selectNum is less than len(invKeys)-1 (the number of items in the inventory minus one), we add one to selectNum. If selectNum is equal to len(invKeys)-1, we set selectNum equal to zero.
Remember, we subtract one from the len(invKeys) becuase lists index [0, 1, 2, 3, ...etc]. The length of the list is one over the last number we can index.
After we blit the scroll and the Inventory header, we then use the selectNum to underline the item selected.
All other inventory items are left not underlined.
The underlined font is called selectFont, add it right after where you created the inventoryFont object:
(2 lines) codebit 13
selectFont = pygame.font.Font(None, 32) selectFont.set_underline(True)
Now all that's left to do is add the final touches to the inventoryMenu function. Adjust inventoryMenu to include the else statement like this:
(10 lines) codebit 14
def inventoryMenu(): playerInv = player.get_inventory() invKeys = [] if playerInv == {}: emptyInvMessage() else: for key in playerInv.keys(): invKeys.append(key) if len(invKeys) <= 8: smallInvMenu(playerInv, invKeys)
Now, starting in the event loop, when the player presses the 'e' key the inventoryMenu function is called. This function gets the player's inventory. If the inventory is empty, emptyInvMessage runs. If it is not empty, all of the keys from the inventory are appended to the invKeys list. If there are less than eight keys in invKeys, smallInvMenu runs, which displays a simple inventory menu.
This menu is very simple, and the fact that we can only display eight different items is not ideal. This inventory will do for now in our small game, but we will need to improve upon it later if we plan on adding more than eight items. Ideally we want an inventory that can hold an infinite amount of items that can be viewed in one fluent menu system.
Run the game, pick up items, and everything should work properly in the inventory menu!
If you get an error, be sure you uncommented #self.updateInventory(item) in the Player class's itemsCollision method.
The Inventory in Action!
You can add any type of item you want using the Item class. Let's create a map item and another coin item that the player can pick up and add to the inventory.
Add this right after the coinImg variable:
(1 line) codebit 15
mapImg = 'items/map.png'
Now let's add some new items to our items group. Adjust from the line that says items = pygame.sprite.Group() to the line that says items.add(coin) to look like the following:
(5 lines) codebit 16
items = pygame.sprite.Group() coin = Item(500,355,{'coin':1},coinImg,coinSound) coin2 = Item(20,400,{'coin':1},coinImg,coinSound) mapitem = Item(300,200,{'map':1},mapImg) items.add(coin, coin2, mapitem)
This just adds a new coin item and a new map item that the player can pick up. You should understand how to add items with different images and items types(this is discussed in the previous article). Try experimenting by adding more items and changing the inventory so it displays more than eight at a time. We will improve the inventory system in a later article!
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.