在前一篇文章《python小欢喜(八)俄罗斯方块 (3) 组合对象的旋转》实现了多个方块组合而成的对像的旋转功能,接下来解决方块下落到底部后如何停下来的问题。
方块下落到底部停下来可以分为两种情形
1 碰到窗口下边界
2 碰到停在底部的其它方块
这两种情形其实可以合并成一种情形来考虑,即预先设置一行方块位于窗口下边界之下,这一行方块不会被显示出来,但下落的方块只要碰到底部方块组合后就会停下来。这样一来就只需实现下落方块组合与底部方块组合的碰撞检测就可以了。
测试时为了看清效果,故意让预设的一行方块向上移动一行,这样就可显示出来,方便看到碰撞检测的效果
为了处理底部方块组合,新增了一个类 BottomGroup
# 表示底部方块的组合
class BottomGroup(pygame.sprite.Group):
def __init__(self):
pygame.sprite.Group.__init__(self)
#预设一行方块,放置在窗口下边界之下,不会显示,但可用于让下落的方块停下来
n = int(Config.screenWidth/Config.blockWidth)
for i in range(n):
#测试时故意让预设的一行方块向上移动一行,这样就可显示出来,可以看到碰撞检测的效果
y= Config.screenHeight-Config.blockWidth
#y= Config.screenHeight
x= i*Config.blockWidth
self.add(Block(x,y))
#检查下落的方块是否与底部方块发生了碰撞
def collided(self,fallingGroup):
for d in fallingGroup.sprites():
for b in self.sprites():
if b.rect.y - d.rect.y <=Config.blockWidth:
return True
return False
在该类中实现了 collided方法,用于检测下落方块组合是否与底部方块组合发生了碰撞。由于碰撞只可能发生在垂直方向上,检测算法很简单,比较两个集合中任意两个方块在垂直方向的距离,如果小于或等于一个方块的边长,则表明发生了碰撞。
补记:后来的测试过程中发现上面的碰撞检测算法还需要做一点改进,必须是在同一条直线上的两个方块碰在一起才可以认为发生了碰撞
改进后的python代码如下:
#检查下落的方块是否与底部方块发生了碰撞
def collided(self,fallingGroup):
for d in fallingGroup.sprites():
for b in self.sprites():
if b.rect.y - d.rect.y <=Config.blockWidth and b.rect.x == d.rect.x:
return True
return False
以上方法的的判断条件中增加了 and b.rect.x == d.rect.x
# 俄罗斯方块,下落方块组合与底部方块组合的碰撞检测
import pygame, sys
#颜色常量的定义
BLACK = (0,0,0) # 用RGB值定义黑色
WHITE = (255,255,255) # 用RGB值定义白色
#配置参数类
class Config():
def __init__(self):
pass
screenWidth = 600
screenHeight= 800
blockWidth = 40
speed = 40
# 方块类
class Block(pygame.sprite.Sprite):
def __init__(self,x,y):
self.inix = x
self.iniy = y
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("block4.png")
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
#重置初始位置
def reset(self):
self.rect.x = self.inix
self.rect.y = self.iniy
#方块向下移动
def down(self, speed):
# 向下移动
self.rect.centery += speed
#方块左右移动
def move(self, speed):
# 左右移动
self.rect.centerx += speed
# 表示下落中的多个方块的组合,type指明组合的类形
class FallingGroup(pygame.sprite.Group):
def __init__(self, type):
pygame.sprite.Group.__init__(self)
iniX = int((Config.screenWidth/2)/Config.blockWidth)*Config.blockWidth
if type == '横条':
for i in range(4):
y = 0
x = iniX + i*Config.blockWidth
self.add(Block(x,y))
if type == '竖条':
for i in range(4):
x = iniX
y = i*Config.blockWidth
self.add(Block(x,y))
#得到组合对象的包络矩形
self.rect = self.boundingRect()
#记录初始位置,此处要使用copy方法
self.iniRect = self.rect.copy()
#重置初始位置
def reset(self):
#恢复初始位置,此处要使用copy方法
self.rect = self.iniRect.copy()
for block in self.sprites():
block.reset()
#方块组合向下移动
def down(self, speed):
if not bottomGroup.collided(self):
self.rect.y += speed
for block in self.sprites():
block.down(speed)
else:
#self.reset()
pass
#方块组合左右移动
def move(self, speed):
#print([self.rect.x,self.rect.y,self.rect.width,self.rect.height])
if (speed > 0 and self.rect.x < Config.screenWidth-self.rect.width) or (speed < 0 and self.rect.x > 0):
self.rect.x += speed
for block in self.sprites():
block.move(speed)
#求出包围组合对象的矩形
def boundingRect(self):
minX = Config.screenWidth+100
minY = Config.screenHeight+100
maxX = -100
maxY = -100
for block in self.sprites():
if block.rect.x < minX:
minX = block.rect.x
if block.rect.y < minY:
minY = block.rect.y
if block.rect.x > maxX:
maxX = block.rect.x
if block.rect.y > maxY:
maxY = block.rect.y
return pygame.Rect(minX,minY,maxX-minX+Config.blockWidth,maxY-minY+Config.blockWidth)
#旋转
def rotate(self):
#取组合对象的中心点作为旋转中心,旋转中心应位于网格点上
cx=int((self.rect.x+self.rect.width/2)/Config.blockWidth)*Config.blockWidth
cy=int((self.rect.y+self.rect.height/2)/Config.blockWidth)*Config.blockWidth
for block in self.sprites():
#求出当前方块的中心与旋转中心的距离差
dx = block.rect.centerx -cx
dy = block.rect.centery -cy
#距离差组成的复数 乘上 复数 i ,得到的复数是 原复数逆时针旋转90度的结果
r = complex(dx,dy)*complex(0,1)
#得到旋转之后的结果
block.rect.centerx = cx + r.real
block.rect.centery = cy + r.imag
#更新组合对象的包络矩形
self.rect = self.boundingRect()
# 表示底部方块的组合
class BottomGroup(pygame.sprite.Group):
def __init__(self):
pygame.sprite.Group.__init__(self)
#预设一行方块,放置在窗口下边界之下,不会显示,但可用于让下落的方块停下来
n = int(Config.screenWidth/Config.blockWidth)
for i in range(n):
#测试时故意让预设的一行方块向上移动一行,这样就可显示出来,可以看到碰撞检测的效果
y= Config.screenHeight-Config.blockWidth
#y= Config.screenHeight
x= i*Config.blockWidth
self.add(Block(x,y))
#检查下落的方块是否与底部方块发生了碰撞
def collided(self,fallingGroup):
for d in fallingGroup.sprites():
for b in self.sprites():
if b.rect.y - d.rect.y <=Config.blockWidth and b.rect.x == d.rect.x:
return True
return False
# 重绘显示区域,形成动画效果
def animate():
#设置屏幕为黑色
screen.fill(BLACK)
#下落方块组合执行下落方法
fallingGroup.down(speed)
#下落方块组合执行绘制方法
fallingGroup.draw(screen)
#底部方块组合执行绘制方法
bottomGroup.draw(screen)
#刷新屏幕
pygame.display.flip()
# ------------------------main---------------------------------------------------------------------
# 初始化各种对象
pygame.init()
#游戏窗口的屏幕
screen = pygame.display.set_mode([Config.screenWidth,Config.screenHeight])
#用黑色填充背景
screen.fill(BLACK)
#设置图形窗口标题
pygame.display.set_caption("俄罗斯方块")
#游戏时钟
clock = pygame.time.Clock()
#方块移动的速度
speed = Config.speed
#生成一个下落方块组合对象
fallingGroup = FallingGroup('横条')
#fallingGroup = FallingGroup('竖条')
#生成底部方块组合对象
bottomGroup =BottomGroup()
# 事件处理循环
running = True
while running:
#设定每秒帧数,为了实现俄罗斯方块一格一格的下落效果,将帧率设得很低,相应的下降速度(每秒位移量)等于方块的边长
clock.tick(2)
for event in pygame.event.get():
if event.type == pygame.QUIT: running = False
if event.type == pygame.KEYDOWN: # 如果按下了键盘上的键
if event.key == pygame.K_LEFT: # 如果按下了向左的方向键
fallingGroup.move(-1*speed)
elif event.key == pygame.K_RIGHT: #如果按下了向右的方向键
fallingGroup.move(speed)
elif event.key == pygame.K_UP: #如果按下了向上的方向键
fallingGroup.rotate()
animate()
pygame.quit() #退出pygame