自制街机游戏:Squish 游戏开发全解析
一、引言
在游戏开发的世界里,Python 的 Pygame 库为开发者们提供了一个强大且便捷的工具。今天我们要详细介绍的是一个名为 Squish 的街机游戏,它的开发过程涵盖了 Pygame 的诸多核心知识点,非常适合想要深入了解游戏开发的初学者。
二、游戏概述
Squish 游戏由五个主要文件构成:
- config.py :包含各种配置变量,如游戏速度、屏幕大小、背景颜色等。
- objects.py :实现了游戏中的对象,如香蕉和重物。
- squish.py :包含主游戏类和各种游戏状态类。
- weight.png 和 banana.png :游戏中使用的两张图片。
三、Pygame 矩形操作基础
在 Pygame 中,矩形( Rect )是一个非常重要的概念。我们可以通过修改矩形的属性(如 top 、 bottom 、 left 、 right 等)或调用方法(如 inflate 、 move )来对其进行操作。这些操作在 Pygame 文档(http://pygame.org/docs/ref/Rect.html)中有详细描述。
四、游戏文件详细解析
(一)配置文件 config.py
# Configuration file for Squish
# -----------------------------
# Feel free to modify the configuration variables below to taste.
# If the game is too fast or too slow, try to modify the speed
# variables.
# Change these to use other images in the game:
banana_image = 'banana.png'
weight_image = 'weight.png'
splash_image = 'weight.png'
# Change these to affect the general appearance:
screen_size = 800, 600
background_color = 255, 255, 255
margin = 30
full_screen = 1
font_size = 48
# These affect the behavior of the game:
drop_speed = 5
banana_speed = 10
speed_increase = 1
weights_per_level = 10
banana_pad_top = 40
banana_pad_side = 20
这个文件包含了游戏的各种配置信息,你可以根据自己的喜好进行修改。例如,如果觉得游戏速度太快或太慢,可以调整 drop_speed 和 banana_speed 变量。
(二)游戏对象文件 objects.py
import pygame, config, os
from random import randrange
"This module contains the game objects of the Squish game."
class SquishSprite(pygame.sprite.Sprite):
"""
Generic superclass for all sprites in Squish. The constructor
takes care of loading an image, setting up the sprite rect, and
the area within which it is allowed to move. That area is governed
by the screen size and the margin.
"""
def __init__(self, image):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image).convert()
self.rect = self.image.get_rect()
screen = pygame.display.get_surface()
shrink = -config.margin * 2
self.area = screen.get_rect().inflate(shrink, shrink)
class Weight(SquishSprite):
"""
A falling weight. It uses the SquishSprite constructor to set up
its weight image, and will fall with a speed given as a parameter
to its constructor.
"""
def __init__(self, speed):
SquishSprite.__init__(self, config.weight_image)
self.speed = speed
self.reset()
def reset(self):
"""
Move the weight to the top of the screen (just out of sight)
and place it at a random horizontal position.
"""
x = randrange(self.area.left, self.area.right)
self.rect.midbottom = x, 0
def update(self):
"""
Move the weight vertically (downwards) a distance
corresponding to its speed. Also set the landed attribute
according to whether it has reached the bottom of the screen.
"""
self.rect.top += self.speed
self.landed = self.rect.top >= self.area.bottom
class Banana(SquishSprite):
"""
A desperate banana. It uses the SquishSprite constructor to set up
its banana image, and will stay near the bottom of the screen,
with its horizontal position governed by the current mouse
position (within certain limits).
"""
def __init__(self):
SquishSprite.__init__(self, config.banana_image)
self.rect.bottom = self.area.bottom
# These paddings represent parts of the image where there is
# no banana. If a weight moves into these areas, it doesn't
# constitute a hit (or, rather, a squish):
self.pad_top = config.banana_pad_top
self.pad_side = config.banana_pad_side
def update(self):
"""
Set the Banana's center x-coordinate to the current mouse
x-coordinate, and then use the rect method clamp to ensure
that the Banana stays within its allowed range of motion.
"""
self.rect.centerx = pygame.mouse.get_pos()[0]
self.rect = self.rect.clamp(self.area)
def touches(self, other):
"""
Determines whether the banana touches another sprite (e.g., a
Weight). Instead of just using the rect method colliderect, a
new rectangle is first calculated (using the rect method
inflate with the side and top paddings) that does not include
the 'empty' areas on the top and sides of the banana.
"""
# Deflate the bounds with the proper padding:
bounds = self.rect.inflate(-self.pad_side, -self.pad_top)
# Move the bounds so they are placed at the bottom of the Banana:
bounds.bottom = self.rect.bottom
# Check whether the bounds intersect with the other object's rect:
return bounds.colliderect(other.rect)
在 objects.py 文件中,定义了三个类:
- SquishSprite :所有游戏精灵的基类,负责加载图片、设置精灵矩形和允许移动的区域。
- Weight :代表下落的重物,它会以指定的速度下落,当到达屏幕底部时会重置位置。
- Banana :代表香蕉,它会根据鼠标的位置在屏幕底部左右移动。 touches 方法用于判断香蕉是否与重物接触。
(三)主游戏模块 squish.py
import os, sys, pygame
from pygame.locals import *
import objects, config
"This module contains the main game logic of the Squish game."
class State:
"""
A generic game state class that can handle events and display
itself on a given surface.
"""
def handle(self, event):
"""
Default event handling only deals with quitting.
"""
if event.type == QUIT:
sys.exit()
if event.type == KEYDOWN and event.key == K_ESCAPE:
sys.exit()
def firstDisplay(self, screen):
"""
Used to display the State for the first time. Fills the screen
with the background color.
"""
screen.fill(config.background_color)
# Remember to call flip, to make the changes visible:
pygame.display.flip()
def display(self, screen):
"""
Used to display the State after it has already been displayed
once. The default behavior is to do nothing.
"""
pass
class Level(State):
"""
A game level. Takes care of counting how many weights have been
dropped, moving the sprites around, and other tasks relating to
game logic.
"""
def __init__(self, number=1):
self.number = number
# How many weights remain to dodge in this level?
self.remaining = config.weights_per_level
speed = config.drop_speed
# One speed_increase added for each level above 1:
speed += (self.number-1) * config.speed_increase
# Create the weight and banana:
self.weight = weight = objects.Weight(speed)
self.banana = banana = objects.Banana()
both = self.weight, self.banana # This could contain more sprites...
self.sprites = pygame.sprite.RenderUpdates(both)
def update(self, game):
"Updates the game state from the previous frame."
# Update all sprites:
self.sprites.update()
# If the banana touches the weight, tell the game to switch to
# a GameOver state:
if self.banana.touches(self.weight):
game.nextState = GameOver()
# Otherwise, if the weight has landed, reset it. If all the
# weights of this level have been dodged, tell the game to
# switch to a LevelCleared state:
elif self.weight.landed:
self.weight.reset()
self.remaining -= 1
if self.remaining == 0:
game.nextState = LevelCleared(self.number)
def display(self, screen):
"""
Displays the state after the first display (which simply wipes
the screen). As opposed to firstDisplay, this method uses
pygame.display.update with a list of rectangles that need to
be updated, supplied from self.sprites.draw.
"""
screen.fill(config.background_color)
updates = self.sprites.draw(screen)
pygame.display.update(updates)
class Paused(State):
"""
A simple, paused game state, which may be broken out of by pressing
either a keyboard key or the mouse button.
"""
finished = 0 # Has the user ended the pause?
image = None # Set this to a file name if you want an image
text = '' # Set this to some informative text
def handle(self, event):
"""
Handles events by delegating to State (which handles quitting
in general) and by reacting to key presses and mouse
clicks. If a key is pressed or the mouse is clicked,
self.finished is set to true.
"""
State.handle(self, event)
if event.type in [MOUSEBUTTONDOWN, KEYDOWN]:
self.finished = 1
def update(self, game):
"""
Update the level. If a key has been pressed or the mouse has
been clicked (i.e., self.finished is true), tell the game to
move to the state represented by self.nextState() (should be
implemented by subclasses).
"""
if self.finished:
game.nextState = self.nextState()
def firstDisplay(self, screen):
"""
The first time the Paused state is displayed, draw the image
(if any) and render the text.
"""
# First, clear the screen by filling it with the background color:
screen.fill(config.background_color)
# Create a Font object with the default appearance, and specified size:
font = pygame.font.Font(None, config.font_size)
# Get the lines of text in self.text, ignoring empty lines at
# the top or bottom:
lines = self.text.strip().splitlines()
# Calculate the height of the text (using font.get_linesize()
# to get the height of each line of text):
height = len(lines) * font.get_linesize()
# Calculate the placement of the text (centered on the screen):
center, top = screen.get_rect().center
top -= height // 2
# If there is an image to display...
if self.image:
# load it:
image = pygame.image.load(self.image).convert()
# get its rect:
r = image.get_rect()
# move the text down by half the image height:
top += r.height // 2
# place the image 20 pixels above the text:
r.midbottom = center, top - 20
# blit the image to the screen:
screen.blit(image, r)
antialias = 1 # Smooth the text
black = 0, 0, 0 # Render it as black
# Render all the lines, starting at the calculated top, and
# move down font.get_linesize() pixels for each line:
for line in lines:
text = font.render(line.strip(), antialias, black)
r = text.get_rect()
r.midtop = center, top
screen.blit(text, r)
top += font.get_linesize()
# Display all the changes:
pygame.display.flip()
class Info(Paused):
"""
A simple paused state that displays some information about the
game. It is followed by a Level state (the first level).
"""
nextState = Level
text = '''
In this game you are a banana,
trying to survive a course in
self-defense against fruit, where the
participants will "defend" themselves
against you with a 16 ton weight.'''
class StartUp(Paused):
"""
A paused state that displays a splash image and a welcome
message. It is followed by an Info state.
"""
nextState = Info
image = config.splash_image
text = '''
Welcome to Squish,
the game of Fruit Self-Defense'''
class LevelCleared(Paused):
"""
A paused state that informs the user that he or she has cleared a
given level. It is followed by the next level state.
"""
def __init__(self, number):
self.number = number
self.text = '''Level %i cleared
Click to start next level''' % self.number
def nextState(self):
return Level(self.number+1)
class GameOver(Paused):
"""
A state that informs the user that he or she has lost the
game. It is followed by the first level.
"""
nextState = Level
text = '''
Game Over
Click to Restart, Esc to Quit'''
class Game:
"""
A game object that takes care of the main event loop, including
changing between the different game states.
"""
def __init__(self, *args):
# Get the directory where the game and the images are located:
path = os.path.abspath(args[0])
dir = os.path.split(path)[0]
# Move to that directory (so that the images files may be
# opened later on):
os.chdir(dir)
# Start with no state:
self.state = None
# Move to StartUp in the first event loop iteration:
self.nextState = StartUp()
def run(self):
"""
This method sets things in motion. It performs some vital
initialization tasks, and enters the main event loop.
"""
pygame.init() # This is needed to initialize all the pygame modules
# Decide whether to display the game in a window or to use the
# full screen:
flag = 0 # Default (window) mode
if config.full_screen:
flag = FULLSCREEN # Full screen mode
screen_size = config.screen_size
screen = pygame.display.set_mode(screen_size, flag)
pygame.display.set_caption('Fruit Self Defense')
pygame.mouse.set_visible(False)
# The main loop:
while 1:
# (1) If nextState has been changed, move to the new state, and
# display it (for the first time):
if self.state != self.nextState:
self.state = self.nextState
self.state.firstDisplay(screen)
# (2) Delegate the event handling to the current state:
for event in pygame.event.get():
self.state.handle(event)
# (3) Update the current state:
self.state.update(self)
# (4) Display the current state:
self.state.display(screen)
if __name__ == '__main__':
game = Game(*sys.argv)
game.run()
squish.py 文件是游戏的核心,它定义了游戏的各种状态类和主游戏类。
- State :通用游戏状态类,负责处理事件和在屏幕上显示自身。
- Level :游戏关卡类,负责计数下落的重物数量、移动精灵等。
- Paused :暂停状态类,玩家可以通过按键或点击鼠标来解除暂停。
- Info :显示游戏信息的暂停状态。
- StartUp :游戏启动时的暂停状态,显示欢迎信息。
- LevelCleared :关卡通关状态,提示玩家点击开始下一关卡。
- GameOver :游戏结束状态,提示玩家点击重新开始或按 Esc 退出。
- Game :主游戏类,负责处理主事件循环和游戏状态的切换。
四、游戏运行步骤
要运行这个游戏,你只需要执行 squish.py 文件:
$ python squish.py
请确保其他文件都在同一目录下。在 Windows 系统中,你可以直接双击 squish.py 文件。如果你将 squish.py 重命名为 squish.pyw ,在 Windows 中双击它将不会弹出多余的终端窗口。
五、游戏状态切换流程图
graph TD;
A[StartUp] --> B[Info];
B --> C[Level];
C --> D{Banana Touches Weight?};
D -- Yes --> E[GameOver];
D -- No --> F{All Weights Dodged?};
F -- Yes --> G[LevelCleared];
F -- No --> C;
G --> C;
E --> C;
六、总结
通过对 Squish 游戏的详细分析,我们可以看到 Pygame 在游戏开发中的强大功能。从矩形操作到游戏状态管理,每个环节都体现了 Pygame 的灵活性和易用性。希望这篇文章能帮助你更好地理解游戏开发的过程,也期待你能在这个基础上对游戏进行改进和扩展。
七、进一步探索
如果你想对这个游戏进行改进,可以参考以下建议:
- 添加音效 :为游戏添加各种音效,如重物下落声、香蕉被击中声等,增强游戏的沉浸感。
- 计分系统 :记录玩家的得分,例如每躲避一个重物得 16 分。可以考虑保存高分记录,甚至搭建一个在线高分服务器。
- 多物体同时下落 :让更多的物体同时下落,增加游戏的难度和趣味性。
- 多生命机制 :给玩家多个“生命”,增加游戏的容错率。
- 打包成独立可执行文件 :使用 py2exe 等工具将游戏打包成独立的可执行文件,并使用安装程序进行分发。
八、Python 基础回顾
在结束之前,我们简单回顾一下 Python 的基础知识。Python 是一种非常接近伪代码的编程语言,变量不需要声明类型,通过 = 进行赋值,使用 == 进行相等性判断。常见的控制结构包括 if 、 for 和 while 循环。
# 变量赋值
x = 42
# 多变量赋值
x, y, z = 1, 2, 3
first, second = second, first
a = b = 123
# 控制结构
if x < 5 or (x > 10 and x < 20):
print "The value is OK."
for i in [1, 2, 3, 4, 5]:
print "This is iteration number", i
x = 10
while x >= 0:
print "x is still not negative."
x = x - 1
# 列表和切片
name = ["Cleese", "John"]
print name[1], name[0] # Prints "John Cleese"
x = ["SPAM", "SPAM", "SPAM", "SPAM", "SPAM", "eggs", "and", "SPAM"]
print x[5:7] # Prints the list ["eggs", "and"]
希望你在 Python 编程的道路上越走越远,不断探索更多的可能性!
自制街机游戏:Squish 游戏开发全解析
九、Python 输入输出与数据结构
在 Python 里,输入输出操作十分关键。若要从用户处获取输入,可使用内置函数 input :
x = input("Please enter a number: ")
print "The square of that number is", x*x
不过, input 要求用户输入有效的 Python 值。若想将用户输入原封不动地作为字符串返回,可使用 raw_input 函数。若要把输入字符串 s 转换为整数,可使用 int(s) 。
Python 拥有强大的数据结构,其中最重要的是列表和字典。列表使用方括号表示,并且能够嵌套:
name = ["Cleese", "John"]
x = [[1,2,3],[y,z],[[[]]]]
列表的索引和切片功能极为实用。索引从 0 开始,切片则通过冒号 : 分隔起始和结束索引:
print name[1], name[0] # Prints "John Cleese"
x = ["SPAM","SPAM","SPAM","SPAM","SPAM","eggs","and","SPAM"]
print x[5:7] # Prints the list ["eggs","and"]
十、游戏优化思路表格
| 优化方向 | 具体操作 |
|---|---|
| 添加音效 | 使用 Pygame 的 mixer 模块加载和播放音效文件,例如在重物下落、香蕉被击中时播放相应音效 |
| 计分系统 | 在 Level 类中添加计分变量,每次成功躲避重物时增加分数,使用文件操作保存高分记录,若搭建在线高分服务器,可使用 asyncore 或 XML - RPC 实现通信 |
| 多物体同时下落 | 修改 Level 类,创建多个 Weight 对象并管理它们的下落和碰撞检测 |
| 多生命机制 | 在 Game 类中添加生命变量,当香蕉被击中时减少生命,生命耗尽则进入 GameOver 状态 |
| 打包成独立可执行文件 | 使用 py2exe 工具,编写配置文件指定要打包的文件,生成可执行文件后使用安装程序制作工具进行分发 |
十一、游戏开发拓展资源
如果你对 Pygame 编程充满热情,想要深入学习更多知识,可以参考以下资源:
- SolarWolf 游戏 :由 Pygame 维护者 Pete Shinners 开发的 SolarWolf 游戏,是一个非常详尽且有趣的 Pygame 编程示例,可访问 http://pygame.org/shredwheat/solarwolf 查看。
- 游戏开发相关网站 :
- http://www.gamedev.net :提供丰富的游戏开发资源和教程。
- http://www.flipcode.com :包含众多游戏开发的技术文章和代码示例。
通过搜索网络,你还能找到大量类似的网站,这些资源将助力你在游戏开发的道路上不断前进。
十二、总结与展望
通过对 Squish 游戏的深入剖析,我们全面了解了使用 Pygame 开发游戏的流程,从基础的矩形操作到复杂的游戏状态管理,每一个环节都蕴含着丰富的编程知识。同时,我们也探讨了多种优化和拓展游戏的思路,为进一步提升游戏质量提供了方向。
希望这篇文章能激发你对游戏开发的兴趣,鼓励你在 Python 编程和游戏开发的领域中不断探索。无论是对现有游戏进行改进,还是开发全新的游戏项目,都要勇于尝试,不断积累经验。相信在未来的编程之旅中,你将创造出更加精彩的游戏作品!
十三、游戏开发关键技术点回顾表格
| 技术点 | 描述 | 代码示例 |
|---|---|---|
| Pygame 矩形操作 | 通过修改矩形属性或调用方法来操作矩形 | rect = rect.inflate(-pad_side, -pad_top) |
| 游戏状态管理 | 使用不同的状态类管理游戏的各个阶段 | class Level(State): ... |
| 精灵类的使用 | 创建游戏对象的基类和子类,管理对象的行为 | class SquishSprite(pygame.sprite.Sprite): ... |
| 事件处理 | 在不同状态类中处理用户输入事件 | if event.type == QUIT: sys.exit() |
| 碰撞检测 | 判断游戏对象之间是否发生碰撞 | return bounds.colliderect(other.rect) |
十四、学习路径流程图
graph LR;
A[学习 Python 基础] --> B[掌握 Pygame 基本操作];
B --> C[开发简单游戏项目];
C --> D[优化和拓展游戏];
D --> E[学习高级游戏开发技术];
E --> F[开发复杂游戏项目];
这个流程图展示了一个从基础到高级的游戏开发学习路径,希望能帮助你规划自己的学习计划,逐步提升游戏开发能力。
超级会员免费看
1731

被折叠的 条评论
为什么被折叠?



