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

如何在Pygame中实现精灵碰撞阻止穿透?

Pygame 物体碰撞穿透问题解决

我用Pygame开发游戏,目前遇到了物体碰撞穿透的问题。尝试过查找相关解决方案,但这些方案要么与我已实现的移动系统不兼容,要么完全无法理解。以下是我的代码实现:

import pygame, sys
from pygame.math import Vector2

pygame.init()
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()

#Environment Variables
gravity = -1
jumpForce = 20
moveSpeed = 10

#Game Objects
playerPos = Vector2(230,230)
playerVelo = Vector2(0,0)
player = pygame.Rect(playerPos.x,playerPos.y, 20, 20) 

boxPos = Vector2(350,480)
box = pygame.Rect(boxPos.x, boxPos.y, 20,20)

def Clamp(var, minClamp, maxClamp):
    if minClamp > maxClamp:
        raise Exception("minClamp must be less than maxClamp")
    if var < minClamp:
        var = minClamp
    if var > maxClamp:
        var = maxClamp
    return var

def Draw():
    global player,box
    screen.fill((255,255,25))

    player = pygame.Rect(playerPos.x,playerPos.y, 20, 20)
    pygame.draw.rect(screen,(0,100,255),player)

    box = pygame.Rect(boxPos.x, boxPos.y, 20,20)
    pygame.draw.rect(screen,(10,200,20),box)

def PlayerVelocity():
    global playerPos,playerVelo,player,box,boxPos

    if player.colliderect(box):
        playerPos = Vector2(boxPos.x,boxPos.y-20)
        print("balls")

    if playerPos.y < 499:
        playerVelo.y += gravity
    #if not pygame.Rect(playerPos.x+playerVelo.x,playerPos.y+playerVelo.y,20,20).colliderect(box):
    playerPos -= playerVelo

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE or event.key == pygame.K_UP:
                
                playerVelo.y = jumpForce
                print(playerVelo)

    keys = pygame.key.get_pressed()

    if keys[pygame.K_a] or keys[pygame.K_LEFT]:
        playerVelo.x = 1*moveSpeed
       
    elif keys[pygame.K_d] or keys[pygame.K_RIGHT]:
        playerVelo.x = -1*moveSpeed
        
    else:
        playerVelo.x = 0

    #Draw things to the screen
    Draw()
    PlayerVelocity()
    playerPos.x = Clamp(playerPos.x, 0, 480)
    playerPos.y = Clamp(playerPos.y,0,480)

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

问题分析

你的碰撞逻辑存在几个关键问题,直接导致了穿透:

  • 先移动玩家再检测碰撞,此时玩家已经部分/完全穿入物体,强行重置位置的方式不仅生硬,还会因为速度过快导致穿透
  • 碰撞后没有重置对应方向的速度,比如碰到箱子后垂直速度仍在累加,会让玩家卡在物体上或者继续穿透
  • 没有分方向处理碰撞,无法区分水平/垂直方向的碰撞响应

修改后的代码

import pygame, sys
from pygame.math import Vector2

pygame.init()
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()

# 环境变量
gravity = -1
jumpForce = 20
moveSpeed = 10

# 游戏对象
playerPos = Vector2(230,230)
playerVelo = Vector2(0,0)
player = pygame.Rect(playerPos.x,playerPos.y, 20, 20) 

boxPos = Vector2(350,480)
box = pygame.Rect(boxPos.x, boxPos.y, 20,20)

def Clamp(var, minClamp, maxClamp):
    if minClamp > maxClamp:
        raise Exception("minClamp必须小于maxClamp")
    if var < minClamp:
        var = minClamp
    if var > maxClamp:
        var = maxClamp
    return var

def Draw():
    global player,box
    screen.fill((255,255,25))

    player = pygame.Rect(playerPos.x,playerPos.y, 20, 20)
    pygame.draw.rect(screen,(0,100,255),player)

    box = pygame.Rect(boxPos.x, boxPos.y, 20,20)
    pygame.draw.rect(screen,(10,200,20),box)

def PlayerVelocity():
    global playerPos,playerVelo,player,box,boxPos

    # 先更新垂直方向重力
    if playerPos.y < 480:  # 地面是y=480(因为玩家高20,y=480时底部刚好贴地面)
        playerVelo.y += gravity

    # 预测水平移动后的位置
    predicted_x = playerPos.x - playerVelo.x
    predicted_player_x = pygame.Rect(predicted_x, playerPos.y, 20, 20)
    
    # 检测水平碰撞
    if not predicted_player_x.colliderect(box):
        playerPos.x = predicted_x
    else:
        # 碰到箱子后水平速度归零
        playerVelo.x = 0

    # 预测垂直移动后的位置
    predicted_y = playerPos.y - playerVelo.y
    predicted_player_y = pygame.Rect(playerPos.x, predicted_y, 20, 20)
    
    # 检测垂直碰撞
    if not predicted_player_y.colliderect(box) and predicted_y <= 480:
        playerPos.y = predicted_y
    else:
        # 碰到箱子或地面后垂直速度归零,防止继续穿透
        playerVelo.y = 0

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE or event.key == pygame.K_UP:
                # 只有在玩家接触地面或箱子时才能跳跃
                if playerPos.y >= 480 or player.colliderect(box):
                    playerVelo.y = jumpForce

    keys = pygame.key.get_pressed()

    if keys[pygame.K_a] or keys[pygame.K_LEFT]:
        playerVelo.x = 1*moveSpeed
    elif keys[pygame.K_d] or keys[pygame.K_RIGHT]:
        playerVelo.x = -1*moveSpeed
    else:
        playerVelo.x = 0

    # 先处理物理逻辑,再绘制
    PlayerVelocity()
    Draw()
    
    playerPos.x = Clamp(playerPos.x, 0, 480)
    playerPos.y = Clamp(playerPos.y,0,480)

    pygame.display.update()
    clock.tick(60)  # 提高帧率让移动更流畅

关键改动说明

  • 预测碰撞:移动前先计算预期位置,检测是否会碰撞,只有不碰撞才执行移动,从根源避免穿透
  • 分方向处理:把水平和垂直方向的碰撞分开检测,分别处理速度和位置,逻辑更清晰
  • 碰撞后速度重置:碰到物体后对应方向的速度归零,防止玩家继续向碰撞方向移动
  • 跳跃优化:只有当玩家接触地面或箱子时才能跳跃,避免空中无限跳
  • 调整帧率:从30帧提高到60帧,让移动和碰撞检测更流畅

内容的提问来源于stack exchange,提问作者Liam Berube

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 17:05:21