Get Started with Pygame, Part 2

0
6665
Gave dev time, again!

Back with Pygame

In the first part of this series, we learnt about uploading a background image, and how to put text with different fonts on the screen. We also went through event-handling. In this part, we are going to learn about some important aspects such as colours, getting input from the user, rotation using the mouse and keyboard, and my favourite: sprites.

The first thing that came to mind when thinking of colours was RGB (red, green, blue). These are the primary colours, and you can create every colour out of these three by just varying the intensity of each one.

In Part 1 of this series, to pass a colour as an argument, I simply passed a tuple of three elements, like (255,0,0). The first element in the tuple corresponds to the intensity of the red colour, the others to green and blue. The intensity ranges from 0 to 255. Brightness increases as you move from 0 to 255. (0,0,0) is black, and (255,255,255) yields white.

Given below is a little script that accepts intensities for the three colours from the user, and displays the resultant colour.

#color.py
import pygame
from pygame.locals import *
from sys import exit

r=min(255, input("Enter the red colour intensity  :"))
g=min(255, input("enter the green colour intensity : "))
b=min(255, input("enter the blue colour intensity  :"))
screen=pygame.display.set_mode((640,480),0,24)
pygame.display.set_caption("colour testing ")

while True:
 for i in pygame.event.get():
  if i.type==QUIT:
   exit()
 screen.fill((r, g, b))
 pygame.display.update()

We have used the min function to limit user-entered values to a maximum of 255. The screen.fill() function fills the screen with the colour denoted by the (r, g, b) tuple passed as the argument. For example, values of (123, 212 and 255) will give a colour as shown in Figure 1.

Colour from tuple (123,212,255)
Figure 1: Colour from tuple (123,212,255)

Blending

Suppose you have a green character in your game and, suddenly, a yellow fireball hits him. The character’s colour must change as soon as the fireball hits him, to a mixture of green and yellow. This technique is known as blending in Python. You can find the mixture by simply subtracting the two colours. In this case, just subtract (0,255,0) and (255,255,0) and the result is (255,0,0) — red.

Next, the fireball will spread on the character’s body, over time — so the intensity of the resultant colour (mixture) should also become brighter over time. Here’s the role of the blending factor, the value of which lies between 0 and 1. You multiply the blended colour with a blending factor, the value of which increases over time, from 0 to 1. As a result, the intensity of the resultant colour will also increase from 0 to 255.

Keyboard input

Getting user input is a necessity not only for game-play, but also to configure the game preferences. In the previous part, we handled various mouse and keyboard events; we will get user inputs via the same events, so you may want to re-visit them.

Every key on the keyboard has a key constant for identification; in Pygame, it’s K_a to K_z for the alphabets. We don’t have anything like K_A because uppercase results from combining the Shift key — and the mod value (see the previous article) of the key event tells us about the combination state.

Let’s use these constants to get user inputs—try out the following script.

#keyinput.py
import pygame
from pygame.locals import *
from sys import exit

pygame.init()
screen=pygame.display.set_mode((640,480),0,24)
pygame.display.set_caption("Key Press Test")
f1=pygame.font.SysFont("comicsansms",24)

while True:
 for i in pygame.event.get():
  if i.type==QUIT:
   exit()
  a=100
  screen.fill((255,255,255))
  if pygame.key.get_focused():           #1
   press=pygame.key.get_pressed()        #2
   for i in xrange(0, len(press)):
    if press[i]==1:
     name=pygame.key.name(i)             #3
     text=f1.render(name, True,(0,0,0))
     screen.blit(text,(100, a))
     a=a+100
   pygame.display.update()

After running the script, press some keys, and you will see the names of the pressed keys on a white screen. I pressed <Left-Ctrl><Left-Shift><a> and got Figure 2 as my result.

Result of <Left-Ctrl><Left-Shift><a>
Figure 2: Result of <Left-Ctrl><Left-Shift><a>

There are three important functions used in the above script:

  1. pygame.key.get_focused(): The Pygame window will handle the key events only if the window is focused. It returns True if the window is focused, and False if not.
  2. pygame.key.get_pressed(): Returns a list of Boolean values, one for every key. It returns 1 if the key is pressed, and 0 if not.
  3. pygame.key.name(argument): It returns the key name from the key constant value; for example, it returns “space” given K_space as the argument. You can get the values easily by looking for pygame.locals in pydoc (run pydoc pygame.locals in a terminal).

There are many more key functions like get_mods(), set_mods(), etc, which you can research on the Web. I am pretty sure that after understanding the above script, you will have no trouble getting user input.

Rotation

Rotation is the key to making any shooting game. You can rotate the image both with the keyboard as well as the mouse — but, generally, rotation is done with the mouse, because movements are done using the keyboard. Rotation in Pygame can be done by using its transform module. The script below demonstrates rotation with both, keyboard and mouse.

#rotation.py
import pygame
from pygame.locals import *
from sys import exit

pygame.init()
screen=pygame.display.set_mode((640,480),0,0)
pygame.display.set_caption("Rotation using transform module")
image=pygame.image.load("naughty.png").convert_alpha()
image2=image
a=0
clock=pygame.time.Clock()                                                      #1
while True:
 for i in pygame.event.get():
  if i.type==QUIT:
   exit()
 screen.fill((0,0,0))
 rotation=pygame.mouse.get_rel()                                               #2
 buttonpress=pygame.mouse.get_pressed()                                        #3
 press=pygame.key.get_pressed()
 screen.blit(image2,(100-(image2.get_width()/2),100-(image2.get_height()/2)))  #4
 if rotation[0] and buttonpress[0]:
  a=a+rotation[0]
  image2=pygame.transform.rotate(image, a)                                     #5
 if press[K_LEFT]:
  a=a+10
  image2=pygame.transform.rotate(image, a)
 if press[K_RIGHT]:
  a=a-10
  image2=pygame.transform.rotate(image, a)
 pygame.display.update()
 clock.tick(15)

Run the script and use the Left/Right arrow keys, or left-click and drag, to rotate the naughty-face icon, as shown in Figure 3.

Rotated image
Figure 3: Rotated image

Let’s understand the code. We loaded the naughty.png image into the video display, and then we performed the following steps:

  1. The time module’s Clock() function is used to determine the speed of the game through frames. When used with the tick(value) function (see the last line of the code), it decides how many frames per second should appear on the video display. When the value is 1, the frame rate is 1 per second. As you increase the value, the speed or frame-rate of the game increases. Thirty frames per second is recommended as the best frame rate for games, as every hardware is capable of providing this rate continuously.
  2. pygame.mouse.get_rel(): It returns a tuple of the change in coordinates with respect to the previous position of the mouse.
  3. pygame.mouse.get_pressed(): It returns a Boolean value for every mouse button (1 if a button is clicked, otherwise 0), in a tuple of three elements.
  4. There is a problem with the inbuilt rotate function: the rotated surface doesn’t have the same dimensions as the original image; so we can not blit the returned surface into the original image because that will lead to flickering, as well as wrong imposition of the surface. So we cut out the centre of the returned surface, from the coordinates where we want to draw. In this way, the centre of every image coincides, and we get the proper rotation.
  5. We use the transform module and its rotate function, which takes the image to be rotated, and the angle, as arguments. It returns the rotated surface, which we stored in the variable image2. If the angle is positive, it will rotate anticlockwise, and if negative, then clockwise.

The transform module is of great use. It has functions like scaling(), flip(), etc., which are required in almost every game. Explore more about them on the Web.

Sprite sheets

This is one of my favourite topics, and I bet will become one of yours too — after reading about it. First of all, animation is nothing but a continuous, fast display of sprite images from sprite sheets. You can also define an animation as a collection of images in a serial order. For an example, look at Figure 4, consisting of 7 images.

Simple sprite sheet
Figure 4: Simple sprite sheet

When they are used in a continuous manner with a high frame-rate, it appears to be an explosion. That’s how animation is created. You can create the whole game using a single sprite sheet; it depends on how you use that sprite sheet.

You must have a good knowledge of surfaces to work with sprites — that comes with experience. Traversing the sprites from the sprite sheet is what we learn next; there is a special sprites module to do this, but we will start from scratch.

#sprite.py
import pygame
from pygame.locals import *
from sys import exit

counter=0

def Update():
 global counter
 counter=(counter+1)%7

def sprite(w, h):
    a=[]
    clock=pygame.time.Clock()
    screen=pygame.display.set_mode((200,200),0,24)
    image = pygame.image.load("Image4.png").convert_alpha()
    width, height=image.get_size()
    for i in xrange(int(width/w)):
        a.append(image.subsurface((i*w,0, w, h)))
    while True:
     for i in pygame.event.get():
      if i.type==QUIT:
       exit()
     screen.fill((0,0,0))
     screen.blit(a[counter],(100,100))
     Update()
     pygame.display.update()
     clock.tick(5)
sprite(20,20)

If you are good with the GIMP, you will find traversing easy, because it is just a game of giving dimensions. In the above script, the sprite sheet used has a width of 140 and height of 20. We have 7 properly and equally spaced images, so let’s have a width of 20 pixels for each image. Let’s work with a rectangular (square, in this case) surface of 20*20 for each image, and then we will store them in a list. Image4.png must be in the same directory as the script — otherwise, use os.path.join to construct the path.

We have created a function called sprite() to cut the image into sub-surfaces of 20*20, and then store them in the list ‘a ‘ in a continuous manner. Then we will display them by incrementing the counter value, which is done in the Update() function. That’s it — the animation is done, and you will see the “blast” on the output when you run the script.

Hungry-snakes

I believe that after reading both parts of this series, you can build a small game. For game development, control over loops is a must. I turned the famous snake game (of Nokia fame) into a 2D practice game, and released it as an open source project on Google, so that others could use it to learn, and modify it in their own way. I named it “Hungry-snakes”, and released it under GPL v3. Get it from here. As of writing this, 125+ downloads have already happened. Feel free to ping me for suggestions and queries.

LEAVE A REPLY

Please enter your comment!
Please enter your name here