You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pygame开发空气曲棍球游戏如何实现球体碰撞后正确方向反弹

Pygame空气曲棍球正确反弹实现

原有代码问题

  • 变量名错误:定义球的x/y轴速度为chx/chy,位置更新时错误调用了不存在的变量ch,球本身无法正常移动
  • 碰撞逻辑缺失:仅打印了碰撞触发提示,没有根据碰撞位置计算反弹方向,之前手动判断上下位置加y轴速度的方案精度极低,无法匹配真实碰撞角度
  • 缺少碰撞后位置修正:碰撞触发后没有把球推出球拍碰撞范围,会出现卡模、反复触发碰撞的问题
  • 碰撞检测不全:仅实现了玩家1和球的碰撞判断,漏掉了玩家2的碰撞逻辑

反弹实现原理

空气曲棍球的球拍和球都可以用圆形做碰撞判定,碰撞后的反弹方向沿两圆心的连线方向(从球拍圆心指向球圆心),不需要手动判断上下位置,直接通过向量计算就能得到准确的反弹角度,还可以叠加球拍自身的移动速度,还原真实击球的手感。
计算步骤:

  1. 碰撞触发时,先计算球和球拍的x、y轴坐标差值,得到碰撞方向向量
  2. 对方向向量做归一化处理,保证不同碰撞位置下球的基础速度大小一致
  3. 将归一化后的向量乘以设定的基础球速,得到球的x/y轴反弹速度
  4. 叠加球拍当前的移动速度,让球拍移动时击球能给球带来额外的速度加成
  5. 修正球的坐标,将球推到球拍碰撞判定范围外,避免卡模

修正后可运行代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:09:28