引言
相信大多数人在小时候都或多或少玩过许多款游戏,当我们第一次接触编程时,心中想到的第一件事情可能也是开发一款非常酷的游戏,可以分享给我们的家人、同学和朋友。
倘若没有现成的框架或足够的学习资料时,常人是难以开发出一款带有高级特效的游戏。现在,学完了Python这门编程语言,结合强大且丰富的Python社区,使得我们制作优秀电脑游戏比以往容易得多。
在本教程中,我们将带你探索几款热门的基于Python的游戏引擎,带领大家学习如何制作自己的第一款Python小游戏!
本文的大纲目录如下:
Source: Top Python Game Engines
Author: Jon Fincher
Date: May 04, 2022
Python Game Engines Overview
Python游戏引擎通常以Python库的形式呈现给大家,因此可以以多种方式进行安装。大多数都可以在PyPI上使用,并且可以通过pip工具进行安装。然而,有一些只能在GitHub、GitLab或其他代码托管仓库上使用,而且它们可能需要其他安装步骤。本文将介绍几个常用游戏引擎的安装方法。
Python是一种通用编程语言,除了编写电脑游戏外,它还被用于各种任务。相比之下,有许多不同的独立游戏引擎是专门为编写游戏而定制的。其中包括:
这些独立的游戏引擎与Python游戏引擎的几个关键区别点在于:
语言支持 C++、C#和javaScript等语言在独立游戏引擎中很受欢迎,因为引擎本身通常是用这些语言编写的。很少有独立引擎支持Python。
专有脚本支持 此外简单游戏引擎代码,许多独立的游戏引擎维护和支持自己的脚本语言,这可能与Python不同。例如,Unity原生使用C#,而Unreal最适合使用C++。
平台支持 许多现代的独立游戏引擎能够为各种平台(包括手机和专用游戏系统)制作游戏,而无需复杂的程序。相比之下,将Python游戏移植到不同平台,特别是移动端平台,可能是一项艰巨的任务。
授权选项 基于所使用的引擎贴图笔刷,使用独立游戏引擎编写的游戏可能有不同的授权选项和限制。
那么为什么我们还要用Python来编写游戏呢?其中一个很重要的原因便是使用独立游戏引擎通常需要你学习一种新的编程或脚本语言,即学习成本很高。而Python游戏引擎只需要你捡起现有的Python基础知识便可以完成许多游戏demo的开发,降低学习难度,让你快速入门。
Python环境中有许多好用的游戏引擎,这里的优势主要是指以下几点:
下面,我们将按照下述步骤介绍每个游戏引擎:
Pygame
所有接触过Python编程的人,第一个能想到的游戏引擎应该就是Pygame。事实上简单游戏引擎代码,在Real Python中已经有一个关于Pygame的很好的入门教程,强烈推荐大家关注学习!后续我们也会同步更新相关的教程呈现给大家。
Pygame是作为被搁置的PySDL库的替换而编写的,它封装并扩展了SDL库,SDL是Simple DirectMedia Layer的缩写。SDL提供对系统底层多媒体硬件组件(如声音、视频、鼠标、键盘和操纵杆)的跨平台访问。SDL和Pygame的跨平台特性意味着你可以为支持它们的每个平台编写游戏和丰富的多媒体Python程序!
Pygame Installation
Pygame在PyPI上可用,因此在创建并激活虚拟环境后,可以使用适当的pip命令安装它:
$ python -m pip install pygame
完成之后,你可以通过运行该库附带的例子来验证安装是否成功:
python -m pygame.examples.aliens
现在你已经安装了Pygame,你可以立即开始使用它。如果在安装过程中遇到问题,那么入门指南将为你提供一切尽可能的解决方案。
Basic Concepts
Pygame被组织成几个不同的模块,它们提供对计算机图形、声音和输入硬件的抽象访问。Pygame还定义了许多类,这些类封装了与硬件无关的概念。例如,绘图是在Surface对象上完成的,其矩形限制由其Rect对象定义。
每款游戏都利用游戏循环来控制游戏玩法。这个循环会随着游戏的进展而不断迭代。Pygame提供了实现游戏循环的方法和函数,但它不自动提供。游戏作者需要实现游戏循环的功能。
游戏循环的每次迭代被称为一个框架。每一帧,游戏执行四个重要动作:
Basic Application
首先,让我们新建一个Pygame程序pygame_basic.py,在屏幕上绘制了一些图形和一些文本:
pygame
实现上述窗口及功能对应的代码片段如下:
Show/Hide
"""
Basic "Hello, World!" program in Pygame
This program is designed to demonstrate the basic capabilities
of Pygame. It will:
- Create a game window
- Fill the background with white
- Draw some basic shapes in different colors
- Draw some text in a specified size and color
- Allow you to close the window
"""
# Import and initialize the pygame library
import pygame
pygame.init()
# Set the width and height of the output window, in pixels
WIDTH = 800
HEIGHT = 600
# Set up the drawing window
screen = pygame.display.set_mode([WIDTH, HEIGHT])
# Run until the user asks to quit
running = True
while running:
# Did the user click the window close button?
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Fill the background with white
screen.fill((255, 255, 255))
# Draw a blue circle with a radius of 50 in the center of the screen
pygame.draw.circle(screen, (0, 0, 255), (WIDTH // 2, HEIGHT // 2), 50)
# Draw a red-outlined square in the top-left corner of the screen
red_square = pygame.Rect((50, 50), (100, 100))
pygame.draw.rect(screen, (200, 0, 0), red_square, 1)
# Draw an orange caption along the bottom in 60-point font
text_font = pygame.font.SysFont("any_font", 60)
text_block = text_font.render(
"Hello, World! From Pygame", False, (200, 100, 0)
)
screen.blit(text_block, (50, HEIGHT - 50))
# Flip the display
pygame.display.flip()
# Done! Time to quit.
pygame.quit()
尽管它的功能很简单,但即使是这个基本的Pygame程序也需要一个游戏循环和事件处理程序。游戏循环从27行开始,由运行变量控制。将此变量设置为False将结束程序。
事件处理从第30行开始,带有一个事件循环。使用pygame.event.get()从队列中检索事件,并在每次循环迭代期间一次处理一个事件。在本例中,唯一要处理的事件是pygame事件,当用户关闭游戏窗口时生成。当处理此事件时,设置running = False,这将最终结束游戏循环和程序。
Pygame提供了各种绘制基本形状的方法,如圆形和矩形。在这个示例中,第38行上画了一个蓝色的圆,第41和42行上画了一个红色的正方形。注意,绘制矩形需要首先创建一个Rect对象。
在屏幕上绘制文本稍微复杂一些。首先,在第45行,选择一种字体并创建一个字体对象。在第46到48行使用该字体,调用.render()方法进行渲染。这将创建一个包含以指定字体和颜色呈现的文本的Surface对象。最后,使用第49行中的.blit()方法将Surface复制到屏幕上。
游戏循环的结束发生在第52行,当之前绘制的所有内容都显示在显示器上时。如果没有这一行,就不会显示任何内容。
Advanced Application
当然,Pygame的设计初衷是用Python编写游戏。为了探索一个真正的Pygame游戏的功能和需求,我们尝试用Pygame编写一个小游戏,包含以下细节:
完成后,游戏看起来会是这样的:
coin
完整的代码如下:
Show/Hide
"""
Complete Game in Pygame
This game demonstrates some of the more advanced features of
Pygame, including:
- Using sprites to render complex graphics
- Handling user mouse input
- Basic sound output
"""
# Import and initialize the pygame library
import pygame
# To randomize coin placement
from random import randint
# To find your assets
from pathlib import Path
# For type hinting
from typing import Tuple
# Set the width and height of the output window, in pixels
WIDTH = 800
HEIGHT = 600
# How quickly do you generate coins? Time is in milliseconds
coin_countdown = 2500
coin_interval = 100
# How many coins can be on the screen before you end?
COIN_COUNT = 10
# Define the Player sprite
class Player(pygame.sprite.Sprite):
def __init__(self):
"""Initialize the player sprite"""
super(Player, self).__init__()
# Get the image to draw for the player
player_image = str(
Path.cwd() / "pygame" / "images" / "alien_green_stand.png"
)
# Load the image, preserve alpha channel for transparency
self.surf = pygame.image.load(player_image).convert_alpha()
# Save the rect so you can move it
self.rect = self.surf.get_rect()
def update(self, pos: Tuple):
"""Update the position of the player
Arguments:
pos {Tuple} -- the (X,Y) position to move the player
"""
self.rect.center = pos
# Define the Coin sprite
class Coin(pygame.sprite.Sprite):
def __init__(self):
"""Initialize the coin sprite"""
super(Coin, self).__init__()
# Get the image to draw for the coin
coin_image = str(Path.cwd() / "pygame" / "images" / "coin_gold.png")
# Load the image, preserve alpha channel for transparency
self.surf = pygame.image.load(coin_image).convert_alpha()
# The starting position is randomly generated
self.rect = self.surf.get_rect(
center=(
randint(10, WIDTH - 10),
randint(10, HEIGHT - 10),
)
)
# Initialize the Pygame engine
pygame.init()
# Set up the drawing window
screen = pygame.display.set_mode(size=[WIDTH, HEIGHT])
# Hide the mouse cursor
pygame.mouse.set_visible(False)
# Set up the clock for a decent frame rate
clock = pygame.time.Clock()
# Create a custom event for adding a new coin
ADDCOIN = pygame.USEREVENT + 1
pygame.time.set_timer(ADDCOIN, coin_countdown)
# Set up the coin_list
coin_list = pygame.sprite.Group()
# Initialize the score
score = 0
# Set up the coin pickup sound
coin_pickup_sound = pygame.mixer.Sound(
str(Path.cwd() / "pygame" / "sounds" / "coin_pickup.wav")
)
# Create a player sprite and set its initial position
player = Player()
player.update(pygame.mouse.get_pos())
# Run until you get to an end condition
running = True
while running:
# Did the user click the window close button?
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Should you add a new coin?
elif event.type == ADDCOIN:
# Create a new coin and add it to the coin_list
new_coin = Coin()
coin_list.add(new_coin)
# Speed things up if fewer than three coins are on-screen
if len(coin_list) < 3:
coin_countdown -= coin_interval
# Need to have some interval
if coin_countdown < 100:
coin_countdown = 100
# Stop the previous timer by setting the interval to 0
pygame.time.set_timer(ADDCOIN, 0)
# Start a new timer
pygame.time.set_timer(ADDCOIN, coin_countdown)
# Update the player position
player.update(pygame.mouse.get_pos())
# Check if the player has collided with a coin, removing the coin if so
coins_collected = pygame.sprite.spritecollide(
sprite=player, group=coin_list, dokill=True
)
for coin in coins_collected:
# Each coin is worth 10 points
score += 10
# Play the coin collected sound
coin_pickup_sound.play()
# Are there too many coins on the screen?
if len(coin_list) >= COIN_COUNT:
# This counts as an end condition, so you end your game loop
running = False
# To render the screen, first fill the background with pink
screen.fill((255, 170, 164))
# Draw the coins next
for coin in coin_list:
screen.blit(coin.surf, coin.rect)
# Then draw the player
screen.blit(player.surf, player.rect)
# Finally, draw the score at the bottom left
score_font = pygame.font.SysFont("any_font", 36)
score_block = score_font.render(f"Score: {score}", False, (0, 0, 0))
screen.blit(score_block, (50, HEIGHT - 50))
# Flip the display to make everything appear
pygame.display.flip()
# Ensure you maintain a 30 frames per second rate
clock.tick(30)
# Done! Print the final score
print(f"Game over! Final score: {score}")
# Make the mouse visible again
pygame.mouse.set_visible(True)
# Quit the game
pygame.quit()
Pygame中的精灵提供了一些基本功能,但它们被设计成子类,而不是单独使用。Pygame精灵在默认情况下没有与它们相关的图像,它们不能自己定位。
为了正确地绘制和管理屏幕上的玩家和硬币,在第35至55行创建了一个player类,在第58至75行创建了一个Coin类。当每个精灵对象被创建时,它首先定位并加载它将显示的图像,并将其保存在self.surf中。self.rect属性用于定位并移动屏幕上的精灵。
定时在屏幕上添加硬币是通过计时器完成的。在·Pygame·中游戏角色,每当计时器过期时就会触发事件,游戏开发者可以将自己的事件定义为整数常量。ADDCOIN事件在第90行上定义,计时器在第91行上的coin_countdown毫秒后触发该事件。
因为ADDCOIN是一个事件,所以需要在事件循环中处理它,这发生在第118行到134行。该事件创建一个新的Coin对象并将其添加到现有的coin_list中。检查屏幕上的硬币数量。如果少于3个,则减少coin_countdown。最后,停止前一个计时器,开始一个新的计时器。
当玩家移动时,他们会与硬币碰撞,并收集它们。这将自动从coin_list中删除收集到的每个硬币。这也会更新乐谱并播放声音。
玩家移动发生在第137行。在第140至142行检查与屏幕上硬币的碰撞。dokill=True参数自动从coin_list中删除硬币。最后,第143行到147行更新分数并播放收集到的每个硬币的声音。
当用户关闭窗口,或者当屏幕上有超过10个硬币时,游戏就结束了。在第150行到152行检查是否有超过10个硬币。
因为Pygame精灵没有内置的图像知识,它们也不知道如何在屏幕上绘制自己。游戏作者需要清除屏幕,以正确的顺序绘制所有精灵,绘制屏幕得分,然后.flip()显示所有内容。这一切都发生在第155到170行。
总的来说,Pygame是一个非常强大和完善的库,但它也有缺点。实现基本的精灵行为和实现游戏循环和基本事件处理程序等关键游戏需求取决于游戏作者。接下来,你将看到其他游戏引擎如何在减少工作量的同时提供类似的结果。
今天将为大家介绍到这里,后续我们将会以相同的案例为大家介绍其它几款游戏引擎,包括Pygame Zero、Arcade、adventurelib和Ren’Py,大家可以综合比较一下。敬请期待,下期不见不散。
往期回顾
原文始发于微信公众号(Pytrick):【Python项目实战】主流游戏引擎(上)