碰撞检测后 Pygame 1.9.2a0 中的列表删除错误

List removal error in Pygame 1.9.2a0 after collision detection

我正在尝试使用 Pygame 1.9.2a0 在 Python 3.2.5 中进行突破克隆。 代码比真正需要的要复杂得多,但这是我在 python/pygame 中的第一个程序,我习惯于将内容拆分成它们自己的 classes/objects.

无论如何 - 手头的问题是当我的球与砖块碰撞时,它检测到碰撞,但它不会将它们从绘图中移除 - 如果它没有从绘图中移除它基本上是' 从列表中删除。

        for brick in bricks_.bricks:
        if brick.collidepoint((ball_.pos.x+ball_.rad), (ball_.pos.y+ball_.rad)) or (brick.collidepoint((ball_.pos.x-ball_.rad),(ball_.pos.y-ball_.rad))):
            ball_.speed.y = -ball_.speed.y
            to_remove = [brick]
            for brick in to_remove:
                bricks_.bricks.remove(brick)

我尝试实施一种在 Whosebug 上发现的先前问题的技术,但它仍然不起作用。

当我将代码放入绘图函数时,它在再次绘制之前删除了块。问题是我只 运行 我的创建积木功能一次,所以我不明白为什么它正在绘制从列表中删除的积木。

这是创建和绘制函数:

def create(self):
    y_margin = 40
    self.bricks = []
    for i in range(2):  
        """ Calculates x margin by subtracting by the number of pixels the blocks will take,
        divides this on 2 so you get the "spacing" on both sides of the blocks. 
        Subtracts half a block width from this number and then it's aligned perfectly.
        I do not know why I need to subtract half a block. 
        """
        x_margin = ((screen[0]-8*BRICK_W)/2)-(BRICK_W/2)
        for j in range(8):
            self.pos = (Vector2D((x_margin), (y_margin)))
            self.bricks.append(pygame.Rect(self.pos.x, self.pos.y, BRICK_W, BRICK_H))
            x_margin += BRICK_W+5
        y_margin += BRICK_H+5

def draw(self):
    for brick in self.bricks:
        pygame.draw.rect(screen_, WHITE, brick)

因为我决定通过碰撞检测走简单的道路,所以我得到了另一个错误,我认为如果块出现了,它就会消失。这是游戏的图片:http://i.stack.imgur.com/0thU6.gif.

完整代码给想看的人: (状态系统并不漂亮,但至少它适用于这个低级代码。)

 # Standard libraries
    import sys, math, random

# Importing third party libraries
import pygame
from pygame.locals import *

# Global constants
screen = (600, 600)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
BLACK = (0, 0 ,0)
PLATFORM_W = 100
PLATFORM_H = 20
ball_dia = 20
BRICK_W = 30
BRICK_H = 15


# Set screen
screen_ = pygame.display.set_mode(screen, 0 , 32)

# Platform Y coordinate
platform_Y = screen[1] - PLATFORM_H - 10 #This can be modified to fit aesthetics 

# Restrictions 
platform_MAX_X = screen[0] - PLATFORM_W
BALL_MAX_X = screen[0] - ball_dia+PLATFORM_H
BALL_MAX_Y = screen[1] - ball_dia


## ======================================*Vector2D*============================================== ##

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(Self):
        return 'Vector(X: {x}, Y: {y})'.format(x = self.x, y = self.y)

    def __add__(self, b):
        return Vector2D(self.x + b.x, self.y +b.y)

    def __sub__(self, b):
        return Vector2D(self.x - b.x, self.y - b.y)

    def __mul__(self, b):

        try:
            b = float(b)
            return Vector2D(self.x * b, self.y * b)
        except ValueError:
            print("Ooops! Right value must be a float")
            raise

    def magnitue(self):
        try:
            m = self.magnitude()
            return Vector2D(self.x / m, self.y / m)
        except ZeroDivisionError:
            print("Ooops, cannot normalize a zero vector")
            raise

    def copy(self):
        return Vector2D(self.x, self.y)



    """
    Get distance is basically getting the distance from a to b, 
    in this case vector a and vector b
    """
def get_distance(a, b):
        """ Using the distance formula which is derived from the Pythagorean theorem,
        http://www.mathwarehouse.com/algebra/distance_formula/index.php """

        a = Vector2D(a.x, a.y)
        b = Vector2D(b.x, b.y)

        return (((((a.x-b.x)**2)+((a.y-b.y)**2)))**.5)

## =========================================*Platform*=========================================== ##


class Platform:
    """ This is the platform that the player can control in x direction with arrow keys """
    def __init__(self):
        self.x = screen[0]/2 - PLATFORM_W/2
        self.y = platform_Y
        self.width = PLATFORM_W
        self.height = PLATFORM_H
        self.pos = Vector2D(self.x, self.y)
        self.tileRect = pygame.Rect(self.x, self.y, self.width, self.height)

    def clique(self):
        """ This is the one doing the magic to the Platform by getting the new x coordinates from
        the input function. It then updates it's position data accordingly and draws the Platform
        on the new information. """
        self.x = Input().x
        self.pos = Vector2D(self.x, self.y)

        # Making a variable that is equal to the rectangle platform
        # This will be used for collision detection.
        self.tileRect = pygame.Rect(self.x, self.y, self.width, self.height)


        self.draw()

    def draw(self):
        pygame.draw.rect(screen_, BLUE, (self.pos.x, self.pos.y, self.width, self.height))



platform_ = Platform()

## ===========================================*Bricks*========================================= ##


class Bricks:
    """ Bricks that will be removed after hit by the ball.
    They are created using for loops. Change the ranges on the for loops to change
    amounts of bricks """
    def __init__(self):
        pass


    def create(self):
        y_margin = 40
        self.bricks = []
        for i in range(2):  
            """ Calculates x margin by subtracting by the number of pixels the blocks will take,
            divides this on 2 so you get the "spacing" on both sides of the blocks. 
            Subtracts half a block width from this number and then it's aligned perfectly.
            I do not know why I need to subtract half a block. 
            """
            x_margin = ((screen[0]-8*BRICK_W)/2)-(BRICK_W/2)
            for j in range(8):
                self.pos = (Vector2D((x_margin), (y_margin)))
                self.bricks.append(pygame.Rect(self.pos.x, self.pos.y, BRICK_W, BRICK_H))
                x_margin += BRICK_W+5
            y_margin += BRICK_H+5

    def draw(self):
        for brick in self.bricks:
            pygame.draw.rect(screen_, WHITE, brick)


bricks_ = Bricks()

## ========================================*Ball*============================================ ##


class Ball:
    """ A ball that will move, change direction if it hits platform, walls or bricks.
    """
    def __init__(self):
        self.rad = ball_dia/2
        self.speed = Vector2D(0, 0)
        self.pos = Vector2D(platform_.x+(PLATFORM_W/2), platform_.y-self.rad)
        self.status = 0
        self.gameover = False

    def move(self):
        ball_.speed = input_.speed
        ball_.pos += ball_.speed

        """ 
        Basic wall detection. Check all walls, subtracts the radius of the ball.
        """
        if self.pos.x > BALL_MAX_X - self.rad:
            self.pos.x = BALL_MAX_X - self.rad
            self.speed.x *= -1
        if self.pos.x < 0 + self.rad:
            self.pos.x = 0 + self.rad
            self.speed.x *= -1
        if self.pos.y > BALL_MAX_Y - self.rad:
            self.gameover = True
        if self.pos.y < 0 + self.rad:
            self.pos.y = 0 + self.rad
            self.speed.y *= -1


        """
        Inter is the centre position of the rectangle platform. This can be used
        for collision detection. """
        inter = Vector2D(platform_.pos.x+PLATFORM_W/2, platform_.pos.y-PLATFORM_H/2)
        d = get_distance(inter, self.pos)

        """ Here we are checking if the rectangle platform are colliding with the point 
        ball's coordinates + its radius. If that is the case we are also checking which
        side of the platform the ball is colliding on and having two different multipliers
        giving it a feel of randomness and having a bounce to the other direction,
        this we get by multiplying it by -1 (i.e)"""
        if platform_.tileRect.collidepoint((self.pos.x+self.rad), (self.pos.y+self.rad)) or (platform_.tileRect.collidepoint((self.pos.x-self.rad),(self.pos.y-self.rad))):
            if self.pos.x > inter.x:
                self.speed.x *= -random.randrange(1,4)
                self.speed.y *= -random.randrange(1,4)
            if self.pos.x < inter.x:
                self.speed.x *= -random.randrange(2, 4)
                self.speed.y *= -random.randrange(2, 4)

        for brick in bricks_.bricks:
            if brick.collidepoint((ball_.pos.x+ball_.rad), (ball_.pos.y+ball_.rad)) or (brick.collidepoint((ball_.pos.x-ball_.rad),(ball_.pos.y-ball_.rad))):
                ball_.speed.y = -ball_.speed.y
                to_remove = [brick]
                for brick in to_remove:
                    bricks_.bricks.remove(brick)

        if self.speed.x > 10:
            self.speed.x *= 0.5
        if self.speed.x < -10:
            self.speed.x *= 0.5
        if self.speed.y > 10:
            self.speed.y *= 0.5
        if self.speed.y < -10:
            self.speed.y *= 0.5

        ball_.draw()

    def collisions(self):
        pass

    def draw(self):
        if self.gameover == False:
            pygame.draw.circle(screen_, WHITE, (int(self.pos.x), int(self.pos.y)), int(self.rad))


ball_ = Ball()



## ======================================*Engine*============================================== ##

class Engine:
    """ The engine initiates the game, takes care of events,
    show stats and messages and basically run all the other parts
    of the program """
    def __init__(self):
        self.alive = True
        self.retarded = False
        pygame.display.set_caption("Rektball by #TeamRekt")
        self.clock = pygame.time.Clock()

        if pygame.font:
            self.font = pygame.font.Font(None, 30)
        else:
            self.font = None


    """ 
    The eventhandler is a function that will check pygame.events,
    for either pygame.QUIT or the ESC button. If either is executed it will set the boolean
    to false and it will quit pygame using the built in pygame.quit() function.
    """
    def event_handler(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.alive = False
                pygame.quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.alive = False
                    pygame.quit()

    """ 
    Show message will basically show a message in the middle of the screen.
    It will use blit to create/render/draw/show the text.
    """
    def show_message(self, message):
        if self.font:
            size = self.font.size(message)
            font_surface = self.font.render(message, False, WHITE)
            x = (screen[0] - size[0]) / 2
            y = (screen[1] - size[1]) /2
            screen_.blit(font_surface, (x,y))


    """ 
    The run-loop which runs this whole game. Everything is handled in this loop.
    """
    def run(self):
        while self.alive:
            self.event_handler()
            screen_.fill(BLACK)
            self.time_passed = self.clock.tick(30)
            statrun.Starts()

            if statrun.start == False:
                statrun.Starts()

            statrun.Runs()

            if ball_.gameover == True:
                statrun.Gameovers()

            pygame.display.update()

## =======================================*Input*============================================= ##


class Input:
    """ This will take care of inputs,
    i.e. keys being pressed down and use it to change
    parts of the code to move the ball, the platform etc. """
    def __init__(self):
        self.x = platform_.pos.x
        self.speed = Vector2D(0,0)
        self.status = False
        self.check_input()

    def check_input(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_LEFT]:
            self.x -= 10
            if self.x < 0:
                self.x = 0
        if keys[pygame.K_RIGHT]:
            self.x += 10
            if self.x > platform_MAX_X:
                self.x = platform_MAX_X
        if keys[pygame.K_SPACE] and (self.status == False):
            self.status = True
            self.speed = Vector2D((-random.randrange(4, 10)),(-random.randrange(4,10)))


input_ = Input()    

## ==================================================================================== ##

class State_run:
    """
    This was supposed to be part of a bigger state-machine code,
    but after hitting the wall for too many hours I decided to abandon
    the project, but keeping a little bit of the structure.
    It is being called by boolean in the run function inside the engine object/class.
    This is not a very good solution, but as I said, I have spent a few hours (days...), 
    and I just had to wrap this up. 
    """


    """ The init function will start the boolean circus, 
    although the boolean will not be used if it works as planned,
    it's a fallback boolean. """
    def __init__(self):
        self.start = False

    def Starts(self):
        platform_.draw()
        ball_.draw()
        bricks_.create()
        bricks_.draw()
        self.start = True

    def Runs(self):
        input_.check_input()
        if input_.status != True:
            Engine().show_message("Press space to start game")
        if input_.status == True:
            ball_.move()
            platform_.clique()
            bricks_.draw()

    def Wins(self):
        Engine().show_message("You have won the game")

    def Gameovers(self):
        Engine().show_message("You have lost the game")
        ball_.speed = Vector2D(0,0)

statrun = State_run()

## ==================================================================================== ##



""" Runs the program by first initiating pygame using the builtin function, pygame.init(),
then it runs the Engine().run() function which is doing all the work. """
if __name__ == "__main__":
    pygame.init()

    Engine().run()
for brick in to_remove:
    bricks_.bricks.remove(brick)

第二个循环中的 'brick' 隐藏了第一个循环中的前一个 'brick'。它必须改变。

for i in to_remove:
    bricks_.bricks.remove(i)  

或者从第一个循环中获取第二个 for 循环。我认为您正在根据某些条件收集要删除的对象。您必须缩进第二个 for 循环,如果是这样,则无需更改循环变量名称。

'to_remove' 列表只有一个元素。似乎它必须有多个元素,在循环之外将其初始化为一个空列表,如 to_remove = [],并附加对象,如 to_remove.append(brick)

看来这个函数调用是可以改变的。 brick.collidepoint((ball_.pos.x+ball_.rad), (ball_.pos.y+ball_.rad))brick.collidepoint(ball_.pos.x + ball_.rad, ball_.pos.y + ball_.rad).

to_remove = []
cpoint1 = brick.collidepoint(ball_.pos.x + ball_.rad, ball_.pos.y + ball_.rad)
cpoint2 = brick.collidepoint(ball_.pos.x - ball_.rad, ball_.pos.y - ball_.rad)
for brick in bricks_.bricks:
    if  cpoint1 or cpoint2:
        ball_.speed.y = -ball_.speed.y
        to_remove.append(brick)

for brick in to_remove:
    bricks_.bricks.remove(brick)