用pygame实现网上游戏‘球球情侣‘(检测矩形和某颜色碰撞的例子)

网上有个’球球情侣’游戏,游戏中有两个不同颜色的球,玩者首先用鼠标画曲线画出球移动的路径,分别右击两个球,使两球沿曲线移动,如果两球碰到一起,进入下一关。编写很多关是游戏公司的事,我们只编写最简单的一关,说明实现游戏的方法。用pygame编写了’球球情侣’游戏。游戏运行效果如下:
在这里插入图片描述
可以看到本程序要实现两个功能,第一,用鼠标画多条曲线,右击球后,球移动,不能再画线。第二,球沿所画曲线向下移动,同时检测两球是否发生碰撞。拖动鼠标画线功能被封装在DrawLines类中。球沿所画曲线向下移动功能被封装在Ball类中。
pygame.draw.lines(Surface,color,closed,pointlist,width=1)方法是pygame画多个线段的方法。其中pointlist是列表,记录所有线段端点的坐标,至少需要有两个点。曲线可认为是由很多非常短的线段组成,将这些非常短的线段的端点保存到列表中,就可以用上边方法画曲线了。实现方法见DrawLines类的drawAline(self,Event)方法,其参数Event是事件。当鼠标左键按下且允许画线,令self.mark=1,表示鼠标已按下(第11,12行)。当鼠标按下移动且允许画线,将当前鼠标坐标保存到self.aLine,self.aLine保存当前正在画的曲线(第18,19行)。当鼠标左键抬起且允许画线(第13行),表示该曲线已完成,mark=0,鼠标抬起标志,如果记录当前曲线的列表self.aLine项数(线段端点数)在2以上,说明列表保存的是1条曲线,要把self.aLine保存到列表self.allLines中,这个列表记录了所画的所有曲线,最后清空self.aLine,准备画下一条曲线。DrawLines类的draw()(第20行)方法负责在每1帧渲染时重画所有的曲线,这种方法直接把图形画在主窗体screen上()(第121行)。
pygame创建主窗体screen=pygame.display.set_mode(size),每一帧首先将screen清空,再将图像拷贝screen显示,从而完成动画效果。用上节所介绍的方法是直接把黑线画在screen上,为了重画黑线到screen,必须把曲线所有点保存到列表中。换一种思路,可创建一个Surface类实例bg,和主窗体等宽高,把黑线画在bg上,然后再把bg拷贝到screen显示,这种方法不需要列表。实现的基本代码如下,非完整程序,只是说明问题,要作为类的实例方法,还需做必要改动。本例没有采用这种方法。

white=pygame.Color('white')             #定义Color类实例为白色
bg=pygame.surface.Surface(size,0,32)    #1个Surface实例,和主窗体尺寸相同
bg.fill(white)				  #背景色为白色
bg.set_colorkey(white)#背景透明,使在bg上画图似乎是在主窗体上,不影响颜色判断
def drawAline(Event):
    global mark,start_pos,bg  		     #下句如鼠标左键按下且允许画线
    if Event.type==MOUSEBUTTONDOWN and Event.button==1 and Ball.canDrawL:
        mark=1       		     #mark=1表示鼠标被按下,如鼠标移动则画线
        start_pos=event.pos#1条线第1点或画线起点,下句鼠标左键抬起且允许画线
    if Event.type==MOUSEBUTTONUP and event.button==1 and Ball.canDrawL:
        mark=0 #1条曲线已完成,mark=0鼠标抬起标志,下句鼠标按下移动且允许画线
    if Event.type==MOUSEMOTION and mark==1 and Ball.canDrawL:            
        pygame.draw.line(bg,black,start_pos,event.pos,10)#将短线段画在bg上
        start_pos=event.pos    #画下个线段的起点
def drawAllLines(aSurface):    #将所有曲线显示在主窗体
    global bg    
    aSurface.blit(bg,(0,0))

右击任意两球中的一个,就要停止画线,被右击的球开始沿曲线移动。为确保球正确沿曲线移动,向下移动的初始位置沿y轴方向不能碰到黑线或红色块,而且距离黑线的距离不大于一次移动距离,即类实例变量self.dy。但是所画的黑线可能不能满足这个条件,因此在球沿曲线移动前,要做一些准备工作。移动前所画黑线可能碰到或没碰到球两种情况,如碰到球,球向上移动直到和黑线距离不大于self.dy,称为状态1,如没碰到球,球向下移动直到和黑线距离不大于self.dy,为状态2,这两个状态是准备状态,而球自动沿所画曲线移动为状态3,用self.state记录状态。第42行state1or2(self,pos)方法根据鼠标右击时鼠标的位置pos,先判断是否右击了球,如右击了球,画线结束,再分辨是状态1还是状态2。在方法update()中,根据self.state状态,做不同工作,注意,状态1或状态2结束后,都转为状态3。状态1和2比较好理解,这里重点介绍状态3,如何使球正确沿曲线移动。每次循环(每1帧)执行1次update(),在执行完状态1或2后,进入状态3的第1帧,如上所述,第1帧球一定没碰到黑或红色,第75条语句一定不成立,直接执行第77-78条语句,第79-81条语句判断是否越界。第82-83条语句判断是否碰到黑或红,如没碰到,第2帧执行第75条语句一定不成立,继续执行第77-78条语句,保持x原方向沿曲线下行。如第1帧执行第82-83条语句判断碰到黑或红,可能是+dx,也可能是+dy使球碰到黑或红,无论那种情况,必须执行第83条语句。第2帧执行第75条语句,若没碰到黑或红,说明第1帧是由于+dy使球碰到黑或红,球保持x原方向沿曲线下行;若碰到黑或红,说明第1帧是由于+dx使球碰到黑或红,此时要求球回到上1帧位置,必须反向移动,注意上1帧y方向已-dy,y方向已回到原位。就这样,一帧接着一帧,使球不断运动。方法collide_color(self,aColor)用来判断球是否碰到某种颜色,参数aColor是要检测碰撞的颜色,其原理可参考本人博文:pygame游戏检测矩形是否碰撞指定颜色的自定义函数(仅5行代码)。
完整程序如下:

import pygame
from pygame.locals import *
class DrawLines():
    canDraw=True  #是否允许画,无论有多少个类实例,类变量是唯一的,所有类实例共用。使用方法:类名.类变量名
    def __init__(self,color):    #color是画线的颜色
        self.color=color
        self.aLine = []          #在鼠标按下移动时,将鼠标坐标保存到该列表,是1条曲线所有点
        self.allLines=[]         #鼠标抬起将aLine列表保存到本列表,因此本列表保存多条曲线
        self.mark=0              #=0,表示鼠标未按下
    def drawAline(self,Event):
        if Event.type==MOUSEBUTTONDOWN and Event.button==1 and DrawLines.canDraw:#鼠标左键按下事件且允许画线
            self.mark=1                        #mark=1表示鼠标被按下,如鼠标移动则画线
        if Event.type==MOUSEBUTTONUP and event.button==1 and DrawLines.canDraw:   #鼠标左键抬起且允许画线
            self.mark=0                         #表示当前曲线已完成,mark=0,鼠标抬起标志
            if len(self.aLine) > 1:             #1条曲线至少两点,>1表示是1条曲线且完成,保存到allLines
                self.allLines.append(self.aLine)
            self.aLine=[]                        #清空self.aLine
        if Event.type==MOUSEMOTION and self.mark==1 and DrawLines.canDraw:  #鼠标按下移动且允许画线            
            self.aLine.append(event.pos)         #将当前鼠标坐标保存到aLine,aLine保存当前正在画的曲线
    def draw(self,aSurface):
        if len(self.aLine)>1:                    #>1,表示当前正在画的曲线已有两个点,两点可画1条线段
            pygame.draw.lines(aSurface,self.color,False,self.aLine,10)#该曲线未保存到self.allLines,需单独画出 
        if len(self.allLines)>0:                   #除当前正在画的曲线,画其它已完成的曲线
            for ps in self.allLines:            
                pygame.draw.lines(aSurface,self.color,False,ps,10)
class Ball():      
    stop=False   #类变量,无论有多少个类实例,是唯一的,所有类实例共用。使用方法是:类名.类变量名
    winFailStr=' Press key r replay!'
    def __init__(self,Screen,color, pos, radius):  #参数3为球颜色,参数4为球圆心,参数5为球半径
        self.screen=Screen
        self.rect=pygame.Rect(1,1,2*radius,2*radius)    #self.rect为球的外切矩形
        self.rect.center=pos
        self.color=color
        self.dx=5                               #每帧沿x轴移动距离dx
        self.dy=5                               #每帧沿y轴移动距离dy
        self.state=0                            #=1,2,3。看下面注释。        
    def collide_color(self,aColor):             #判断在self.screen的self.rect区域是否包含aColor颜色
        pixel=pygame.PixelArray(self.screen)    #锁定对象screen,将其各点颜色保存在2维数组
        aPixel=pixel[self.rect.x:self.rect.x+self.rect.width,self.rect.y:self.rect.y+self.rect.height]#2维数组切片
        pygame.PixelArray.close(pixel)          #解锁screen对象
        return aColor in aPixel                 #如包含aColor颜色返回真,否则为假    
    def state1or2(self,pos):                    #首先判断是否结束画曲线,如结束,判断是state=1或2
        if self.state>0:                        #返回False原因,见第125行注释
            return False                        #返回False,表示已确定了等级,不必再一次计算
        if self.rect.collidepoint(pos):         #如鼠标点击球结束画曲线
            DrawLines.canDraw=False             #不允许再画曲线            
            if self.collide_color(black):       #调用实例方法检测是否碰到黑色,画线碰到黑线,为状态1
                self.state=1        
            else:                               #画线碰到黑线,为状态2
                self.state=2        
            return True                         #如点击球,返回True
        else:
            return False                        #如未点击球,返回False
    def update(self):
        if DrawLines.canDraw:
            return
        if self.state==1:           #为状态1,球将沿y轴向上移动,如能使球距黑线<dy,转state=3,出界游戏结束
            if self.collide_color(black):
                self.rect.centery-=self.dy
                if self.rect.centery<25:                    
                    Ball.stop=True                  #越界,结束程序,输了
                    Ball.winFailStr='You fail!'+Ball.winFailStr
            else:
                self.state=3
        elif self.state==2:         #为状态2,沿y轴向下移动,如能使球距黑线<dy,转state=3,碰到红色游戏结束
            if not(self.collide_color(black)):
                self.rect.centery+=self.dy
                if self.collide_color(red):                    
                    Ball.stop=True                  #没画黑线,结束程序,输了
                    Ball.winFailStr='You fail!'+Ball.winFailStr
            else:
                self.rect.centery-=10
                self.state=3
        elif self.state==3:       #为状态3,沿所画曲线移动,x和y坐标都要变,遇到黑或红反向移动,移到边界结束
            if self.collide_color(black) or self.collide_color(red):    #初始设置保证第1次肯定不成立
                self.dx=-self.dx
            self.rect.centerx+=self.dx      #y和x方向增加dy或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!'+Ball.winFailStr
            if self.collide_color(black) or self.collide_color(red):  #如碰到黑或红,y方向退回原位置
                self.rect.centery-=self.dy  #此时有两种可能,碰到黑或红,下次x方向反向,否则不反向
    def draw(self):
        pygame.draw.circle(self.screen,self.color,self.rect.center,self.rect.width//2,0)
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(black)      
    mousePos=(0,0)
    rightClick=False
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:     #按r键后,重玩游戏
            reSet()
        drawLines.drawAline(event)                  #用鼠标画线,将曲线的各端点保存到列表
        if event.type==MOUSEBUTTONDOWN and event.button==3:     #鼠标右键按下事件
            mousePos=event.pos                                  #鼠标右键按下事件时坐标
            rightClick=True                                     #鼠标右键按下标志    
    drawLines.draw(screen)
    pygame.draw.rect(screen,red, (100,200,300,50), 0)           #画两个红色方块
    pygame.draw.rect(screen,red, (230,10,40,200), 0)
    if rightClick:
        rightClick=False    #下句,or前方法返回True,就不再执行or后方法,如or前方法已做过判断,要返回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):#检测1个Rect中心是否在另1Rect中,是返回真    
        Ball.stop=True                                  #显示你赢了,圆停止运动
        Ball.winFailStr='You win!'+Ball.winFailStr
    fclock.tick(fps)
pygame.quit()
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值