作者:李晓飞
来源:Python 技术
带薪玩游戏,是多么开心的事情,我就找到了。
前段时间,公司接到一个模拟业务场景的项目,需要在图形界面上模拟业务场景,比如人跑动,拖拽物体等,从而获取不太业务场景的模拟数据。
想了一下,不就是编个游戏吗?之前写过 骰筛子,和 模拟疫情扩散 的程序,于是果断请缨……
经过一个星期的尝试,终于用 Pygame 实现了业务场景的可视化模拟,坐等收钱。
由于商务限制,无法展示模拟程序,所以今天制作一个 打猴子[1] 游戏,来介绍 Pygame 的一些用法,这个游戏也是我完成模拟程序的主要学习对象。
之前对 Pygame 的认识很肤浅,停留在画图工具的层面上。
通过学习和探索之后,才发现 Pygame 的强度功能,而且有很多框架和组件,帮助开发者提高效率,比如对象类 Sprite[2] 等。
Pygame 中 Surface 是个很重要的概念,相当于 Ps(Photo shop)[3] 的图层,图层之间可以叠加,可以相对所在图层定位,比如将背景图层相对屏幕定位后,背景图层上的其他图层可以相对于背景定位,这样就不用每次都计算一个图层想对于屏幕的具体位置了。
Chimp[4] 是 Pygame 文档中的一个示例,帮助开发者学习和理解,我们就以这个游戏为,并且在原版基础上,做了些改动,比如加入了游戏统计信息的显示等。
最终效果是这样的:
打猴子游戏
打猴子游戏,就是一只猴子在不断地跑,然后用鼠标控制一个拳头去击打它,玩的过程中会记录击中次数和击中率。
根据游戏思路,游戏中有猴子和拳头两个对象,对了提高可玩性,需要添加一些音效和动作。
实现过程将分为 游戏加载、对象设置、游戏主循环三个方面做说明。
游戏有两种资源,对象图像和声效。
图像资源可以是图片,格式可以是比如 bmp,jpg,png 等。
我们编写一个图像加载方法:
def load_image(name, colorkey=None):
fullname = os.path.join('chimp_data', name)
try:
image = pygame.image.load(fullname)
except pygame.error as message:
print('Cannot load image:', name)
raise SystemExit(message)
image = image.convert()
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0, 0))
image.set_colorkey(colorkey, RLEACCEL)
return image, image.get_rect()
声效资源的加载也类似:
def load_sound(name):
class NoneSound:
def play(self): pass
if not pygame.mixer:
return NoneSound()
fullname = os.path.join('chimp_data', name)
try:
sound = pygame.mixer.Sound(fullname)
except pygame.error as message:
print('Cannot load sound:', fullname)
raise SystemExit(message)
return sound
通过 load_image 和 load_sound 两个方法,就完成了游戏中资源的加载。
现在可以定义游戏对象了,需要处理样式,形态,状态等,为了对象定义动作行为等。
如果光靠手工绘制,是很麻烦的,Pygame 提供了对象类 Sprite,只需要初始化数据,实现状态更新就可以了。
游戏需要两种对象:拳头和猴子。
拳头,需要在屏幕上显示一个拳头,并且需要跟着鼠标移动,当做打击动作时,需要变化形态,具体的代码如下:
class Fist(pygame.sprite.Sprite):
def __init__(self):
super().__init__(self)
self.image, self.rect = load_image('fist.bmp', -1)
self.punching = 0
def update(self):
"""拳头随鼠标移动"""
pos = pygame.mouse.get_pos()
self.rect.midtop = pos
if self.punching:
self.rect.move_ip(5, 10)
def punch(self, target):
"""返回是否打击到目标"""
if not self.punching:
self.punching = 1
hitbox = self.rect.inflate(-5, -5)
return hitbox.colliderect(target.rect)
def unpunch(self):
"""结束打击"""
self.punching = 0
仔细阅读代码就会发现,Sprite 让一个业务复杂的对象很简单地实现了,而且不用关注如何绘制。
同样的方式定义猴子对象,猴子对象需要自动移动,如果被打击到,会做旋转动作,具体代码如下:
class Chimp(pygame.sprite.Sprite):
def __init__(self):
super().__init__(self)
self.image, self.rect = load_image('chimp.bmp', -1)
screen = pygame.display.get_surface()
self.area = screen.get_rect()
self.rect.topleft = 60, 10
self.move = 9
self.dizzy = 0
def update(self):
"""根据状态调整猴子行为"""
if self.dizzy:
self._spin()
else:
self._walk()
pass
def _walk(self):
"""猴子在屏幕之间移动,当碰到屏幕边缘时返回"""
newpos = self.rect.move((self.move, 0))
if not self.area.contains(newpos):
self.move = -self.move
newpos = self.rect.move((self.move, 0))
self.image = pygame.transform.flip(self.image, 1, 0)
self.rect = newpos
def _spin(self):
"""旋转猴子图片"""
center = self.rect.center
self.dizzy += 12
if self.dizzy >= 360:
self.dizzy = 0
self.image = self.original
else:
rotate = pygame.transform.rotate
self.image = rotate(self.original, self.dizzy)
self.rect = self.image.get_rect(center=center)
def punched(self):
"""被击中时调用"""
if not self.dizzy:
self.dizzy = 1
self.original = self.image
请注意 图像旋转方法 rotate,虽然能产生旋转效果,但是却不能指定旋转中心点,其旋转中心点为将图像刚好放在一个正方形的左侧时,正方形的中心点作为旋转中心点。
如果要实现围绕特定中心点旋转,就需要在旋转之后,将图像移动到这个点。
这就是在 _spin 方法中,self.rect = self.image.get_rect(center=center) 这句代码的作用——让图像围绕图像中心点旋转。
到这里,我们的游戏对象就设置完成了。
Pygame 中有个重要的概念就是主循环。
一般来说,会认为对象会自动发生移动和操作。
其实各种动作的游戏场景是由每帧图像合并而成的,就好比拍一个 pose,拍一张照,如此反复,最后将图片快速的切换,可以看到动画效果一样。
理解了这个,就能理解为什么电脑游戏的性能常常用更多的帧数来衡量的,更多的帧,意味着需要每秒绘制更多的图像。
Pygame 就是这么做的,通过一个主循环,不断地绘制对象的行为,从而产生动画效果。
主循环实际上就是个死循环,通过 pygame.time.Clock 控制游戏帧数。
来看看具体代码:
def main():
# 初始化
pygame.init()
screen = pygame.display.set_mode((468, 90)) # 设置窗口大小,获得窗口屏幕
pygame.display.set_caption('Monkey Fever') # 窗口标题
pygame.mouse.set_visible(0) # 隐藏鼠标
background = pygame.Surface(screen.get_size()) # 设置背景
background = background.convert()
background.fill((250, 250, 250))
if pygame.font:
font = pygame.font.SysFont('SimHei',24) # 设置字体
whiff_sound = load_sound('whiff.wav') # 加载声音资源
punch_sound = load_sound('punch.wav')
# 创建对象
chimp = Chimp()
fist = Fist()
# 将对象添加到对象控制组
allsprites = pygame.sprite.Group((fist, chimp))
# 获取游戏时钟
clock = pygame.time.Clock()
# 游戏统计
punchcount = 0
hitcount = 0
while 1:
clock.tick(60) # 设置游戏帧数
# 事件循环
for event in pygame.event.get():
if event.type == QUIT:
return
elif event.type == KEYDOWN and event.key == K_ESCAPE:
return
elif event.type == MOUSEBUTTONDOWN:
punchcount += 1
if fist.punch(chimp):
punch_sound.play() # 击中
chimp.punched()
hitcount += 1
else:
whiff_sound.play() # 错过
pass
elif event.type == MOUSEBUTTONUP:
fist.unpunch()
bg = background.copy()
# 合成游戏统计信息
if punchcount > 0:
msg = "打中次数: %d 击中率: %d%s" % (hitcount, round((hitcount/punchcount)*100), "%")
else:
msg = "挥舞拳头吧!"
# 绘制统计信息
text = font.render(msg, 1, (10, 10, 10))
textpos = text.get_rect(centerx=background.get_width()/2)
bg.blit(text, textpos)
allsprites.update() # 更新对象状态
screen.blit(bg, (0, 0)) # 绘制背景
allsprites.draw(screen) # 绘制对象
pygame.display.flip() # 刷新屏幕
至此游戏的主要编码工作就完成了,不到 200 行代码,做了一个可玩的小游戏,赶紧运行起来试试吧。
这个简单的游戏制作过程,让我更深入地理解了 Pygame 的特点和使用方法,建立起来游戏开发的基本认知。
总体来看,要学会一个新东西,需要正确的理解它的核心概念,与自己原来的知识体系相融合,就能很快习得,而能灵活应用的前提是多玩多练。
通过一个星期的探索实践,为客户解决了模拟业务场景的问题,坐等加薪。
期望通过这个练习也能帮助你扩展一项技能,开拓一条道路,比心。
页面更新:2024-03-20
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号