【问题标题】:How to stop player sprite passing through blocks with collision detection?如何通过碰撞检测阻止玩家精灵穿过块?
【发布时间】:2019-09-01 00:13:55
【问题描述】:

我正试图阻止玩家穿过方块。我希望他们能够降落在积木上,但如果在积木的另一侧发生碰撞,它们会弹开

我之前尝试过改变玩家在击中方块时重置的距离

运行每一帧以检查是否有碰撞:

hits = pygame.sprite.spritecollide(player, walls, False)
if hits:
    player.checkCollisionWall(hits)

播放器类

import pygame, time, Settings
from pygame.locals import *
vec = pygame.math.Vector2

pygame.init()

class player(pygame.sprite.Sprite):
    ACCEL = 0.5 # Acceleration
    GFRICTION = vec(-0.2, 0) # Ground Friction
    AFRICTION = vec(-0.2, 0) # Air Friction
    GRAVITY = 0.8 # must be greater than 0.6
    JUMP_HEIGHT = 10
    START_X = 25 
    START_Y = 600
    WIDTH = 10
    HEIGHT = 10
    START_POS = vec(START_X + WIDTH/2, START_Y + HEIGHT) # point at bottom middle
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.Pos = self.START_POS
        self.image = pygame.surface.Surface((self.WIDTH, self.HEIGHT))
        self.image.fill((0, 255, 0))
        self.rect = self.image.get_rect()
        self.rect.center = (self.Pos.x, self.Pos.y)
        self.vel = vec(0,0) # set velocity as a vector
        self.acc = vec(0,0) # set acceleration as a vector
        self.inJump = False
        self.tryingToJump = False
    def update(self):
        self.tryingToJump = False
        self.acc = vec(0, self.GRAVITY)
        self.move()
    def draw(self):
        # draw the rectangle
        self.Pos += self.vel + 0.5 *self.acc
        self.rect.center = self.Pos
    def move(self):
        # identify which keys are pressed
        pressed_keys = pygame.key.get_pressed()
        if pressed_keys[K_LEFT]:
            self.changeX("left")
        elif pressed_keys[K_RIGHT]:
            self.changeX("right")
        if pressed_keys[K_UP]:
            self.jump()
        # check player is on screen and place player where it should be if neccessary
        if self.Pos.y > Settings.WINDOW_HEIGHT:
            self.Pos.x = self.START_POS.x
            self.Pos.y = self.START_POS.y
        if self.Pos.x < 0:
            self.vel.x = 2
        if self.Pos.x > Settings.WINDOW_WIDTH:
            self.vel.x = -2
        # apply friction 
        if self.inJump: #in the air
            self.acc.x += self.vel.x * self.AFRICTION.x
        else: #touching the ground
            self.acc.x += self.vel.x * self.GFRICTION.x
        # move the player
        self.vel += self.acc
    def changeX(self, direction):
        # move left or right
        if direction == "right":
            self.acc.x = self.ACCEL
        elif direction == "left":
            self.acc.x = -self.ACCEL
    def jump(self):
        # jump only if standing on a platform
        if self.inJump == False:
            self.tryingToJump = True
            self.inJump = True
            self.vel.y -= self.JUMP_HEIGHT
    def checkCollisionWall(self, hits):
        self.lowestWall = self.highestWall = hits[0]
        for i in hits:
            if i.rect.bottom > self.lowestWall.rect.bottom:
                self.lowestWall = i # find the lowest wall that the player is touching
            if i.rect.top < self.highestWall.rect.top:
                self.highestWall = i # find the highest wall that the player is touching
        if self.vel.y > 0: # check if a block is below
            print("below")
            self.rect.bottom = self.lowestWall.rect.top
            self.acc.y = self.vel.y = 0 # set acceleration and velocity to 0 on the y axis
            self.inJump = False
        if self.vel.y < 0: # check if a block is above
            if not self.tryingToJump: # if the block isn't trying to jump (I have this in otherwise player doesn't jump)
                print("above")
                self.rect.top = self.highestWall.rect.bottom
                self.acc.y = self.vel.y = 0 # set acceleration and velocity to 0 on the y axis
        if self.highestWall.rect.top < self.lowestWall.rect.top and self.rect.bottom == self.lowestWall.rect.top: # I have this line in too make sure that the player does not snap to the side of the block it is in when it moves side to side
            if self.vel.x > 0:
                print("right")
                self.rect.right = self.highestWall.rect.left
                self.acc.x = self.vel.x = -self.ACCEL # set acceleration and velocity to -0.5 on the x axis
            if self.vel.x < 0:
                print("left")
                self.rect.left = self.highestWall.rect.right
                self.acc.x = self.vel.x = self.ACCEL # set acceleration and velocity to 0.5 on the x axis


当我尝试这个时,玩家会很好地降落在方块上,但是当他们接触到方块的底部时,玩家会捕捉到方块的顶部而不是弹开它。当玩家在移动到块中时跳跃时,玩家会捕捉到更高块的顶部。

【问题讨论】:

  • 这里涉及到多少个对象?按照您描述问题的方式,我假设您希望玩家在向上跳入一个街区时向下弹跳,但玩家正在翘曲到街区的顶部?
  • @AndrewFan 目前,我有大约 100 个 25x25 的积木和一名球员。玩家从一排 25 个街区开始。是的,玩家正在向上翘曲
  • 对于初学者,我强烈建议使用elif 构造。毕竟,我假设这些块中只有一个应该触发。我不知道关卡设计,但这里有很多潜在的边缘案例。你在哪里设置y加速度和self.tryingToJump?我有一种感觉,玩家在向下跳跃后触发了self.vel.y &gt; 0 案例(加速度仍然为正,与速度相同),同时仍然与上方的单个方块发生碰撞。
  • @AndrewFan 我也是这么想的。当然第二个if 应该是elif。第三个处理x 坐标,因此应该保持if。我认为第三个if 也有错字:self.highestWall.rect.top &lt; self.lowestWall.rect.top 应该是self.rect.top &lt; self.lowestWall.rect.top
  • @Valentino 我也有if self.highestWall.rect.top &lt; self.lowestWall.rect.top 确保玩家在左右移动时不会捕捉到它所在的块的一侧

标签: python pygame collision


【解决方案1】:

根据我(不是那么广泛)的经验,如果没有针对每个维度单独测试玩家运动,则可能会出现此类问题。

我建议你:

  1. 单独的 x 和 y 移动和碰撞测试,以便您可以沿 x 移动播放器、测试碰撞并在需要时固定 x 位置,然后对 y 重复该过程。

  2. 编辑checkCollisionWall 方法,使其采用单个块,而不是块列表。如果将 x 和 y 运动分开,玩家最多会碰撞一个方块。所以无需寻找最低的墙壁和最高的墙壁。你的代码会更简单。

在这里,我建议您进行一些修改。 move 已拆分为 move_xmove_ycheckCollisionWall 转换为 checkCollision_xcheckCollision_y
我还编辑了 update 方法:现在它完成了第 1 点中描述的整个过程,所以调用 update 也会检查碰撞。

def update(self):
    self.tryingToJump = False
    self.acc = vec(0, self.GRAVITY)

    self.move_x()
    hits = pygame.sprite.spritecollide(player, walls, False)
    for bhit in hits:
        player.checkCollision_x(bhit)

    self.move_y()
    hits = pygame.sprite.spritecollide(player, walls, False)
    for bhit in hits:
        player.checkCollision_y(bhit)

def move_x(self):
    # identify which keys are pressed
    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_LEFT]:
        self.changeX("left")
    elif pressed_keys[K_RIGHT]:
        self.changeX("right")
    # check player is on screen and place player where it should be if neccessary
    if self.Pos.x < 0:
        self.vel.x = 2
    if self.Pos.x > Settings.WINDOW_WIDTH:
        self.vel.x = -2
    # apply friction 
    if self.inJump: #in the air
        self.acc.x += self.vel.x * self.AFRICTION.x
    else: #touching the ground
        self.acc.x += self.vel.x * self.GFRICTION.x
    # move the player
    self.vel.x += self.acc.x

def move_y(self):
    # identify which keys are pressed
    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_UP]:
        self.jump()
    # check player is on screen and place player where it should be if neccessary
    if self.Pos.y > Settings.WINDOW_HEIGHT:
        self.Pos.x = self.START_POS.x
        self.Pos.y = self.START_POS.y
    # move the player
    self.vel.y += self.acc.y


def checkCollision_x(self, coll_block):
    if self.vel.x > 0:
        print("right")
        self.rect.right = coll_block.rect.left
        self.acc.x = self.vel.x = -self.ACCEL # set acceleration and velocity to -0.5 on the x axis
    elif self.vel.x < 0:
        print("left")
        self.rect.left = coll_block.rect.right
        self.acc.x = self.vel.x = self.ACCEL # set acceleration and velocity to 0.5 on the x axis


def checkCollision_y(self, coll_block):
    if self.vel.y > 0: # check if a block is below
        print("below")
        self.rect.bottom = coll_block.rect.top
        self.acc.y = self.vel.y = 0 # set acceleration and velocity to 0 on the y axis
        self.inJump = False
    elif self.vel.y < 0: # check if a block is above
        if not self.tryingToJump: # if the block isn't trying to jump (I have this in otherwise player doesn't jump)
            print("above")
            self.rect.top = coll_block.rect.bottom
            self.acc.y = self.vel.y = 0 # set acceleration and velocity to 0 on the y axis

这段代码当然没有经过测试,但即使不能解决你的问题,我认为应该让你朝着正确的方向前进。

【讨论】:

  • 当播放器下方有块时,将播放器的底部设置为块的顶部。因此,当玩家站在两个方块(地面由多个 25x25 方块组成)上时,它会同时接触两个方块。当玩家站在另一个块上时触摸块的侧面时也会发生这种情况
  • 我明白了。那么你图片中较长的矩形是由更多的块组成的吗?无论如何,我认为我的解决方案无论如何都应该有效,因为这些块具有相同的 y。
猜你喜欢
  • 1970-01-01
  • 2014-01-16
  • 2019-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多