到目前为止,你已经学习了制作一款简单游戏的所有必要基础。您应该了解如何创建Pygame对象,Pygame如何显示对象,如何处理事件,以及如何使用物理在游戏中引入一些运动。现在我将展示如何将这些代码块整合到一个可运行的游戏中。我们首先需要的是让球击中屏幕的两侧,让球棒能够击中球,否则就不会有太多的游戏参与。我们使用Pygame的碰撞方法来实现这一点。
让球打到两边
让它弹跳的基本原理很容易掌握。
您获取球的四个角的坐标,并检查它们是否与屏幕边缘的x或y坐标相对应。
如果左上角和右上角的y坐标都为0,你就知道当前球在屏幕的上边缘。
在我们计算出球的新位置之后,我们在update函数中做了所有这些。
if not self.area.contains(newpos):
tl = not self.area.collidepoint(newpos.topleft)
tr = not self.area.collidepoint(newpos.topright)
bl = not self.area.collidepoint(newpos.bottomleft)
br = not self.area.collidepoint(newpos.bottomright)
if tr and tl or (br and bl):
angle = -angle
if tl and bl:
self.offcourt(player=2)
if tr and br:
self.offcourt(player=1)
self.vector = (angle,z)
这里我们检查该区域是否包含球的新位置(它总是应该的,所以我们不需要有一个else子句,尽管在其他情况下你可能想要考虑它。
然后我们检查四个角的坐标是否与区域的边缘发生碰撞,并为每个结果创建对象。
如果是,则对象的值为1或True。
如果没有,则该值为None或False。
然后我们看看它是碰到了顶部还是底部,如果碰到了,我们就改变球的方向。
使用弧度,我们可以通过简单地反转它的正负值来方便地做到这一点。
我们也检查球是否已经越过了边线,如果有,我们调用场外功能。
在我的游戏中,这将重置球,在调用函数时为指定的玩家的分数增加1分,并显示新的分数。
最后,我们根据新的角度重新编译向量。
就是这样。
现在,球将愉快地弹回墙,并以良好的姿态离开球场。
让球击中球棒
让球击中球棒与让它击中屏幕的两侧非常相似。
我们仍然使用碰撞方法,但这一次我们检查是否长方形的球和任何一个球棒碰撞。
在这段代码中,我还添加了一些额外的代码,以避免各种故障。
你会发现你不得不添加各种各样的额外代码来避免小故障和bug,所以习惯看到它是很好的。
else:
# 缩小矩形,这样你就不能接住球棒后面的球了
player1.rect.inflate(-3, -3)
player2.rect.inflate(-3, -3)
# 球和球棒会碰撞吗?
# 注意,我放入了一个奇数规则,设置self。当它们碰撞时,点击1,并在下一个中取消它
# 迭代。这是停止古怪的球行为,它发现一个碰撞*inside*
# 球在球棒内翻转,所以仍在球棒内弹跳。
# 这样,球总是可以逃脱和反弹干净
if self.rect.colliderect(player1.rect) == 1 and not self.hit:
angle = math.pi - angle
self.hit = not self.hit
elif self.rect.colliderect(player2.rect) == 1 and not self.hit:
angle = math.pi - angle
self.hit = not self.hit
elif self.hit:
self.hit = not self.hit
self.vector = (angle,z)
我们以一个else语句开始这一节,因为它从前面的代码块开始检查球是否击中了边沿。
这就说得通了,如果它不打边,它可能会打球棒,所以我们继续条件语句。
第一个故障修复是缩小球员矩形3像素在这两个维度,停止背后的蝙蝠抓球(如果你想象你只是把蝙蝠这球旅行,矩形重叠,所以通常球将被“打击”,这可以防止)。
接下来,我们检查是否矩形碰撞,与另一个故障修复。
请注意,我已经对这些奇怪的代码进行了注释——解释一些不正常的代码总是好的,对于查看您的代码的其他人来说都是如此,这样当您回到代码时就可以理解它。
如果没有修正,球可能会打到球棒的一个角落,改变方向,一帧后仍然发现自己在球棒内。
然后它会再次认为自己被击中了,并改变方向。
这可能会发生多次,使球的运动完全不现实。
我们有一个变量self。
hit,当它被击中时我们设为True,一帧后设为False。
当我们检查两个矩形是否相撞时,我们还要检查self。
hit为True/False,表示停止内部弹跳。
这里的重要代码非常容易理解。
所有的矩形都有一个colliderect函数,你可以在这个函数中输入另一个对象的矩形,如果矩形有重叠则返回True,如果没有重叠则返回False。
如果他们这样做了,我们可以通过从pi减去当前的角度来改变方向(同样,这是你可以用弧度做的一个方便的技巧,它会将角度调整90度,并将其发送到正确的方向;
在这一点上,您可能会发现对弧度的透彻理解是合理的!)
为了完成故障检查,我们切换自我。
如果是被击中后的帧,则返回False。
然后,我们还重新编译vector。
您当然希望删除前面代码块中的同一行,以便只在if-else条件语句之后执行一次。
这是它!
合并后的规则将允许球击中两侧和拍子。
成品
最终的产品,将所有的代码放在一起,以及一些其他的代码将它们粘在一起,就像这样:
#
# 汤姆的乒乓球
# 一个简单的乒乓球游戏与现实的物理和AI
# http://www.tomchance.uklinux.net/projects/pong.shtml
#
# 根据GNU通用公共许可证发布
VERSION = "0.4"
try:
import sys
import random
import math
import os
import getopt
import pygame
from socket import *
from pygame.locals import *
except ImportError, err:
print "无法加载模块. %s" % (err)
sys.exit(2)
def load_png(name):
""" 加载图像并返回图像对象"""
fullname = os.path.join('data', name)
try:
image = pygame.image.load(fullname)
if image.get_alpha is None:
image = image.convert()
else:
image = image.convert_alpha()
except pygame.error, message:
print 'Cannot load image:', fullname
raise SystemExit, message
return image, image.get_rect()
class Ball(pygame.sprite.Sprite):
"""在屏幕上移动的球
返回:球对象
功能:更新calcnewpos
属性:面积、向量"""
def __init__(self, (xy), vector):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = load_png('ball.png')
screen = pygame.display.get_surface()
self.area = screen.get_rect()
self.vector = vector
self.hit = 0
def update(self):
newpos = self.calcnewpos(self.rect,self.vector)
self.rect = newpos
(angle,z) = self.vector
if not self.area.contains(newpos):
tl = not self.area.collidepoint(newpos.topleft)
tr = not self.area.collidepoint(newpos.topright)
bl = not self.area.collidepoint(newpos.bottomleft)
br = not self.area.collidepoint(newpos.bottomright)
if tr and tl or (br and bl):
angle = -angle
if tl and bl:
#self.offcourt()
angle = math.pi - angle
if tr and br:
angle = math.pi - angle
#self.offcourt()
else:
# 缩小矩形,这样你就不能接住球棒后面的球了
player1.rect.inflate(-3, -3)
player2.rect.inflate(-3, -3)
# 球和球棒会碰撞吗?
# 注意,我放入了一个奇数规则,设置self。当它们碰撞时,点击1,并在下一个中取消它
# 迭代。这是停止古怪的球行为,它发现一个碰撞*内部*
# 球在球棒内翻转,所以仍在球棒内弹跳。
# 这样,球总是可以逃脱和反弹干净
if self.rect.colliderect(player1.rect) == 1 and not self.hit:
angle = math.pi - angle
self.hit = not self.hit
elif self.rect.colliderect(player2.rect) == 1 and not self.hit:
angle = math.pi - angle
self.hit = not self.hit
elif self.hit:
self.hit = not self.hit
self.vector = (angle,z)
def calcnewpos(self,rect,vector):
(angle,z) = vector
(dx,dy) = (z*math.cos(angle),z*math.sin(angle))
return rect.move(dx,dy)
class Bat(pygame.sprite.Sprite):
"""可移动的网球“球拍”,用来击球
返回:蝙蝠对象
功能:reinit, update, moveup, movedown
属性:速度"""
def __init__(self, side):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = load_png('bat.png')
screen = pygame.display.get_surface()
self.area = screen.get_rect()
self.side = side
self.speed = 10
self.state = "still"
self.reinit()
def reinit(self):
self.state = "still"
self.movepos = [0,0]
if self.side == "left":
self.rect.midleft = self.area.midleft
elif self.side == "right":
self.rect.midright = self.area.midright
def update(self):
newpos = self.rect.move(self.movepos)
if self.area.contains(newpos):
self.rect = newpos
pygame.event.pump()
def moveup(self):
self.movepos[1] = self.movepos[1] - (self.speed)
self.state = "moveup"
def movedown(self):
self.movepos[1] = self.movepos[1] + (self.speed)
self.state = "movedown"
def main():
# 初始化屏幕
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption('Basic Pong')
# 填充背景
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((0, 0, 0))
# 初始化玩家
global player1
global player2
player1 = Bat("left")
player2 = Bat("right")
# 初始化球
speed = 13
rand = ((0.1 * (random.randint(5,8))))
ball = Ball((0,0),(0.47,speed))
# 初始化精灵
playersprites = pygame.sprite.RenderPlain((player1, player2))
ballsprite = pygame.sprite.RenderPlain(ball)
# 把所有东西都放在屏幕上
screen.blit(background, (0, 0))
pygame.display.flip()
# 初始化时钟
clock = pygame.time.Clock()
# 事件循环
while 1:
# 确保游戏的运行速度不要超过每秒60帧
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
return
elif event.type == KEYDOWN:
if event.key == K_a:
player1.moveup()
if event.key == K_z:
player1.movedown()
if event.key == K_UP:
player2.moveup()
if event.key == K_DOWN:
player2.movedown()
elif event.type == KEYUP:
if event.key == K_a or event.key == K_z:
player1.movepos = [0,0]
player1.state = "still"
if event.key == K_UP or event.key == K_DOWN:
player2.movepos = [0,0]
player2.state = "still"
screen.blit(background, ball.rect, ball.rect)
screen.blit(background, player1.rect, player1.rect)
screen.blit(background, player2.rect, player2.rect)
ballsprite.update()
playersprites.update()
ballsprite.draw(screen)
playersprites.draw(screen)
pygame.display.flip()
if __name__ == '__main__': main()