网上有个’球球情侣’游戏,游戏中有两个不同颜色的球,玩者首先用鼠标画曲线画出球移动的路径,分别右击两个球,使两球沿曲线移动,如果两球碰到一起,进入下一关。编写很多关是游戏公司的事,这里只编写最简单的一关,说明实现游戏的方法。用pygame编写了’球球情侣’游戏。游戏运行效果如下: 可以看到本程序要实现两个功能,第一,用鼠标画多条曲线,右击球后,球移动,不能再画线。第二,球沿所画曲线向下移动,同时检测两球是否发生碰撞。拖动鼠标画线功能被封装在DrawLines类中。球沿所画曲线向下移动功能被封装在Ball类中。本程序使用pygame.mask完成碰撞检测,使两球沿所画曲线向下移动。 在DrawLines类中,创建一个Surface类实例self.image,和主窗体等宽高,把黑线画在self.image上,然后再把self.image拷贝到窗体上显示。可画多条曲线。当鼠标按下,记录曲线第1点,也是鼠标移到新位置后画线段的起点。鼠标移动,从记录的线段起点到鼠标当前位置画线段,并记录鼠标当前位置为鼠标移到新位置后画线段的起点。鼠标抬起,结束画曲线。实现的基本代码如下,非完整程序,只是说明问题。
white=pygame.Color('white')
self.image=pygame.surface.Surface(size,0,32)
self.image.fill(white)
self.image.set_colorkey(white)
def drawAline(Event):
if Event.type==MOUSEBUTTONDOWN and Event.button==1 and DrawLines.canDraw:
self.mark=1
self.start_pos=event.pos
if Event.type==MOUSEBUTTONUP and event.button==1 and DrawLines.canDraw:
self.mark=0
if Event.type==MOUSEMOTION and self.mark==1 and DrawLines.canDraw:
pygame.draw.line(self.image,black,self.start_pos,event.pos,10)
self.start_pos=event.pos
def draw(self,aSurface):
aSurface.blit(self.image,(0,0))
球沿所画曲线向下移动功能被封装在Ball类中。右击任意两球中的一个,就要停止画线,被右击的球开始沿曲线移动。为确保球正确沿曲线移动,向下移动的初始位置沿y轴方向不能碰到黑线或红色块,而且距离黑线的距离不大于一次移动距离,即类实例变量self.dy。但是所画的黑线可能不能满足这个条件,因此在球沿曲线移动前,要做一些准备工作。移动前所画黑线可能碰到或没碰到球两种情况,如碰到球,球向上移动直到和黑线距离不大于self.dy,称为状态1,如没碰到球,球向下移动直到和黑线距离不大于self.dy,称为状态2,这两个状态是准备状态,而球自动沿所画曲线移动称为状态3,用self.state记录状态。第43行方法state1or2(self,pos)根据鼠标右击时鼠标的位置pos,先判断是否右击了球,如右击了球,画线结束,再分辨是状态1还是状态2。在方法update()中,根据self.state状态,做不同工作,注意,状态1或状态2结束后,都转为状态3。状态1和2比较好理解,这里重点介绍状态3,如何使球正确沿曲线移动。每次循环(每1帧)执行1次update(),在执行完状态1或2后,进入状态3的第1帧,如上所述,第1帧球一定没碰到黑或红色,第79条语句一定不成立,直接执行第81-82条语句,第83-85条语句判断是否越界。第86-87条语句判断是否碰到黑或红,如没碰到,第2帧执行第79条语句一定不成立,继续执行第81-82条语句,保持x原方向沿曲线下行。如第1帧执行第86-87条语句判断碰到黑或红,可能是+dx,也可能是+dy使球碰到黑或红,无论那种情况,必须执行第87条语句。第2帧执行第79条语句,若没碰到黑或红,说明第1帧是由于+dy使球碰到黑或红,球保持x原方向沿曲线下行;若碰到黑或红,说明第1帧是由于+dx使球碰到黑或红,此时要求球回到上1帧位置,必须反向移动,注意上1帧y方向已-dy,y方向已回到原位。就这样,一帧接着一帧,使球不断运动。 方法collide_color(self)用来判断球是否碰到障碍(第40行),包括红色或黑色。要检测碰撞到那种颜色,要执行第70-72行语句。该方法中碰撞检测是使用pygame.mask,该mask用来记录图形中哪些点颜色是透明的,标记为0,那些点颜色是不透明的,标记为1,在用mask碰撞检测时,只检测不透明点是否发生碰撞,不检测透明点是否发生碰撞。例如Ball类中创建mask语句如下(第30-35行)。
self.image=pygame.surface.Surface((2*radius,2*radius),0,32)
self.image.fill(white)
self.image.set_colorkey(white)
pygame.draw.circle(self.image,color,(radius,radius),radius)
self.mask=pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect(center=pos)
保存黑线的Surface类实例使用同样方法建立mask(第6-10行),但创建mask必须在画完黑线后(第127行)。有了球和保存黑线的Surface类实例的两个mask,就可以用第41-42行检测碰撞,如返回None,说明没有发生碰撞,如发生碰撞,将返回一个坐标元组,是Ball.mask中发生碰撞的第一个点的坐标,也是保存黑线的Surface类实例的图形的坐标点,因此通过该坐标可以得到该坐标点的颜色值(第70-72行)。也可把判断是否碰到红色和碰到黑色或红色(所有颜色)放到一个方法中,方法定义如下:
def collide_color(self,whatColor='allColor'):
offset = self.rect.x, self.rect.y
p=Ball.mask.overlap(self.mask,offset)
if whatColor=='red' and p!=None:
return Ball.bg.get_at(p)==pygame.Color('red')
else:
return p!=None
self.collide_color()
self.collide_color('red')
mask碰撞详细原理可参考本人博文:pygame.mask原理及使用pygame.mask实现精准碰撞检测。该程序也可使用pygame.mask.from_threshold()方法检测颜色的碰撞,详细原理可参考本人博文:函数pygame.mask.from_threshold()用阈值确定mask碰撞点原理及使用方法。 完整程序如下:
import pygame
from pygame.locals import *
class DrawLines():
canDraw=True
def __init__(self,size):
self.image=pygame.surface.Surface(size, 0, 32)
self.image.fill(white)
self.image.set_colorkey(white)
pygame.draw.rect(self.image,red,(100,200,300,50),0)
pygame.draw.rect(self.image,red, (230,10,40,200), 0)
self.mark=0
self.start_pos=(0,0)
def drawAline(self,Event):
if Event.type==MOUSEBUTTONDOWN and Event.button==1 and DrawLines.canDraw:
self.mark=1
self.start_pos=event.pos
if Event.type==MOUSEBUTTONUP and event.button==1 and DrawLines.canDraw:
self.mark=0
if Event.type==MOUSEMOTION and self.mark==1 and DrawLines.canDraw:
pygame.draw.line(self.image,black,self.start_pos,event.pos,10)
self.start_pos=event.pos
def draw(self,aSurface):
aSurface.blit(self.image,(0,0))
class Ball():
stop=False
winFailStr=' Press key r replay!'
mask=None
bg=None
def __init__(self,Screen,color, pos, radius):
self.image=pygame.surface.Surface((2*radius,2*radius), 0, 32)
self.image.fill(white)
self.image.set_colorkey(white)
pygame.draw.circle(self.image,color,(radius,radius),radius)
self.mask=pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect(center=pos)
self.screen=Screen
self.dx=5
self.dy=5
self.state=0
def collide_color(self):
offset = self.rect.x, self.rect.y
return Ball.mask.overlap(self.mask,offset)!=None
def state1or2(self,pos):
if self.state>0:
return False
if self.rect.collidepoint(pos):
DrawLines.canDraw=False
if self.collide_color():
self.state=1
else:
self.state=2
return True
else:
return False
def update(self):
if DrawLines.canDraw:
return
if self.state==1:
if self.collide_color():
self.rect.centery-=self.dy
if self.rect.centery<25:
Ball.stop=True
Ball.winFailStr='You fail!1'+Ball.winFailStr
else:
self.state=3
elif self.state==2:
if not(self.collide_color()):
self.rect.centery+=self.dy
else:
offset = self.rect.x, self.rect.y
p=Ball.mask.overlap(self.mask,offset)
if Ball.bg.get_at(p)==red:
Ball.stop=True
Ball.winFailStr='You fail!'+Ball.winFailStr
else:
self.rect.centery-=10
self.state=3
elif self.state==3:
if self.collide_color():
self.dx=-self.dx
self.rect.centerx+=self.dx
self.rect.centery+=self.dy
if self.rect.centerx<24 or self.rect.centerx>475 or self.rect.centery>475:
Ball.stop=True
Ball.winFailStr='You fail!3'+Ball.winFailStr
if self.collide_color():
self.rect.centery-=self.dy
def draw(self):
self.screen.blit(self.image,self.rect)
def reSet():
global ballB,ballG,mousePos,rightClick,drawLines,black
ballB=Ball(screen,blue,(160,125),25)
ballG=Ball(screen,green,(340,125),25)
Ball.stop=False
DrawLines.canDraw=True
Ball.winFailStr=' Press key r replay!'
drawLines=DrawLines(size)
Ball.bg=drawLines.image
mousePos=(0,0)
rightClick=False
white=pygame.Color('white')
bgcolor = pygame.Color('cyan')
blue=pygame.Color('blue')
red=pygame.Color('red')
green=pygame.Color('green')
black=pygame.Color('black')
pygame.init()
size = width, height = 500,500
screen = pygame.display.set_mode(size)
pygame.display.set_caption("球球情侣")
reSet()
fclock = pygame.time.Clock()
fps = 20
running = True
font1 = pygame.font.SysFont("arial", 25)
while running:
screen.fill(bgcolor)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYUP and event.key == pygame.K_r:
reSet()
drawLines.drawAline(event)
if event.type==MOUSEBUTTONDOWN and event.button==3:
mousePos=event.pos
rightClick=True
Ball.mask=pygame.mask.from_surface(drawLines.image)
drawLines.draw(screen)
if rightClick:
rightClick=False
if ballB.state1or2(mousePos) or ballG.state1or2(mousePos):
mousePos=(0,0)
if not(Ball.stop):
ballB.update()
ballG.update()
ballB.draw()
ballG.draw()
surface1=font1.render(Ball.winFailStr,True,[255,0,0])
screen.blit(surface1, (10, 470))
pygame.display.update()
if ballB.rect.collidepoint(ballG.rect.center) and not(Ball.stop):
Ball.stop=True
Ball.winFailStr='You win!'+Ball.winFailStr
fclock.tick(fps)
pygame.quit()
|