玩转pygame——游戏开发

使用pygame进行游戏开发

Pygame是一个开源的Python模块,专门用于多媒体应用(如电子游戏)的开发,其中包含对图像、声音、视频、事件、碰撞等的支持。Pygame建立在SDL的基础上,SDL是一套跨平台的多媒体开发库,用C语言实现,被广泛的应用于游戏、模拟器、播放器等的开发。而Pygame让游戏开发者不再被底层语言束缚,可以更多的关注游戏的功能和逻辑。
具体功能可以查看pygame的官方网站

五子棋游戏制作

制作游戏窗口

可以通过pygame中draw模块的函数在窗口上绘图,可以绘制的图形包括:线条、矩形、多边形、圆、椭圆、圆弧等。需要说明的是,屏幕坐标系是将屏幕左上角设置为坐标原点(0, 0),向右是x轴的正向,向下是y轴的正向,在表示位置或者设置尺寸的时候,我们默认的单位都是像素。所谓像素就是屏幕上的一个点,你可以用浏览图片的软件试着将一张图片放大若干倍,就可以看到这些点。pygame中表示颜色用的是色光三原色表示法,即通过一个元组或列表来指定颜色的RGB值,每个值都在0~255之间,因为是每种原色都用一个8位(bit)的值来表示,三种颜色相当于一共由24位构成,这也就是常说的“24位颜色表示法”。

import pygame


def main():
    # 初始化导入的pygame中的模块
    pygame.init()
   screen = pygame.display.set_mode([640, 640]) # 初始化用于显示的窗口并设置窗口尺寸
    pygame.display.set_caption('五子棋')  # 设置当前窗口的标题
    screen.fill([218, 165, 105])  # 填充背景色 红绿蓝三原色
    running = True
    while running:  # 开启一个事件循环处理发生的事件
        for event in pygame.event.get():  # 从消息队列中获取事件并对事件进行处理
            if event.type == pygame.QUIT:
                running = False

if __name__ == '__main__':
    main()

画棋盘

创建棋子类对象和棋盘类对象,棋盘类里面创建一个15*15的数组的属性,再写一个重置棋盘的方法,令这个棋盘数组全部等于空,还有画棋盘的方法,通过pygame的draw模块在窗口上绘画棋盘、天元、边框和棋子,还有一个判断该位置是否有棋子,可以下的方法

class Chess(object):
    """棋子类"""
    def __init__(self, x, y, is_black):
        self._is_black = is_black
        self._row = x
        self._col = y

    @property
    def is_black(self):
        return self._is_black

    @property
    def row(self):
        return self._row

    @property
    def col(self):
        return self._col

class RenjuBoard(object):
    """棋盘类"""
    def __init__(self):
        self._board = [[]] * 15
        self.reset()

    def reset(self):
        """重置棋盘"""
        for row in range(len(self._board)):
            self._board[row] = [EMPTY] * 15

    def move(self, row, col, is_black):
        """判断是否有棋子"""
        if self._board[row][col] == EMPTY:
            self._board[row][col] = BLACK if is_black else WHITE
            return True
        return False

    def draw(self, screen):
        """画棋盘"""
        for i in range(1, 16):  # 画格子:窗口,颜色,起始位置,结束位置,粗细
            pygame.draw.line(screen, black_color, [40, 40 * i], [600, 40 * i], 1)
            pygame.draw.line(screen, black_color, [40 * i, 40], [40 * i, 600], 1)
        pygame.draw.rect(screen, black_color, [36, 36, 568, 568], 4)  # 画边框
        # 画圆 窗口,颜色,位置,半径,0实心
        pygame.draw.circle(screen, black_color, [320, 320], 4, 0)
        pygame.draw.circle(screen, black_color, [160, 160], 3, 0)
        pygame.draw.circle(screen, black_color, [160, 480], 3, 0)
        pygame.draw.circle(screen, black_color, [480, 480], 3, 0)
        pygame.draw.circle(screen, black_color, [480, 160], 3, 0)
        for row in range(len(self._board)):
            for col in range(len(self._board[row])):
                if self._board[row][col] != EMPTY:
                    ccolor = black_color if self._board[row][col] == BLACK else white_color
                    pos = [40 * (col + 1), 40 * (row + 1)]
                    pygame.draw.circle(screen, ccolor, pos, 20, 0)

完善主函数 事件处理

判断输赢,绘画文字,获取鼠标点击事件,绘画棋盘等

def main():
    board = RenjuBoard()  # 创建棋盘对象
    chess = [[EMPTY] * 16 for _ in range(16)]  # 棋子位置
    is_black = True  # 黑白棋分辨变量
    pygame.init()  # 初始化pygame
    screen = pygame.display.set_mode([640, 640])  # 初始化用于显示的窗口并设置窗口尺寸
    pygame.display.set_caption('五子棋')  # 设置当前窗口的标题
    screen.fill([218, 165, 105])  # 填充背景色 红绿蓝三原色
    board.draw(screen)  # 画棋盘
    pygame.display.flip()  # 刷新窗口
    runing = True  # 是否退出游戏变量
    gameover = True  # 游戏是否结束变量
    while runing:  # 开启一个事件循环处理发生的事件
        for event in pygame.event.get():  # 从消息队列中获取事件并对事件进行处理
            if event.type == pygame.QUIT:
                runing = False
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_F2:
                    board.reset()
                    gameover = True
                    is_black = True
                    pygame.display.flip()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and gameover:  # 鼠标点击事件
                x, y = event.pos
                if 20 < x <= 600 and 20 < y < 600:
                    row = round((y - 40) / 40)
                    col = round((x - 40) / 40)
                    if board.move(row, col, is_black):
                        screen.fill([218, 165, 105])
                        board.draw(screen)
                        chess[col][row] = 1 if is_black else 2
                        pygame.display.flip()
                        if board.who_wins(col, row, chess):
                            my_font = pygame.font.SysFont("宋体", 60)  # 字体,大小
                            if is_black:
                                position = my_font.render('game over, black win', True,
                                                          (255, 0, 0))  # 内容,抗锯齿,颜色,背景色(可选)
                            else:
                                position = my_font.render('game over, white win', True, (255, 0, 0))
                            screen.blit(position, (120, 310))
                            pygame.display.flip()
                            gameover = False
                            chess = [[EMPTY] * 16 for _ in range(16)]
                        is_black = not is_black
    pygame.quit()

判断输赢

    def who_wins(self, x, y, chess):
        """判断输赢"""
        # 三维数组记录横向,纵向,左斜,右斜
        dir = [[[-1, 0], [1, 0]], [[0, -1], [0, 1]], [[-1, -1], [1, 1]], [[1, -1], [-1, 1]]]
        tempx = x
        tempy = y
        # 循环四个大方向
        for i in range(4):
            count = 1
            # 循环两边方向
            for j in range(2):
                flag = True  # 一直向一个方向遍历,有相同的,count+1,否则置flag为False
                while flag:
                    tempx += dir[i][j][0]
                    tempy += dir[i][j][1]
                    if chess[x][y] == chess[tempx][tempy]:
                        count += 1
                    else:
                        flag = False
                tempx = x
                tempy = y
                if count >= 5:
                    return True
        return False

还可以有很多拓展,比如添加背景音乐和下棋时的音乐,可以使用pygame的music和mixer,还可以写成联网游戏,用网络编程来实现
如果开发3d游戏,可以使用Panda3D
运行结果图


玩转pygame——游戏开发_第1张图片
sjx.png

飞机大战游戏

我自己结合所学知识用pygame做的一个小游戏,用了一些老师没就讲的碰撞精灵和音乐知识
首先,我创建了一个游戏对象的大类,有横坐标,纵坐标,速度,图片四个公共属性,并写成抽象类,然后分别创建了玩家类,敌机类,背景类,子弹类,并让他们继承来游戏对象类,除背景外,因为要用到碰撞精灵,所以使用了多重继承让他们还继承了pygame.sprite.Sprite。并重写了update精灵行为方法

from abc import ABCMeta,abstractclassmethod
import pygame

class GameObject(object, metaclass=ABCMeta):
    """游戏对象"""
    def __init__(self, x=0, y=0,speed=0, image=None):
        self._x = x
        self._y = y
        self._speed = speed
        self._image = image

    @property
    def image(self):
        return self._image

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    @property
    def speed(self):
        return self._speed

    @x.setter
    def x(self,x):
        self._x = x

    @y.setter
    def y(self, y):
        self._y = y

    @speed.setter
    def speed(self, speed):
        self._speed = speed

    @image.setter
    def image(self, image):
        self._image = image

class Player(GameObject, pygame.sprite.Sprite):
    """玩家"""
    def __init__(self):
        super().__init__(260, 600, 6)
        pygame.sprite.Sprite.__init__(self)
        self._image = pygame.image.load('picture/Player01.png')
        self.rect = self._image.get_rect()
        self.rect.topleft = (self._x, self._y)
        self._score = 0  # 分数

    def draw(self, screen):
        """绘制玩家"""
        screen.blit(self._image, (self.rect.left, self.rect.top))

    def update(self,dir,screen):
        """玩家移动"""
        if dir[pygame.K_d] and self.rect.left + self._speed < 520:
            self.rect.left += self._speed
        if dir[pygame.K_a] and self.rect.left + self._speed > 0:
            self.rect.left -= self._speed
        if dir[pygame.K_w] and self.rect.top > 0:
            self.rect.top -= self._speed
        if dir[pygame.K_s] and self.rect.top +self._speed < 645:
            self.rect.top += self._speed
        self.draw(screen)

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self,score):
        self._score = score

class Enemy(GameObject,pygame.sprite.Sprite):
    """敌机类"""
    def __init__(self, x, y, speed):
        super().__init__(x, y, speed)
        pygame.sprite.Sprite.__init__(self)
        self._image = pygame.image.load('picture/enemy_0.png')
        self.rect = self._image.get_rect()
        self.rect.topleft = (self._x,self._y)

    def update(self):
        self.rect.top += self._speed
        if self.rect.top > 680:
            self.kill()


class Background(GameObject):
    """背景类"""
    def __init__(self):
        super().__init__(0, 0, 2)
        bg_color1 = pygame.image.load('picture/Background.png')
        bg_color2 = pygame.image.load('picture/Background.png')
        self._image = [bg_color1, bg_color2]

    def move(self, screen):
        """背景移动"""
        self._y += self._speed
        if self._y >= 960:
            self._image = self._image[1:] + self._image[:1]
            self._y = 0
        screen.blit(self._image[0], (self._x, self._y))
        screen.blit(self._image[1], (self._x, self._y - 960))


class Bullet(GameObject,pygame.sprite.Sprite):
    """子弹类"""
    def __init__(self, x, y, image, speed, speed2=0):
        super().__init__(x, y, speed, image)
        pygame.sprite.Sprite.__init__(self)
        self._speed2 = speed2
        self.rect = self._image.get_rect()
        self.rect.topleft = (self._x, self._y)

    def update(self):
        self.rect.top -= self._speed
        self.rect.left += self._speed2
        if self.rect.top < 0 or self.rect.top > 680:
            self.kill()

主函数

首先用pygame创建窗口,窗口名,背景色,玩家对象,玩家子弹精灵组,敌机子弹精灵组,敌机精灵组,死亡敌机精灵组(因为要在死亡时产生爆炸效果),爆炸效果图片数组
创建了一个播放音乐的方法,然后用线程启动它,首先用mixer.music.load('dfj.MP3')来加载音乐文件,然后用mixer.music.play启动音乐
创建背景对象,在while循环里调用对象的move进行移动
编写方法:创建玩家子弹并移动和绘画,创建敌机子弹并移动和绘画,创建敌机并移动和绘画,绘画使用精灵的内部方法draw方法,把精灵组的全部精灵绘画到窗口上
然后在主函数判断游戏是否结束,如果没有,就获取键盘输入事件,再调用上面所创建的方法进行绘画和移动
接着检测玩家子弹与敌机是否相碰,两个精灵组相碰用的是sprite的groupcollide方法,相碰的话两者都消失,并把相碰的敌机添加到死亡敌机精灵组,再遍历敌机精灵组,绘画爆炸效果,就是五张图片连续播放出来
继续检测敌机子弹或敌机是否与玩家相撞,一个精灵与一组精灵相撞,使用sprite的spritecollideany的方法,如果相撞,gameover置为True游戏结束,游戏结束会绘画一段字体在屏幕上,按f2会重开游戏,鼠标点击窗口关闭按钮会退出游戏
代码如下:

import pygame
from threading import Thread
from random import *
import GameObject

def main():
    def creat_player_bullet(dir):
        """创建玩家子弹"""
        nonlocal count2
        count2 += 1
        if dir[pygame.K_j] and count2 % 10 == 0:
            speed = 3
            image = pygame.image.load('picture/shotw_0.png')
            bullet_player = GameObject.Bullet(player.rect.left + 14, player.rect.top, image, speed, 0)
            bullet_player_group.add(bullet_player)
        bullet_player_group.update()  # 玩家子弹移动
        bullet_player_group.draw(screen)  # 绘制玩家子弹

    def creat_enemy():
        """创建敌机"""
        if randint(1, 20) == 1:
            x = randint(20, 520)
            enemy_group.add(GameObject.Enemy(x, 0, randint(3, 5)))
        enemy_group.update()  # 敌机移动
        enemy_group.draw(screen)  # 绘制敌机

    def creat_enemy_bullet():
        """创建敌机子弹"""
        nonlocal count1
        count1 += 1
        if count1 % 50 == 0:
            for i in enemy_group:
                speed = -6
                speed2 = int((player.rect.left - i.rect.left) * 0.008571428571428572)
                image = pygame.image.load('picture/Bullet.png')
                bullet_enemy = GameObject.Bullet(i.rect.left, i.rect.top, image, speed, speed2)
                bullet_enemy_group.add(bullet_enemy)
        bullet_enemy_group.update()
        bullet_enemy_group.draw(screen)

    def resetgame():
        """重置游戏"""
        nonlocal player, bullet_enemy_group, bullet_player_group, enemy_group, gameover
        player = GameObject.Player()
        bullet_player_group = pygame.sprite.Group()
        bullet_enemy_group = pygame.sprite.Group()
        enemy_group = pygame.sprite.Group()
        pygame.display.update()
        gameover = False

    def music_play():
        """播放音乐"""
        pygame.mixer.music.load('dfj.MP3')
        pygame.mixer.music.play(loops=0, start=0.0)

    pygame.init()
    player = GameObject.Player()  # 创建玩家
    bullet_player_group = pygame.sprite.Group()  # 创建玩家子弹精灵组
    bullet_enemy_group = pygame.sprite.Group()  # 创建敌机子弹精灵组
    enemy_group = pygame.sprite.Group()  # 创建敌机精灵组
    enemy_hit_group = pygame.sprite.Group()  # 死亡敌机精灵组
    enemy_bomb_picture = []  # 爆炸效果图片数组
    for i in range(6):
        enemy_bomb_picture.append(pygame.image.load('picture/bomb_enemy_%d.png' % i))
    background = GameObject.Background()  # 创建背景
    screen = pygame.display.set_mode((540, 680))  # 创建游戏窗口
    pygame.display.set_caption('雷电')  # 窗口名
    screen.fill((230, 230, 230))  # 填充背景色
    Thread(target=music_play, daemon=True).start()
    gameover = False
    running = True
    count = count1 = count2 = 0
    while running:
        background.move(screen)  # 背景移动
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_F2:  # f2重置游戏
                    resetgame()
        if not gameover:
            dir = pygame.key.get_pressed() # 获取键盘事件
            player.update(dir,screen)  # 玩家移动并绘画
            creat_player_bullet(dir)  # 创建玩家子弹并移动和绘画
            creat_enemy()  # 创建敌机并移动和绘画
            creat_enemy_bullet()  # 创建敌机子弹
            if enemy_hit_group.add(pygame.sprite.groupcollide\
                    (enemy_group, bullet_player_group, True, True)):  # 检测玩家子弹与敌机碰撞
                player.score += 1
            if pygame.sprite.spritecollideany(player,bullet_enemy_group) \
                or pygame.sprite.spritecollideany(player, enemy_group):  # 检测玩家与敌机和敌机子弹碰撞
                gameover = True
            for enemy_hit in enemy_hit_group:  # 爆炸
                screen.blit(enemy_bomb_picture[count], enemy_hit.rect)
                # time.sleep(0.001)
                if count < 5:
                    count += 1
                else:
                    enemy_hit_group.remove(enemy_hit)
            pygame.display.flip()
        else:
            my_font1 = pygame.font.SysFont("宋体", 60)
            position1 = my_font1.render('game over', True, (255, 0, 0))
            screen.blit(position1, (200, 300))
            pygame.display.flip()

    pygame.quit()


if __name__ == '__main__':
    main()

游戏运行截图:


玩转pygame——游戏开发_第2张图片
dfj.png

贪吃蛇

上面两个一个没动,一个连续移动,所以没用帧数控制,而贪吃蛇需要一节一节的动,就需要用帧数控制
蛇是由一个一个蛇节点对象构成,移动的时候把最后的节点删除,然后在头部新增一个头结点
吃食物使用头结点判断,然后添加在尾节点上
其他逻辑没什么想说的,直接上代码。。。

import pygame
import json
from threading import Thread
from random import *
from abc import ABCMeta, abstractclassmethod
BLACK_COLOR = (0, 0, 0)
FOOD_COLOR = (236, 189, 187)
GREEN_COLOR = (0, 255, 0)
UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3


class GameObject(object, metaclass=ABCMeta):
    """游戏对象"""
    def __init__(self, x=0, y=0, color=BLACK_COLOR):
        self._x = x
        self._y = y
        self._color = color

    @abstractclassmethod
    def draw(self, screen):
        pass

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y


class Wall(GameObject):
    """围墙类"""
    def __init__(self, x, y, lenth, width, color=BLACK_COLOR):
        super().__init__(x, y, color)
        self._lenth = lenth
        self._width = width

    def draw(self, screen):
        pygame.draw.rect(screen, self._color, (self._x, self._y, self._width, self._lenth), 4)

    @property
    def lenth(self):
        return self._lenth

    @property
    def width(self):
        return self._width


class Food(GameObject):
    """食物类"""
    def __init__(self, x, y, length, color=FOOD_COLOR):
        super().__init__(x, y, color)
        self._length = length
        self._hidden = False  # 是否隐藏

    def draw(self, screen):
        if not self._hidden:
            pygame.draw.circle(screen, self._color,
                               (self._x + self._length // 2, self._y + self._length // 2),
                               self._length // 2, 0)
        self._hidden = not self._hidden


class SnakeNode(GameObject):
    """蛇身节点类"""
    def __init__(self, x, y, size, color=GREEN_COLOR):
        super().__init__(x, y, color)
        self._size = size

    def draw(self, screen):
        pygame.draw.rect(screen, self._color,
                         (self._x, self._y, self._size, self._size), 0)
        pygame.draw.rect(screen, BLACK_COLOR,
                         (self._x, self._y, self._size, self._size), 1)

    @property
    def size(self):
        return self._size


class Snake(GameObject):
    """蛇"""
    def __init__(self):
        super().__init__()
        self._dir = LEFT  # 方向
        self._nodes = []  # 身体数组
        for index in range(5):
            node = SnakeNode(290 + index * 20, 250, 20)
            self._nodes.append(node)

    def collide(self, wall):
        """撞墙"""
        head = self.head
        return head.x < wall.x or head.y < wall.y or head.x + head.size > wall.x + wall.lenth\
            or head.y + head.size > wall.y + wall.width

    def eat_food(self, food):
        """吃食物"""
        if self.head.x == food.x and self.head.y == food.y:
            tail = self._nodes[-1]
            self._nodes.append(tail)
            return True
        return False

    def eat_self(self):
        for i in range(4, len(self.nodes)):
            if self.head.x == self.nodes[i].x and self.head.y == self.nodes[i].y:
                return True
        return False

    def draw(self, screen):
        """画蛇"""
        for node in self._nodes:
            node.draw(screen)

    def move(self):
        """移动"""
        head = self.head
        snake_dir = self._dir
        x, y, size = head.x, head.y, head.size
        if snake_dir == UP:
            y -= size
        elif snake_dir == RIGHT:
            x += size
        elif snake_dir == DOWN:
            y += size
        else:
            x -= size
        new_head = SnakeNode(x, y, size)
        self._nodes.insert(0, new_head)
        self._nodes.pop()

    @property
    def dir(self):
        return self._dir

    @property
    def nodes(self):
        return self._nodes

    @property
    def head(self):
        return self._nodes[0]

    def change_dir(self, new_dir):
        if (self._dir + new_dir) % 2 != 0:
            self._dir = new_dir


def main():
    def refresh():
        """刷新游戏窗口"""
        screen.fill((242, 242, 242))
        wall.draw(screen)  # 画围墙
        food.draw(screen)  # 画食物
        snake.draw(screen)  # 画蛇
        show_info(screen, snake)
        pygame.display.flip()

    def reset_game():
        nonlocal food, snake, gameover, rank
        food = creat_food()
        snake = Snake()
        gameover = False

    def handle_key_event(key_event):
        """处理按键事件"""
        key = key_event.key
        new_dir = snake.dir
        if key == pygame.K_w or key == pygame.K_UP:
            new_dir = UP
        if key == pygame.K_d or key == pygame.K_RIGHT:
            new_dir = RIGHT
        if key == pygame.K_s or key == pygame.K_DOWN:
            new_dir = DOWN
        if key == pygame.K_a or key == pygame.K_LEFT:
            new_dir = LEFT
        if key == pygame.K_F2:
            reset_game()
            return
        if new_dir != snake.dir:
            snake.change_dir(new_dir)

    def creat_food():
        """吃食物"""
        row = randint(0, 29)
        col = randint(0, 29)
        return Food(10 + 20 * col, 10 + 20 * row, 20)

    def show_info(screen1, snake1):
        """展示分数"""
        my_font2 = pygame.font.SysFont("宋体", 30)
        position3 = my_font2.render('high score: %d' % max(rank), True, (255, 0, 0))
        screen1.blit(position3, (453, 10))
        position2 = my_font2.render('score: %d' % (len(snake1.nodes) - 5), True, (255, 0, 0))
        screen1.blit(position2, (500, 30))
        pygame.display.flip()
    try:
        with open('rank.json', 'r', encoding='utf-8') as fs:
            if len(fs.read()) == 0:
                rank = [0]  # 排行榜
            else:
                with open('rank.json', 'r', encoding='utf-8') as fs2:
                    rank = json.load(fs2)
    except IOError as e:
        print(e)
    pygame.init()
    screen = pygame.display.set_mode((620, 620))  # 创建游戏窗口
    pygame.display.set_caption('贪吃蛇')  # 窗口名
    screen.fill((242, 242, 242))  # 填充背景色
    pygame.display.flip()  # 刷新窗口
    clock = pygame.time.Clock()  # 创建计时器,控制帧数
    wall = Wall(10, 10, 600, 600)  # 围墙
    food = creat_food()  # 食物
    snake = Snake()  # 蛇
    running = True
    gameover = False
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                try:
                    with open('rank.json', 'w', encoding='utf-8') as fs:
                        json.dump(rank, fs)
                except IOError as e:
                    print(e)
                running = False
            elif event.type == pygame.KEYDOWN:
                handle_key_event(event)
            elif event.type == pygame.MOUSEBUTTONDOWN:
                pass
        if not gameover:
            Thread(target=refresh,daemon=True).start()
            snake.move()
            if snake.collide(wall) or snake.eat_self():
                gameover = True
            if snake.eat_food(food):
                food = creat_food()
            if gameover:
                rank.append(len(snake.nodes) - 5)
        else:
            my_font1 = pygame.font.SysFont("宋体", 60)
            position1 = my_font1.render('game over', True, (255, 0, 0))
            screen.blit(position1, (200, 300))
            pygame.display.flip()
        clock.tick(6)  # 6帧
    pygame.quit()


if __name__ == '__main__':
    main()

游戏运行结果:


玩转pygame——游戏开发_第3张图片
snake.png

你可能感兴趣的:(玩转pygame——游戏开发)