Pygame开发空气曲棍球游戏如何实现球体碰撞后正确方向反弹
Pygame空气曲棍球正确反弹实现
原有代码问题
- 变量名错误:定义球的x/y轴速度为
chx/chy,位置更新时错误调用了不存在的变量ch,球本身无法正常移动 - 碰撞逻辑缺失:仅打印了碰撞触发提示,没有根据碰撞位置计算反弹方向,之前手动判断上下位置加y轴速度的方案精度极低,无法匹配真实碰撞角度
- 缺少碰撞后位置修正:碰撞触发后没有把球推出球拍碰撞范围,会出现卡模、反复触发碰撞的问题
- 碰撞检测不全:仅实现了玩家1和球的碰撞判断,漏掉了玩家2的碰撞逻辑
反弹实现原理
空气曲棍球的球拍和球都可以用圆形做碰撞判定,碰撞后的反弹方向沿两圆心的连线方向(从球拍圆心指向球圆心),不需要手动判断上下位置,直接通过向量计算就能得到准确的反弹角度,还可以叠加球拍自身的移动速度,还原真实击球的手感。
计算步骤:
- 碰撞触发时,先计算球和球拍的x、y轴坐标差值,得到碰撞方向向量
- 对方向向量做归一化处理,保证不同碰撞位置下球的基础速度大小一致
- 将归一化后的向量乘以设定的基础球速,得到球的x/y轴反弹速度
- 叠加球拍当前的移动速度,让球拍移动时击球能给球带来额外的速度加成
- 修正球的坐标,将球推到球拍碰撞判定范围外,避免卡模
修正后可运行代码
import pygame import math pygame.init() screen = pygame.display.set_mode((800, 400)) # 注:请确保素材路径正确,没有素材可以先用pygame.draw画圆形代替 background = pygame.image.load("ER.png") player1 = pygame.image.load("eee.png") px = 20 py = 148 p1_vx = 0 p1_vy = 0 player2 = pygame.image.load("a.png") px2 = 700 py2 = 148 p2_vx = 0 p2_vy = 0 ball = pygame.image.load("qw.png") bx = 351 by = 148 ball_vx = 0 ball_vy = 0 # 配置参数 BALL_RADIUS = 15 PADDLE_RADIUS = 25 BALL_SPEED = 5 BORDER_BOUNCE = 0.98 # 撞墙反弹的能量保留 def draw_ball(x,y): screen.blit(ball,(x,y)) def draw_p1(x, y): screen.blit(player1,(x , y)) def draw_p2(x, y): screen.blit(player2,(x , y)) def check_collision(paddle_x, paddle_y, ball_x, ball_y): distance = math.hypot(paddle_x - ball_x, paddle_y - ball_y) return distance < (BALL_RADIUS + PADDLE_RADIUS) def calculate_bounce(paddle_x, paddle_y, paddle_vx, paddle_vy, ball_x, ball_y): # 计算碰撞方向向量(球拍指向球) dx = ball_x - paddle_x dy = ball_y - paddle_y # 归一化向量 dist = math.hypot(dx, dy) dx_norm = dx / dist dy_norm = dy / dist # 计算反弹速度,叠加球拍速度 new_vx = dx_norm * BALL_SPEED + paddle_vx * 0.8 new_vy = dy_norm * BALL_SPEED + paddle_vy * 0.8 # 修正球位置,避免卡模 overlap = (BALL_RADIUS + PADDLE_RADIUS) - dist new_bx = ball_x + dx_norm * overlap new_by = ball_y + dy_norm * overlap return new_vx, new_vy, new_bx, new_by run = True while run: screen.blit(background,(0,0)) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_d: p1_vx += 1 if event.key == pygame.K_a: p1_vx += -1 if event.key == pygame.K_s: p1_vy += 1 if event.key == pygame.K_w: p1_vy += -1 if event.key == pygame.K_LEFT: p2_vx -= 1 if event.key == pygame.K_RIGHT: p2_vx += 1 if event.key == pygame.K_DOWN: p2_vy += 1 if event.key == pygame.K_UP: p2_vy += -1 if event.type == pygame.KEYUP: if event.key == pygame.K_d: p1_vx -= 1 if event.key == pygame.K_a: p1_vx += 1 if event.key == pygame.K_s: p1_vy -= 1 if event.key == pygame.K_w: p1_vy += 1 if event.key == pygame.K_LEFT: p2_vx += 1 if event.key == pygame.K_RIGHT: p2_vx -= 1 if event.key == pygame.K_DOWN: p2_vy -= 1 if event.key == pygame.K_UP: p2_vy += 1 # 碰撞检测与反弹计算 if check_collision(px, py, bx, by): ball_vx, ball_vy, bx, by = calculate_bounce(px, py, p1_vx, p1_vy, bx, by) if check_collision(px2, py2, bx, by): ball_vx, ball_vy, bx, by = calculate_bounce(px2, py2, p2_vx, p2_vy, bx, by) # 更新位置 bx += ball_vx by += ball_vy # 球撞上下边界反弹 if by <= BALL_RADIUS or by >= 400 - BALL_RADIUS: ball_vy *= -BORDER_BOUNCE by = max(BALL_RADIUS, min(by, 400 - BALL_RADIUS)) # 左右边界得分后重置球位置(可自行扩展得分逻辑) if bx < 0 or bx > 800: bx, by = 400, 200 ball_vx, ball_vy = 0, 0 py2 += p2_vy px2 += p2_vx py += p1_vy px += p1_vx # 球拍移动边界限制 if px <= -8: px = -8 if px >= 326: px = 326 if py <= -8: py = -8 if py >= 312: py = 312 if px2 >= 712: px2 = 712 if py2 <= -8: py2 = -8 if py2 >= 312: py2 = 312 if px2 <= 378: px2 = 378 draw_ball(bx,by) draw_p1(px,py) draw_p2(px2, py2) pygame.display.update()
额外优化提示
- 如果需要更真实的物理效果,可以给球加摩擦力,每帧让球速乘以0.99左右的系数,球会逐渐减速
- 可以调整
BALL_SPEED和球拍速度叠加的系数(代码里是0.8),匹配想要的击球手感 - 没有图片素材的话,可以直接用
pygame.draw.circle()画圆形代替加载的图片,方便调试
内容的提问来源于stack exchange,提问作者AdamIya
相关产品推荐
相关产品推荐

