鱼C论坛

 找回密码
 立即注册
查看: 2121|回复: 12

小球摩擦碰撞

[复制链接]
发表于 2022-8-8 01:01:48 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 lzb1001 于 2022-8-8 16:33 编辑

import pygame
import sys
from pygame.locals import *
from random import *

# 球类继承自Spirte类
class Ball(pygame.sprite.Sprite):
    def __init__(self, grayball_image, greenball_image, position, speed, bg_size, target):
        # 初始化动画精灵
        pygame.sprite.Sprite.__init__(self)

        self.grayball_image = pygame.image.load(grayball_image).convert_alpha()  #加载灰色小球
        self.greenball_image = pygame.image.load(greenball_image).convert_alpha()  #加载绿色小球
        self.rect = self.grayball_image.get_rect()  #两颜色小球矩形位置是一样的
        # 将小球放在指定位置
        self.rect.left, self.rect.top = position
        #加多一个side表示速度的方向
        self.side = [choice([-1, 1]), choice([-1, 1])]#第一个元素表示水平方向,第二个元素表示垂直方向,使用random 的choice() 方法随机选择-1和1
        self.speed = speed
        #加多一个属性表示是否碰撞了
        self.collide = False
        #1、为每个小球设定一个不同的目标;
        self.target = target
        #5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
        self.control = False
        self.width, self.height = bg_size[0], bg_size[1]
        self.radius = self.rect.width / 2  #增加半径属性

    def move(self):      
        if self.control: #如果是玩家控制,就还是带方向的速度
            self.rect = self.rect.move(self.speed)
        else:
            self.rect = self.rect.move((self.side[0] * self.speed[0], \
                                                    self.side[1] * self.speed[1])) #移动的速度就变为速度的大小乘以方向了

        # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
        # 这样便实现了从左边进入,右边出来的效果
        if self.rect.right <= 0:
            self.rect.left = self.width

        elif self.rect.left >= self.width:
            self.rect.right = 0

        elif self.rect.bottom <= 0:
            self.rect.top = self.height

        elif self.rect.top >= self.height:
            self.rect.bottom = 0
    #3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
    def check(self, motion):
        if self.target < motion < self.target + 5:
            return True
        else:
            return False

class Glass(pygame.sprite.Sprite):
    def __init__(self, glass_image, mouse_image, bg_size):
        #初始化动画精灵
        pygame.sprite.Sprite.__init__(self)

        self.glass_image = pygame.image.load(glass_image).convert_alpha()
        self.glass_rect = self.glass_image.get_rect()
        self.glass_rect.left, self.glass_rect.top = \
                              (bg_size[0] - self.glass_rect.width) // 2, \
                              bg_size[1] - self.glass_rect.height

        self.mouse_image = pygame.image.load(mouse_image).convert_alpha()#加载小手光标
        self.mouse_rect = self.mouse_image.get_rect()#获取小手光标矩形位置
        self.mouse_rect.left, self.mouse_rect.top = \
                              self.glass_rect.left, self.glass_rect.top  #小手光标初始在玻璃面板左上角
        pygame.mouse.set_visible(False)#设置Pygame 光标不可见
      
def main():
    pygame.init()

    grayball_image = "gray_ball.png"
    greenball_image = "green_ball.png"
    bg_image = "background.png"
    glass_image = "glass.png"
    mouse_image = "hand.png"

    running = True

    #添加背景音乐
    pygame.mixer.music.load('bg_music.ogg')
    pygame.mixer.music.set_volume(0.2)#设置音量   
    pygame.mixer.music.play()#播放

    #加载音效
    winner_sound = pygame.mixer.Sound("winner.wav")
    winner_sound.set_volume(0.2)
    loser_sound = pygame.mixer.Sound("loser.wav")
    loser_sound.set_volume(0.2)
    laugh_sound = pygame.mixer.Sound("laugh.wav")
    laugh_sound.set_volume(0.2)
    hole_sound = pygame.mixer.Sound("hole.wav")
    hole_sound.set_volume(0.2)

    #背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,
        
    #音乐播放完时,游戏结束
    GAMEOVER = USEREVENT
    pygame.mixer.music.set_endevent(GAMEOVER)  
   
    # 根据背景图片指定游戏界面尺寸
    bg_size = width, height = 1024, 681
    screen = pygame.display.set_mode(bg_size)
    pygame.display.set_caption("Play the ball - Python Demo")

    background = pygame.image.load(bg_image).convert_alpha()

    # 用来存放小球对象的列表
    balls = []
    group = pygame.sprite.Group()

    # 创建五个小球
    for i in range(5):
        # 位置随机,速度随机
        position = randint(0, width-100), randint(0, height-100)
        #speed 初始化就只为正数了
        speed = [randint(1, 10), randint(1, 10)]
        ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i + 1)) #target 设为5-30,比较适中
        while pygame.sprite.spritecollide(ball, group, False, pygame.sprite.collide_circle):#在创建小球这里必须进行一下碰撞检测
            ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
        balls.append(ball)
        group.add(ball)

    glass = Glass(glass_image, mouse_image, bg_size)
    #2、创建一个motion 变量来记录每一秒钟产生事件数量;
    motion = 0
    #########################################
    #4.1、添加一个自定义事件,每一秒钟触发一次。
    MYTIMER = USEREVENT + 1   #自定义事件的知识点可以查看上一节课的末尾注解
    pygame.time.set_timer(MYTIMER, 1000)

    pygame.key.set_repeat(100, 100)
   
    clock = pygame.time.Clock()

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
         
            elif event.type == GAMEOVER: #判断事件是否为我们自定义的GAMEOVER事件
                loser_sound.play()  
                pygame.time.delay(2000)#暂停2秒
                laugh_sound.play()
                running = False

            #4.2、 调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
            elif event.type == MYTIMER:
                if motion:
                    for each in group:
                        if each.check(motion):
                            each.speed = [0, 0]
                            each.control = True
                    motion = 0
            #需要计算一下motion
            elif event.type == MOUSEMOTION:
                motion += 1

            #用户通过WSAD按键移动绿色小球,使用+= 和 -= 而不是 +1 或者 -1,是为了体验加速度的感觉
            elif event.type == KEYDOWN:
                if event.key == K_w:  #W,上
                    for each in group:
                        if each.control:
                            each.speed[1] -= 1
                        
                if event.key == K_a:  #a,左
                    for each in group:
                        if each.control:
                            each.speed[0] -= 1
                        
                if event.key == K_s:  #s,下
                    for each in group:
                        if each.control:
                            each.speed[1] += 1
                        
                if event.key == K_d:  #d, 右
                    for each in group:
                        if each.control:
                            each.speed[0] += 1
        #在默认我们是不能感受到加速度的感觉的,
        #因为在默认情况下,无论你是简单的按一下按键或是按住按键不放,
        #Pygame都只为你发送一个键盘按下的事件
        #不过我们可以通过 key 模块的 set_repeat() 方法来设置是否重复响应按下某个按键
        # pygame.key.set_repeat(delay, interval)
        #--delay 参数制动第一次发送事件的延迟时间
        #--interval 参数指定重复发送事件的时间间隔
        #--如果不带任何参数,表示取消重复发送事件
        #为了使小球获得加速度的快感,我们设置按键的重复间隔为100毫秒,在while 循环前面设置。
            
               
        screen.blit(background, (0, 0))
        
        screen.blit(glass.glass_image, glass.glass_rect)

        #将小手光标画在Pygame 默认光标位置上
        glass.mouse_rect.left, glass.mouse_rect.top = pygame.mouse.get_pos()
        
        #限制光标只能在玻璃面板范围内移动(摩擦摩擦)
        if glass.mouse_rect.left < glass.glass_rect.left:
            glass.mouse_rect.left = glass.glass_rect.left
        if glass.mouse_rect.left > glass.glass_rect.right - glass.mouse_rect.width:
            glass.mouse_rect.left = glass.glass_rect.right - glass.mouse_rect.width
        if glass.mouse_rect.top < glass.glass_rect.top:
            glass.mouse_rect.top = glass.glass_rect.top
        if glass.mouse_rect.top > glass.mouse_rect.bottom - glass.mouse_rect.height:
            glass.mouse_rect.top = glass.mouse_rect.bottom - glass.mouse_rect.height

        screen.blit(glass.mouse_image, glass.mouse_rect)

        for each in balls:
            each.move()
            #
            if each.collide:
                each.speed = [randint(1, 10), randint(1, 10)]
                each.collide = False
            #如果小球的 control 属性为 True,就画绿球
            if each.control:
                #画绿色小球
                screen.blit(each.greenball_image, each.rect)
            else:
                #画灰色小球
                screen.blit(each.grayball_image, each.rect)
               
        #碰撞检测
        for each in group:
            group.remove(each) #把自身拿出来

            if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
                #发生碰撞,方向取反,这样就会以多大速度相撞,就以相反速度相离
                each.side[0] = -each.side[0]
                each.side[1] = -each.side[1]

                each.collide = True
                                
               
                if each.control: # 即if each.control == True:,若小球受控(即处于由玩家控制状态)---这样理解对吗???
                    each.side[0] = -1 # 无法理解!!!
                    each.side[1] = -1 # 无法理解!!!
                    each.control = False


            group.add(each)#还要把自己放进去

        pygame.display.flip()
        clock.tick(30)


if __name__ == "__main__":
    main()

------------------------------------------
Windows 10 专业版 | Python 3.7.6
------------------------------------------

【我的问题】

1、恕本人愚钝,对代码中红色部分不理解,恳请大神予以指点

小甲鱼教材和视频上不是都说:

将小球的移动给区分开处理:
(1)随机移动时(即小球处于失控状态)按照方向乘以速度(仅描述速度的大小即速率)的形式;
(2)而为了保留玩家操控小球是带加速度的特性,由玩家操控移动时(即小球处于受控状态)仍采用带方向描述的速度形式(也就是说仍保持原来带方向的speed)。

代码中:

if each.control: 也就是if each.control == True:,所以这行代码不是表示如果小球受控(即处于由玩家控制状态),则……?那应该按上面(2)的方法处理,为何下面又冒出:

each.side[0] = -1
each.side[1] = -1

上面这两行代码又是什么意思呢???

******************************

感谢大神不吝赐教,为新手解疑释惑。

赠人玫瑰,手有余香,好人一生平安!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-8-8 16:08:48 | 显示全部楼层
本帖最后由 鱼cpython学习者 于 2022-8-8 16:12 编辑
if each.control:
判断的确实是小球是否受控,但你看这个判断,它是被包在碰撞判断里的。
也就是说,当小球碰撞的时候,会检测该小球是否受控,受控的话就让其失控
each.side[0] = -1
each.side[1] = -1
这两句我的看法是这样的:
在游戏刚开始时,所有的speed都为正数,负数的只有side
而到了后期,玩家控制过一些小球,就会出现速度为负数的情况,因为有
                if event.key == K_w:  #W,上
                    for each in group:
                        if each.control:
                            each.speed[1] -= 1
                        
                if event.key == K_a:  #a,左
                    for each in group:
                        if each.control:
                            each.speed[0] -= 1
那么就要把方向调成-1,相当于对速度取反,因为碰撞就会设置self.control = False
而在Ball.move方法中,self.control如果为False,那么就需要side参与计算,如果取反速度的话,还有side可能影响运动方向
我说的有点乱,举个例子
假设一个小球A速度为[5, 3], 方向为[1, -1],移动方向为向下,向左
过了一会它变成受控状态,此时Ball.move判断self.control为True,调用self.rect.move(self.speed),代表self.side不用参与运算
又过了一会,经过玩家的操控,A的速度为[-5, 3], 方向仍然为[1, -1],移动方向为向上,向右,因为此时不依靠side来指定移动方向了
此时,A和另一个小球B碰撞,B方向取反,往反方向移动。A失控,self.control变为False
那么此时A应该往反方向移动,即向下,向左移动,为[5, -3]
如果把A的速度取反,变为[5, -3],移动方向为向下,向左,似乎是正确的
但在Ball.move方法中,判断self.control为False,那么调用self.rect = self.rect.move((self.side[0] * self.speed[0], self.side[1] * self.speed[1]))
那么此时传给self.rect.move()的,则是(5 * 1, -3 * -1),即(5, 3),移动方向为向下,向右
这样就不是完全的反方向了。
而把A的方向设为[-1, -1],传给self.rect.move的,是(-5 * -1, 3 * -1),为[5, -3],刚好是我们想要的目标
我说的可能不太清楚,你可以琢磨一下,如果我的看法有错误请指出
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-8-9 17:19:14 | 显示全部楼层
感觉小甲鱼在这节教程的这个地方讲得不够明白和透彻……
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-8-10 17:15:37 | 显示全部楼层
鱼cpython学习者 发表于 2022-8-8 16:08
判断的确实是小球是否受控,但你看这个判断,它是被包在碰撞判断里的。
也就是说,当小球碰撞的时候,会 ...

感谢大神分析和指点,

关于:
……
判断的确实是小球是否受控,但你看这个判断,它是被包在碰撞判断里的。
也就是说,当小球碰撞的时候,会检测该小球是否受控,受控的话就让其失控
……

我的疑问:

当小球碰撞的时候,小球肯定失控,怎么会去检测小球是否受控呢?也就是说我的疑问是为什么将检测小球是否受控的代码放在此处(即碰撞判断里)?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-10 19:26:31 | 显示全部楼层
lzb1001 发表于 2022-8-10 17:15
感谢大神分析和指点,

关于:

为什么肯定失控?碰撞有两种可能,一种是两个失控的小球碰撞,另一种是当玩家控制了一个小球并移动它时,不小心让这个被控制的小球和另一个失控的小球碰撞
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-8-10 21:08:40 | 显示全部楼层
鱼cpython学习者 发表于 2022-8-10 19:26
为什么肯定失控?碰撞有两种可能,一种是两个失控的小球碰撞,另一种是当玩家控制了一个小球并移动它时, ...

是哦,你说的第二种我怎么没想到……
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-8-10 23:56:51 | 显示全部楼层
鱼cpython学习者 发表于 2022-8-8 16:08
判断的确实是小球是否受控,但你看这个判断,它是被包在碰撞判断里的。
也就是说,当小球碰撞的时候,会 ...

烦请大神看下下面的注释是否正确?

each.side[0] = -1 # X轴横向坐标向左?
each.side[1] = -1 # Y轴纵向坐标向上?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-11 10:50:02 | 显示全部楼层
lzb1001 发表于 2022-8-10 23:56
烦请大神看下下面的注释是否正确?

each.side[0] = -1 # X轴横向坐标向左?

从这个变量的字面意思来看确实是这样。当然你也可以写对速度取反,只要意思理解就行
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-8-11 11:35:40 | 显示全部楼层
鱼cpython学习者 发表于 2022-8-11 10:50
从这个变量的字面意思来看确实是这样。当然你也可以写对速度取反,只要意思理解就行


对速度取反?

1、side不是方向吗?如果方向取反,不是应该是each.side[0] = -each.side[0]吗?

2、现在说速度有点被搞晕了:之前带方向描述的叫速度,方向和速度分开后不带方向的速度应该叫速率才对,所以不知大神说的对速度取反是指前者还是后者?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-11 11:58:12 | 显示全部楼层
本帖最后由 鱼cpython学习者 于 2022-8-11 12:02 编辑
lzb1001 发表于 2022-8-11 11:35
对速度取反?

1、side不是方向吗?如果方向取反,不是应该是each.side[0] = -each.side[0]吗?


好吧,是我表述有歧义了,先道个歉
1.side确实是方向,但这里不是方向取反。在这个判断里,小球会从受控状态变为不受控状态。而不受控状态的运动是用速率+方向描述的,这个你能理解对吧
而受控状态的运动是速度描述,这个速度是带方向的,即取值是有正负的,原来用于描述方向的side变量被弃用。
那么从受控改为不受控状态,小球运动的计算就会复用side变量。因为Ball.move方法定义了
if self.control: #如果是玩家控制,就还是带方向的速度
        self.rect = self.rect.move(self.speed)
else:
        self.rect = self.rect.move((self.side[0] * self.speed[0], \
                                                    self.side[1] * self.speed[1])) #移动的速度就变为速度的大小乘以方向了
而我们知道,小球受碰撞后要向反方向运动,在这个情景里,一个受控的小球和一个不受控的小球碰撞。不受控的小球很简单,对方向取反,因为不受控的小球是速率和方向严格分开描述的
而受控的小球就难了,因为受控后就用带方向的速度描述运动,而原来的用于描述方向的side变量被弃用了
现在这个受控的小球要变成不受控状态,还要朝反方向运动,还要考虑side变量的复用
那么最好的方法,就是把描述方向的side变量变成[-1, -1]
因为小球变成不受控状态了,运动的计算就变成
 self.rect = self.rect.move((self.side[0] * self.speed[0], self.side[1] * self.speed[1]))
                                       
 self.rect = self.rect.move((-1 * self.speed[0], -1 * self.speed[1]))
            
那么不就等于对self.speed取反了吗,而self.speed又是带方向的速度,那么效果就是小球往反方向运动了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-8-11 18:42:33 | 显示全部楼层
鱼cpython学习者 发表于 2022-8-11 11:58
好吧,是我表述有歧义了,先道个歉
1.side确实是方向,但这里不是方向取反。在这个判断里,小球会从受 ...

谢谢大神,我是因为确实不明白,看得有点懵,斗胆指出发表下看法。所以大神不用道歉哈。

第1次+第2次的解答有助于对这个问题的理解。

再次感谢!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-8-18 23:00:07 | 显示全部楼层
鱼cpython学习者 发表于 2022-8-8 16:08
判断的确实是小球是否受控,但你看这个判断,它是被包在碰撞判断里的。
也就是说,当小球碰撞的时候,会 ...

@鱼cpython学习者

大神你好!以下你第一次解说的节选,其中标记为红色字体的内容是否有错?
……
假设一个小球A速度为[5, 3], 方向为[1, -1],移动方向为向下,向左
过了一会它变成受控状态,此时Ball.move判断self.control为True,调用self.rect.move(self.speed),代表self.side不用参与运算
又过了一会,经过玩家的操控,A的速度为[-5, 3], 方向仍然为[1, -1],移动方向为向上,向右,因为此时不依靠side来指定移动方向了
此时,A和另一个小球B碰撞,B方向取反,往反方向移动。A失控,self.control变为False
那么此时A应该往反方向移动,即向下,向左移动,为[5, -3]
如果把A的速度取反,变为[5, -3],移动方向为向下,向左,似乎是正确的
但在Ball.move方法中,判断self.control为False,那么调用self.rect = self.rect.move((self.side[0] * self.speed[0], self.side[1] * self.speed[1]))
那么此时传给self.rect.move()的,则是(5 * 1, -3 * -1),即(5, 3),移动方向为向下,向右
这样就不是完全的反方向了。
而把A的方向设为[-1, -1],传给self.rect.move的,是(-5 * -1, 3 * -1),为[5, -3],刚好是我们想要的目标
……
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-19 18:39:31 | 显示全部楼层
lzb1001 发表于 2022-8-18 23:00
@鱼cpython学习者

大神你好!以下你第一次解说的节选,其中标记为红色字体的内容是否有错?

好吧,实在抱歉实在抱歉,我每一个方向都写反了
第一个改成向右,向上
第二个改成向左,向下
第三个改成向右,向上
第四个同上
第五个是对的,向右,向下
实在抱歉,给你带来了麻烦
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-17 00:45

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表