如何在 Pygame 中正确实施碰撞响应?

How do I properly implement collision response in Pygame?

我正在尝试在 Pygame 中编写一个简单的自上而下的地牢爬虫程序,但我在设计碰撞响应时遇到了障碍。到目前为止,我已经编写了玩家移动、碰撞检测(仅知道玩家何时撞墙)和玩家到鼠标的旋转,后者被禁用以简化此解决方案。

我尝试了移动角色、检查碰撞、根据需要更改角色位置然后将所有元素绘制到屏幕的常规方法。

import pygame
from pygame.locals import *
import os
import sys
import math

os.environ["SDL_VIDEO_CENTERED"] = "1"
pygame.init()
pygame.font.init()
clock = pygame.time.Clock()
myfont = pygame.font.SysFont('sourcecodeproblack', 12)

BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
WALLS = [220, 40, 30]

SIZE = [240, 240]
DSIZE = [480, 480]
TSIZE = [720, 720]

dub = False
trip = False

def normal():
    global screen
    screen = pygame.display.set_mode(SIZE)
    pygame.display.set_caption("METAL PACKET")

def full():
    global screen
    screen = pygame.display.set_mode((SIZE), pygame.FULLSCREEN)
    pygame.display.set_caption("METAL PACKET: Fullscreen Edition")

def doubled():
    global screen
    global window
    global res
    global invscale
    invscale = 1/2
    res = DSIZE
    window = pygame.display.set_mode(DSIZE)
    screen = pygame.Surface(SIZE)
    pygame.display.set_caption("METAL PACKET: Double Boogaloo")

def doubledfull():
    global screen
    global window
    global res
    global invscale
    invscale = 1/2
    res = DSIZE
    window = pygame.display.set_mode((DSIZE), pygame.FULLSCREEN)
    screen = pygame.Surface(SIZE)
    pygame.display.set_caption("METAL PACKET: Fullscreen Boogaloo")

def tripled ():
    global screen
    global window
    global res
    global invscale
    invscale = 1/3
    res = TSIZE
    window = pygame.display.set_mode(TSIZE)
    screen = pygame.Surface(SIZE)
    pygame.display.set_caption("METAL PACKET: Trifecta of Resolution")

def tripledfull ():
    global screen
    global window
    global res
    global invscale
    invscale = 1/3
    res = TSIZE
    window = pygame.display.set_mode((TSIZE), pygame.FULLSCREEN)
    screen = pygame.Surface(SIZE)
    pygame.display.set_caption("METAL PACKET: Fullscreen Trifecta Edition")

print("")
print("1: 240 x 240")
print("2: 480 x 480")
print("3: 720 x 720")
print("4: 240 x 240 Fullscreen")
print("5: 480 x 480 Fullscreen")
print("6: 720 x 720 Fullscreen")
print("")
res = input("Choose a video mode. ")
if res == ("1"):
    normal()
if res == ("2"):
    doubled()
    dub = True
if res == ("3"):
    tripled()
    trip = True
if res == ("4"):
    full()
if res == ("5"):
    doubledfull()
    dub = True
if res == ("6"):
    tripledfull()
    trip = True

class Walls(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.currentsprite = ()
    def draw(self):
        self.image = pygame.image.load(self.currentsprite)
        screen.blit(self.image, [0, 0])

room1 = Walls()
room1.currentsprite = ("room1.png")

collidelist = pygame.sprite.Group()

class WallColliders(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self, collidelist)
        self.rectval = []
    def collide(self):
        self.rect = (self.rectval)

topwall = WallColliders()
topwall.rectval = [0, 0, 240, 20]
bottomwall = WallColliders()
bottomwall.rectval = [0, 160, 240, 20]
leftwall = WallColliders()
leftwall.rectval = [0, 0, 20, 180]

class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.collide = 0
        self.x = 0
        self.y = 0
        self.speed = 0
        self.currentsprite = ("pac.png")
        self.image = pygame.image.load(self.currentsprite)
        self.rect = self.image.get_rect()
    def collidedetect(self):
        if pygame.sprite.spritecollideany(player, collidelist):
            self.collide = 1
        else:
            self.collide = 0
    def move(self):
        key = pygame.key.get_pressed()
        if key[K_LEFT] or key[ord("a")]:
                self.x -= self.speed
                if self.collide == 1:
                    self.x += self.speed
                    self.collide = 0
        if key[K_RIGHT] or key[ord("d")]:
                self.x += self.speed
                if self.collide == 1:
                    self.x -= self.speed
                    self.collide = 0
        if key[K_UP] or key[ord("w")]:
                self.y -= self.speed
                if self.collide == 1:
                    self.y += self.speed
                    self.collide = 0
        if key[K_DOWN] or key[ord("s")]:
                self.y += self.speed
                if self.collide == 1:
                    self.y -= self.speed
                    self.collide = 0
        if self.collide == 1:
            print("collide")
    def rotate(self):
        pos = pygame.mouse.get_pos()
        if dub == True or trip == True:
            pos = pos[0] * invscale, pos[1] * invscale
            mouse_x, mouse_y = pos
        mouse_x, mouse_y = pos
        rel_x, rel_y = mouse_x - self.x, mouse_y - self.y
        self.angle = (180 / math.pi) * -math.atan2(rel_y, rel_x) 
        self.rotimage = pygame.transform.rotozoom(self.image, self.angle, 1)
        self.rect = self.rotimage.get_rect(center=(self.rect.center))
    def monitor(self):
        textsurface = myfont.render(str(self.angle), False, (255, 255, 255))
        screen.blit(textsurface,(50,210))
    def draw(self):
        #screen.blit(self.rotimage, [self.x, self.y])
        screen.blit(self.image, [self.x, self.y])
        self.rect = pygame.Rect(self.x, self.y, 20, 20)

player = Player()
player.x = 29
player.y = 89
player.speed = 3

done = False

while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            print("")
            print("See you next time.")
            pygame.quit()
            sys.exit(0)
            done = True

    screen.fill(BLACK)

    room1.draw()

    for x in collidelist:
        x.collide()

    player.collidedetect()
    player.move()
    #player.rotate()
    #player.monitor()
    player.draw()

    if dub == True or trip == True:        
        pygame.transform.scale(screen, res, window)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

出于某种原因,这会将玩家粘在墙上,除非他们按对角线移动而不是将玩家移出墙并重置碰撞。如果有人能阅读我的代码并提出任何改进、优化或解决方案,我将不胜感激。任何示例代码都会很棒。谢谢

不要过于复杂。当玩家移动时,计算新位置并更新 Player 对象的 .rect 属性:

class Player(pygame.sprite.Sprite):

    # [...]

    def move(self):
        key = pygame.key.get_pressed()
        if key[K_LEFT] or key[ord("a")]:
                self.x -= self.speed
        if key[K_RIGHT] or key[ord("d")]:
                self.x += self.speed
        if key[K_UP] or key[ord("w")]:
                self.y -= self.speed
        if key[K_DOWN] or key[ord("s")]:
                self.y += self.speed
        self.updaterect()

    def updaterect(self):
        self.rect = pygame.Rect(self.x, self.y, 20, 20)

在主循环中:

  • 备份玩家位置
  • 移动玩家
  • 检查碰撞
  • 如果发生碰撞,则恢复玩家的位置
while not done:

    # [...]

    pos = (player.x, player.y)
    player.move()
    player.collidedetect()
    if player.collide:
        player.x, player.y = pos
        player.updaterect()
        player.collide = False

    player.draw()

    # [...]

当然这也可以用方法来完成:

class Player(pygame.sprite.Sprite):

    # [...]

    def moveandcollide(self):
        pos = (self.x, self.y)
        self.move()
        self.collidedetect()
        if player.collide:
            self.x, self.y = pos
            self.updaterect()
            self.collide = False
while not done:

    # [...]

    player.moveandcollide()