如何使碰撞与平台中的平台侧面一起工作?

时间:2020-08-28 15:28:36

标签: python pygame collision-detection collision detection

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

1 个答案:

答案 0 :(得分:3)

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

我打算将一些代码移到类Screen上,然后使用该类来创建GameScreenStartScreenGameOverScreen等。