我怎样才能让我的碰撞在我的平台中与平台的侧面一起工作?

How can I get my collisions to work with the sides of a platform in my platform?

我目前正在使用 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()

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

编辑

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

我无法测试您的代码,但通常问题是 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

您应该在 platform examples on page Program Arcade Games With Python And Pygame

中看到工作示例

编辑:

完整代码

main.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

我想将一些代码移动到 class Screen 然后我会使用这个 class 来创建 GameScreen, StartScreen, GameOverScreen,等等