2
头图

Many developers have dreamed of developing a game of their own, which can be easily achieved with Python. The picture below is a simple game I developed with PyGame.
image.png

In this article, I will introduce to you, how to use PyGame to implement a pig that moves on the map.

Texture source: itch.io
Reference tutorial: PyGame: A Primer on Game Programming in Python

image.png

basic framework

First of all, no matter what game you are doing, don't worry about 3721, copy and paste the following code into your editor first. All games require these lines of code:

 import pygame
​
​
def main():
    pygame.init()
    pygame.display.set_caption('未闻Code:青南做的游戏')  # 游戏标题
    win = pygame.display.set_mode((800, 600))  # 窗口尺寸,宽800高600
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:  # 点击左上角或者右上角的x关闭窗口时,停止程序
                running = False
​
​
main()

The operation effect is shown in the following figure:

Load material

Now, let's randomly find two pictures, one for the background and one for the protagonist. Don't worry too much about the size, it's almost fine, because we can dynamically adjust it with code. The following two pictures are the materials I found casually. Please pay attention to the place enclosed by the red frame in the picture, which is the size of these two pictures.

We use the following code to load the image:

 img_surf = pygame.image.load('图片地址').convert_alpha()

The .convert_alpha() is to preserve the transparent background of the png image. If the image you loaded is not a png image, you can change convert_alpha() to convert().
If you want to modify the image size, use the following code:

 img_surf = pygame.transform.scale(img_surf, (宽, 高))

To display the image in the window, use the following two lines of code:

 win.blit(素材对象, (素材左上角的横坐标, 素材左上角的纵坐标))
pygame.display.flip()

The complete code is as follows:

 import pygame
​
​
def main():
    pygame.init()
    pygame.display.set_caption('未闻Code:青南做的游戏')  # 游戏标题
    win = pygame.display.set_mode((800, 600))  # 窗口尺寸
    bg_small = pygame.image.load('bg.png').convert_alpha()
    bg_big = pygame.transform.scale(bg_small, (800, 600))
    pig = pygame.image.load('pig_in_car.png').convert_alpha()
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:  # 点击左上角或者右上角的x关闭窗口时,停止程序
                running = False
​
        win.blit(bg_big, (0, 0))  # 背景图最先加载,坐标是(left, top)
        win.blit(pig, (200, 300))
        pygame.display.flip()
​
​
main()

The operation effect is shown in the following figure:

It should be noted that both win.blit and pygame.display.flip() are placed inside the while loop. where the first parameter of win.blit is the material object we just loaded. The second parameter is a tuple, marking the coordinates of the upper left corner of the image on the canvas. The upper left corner of the entire canvas corresponds to the coordinates (0, 0). Since the size of the background image is also (800, 600), the upper left corner of the background image is placed at (0, 0), which can just cover the entire canvas.

Where can I find materials?

What we are doing is a pixel style game, you can find materials on itch.io:

This site improves a lot of game material, and most of the material is free for personal non-commercial use. After you find the material you like, you can download it directly, and you don't even need to log in during the whole process (more conscience than domestic junk material websites).

Why does my material look like this?

After you download the material, you may find a very strange thing, how come all the materials are drawn on one picture?

In fact, this is the industry practice. The person who makes the material will arrange each type of material on a picture. When you want to use it, you need to cut it yourself. For example, all plants on one map, all statues on one map, and foundation textures on one map.

The background image we used for the demonstration above looks like a green image at first glance, but it actually contains multiple foundation elements. Please note the part I framed in red:

In the official game, we have to take out every basic element and put it together again. When reorganizing, some elements need to be copied and reused, and some elements need to be rotated and scaled. Finally combined into the following map that looks good:

Generally speaking, pixel-style materials are mostly 16x16, 32x32, 64x64, 128x128 in size. Material authors normally provide cropping instructions. If not provided, you can also look at it with the naked eye and take a guess.

For example, I want to cut out the red framed goddess from the statue material:

So, I can write code like this:

 img_surf = pygame.image.load('雕像素材.png').convert_alpha()
goddess= img_surf.subsurface(( 女神像左上角的横坐标 , 女神像左上角的纵坐标, 女神像的宽, 女神像的高))

The operation effect is shown in the following figure:

Some students may ask: Why are the coordinates of the goddess like this? I can only say that this coordinate is what I have tried many times and tried out.

Use sprites to manage objects

Except for the background image, every element we add is an object, such as the pig and the goddess above. In principle, the above code is enough for you to make the game beautiful. If you want to add something, just keep loading the picture material and put it in the right place.

But we can use object-oriented design methods to make the code easier to maintain and simpler. In PyGame, there is a class called Sprite. We can implement a class for each object, inherit Sprite, and then set the material of the object to the .surf property and the position of the object to the .rect property. For example, the above code, let's modify it:

 import pygame
​
​
class Bg(pygame.sprite.Sprite):
    def __init__(self):
        super(Bg, self).__init__()
        bg_small = pygame.image.load('bg.png').convert_alpha()
        grass_land = bg_small.subsurface((0, 0, 128, 128))
        self.surf = pygame.transform.scale(grass_land, (800, 600))
        self.rect = self.surf.get_rect(left=0, top=0)  # 左上角定位
​
​
class Pig(pygame.sprite.Sprite):
    def __init__(self):
        super(Pig, self).__init__()
        self.surf = pygame.image.load('pig_in_car.png').convert_alpha()
        self.rect = self.surf.get_rect(center=(400, 300))  # 中心定位
​
​
class Goddess(pygame.sprite.Sprite):
    def __init__(self):
        super(Goddess, self).__init__()
        building = pygame.image.load('building.png').convert_alpha()
        self.surf = building.subsurface(((7 * 64 - 10, 0, 50, 100)))
        self.rect = self.surf.get_rect(center=(500, 430))  # 女神像的中心放到画布(500, 430)的位置
​
​
def main():
    pygame.init()
    pygame.display.set_caption('未闻Code:青南做的游戏')  # 游戏标题
    win = pygame.display.set_mode((800, 600))  # 窗口尺寸
​
    bg = Bg()
    goddess = Goddess()
    pig = Pig()
    all_sprites = [bg, goddess, pig]  # 注意添加顺序,后添加的对象图层在先添加的对象的图层上面
​
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:  # 点击左上角或者右上角的x关闭窗口时,停止程序
                running = False
​
        for sprite in all_sprites:
            win.blit(sprite.surf, sprite.rect)
        pygame.display.flip()
​
​
if __name__ == '__main__':
    main()

The operation effect is shown in the following figure:

Note all_sprites = [bg, goddess, pig] in the code, here I am using a list. Later there will be a more advanced data structure SpriteGroup to store them. Using a list is enough today.

The material object .get_rect() will return a coordinate positioning object, this object has multiple properties, such as .left, .top, .center, .width, .height. In the case of no parameters, the default .left=0, .top=0, PyGame will automatically calculate .width, .height and .center according to the size of this object. We can actively set it in the form of incoming parameters. When you set the upper left corner, it can automatically calculate the coordinates of the center point; when you pass in the center coordinates, it can automatically calculate the coordinates of the upper left corner.

In theory, within each class, the material object can have any name, not necessarily .surf. The coordinate positioning object does not have to use .rect, as long as you correspond to it in win.blit. But if you use .surf and .rect uniformly it will give you a lot of benefits. This point we will talk about the place where the object collides. So I suggest you just use both names.

make the pig move

Since it is a game, you must press the keyboard to make the protagonist move. Otherwise, what's the difference with a painting? Everyone pay attention to the while running loop in the main() function. If you add a line of code to the loop: print(111), you will find that when you run the game, 111 will be continuously printed out.

In essence, PyGame is to draw pictures continuously through win.blit. Since this while loop runs many times per second, if every time it runs, we let the second parameter of win.blit, that is, the coordinates of the material object have Subtle differences, then to the human eye, the material object is moving.

Our goal is to hold down the up, down, left, and right arrow keys on the keyboard, and the piglet moves in 4 different directions. In PyGame, get the key that the keyboard presses and hold, and use the following code to achieve:
keys = pygame.key.get_pressed()
It returns an object that looks like a list (but not a list). When we want to judge whether a key is pressed, we only need to judge if keys [the key to judge]. If it returns True, it means that the key is pressed. Press and hold. Based on this principle, let's write two pieces of code. First modify the Pig class and add a .update method:

 class Pig(pygame.sprite.Sprite):
    def __init__(self):
        super(Pig, self).__init__()
        self.surf = pygame.image.load('pig_in_car.png').convert_alpha()
        self.rect = self.surf.get_rect(center=(400, 300))  # 中心定位
​
    def update(self, keys):
        if keys[pygame.K_LEFT]:
            self.rect.move_ip((-5, 0))  # 横坐标向左
        elif keys[pygame.K_RIGHT]:
            self.rect.move_ip((5, 0))  # 横坐标向右
        elif keys[pygame.K_UP]:
            self.rect.move_ip((0, -5))  #纵坐标向上
        elif keys[pygame.K_DOWN]:
            self.rect.move_ip((0, 5))  # 纵坐标向下
​
        # 防止小猪跑到屏幕外面
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.right > 800:
            self.rect.right = 800
        if self.rect.top < 0:
            self.rect.top = 0
        if self.rect.bottom > 600:
            self.rect.bottom = 600

The .update method receives a parameter keys, which is the list-like object returned by the key. Then determine which direction key was pressed. Depending on the key being pressed, the .rect coordinate positioning object modifies the value of the corresponding direction. rect.move_ip The ip here is the abbreviation of inplace, which is to modify the .rect attribute itself. Its argument is a tuple corresponding to the abscissa and ordinate. If the horizontal and vertical coordinates are less than 0, it means to the left or up, and if it is greater than 0, it means to the right or down.

The original main() function only needs to add two lines of code before win.blit:

keys = pygame.key.get_pressed()
pig.update(keys)
The complete code is as follows:

 import pygame
​
​
class Bg(pygame.sprite.Sprite):
    def __init__(self):
        super(Bg, self).__init__()
        bg_small = pygame.image.load('bg.png').convert_alpha()
        grass_land = bg_small.subsurface((0, 0, 128, 128))
        self.surf = pygame.transform.scale(grass_land, (800, 600))
        self.rect = self.surf.get_rect(left=0, top=0)  # 左上角定位
​
​
class Pig(pygame.sprite.Sprite):
    def __init__(self):
        super(Pig, self).__init__()
        self.surf = pygame.image.load('pig_in_car.png').convert_alpha()
        self.rect = self.surf.get_rect(center=(400, 300))  # 中心定位
​
    def update(self, keys):
        if keys[pygame.K_LEFT]:
            self.rect.move_ip((-5, 0))
        elif keys[pygame.K_RIGHT]:
            self.rect.move_ip((5, 0))
        elif keys[pygame.K_UP]:
            self.rect.move_ip((0, -5))
        elif keys[pygame.K_DOWN]:
            self.rect.move_ip((0, 5))
​
        # 防止小猪跑到屏幕外面
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.right > 800:
            self.rect.right = 800
        if self.rect.top < 0:
            self.rect.top = 0
        if self.rect.bottom > 600:
            self.rect.bottom = 600
​
​
class Goddess(pygame.sprite.Sprite):
    def __init__(self):
        super(Goddess, self).__init__()
        building = pygame.image.load('building.png').convert_alpha()
        self.surf = building.subsurface(((7 * 64 - 10, 0, 50, 100)))
        self.rect = self.surf.get_rect(center=(500, 430))  # 女神像的中心放到画布(500, 430)的位置
​
​
def main():
    pygame.init()
    pygame.display.set_caption('未闻Code:青南做的游戏')  # 游戏标题
    win = pygame.display.set_mode((800, 600))  # 窗口尺寸
​
    bg = Bg()
    goddess = Goddess()
    pig = Pig()
    all_sprites = [bg, goddess, pig]  # 注意添加顺序,后添加的对象图层在先添加的对象的图层上面
​
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:  # 点击左上角或者右上角的x关闭窗口时,停止程序
                running = False
​
        keys = pygame.key.get_pressed()
        pig.update(keys)
        for sprite in all_sprites:
            win.blit(sprite.surf, sprite.rect)
        pygame.display.flip()
​
​
if __name__ == '__main__':
    main()

The final running effect is shown in the following video:

Summarize

PyGame is really very simple to make games. As long as you can load the materials, you can make a game that can still be seen. Today we learned how to add material and how to capture keyboard events.

PyGame can read Gif images, but you will find that after loading, the Gif will not move. Soon, we will talk about how to make a character you control move, for example, control a small doll, and when it moves, its feet also move. And object collision detection.

References

  • [1] itch.io:

https://itch.io/game-assets

  • [2] PyGame: A Primer on Game Programming in Python:

https://realpython.com/pygame-a-primer


Long press to identify the QR code and follow Microsoft China MSDN


微软技术栈
418 声望994 粉丝

微软技术生态官方平台。予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。