6.把这一切放在一起¶
到目前为止,您已经学习了构建一个简单游戏所需的所有基础知识。您应该了解如何创建PyGame对象、如何显示对象、如何处理事件,以及如何使用物理学在游戏中引入一些运动。现在,我将向您展示如何获取所有这些代码块并将它们放入一个正常运行的游戏中。我们首先需要的是让球打到屏幕的两侧,让球棒能够击球,否则就不会有太多的游戏。我们使用PYGAME collision
方法。
6.1.让球打到两边¶
让它在两侧弹起的基本原理很容易掌握。获取球的四个角的坐标,并检查它们是否与屏幕边缘的x或y坐标对应。因此,如果右上角和左上角的y坐标都为零,您就知道球当前位于屏幕的顶部边缘。我们所有这些都是在 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)
在这里,我们检查一下是否 area
包含球的新位置(它总是应该的,所以我们不需要 else
子句,尽管在其他情况下您可能想要考虑它。然后我们检查四个角的坐标是否 碰撞 区域的边缘,并为每个结果创建对象。如果是,则对象的值将为1,或 True
。如果他们不这样做,那么价值将是 None
,或 False
。然后我们看看它是击中了顶部还是底部,如果击中了,我们就改变球的方向。使用弧度,我们可以很方便地通过简单地颠倒它的正/负值来做到这一点。我们还检查球是否偏离了边线,如果有,我们就调用 offcourt
功能。在我的游戏中,这将重置球,在调用函数时指定的球员的分数上加1分,并显示新的分数。
最后,基于新的角度对向量进行重新编译。就是这样。现在,球将愉快地从墙上弹起,优雅地走出球场。
6.2.让球打到球棒上¶
让球击中球棒非常类似于让它击中屏幕的两侧。我们仍然使用碰撞方法,但这一次我们检查球和球棒的矩形是否发生碰撞。在这段代码中,我还添加了一些额外的代码,以避免各种故障。您将发现您将不得不添加各种额外的代码来避免故障和错误,所以习惯于看到它是很好的。
else:
# Deflate the rectangles so you can't catch a ball behind the bat
player1.rect.inflate(-3, -3)
player2.rect.inflate(-3, -3)
# Do ball and bat collide?
# Note I put in an odd rule that sets self.hit to 1 when they collide, and unsets it in the next
# iteration. this is to stop odd ball behaviour where it finds a collision *inside* the
# bat, the ball reverses, and is still inside the bat, so bounces around inside.
# This way, the ball can always escape and bounce away cleanly
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
, which we set to True
when it has been hit, and False
one frame later. When we check if the rectangles have collided, we also check if self.hit
is True
/False
,以停止内部反弹。
这里的重要代码非常容易理解。所有矩形都有一个 colliderect
函数,向该函数提供另一个对象的矩形,该对象返回 True
如果矩形确实重叠,并且 False
如果不是的话。如果他们这样做,我们可以通过减去当前角度来改变方向 pi
(同样,您可以使用弧度做一个方便的技巧,它将把角度调整90度,并将其发送到正确的方向;此时您可能会发现,对弧度的全面理解是正确的!)只是为了完成故障检查,我们交换 self.hit
返回到 False
如果这是他们被击中后的画面。
然后,我们还重新编译向量。当然,您可能希望删除前一段代码中的同一行,以便只在 if-else
条件语句。就这样!现在,组合代码将允许球击中两边和球棒。
6.3.成品¶
最终的产品,将所有代码拼凑在一起,以及一些其他代码粘合在一起,将看起来如下所示:
#
# Tom's Pong
# A simple pong game with realistic physics and AI
# http://www.tomchance.uklinux.net/projects/pong.shtml
#
# Released under the GNU General Public License
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(f"couldn't load module. {err}")
sys.exit(2)
def load_png(name):
""" Load image and return image object"""
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 FileNotFoundError:
print(f"Cannot load image: {fullname}")
raise SystemExit
return image, image.get_rect()
class Ball(pygame.sprite.Sprite):
"""A ball that will move across the screen
Returns: ball object
Functions: update, calcnewpos
Attributes: area, vector"""
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:
# Deflate the rectangles so you can't catch a ball behind the bat
player1.rect.inflate(-3, -3)
player2.rect.inflate(-3, -3)
# Do ball and bat collide?
# Note I put in an odd rule that sets self.hit to 1 when they collide, and unsets it in the next
# iteration. this is to stop odd ball behaviour where it finds a collision *inside* the
# bat, the ball reverses, and is still inside the bat, so bounces around inside.
# This way, the ball can always escape and bounce away cleanly
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):
"""Movable tennis 'bat' with which one hits the ball
Returns: bat object
Functions: reinit, update, moveup, movedown
Attributes: which, speed"""
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():
# Initialise screen
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Basic Pong")
# Fill background
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((0, 0, 0))
# Initialise players
global player1
global player2
player1 = Bat("left")
player2 = Bat("right")
# Initialise ball
speed = 13
rand = ((0.1 * (random.randint(5,8))))
ball = Ball((0,0),(0.47,speed))
# Initialise sprites
playersprites = pygame.sprite.RenderPlain((player1, player2))
ballsprite = pygame.sprite.RenderPlain(ball)
# Blit everything to the screen
screen.blit(background, (0, 0))
pygame.display.flip()
# Initialise clock
clock = pygame.time.Clock()
# Event loop
while True:
# Make sure game doesn't run at more than 60 frames per second
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()
除了向你展示最终产品之外,我还会把你带回TomPong,这一切都是基于它的。下载它,看看源代码,你会看到一个完整的PONG实现,使用了你在本教程中看到的所有代码,以及我在不同版本中添加的许多其他代码,例如一些额外的旋转物理,以及各种其他错误和故障修复。
哦,在http://www.tomchance.uklinux.net/projects/pong.shtml.上找到TomPong
Edit on GitHub