使用 pygame 搭建网页游戏框架

本文主要介绍了如何使用 pygame 和 pygbag 搭建游戏框架,使其可以直接在浏览器网页运行。

准备
关于 pygamepygbag 的简介和安装请参考本站 使用 pygame 制作游戏并发布到个人网站
pygame 网页框架 https://github.com/Newverse-Wiki/Code-for-Blog/tree/main/Pygame-On-Web/Skeleton

总览

本文给出网页运行 pygame 的基本框架,后续程序仅需考虑游戏内容,无需关注网页适配等问题。

项目代码可从上述 GitHub - Skeleton 链接下载。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 项目结构
$ tree Skeleton
Skeleton
├── build
│   └── web
│       ├── favicon.png
│       ├── index.html
│       └── skeleton.apk
├── debug.py
├── game.py
└── main.py

# python 运行
$ python Skeleton/main.py

# pygbag 打包
$ pygbag Skeleton

build/web 目录下为 pygbag 打包好的网页项目,将该目录下的所有内容上传至网站服务器即可完成部署。

main.py

pygbag 打包游戏内容时要求游戏以 main.py 为入口

将涉及到与 asyncio 相关的代码均放置在 main.py 中, 后续代码中无序考虑 asyncio 异步处理、等待等问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""pygbag 打包游戏内容时要求游戏以 main.py 为入口

将涉及到与 asyncio 相关的代码均放置在 main.py 中,
后续代码中无序考虑 asyncio 异步处理、等待等问题。

"""

# 网页运行 python 需要引入 asyncio
import asyncio

# 游戏主代码存放在 game.py 中,作为模块引入
from game import Game

# main() 主函数需要由 async 定义
async def main():
    # 游戏主体为 Game 类的实例
    # 初始化时确定游戏界面的尺寸、刷新率
    game = Game((800, 600), FPS = 60)
    # 游戏的主循环(while True)所在函数的调用需有 await
    # 相应的游戏主循环所在函数也需要由 async 定义
    await game.start()

if __name__ == "__main__":
    # 经由 asyncio.run() 调用 main() 主函数
    asyncio.run(main())

game.py

Game 类承载游戏主循环:Game(dims, FPS)

定义游戏界面的尺寸 dims,游戏帧数 FPS,控制游戏流程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 引入 pygame 模块
import pygame
# 引入 asyncio 模块,game.py 中仅需 2 行与之相关的代码
import asyncio

# 引入 debug 函数,方便在游戏界面上输出调试信息
from debug import debug

class Game:

    """Game 类承载游戏主循环

    Game(dims, FPS)

    定义游戏界面的尺寸 dims,游戏帧数 FPS,控制游戏流程

    """

    def __init__(self, dims, FPS = 60):
        self.dims = dims
        self.FPS  = FPS

        # 初始化pygame,预定义各种常量
        pygame.init()

    # 游戏主循环所在函数需要由 async 定义
    async def start(self):
        # 初始化游戏界面(screen):尺寸、背景色等
        screen = pygame.display.set_mode(self.dims)
        screen_width, screen_height = self.dims
        screen_color = 'Black'

        # 初始化游戏时钟(clock),由于控制游戏帧率
        clock = pygame.time.Clock()

        # 游戏运行控制变量(gamen_running)
        # True:游戏运行
        # False:游戏结束
        game_running = True
        # 游戏主循环
        while game_running:
            # 按照给定的 FPS 刷新游戏
            # clock.tick() 函数返回上一次调用该函数后经历的时间,单位为毫秒 ms
            # dt 记录上一帧接受之后经历的时间,单位为秒 m
            # 使用 dt 控制物体运动可以使游戏物理过程与帧率无关
            dt = clock.tick(self.FPS) / 1000.0
            # 使用 asyncio 同步
            # 此外游戏主体代码中不需要再考虑 asyncio
            await asyncio.sleep(0)

            # 游戏事件处理
            # 包括键盘、鼠标输入等
            for event in pygame.event.get():
                # 点击关闭窗口按钮或关闭网页
                if event.type == pygame.QUIT:
                    game_running = False

            # 以背景色覆盖刷新游戏界面
            screen.fill(screen_color)

            # 调用 debug 函数在游戏界面左上角显示游戏帧率
            debug(f"{clock.get_fps():.1f}", color = 'green')

            # 将游戏界面内容输出至屏幕
            pygame.display.update()

        # 当 game_running 为 False 时,
        # 跳出游戏主循环,退出游戏
        pygame.quit()

debug.py

调试模块:debug(info, x = 10, y = 10, color = 'white')

将调试信息 info,以字符串格式输出至游戏界面 (x, y) 位置,可以自定义字体颜色 color

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
""" 调试模块

debug(info, x = 10, y = 10, color = 'white')

将调试信息 info,以字符串格式输出至游戏界面 (x, y) 位置
可以自定义字体颜色 color

"""

# 引入 pygame 模块
import pygame
# 初始化pygame,预定义各种常量
pygame.init()
# 调用 pygame 内置默认字体
font = pygame.font.Font(None, 30)

def debug(info, x = 10, y = 10, color = 'white'):
    # 获取游戏界面
    screen = pygame.display.get_surface()

    # 渲染调试信息
    debug_surf = font.render(str(info), True, color)
    # 给定调试信息输出位置
    debug_rect = debug_surf.get_rect(topleft = (x, y))
    # 将调试信息背景设置为黑色,以覆盖游戏界面中的其他元素
    pygame.draw.rect(screen, 'black', debug_rect)
    # 将调试信息输出至游戏界面
    screen.blit(debug_surf, debug_rect)

【参考】:

0%