【问题标题】:How can I get my collisions to work with the sides of a platform in my platform?如何让我的碰撞与我平台中的平台侧面一起工作?
【发布时间】:2020-08-28 15:28:36
【问题描述】:

我目前正在使用 python pygame 创建一个平台游戏,但遇到了碰撞问题。我已经获得了底部和顶部碰撞来处理我的角色精灵,但到目前为止,我的角色不会停止或从平台的两侧反弹。在进行碰撞时,我使用的是 sprite.spritecollide() 方法,如果有人可以提供帮助,我想以同样的方式进行操作。我已经正确完成了碰撞检查,但我处理碰撞的代码似乎无法正确完成。我在游戏更新功能的 main.py 中进行碰撞检测的代码如下:

import pygame
import random
from settings import *
from sprites import *
from camera import *
from os import path
class Game:
     def __init__(self):
          pygame.init() # initialises pygame
          pygame.mixer.init()
          self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) # sets the width and height of the pygame window
          pygame.display.set_caption(TITLE)
          self.clock = pygame.time.Clock()
          self.running = True
          self.font_name = pygame.font.match_font(FONT_NAME)
          self.load_data()

     def load_data(self):
         pass

     def new(self):
         self.all_sprites = pygame.sprite.Group()
         self.platforms = pygame.sprite.Group()
         self.player = Player(self)
         self.all_sprites.add(self.player)
         for plat in PLATFORM_LIST:
             p = Platform(*plat)
             self.all_sprites.add(p)
             self.platforms.add(p)
         self.camera = Camera(WIDTH, HEIGHT) # creates the camera with WIDTH and HEIGHT of the screen
         self.run()

     def run(self): # Game Loop - runs the game
         self.playing = True
         while self.playing:
             self.clock.tick(FPS)
             self.events()
             self.update()
             self.draw()

     def update(self): # Game loop - update
         self.all_sprites.update()
         # collision with top of platform
         if self.player.vel.y > 0:
              hits = pygame.sprite.spritecollide(self.player, self.platforms, False) # returns a list of platform sprites that hit the player
              if hits:
                   self.player.pos.y = hits[0].rect.top
                   self.player.vel.y = 0
         # collision with the bottom of a platform
         if self.player.vel.y < 0:
              hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
              if hits:
                   self.player.top = hits[0].rect.bottom
                   self.player.vel.y = -self.player.vel.y
         # collision with the right side of a platform (moving left), here is the code for the right side of the platform 
         if self.player.acc.x < 0:
              hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
              if hits:
                   self.player.left = hits[0].rect.right
                   self.player.acc.x = 0

         # screen moves with player
         self.camera.update(self.player) # is the camera that tracks players movement

     def events(self): # Game loop - events
         for event in pygame.event.get():
             if event.type == pygame.QUIT:
                  if self.playing:
                      self.playing = False
                  self.running = False
             if event.type == pygame.KEYDOWN:
                  if event.key == pygame.K_SPACE:
                    self.player.jump()

     def draw(self): # Game loop - draw
         self.screen.fill(RED)
         #self.all_sprites.draw(self.screen)
         for sprite in self.all_sprites:
              self.screen.blit(sprite.image, self.camera.apply(sprite)) # loops through the all_sprites group and blit's each sprite onto the screen
         pygame.display.flip()

     def start_screen(self):
         pass

     def game_over_screen(self):
         pass

     def wait_for_key(self):
         pass

     def draw_text(self,text, size, colour, x, y):
         pass

g = Game()
g.start_screen()
while g.running:
     g.new()
     g.game_over_screen()

pygame.quit()

到目前为止,我只尝试对平台的右侧进行碰撞,一旦我完成了一侧,我就可以为另一侧复制。 附言如果您需要更多我的代码,如果需要,我会将其添加到问题中。

编辑

sprites.py

# will hold the sprite classes
import pygame
from settings import *
import random
vec = pygame.math.Vector2

class Player(pygame.sprite.Sprite):
    def __init__(self, game):
        pygame.sprite.Sprite.__init__(self)
        self.game = game
        self.image = pygame.Surface((30, 40))
        self.image.fill(BLUE)
        self.rect = self.image.get_rect()
        self.rect.center = (WIDTH / 2, HEIGHT / 2)
        self.pos = vec(WIDTH / 2, HEIGHT / 2)
        self.vel = vec(0, 0)
        self.acc = vec(0, 0)

    def jump(self):
        # jump only if on a platform
        self.rect.x += 1
        hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
        self.rect.x -= 1
        if hits:
            self.vel.y = -20

    def update(self):
        self.acc = vec(0, PLAYER_GRAV)
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.acc.x = -PLAYER_ACC
        if keys[pygame.K_RIGHT]:
            self.acc.x = PLAYER_ACC

        # apply friction
        self.acc.x += self.vel.x * PLAYER_FRICTION

        # equations of motion
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc

        # stop from running of the left side of the screen
        if self.pos.x < 0:
            self.pos.x = 0
        self.rect.midbottom = self.pos

class Platform(pygame.sprite.Sprite):
    def __init__(self, x, y, width, height):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((width, height))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

camera.py

import pygame
from settings import *
# A camera that keeps track of an offset that will be, how far we want to draw the screen which will include all objects on the screen. We are just shifting the drawing of our screen according to the offset. Camera needs to do two things, apply the offset and then update the movement of where the player is on the screen.
class Camera:
    def __init__(self, width, height): # we will need to tell the camera how wide and high we want it to be
        self.camera = pygame.Rect(0, 0, width, height) # is the rectangle we set to keep track of the screen/be the camera
        self.width = width
        self.height = height

    def apply(self, entity): # method to apply the offset to the screen, by shifting the screen according to the movement of the entity within the camera screen
        return entity.rect.move(self.camera.topleft)

    def update(self, target): # method to update where the player/target has moved to, updates are done according to last known position of the target
        # as the target moves the camera moves in the opposite direction of the target and stays within the center of the screen
        x = -target.rect.x + int(WIDTH/2)  # left to right
        y = -target.rect.y + int(HEIGHT/2) # up and down

        # limit scrolling to map size, keeps the 'camera' from going over the edges
        x = min(0, x) # left
        y = min(0, y) # top
        y = max(-(self.height - HEIGHT), y) # bottom
        self.camera = pygame.Rect(x, y, self.width, self.height) # adjusts the camera's rectangle with the new x and y

settings.py

# Game options/settings
TITLE = 'Platformer'
WIDTH = 900
HEIGHT = 500
FPS = 60
FONT_NAME = 'arial'
HS_FILE = 'highscore.txt'
SPRITESHEET = 'spritesheet_jumper.png'

# Game colours
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)

# Starting Platforms:
PLATFORM_LIST = [(0, HEIGHT - 50,  WIDTH, 50), (WIDTH / 2, HEIGHT * 1 / 2, 200, 30), (WIDTH + 150, HEIGHT - 50, WIDTH, 50), (WIDTH / 2, HEIGHT * 4 / 5, 200, 30)]
# player properties
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8

【问题讨论】:

  • 您在所有if/else 中运行相同的spritecollide() - 所以您可以在if/else 之前运行它
  • collision 的函数不会提供您在哪一侧发生碰撞的信息 - 所以最好先更新y,检查碰撞,然后使用if/else with vel.y, next udate x, check collisons again and use if/否则vel.x。您应该在Program Arcade Games With Python And Pygame页面上的platform examples 之一中看到此方法

标签: python pygame collision-detection collision detection


【解决方案1】:

我无法测试您的代码,但通常问题是spritecollide 不会通知您是否在xy 或两者上发生碰撞。

当您同时移动xy 并检查碰撞时,您不知道您是否在xy 上发生碰撞或两者都发生碰撞。如果您仅在y 上发生碰撞并且您将检查vel.x 并移动播放器,那么您会得到错误的结果。如果你只在x 上发生碰撞,你会检查vel.y 并移动玩家,那么你也会得到错误的结果。

你必须分开做:

  • 仅先移动x,检查碰撞并仅检查vel.x
  • 仅下一步移动y,再次检查碰撞并仅检查vel.y

类似这样的:

 def update(self):
     #self.all_sprites.update()

     # collision with top and bottom of platform

     # update only y

     self.player.pos.y += self.player.vel.y + 0.5 * self.player.acc.y
     
     hits = pygame.sprite.spritecollide(self.player, self.platforms, False)
     
     if hits:
         if self.player.vel.y > 0:
             self.player.pos.y = hits[0].rect.top
             self.player.vel.y = 0
    
         elif self.player.vel.y < 0:
             self.player.top = hits[0].rect.bottom
             self.player.vel.y = -self.player.vel.y

     # collision with left and right of platform

     # update only x

     self.player.pos.x += self.player.vel.x + 0.5 * self.player.acc.x
     
     hits = pygame.sprite.spritecollide(self.player, self.platforms, False)

     if hits:
         if self.player.acc.x < 0:
             self.player.left = hits[0].rect.right
             self.player.acc.x = 0
             
         elif self.player.acc.x > 0:
             self.player.right = hits[0].rect.left
             self.player.acc.x = 0

您应该在 Program Arcade Games With Python And Pygame 页上的platform examples 中看到工作示例


编辑:

完整代码

ma​​in.py

#import random
#from os import path
import pygame

from settings import *
from sprites import *
from camera import *

class Game:
    
    def __init__(self):
        # initialises pygame
        pygame.init()
        #pygame.mixer.init()  # `pygame.init()` should aready runs `pygame.mixer.init()`
          
        # sets the width and height of the pygame window
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        pygame.display.set_caption(TITLE)

        self.font_name = pygame.font.match_font(FONT_NAME)
        self.load_data()
        
        # main loop elements
        self.clock = pygame.time.Clock()
        self.running = True

    def load_data(self):
        pass

    def new(self):
        """Run game"""
        
        self.reset()
        self.run()
        
    def reset(self):
        """Reset data"""
        
        self.all_sprites = pygame.sprite.Group()
        self.platforms = pygame.sprite.Group()
        self.player = Player(self)
        self.all_sprites.add(self.player)
         
        for plat in PLATFORM_LIST:
            p = Platform(*plat)
            self.all_sprites.add(p)
            self.platforms.add(p)

        # creates the camera with WIDTH and HEIGHT of the screen
        self.camera = Camera(WIDTH, HEIGHT)

    def run(self):
        """Game Loop - runs the game"""
        
        self.playing = True
        while self.playing:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()

    def update(self):
        """Game loop - update"""
        
        self.all_sprites.update()
        # screen moves with player
        self.camera.update(self.player) # is the camera that tracks players movement

    def events(self):
        """Game loop - events"""
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                if self.playing:
                    self.playing = False
                    self.running = False
            if event.type == pygame.KEYDOWN:
                # reset game but not exit 
                if event.key == pygame.K_ESCAPE:
                    if self.playing:
                        self.playing = False
                    
            # send event(s) to sprite(s) 
            self.player.events(event)
             
    def draw(self):
        """Game loop - draw"""

        self.screen.fill(RED)
        
        # loops through the all_sprites group and blit's each sprite onto the screen
        for sprite in self.all_sprites:
            sprite.draw(self.screen, self.camera)
        
        pygame.display.flip()

    def start_screen(self):
        pass

    def game_over_screen(self):
        pass

    def wait_for_key(self):
        pass

    def draw_text(self,text, size, colour, x, y):
        pass

# --- main ---

g = Game()

g.start_screen()

while g.running:
     g.new()
     g.game_over_screen()

#g.exit_screen()

pygame.quit()

sprites.py

# will hold the sprite classes
import random
import pygame
from settings import *

vec = pygame.math.Vector2


class BaseSprite(pygame.sprite.Sprite):
    """Base class with functions for all sprites"""

    def draw(self, screen, camera):
        screen.blit(self.image, camera.apply(self))
    
    
class Player(BaseSprite):
    
    def __init__(self, game):
        #pygame.sprite.Sprite.__init__(self)
        super().__init__()
        
        self.game = game
        
        self.image = pygame.Surface((30, 40))
        self.image.fill(BLUE)

        self.pos = vec(WIDTH / 2, HEIGHT / 2)
        self.vel = vec(0, 0)
        self.acc = vec(0, 0)

        self.rect = self.image.get_rect()
        self.rect.center = self.pos

        self.on_ground = True
        
    def jump(self):
        if self.on_ground:
            self.vel.y = -20
            self.on_ground = False

    def events(self, event):
        if event.type == pygame.KEYDOWN:
           if event.key == pygame.K_SPACE:
              self.jump()
        
    def update(self):

        self.acc = vec(0, PLAYER_GRAV)
        keys = pygame.key.get_pressed()

        if keys[pygame.K_LEFT]:
            self.acc.x = -PLAYER_ACC
        if keys[pygame.K_RIGHT]:
            self.acc.x = PLAYER_ACC

        # apply friction
        self.acc.x += self.vel.x * PLAYER_FRICTION

        # equations of motion
        self.vel += self.acc

        # --- horizontal collision ---
        
        self.pos.x += self.vel.x + 0.5 * self.acc.x
        self.rect.centerx = self.pos.x
        
        # stop from running of the left side of the screen
        if self.rect.left < 0:
            self.rect.left = 0
            self.pos.x = self.rect.centerx
            
        hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
        
        if hits:
            if self.vel.x > 0:
                self.rect.right = hits[0].rect.left
                self.pos.x = self.rect.centerx
                self.vel.x = 0
            elif self.vel.x < 0:
                self.rect.left = hits[0].rect.right
                self.pos.x = self.rect.centerx
                self.vel.x = 0

        # --- vertical collision ---

        self.pos.y += self.vel.y + 0.5 * self.acc.y
        self.rect.centery = self.pos.y

        # game over when left screen 
        if self.rect.top > HEIGHT:
            self.game.playing = False

        hits = pygame.sprite.spritecollide(self, self.game.platforms, False)
        
        if hits:
            if self.vel.y > 0:
                self.rect.bottom = hits[0].rect.top
                self.pos.y = self.rect.centery
                self.vel.y = 0
                self.on_ground = True
            elif self.vel.y < 0:
                self.rect.top = hits[0].rect.bottom
                self.pos.y = self.rect.centery
                self.vel.y = 0


class Platform(BaseSprite):
    
    def __init__(self, x, y, width, height, color):
        #pygame.sprite.Sprite.__init__(self)
        super().__init__()
        
        self.image = pygame.Surface((width, height))
        self.image.fill(color)
        
        #self.rect = self.image.get_rect()
        #self.rect.x = x
        #self.rect.y = y
        
        # shorter
        self.rect = self.image.get_rect(x=x, y=y)

camera.py

没有变化

settings.py

我添加了几个平台

# Game options/settings
TITLE = 'Platformer'
WIDTH = 900
HEIGHT = 500
FPS = 60
FONT_NAME = 'arial'
HS_FILE = 'highscore.txt'
SPRITESHEET = 'spritesheet_jumper.png'

# Game colours
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)

# Starting Platforms:
PLATFORM_LIST = [
    # grounds
    (0, HEIGHT - 50,  WIDTH, 50, GREEN),
    (WIDTH + 150, HEIGHT - 50, WIDTH, 50, GREEN),
    # platforms
    (WIDTH / 2, HEIGHT * 1 / 2, 200, 30, YELLOW),
    (WIDTH / 2, HEIGHT * 4 / 5, 200, 30, YELLOW),
    # walls
    (WIDTH - 30, HEIGHT - 250, 30, 200, WHITE),
    (WIDTH + 150, HEIGHT - 250, 30, 200, WHITE),
]

# player properties
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8

我打算将一些代码移动到类 Screen,然后我会使用这个类来创建 GameScreenStartScreenGameOverScreen 等。

【讨论】:

  • 感谢您的帮助,谢谢,所以您的意思是我需要分别更新 x 和 y?...我在我的播放器类的更新方法中使用向量它仍然可以吗像你说的那样工作?...我对pygame还是很陌生,所以请原谅我缺乏知识(我将在我的问题中包含我的精灵模块)
  • 是的,首先你必须只在 x 方向移动并检查碰撞 - 如果你被击中然后你会知道它是 x 方向的碰撞,而不是 y 方向。接下来,您沿 y 方向移动并再次检查碰撞 - 如果您被击中,那么您将知道它是 y 方向的碰撞,而不是 x 方向的碰撞。当您同时在两个方向上移动并检查碰撞时,您无法知道它是仅在 x 方向上发生碰撞,还是仅在 y 方向上或两个方向上发生碰撞。
  • 我已经给了你函数——你可以直接在update()中更新位置——并使用真实值self.vel.x + 0.5 * self.acc——不是假值+1。并且不要将其移回 - 不要使用-1
  • 我更改了代码,现在我直接在update() 更改了玩家位置,没有update_in_x()update_in_y()
  • 我得到了您的代码并运行它并进行了更改 - 现在作为答案,您拥有包含我所有更改的完整运行代码 - 不仅有冲突,还有其他一些更改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-22
  • 2021-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多