为什么我的球会粘在一起?

时间:2018-06-09 10:36:56

标签: python pygame

我正在制作Ballz的克隆版,这是一款手机游戏,你需要在多次击中后在一些块上拍摄一大堆球。就像BrickBreaker类固醇一样。我大部分时间都在工作,但我无法弄清楚如何一个接一个地射球。我从测试中知道,在拍摄时,球在不同的地方,但在此之后它们会占据相同的空间。

哦,顺便说一句,我将它们分开的方式是让球再次回到屏幕之外。所以你可以想象它就像把它们全部放在一个接一个,在屏幕外,在播放器底部之下。

这是我的代码:

import pygame
import math
import random
from vector import *

backgroundColor = (0, 0, 0)
ballColor = (255, 255, 255)

sizeOfOneBlock = 50.0
realDimension = 600.0
blockNumberInLine = int(realDimension/sizeOfOneBlock)
size = [int(realDimension), int(realDimension)]

# eg. probability(1/3)
def probability(chance):
    return random.random() <= chance

def abs(x):
    if x>=0:
        return x
    else:
        return -x

# the classes used:
# Block, BlockHandler, Ball, Player

class Block():
    def __init__(self, strength, i, j):
        self.strength = strength
        # i and j are numbers between 0 and blockNumberInLine-1
        self.i, self.j = i, j
        self.refreshStats()

    def refreshStats(self):
        self.color = (100, 224, 89)

    def display(self, Surface):
        pygame.draw.rect(Surface, (0, 0, 255), (self.i*sizeOfOneBlock, self.j*sizeOfOneBlock, sizeOfOneBlock, sizeOfOneBlock), 0)

class BlockHandler():
    def __init__(self):
        self.blockList = []
        self.blockPositions = []

    def resetPositionArray(self):
        self.blockPositions = []
        for block in self.blockList:
            self.blockPositions.append([block.i*sizeOfOneBlock, block.j*sizeOfOneBlock])

    def addNewLayer(self, gameLevel):
        # move every existing block down
        for block in self.blockList:
            block.j += 1
        # add new layer
        for i in range(blockNumberInLine):
            if probability(1/3):
                # gameLevel determines the strength of the block
                self.blockList.append(Block(gameLevel, i, 0))
        # after all blocks are loaded, do this
        self.resetPositionArray()

    def displayBlocks(self, Surface):
        for block in self.blockList:
            block.display(Surface)

class Ball():
    def __init__(self, posVector, moveVector):
        self.posVector = posVector
        self.moveVector = moveVector
        self.radius = 2
        self.x = int(self.posVector.x)
        self.y = int(self.posVector.y)

    def move(self):
        self.posVector.add(self.moveVector)
        self.x = int(self.posVector.x)
        self.y = int(self.posVector.y)

    def display(self, Surface):
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), self.radius)

    def changeDirection(self, tuple):
        # east
        if tuple[0]>0:
            self.moveVector.x = abs(self.moveVector.x)
        # west
        if tuple[0]<0:
            self.moveVector.x = -abs(self.moveVector.x)
        # south
        if tuple[1]>0:
            self.moveVector.y = abs(self.moveVector.y)
        # north
        if tuple[1]<0:
            self.moveVector.y = -abs(self.moveVector.y)

    def collisionDetect(self, blockX, blockY, blockSize, circleX, circleY, circleRadius):
        xDeflect, yDeflect = 0, 0

        # if in the same column
        if (circleX>=blockX) and (circleX<=(blockX+blockSize)):
            # if touching block from above or below
            distance = circleY-(blockY+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                # either 1 or -1
                if distance!=0:
                    yDeflect = distance/abs(distance)
        # if in the same row
        if (circleY>=blockY) and (circleY<=(blockY+blockSize)):
            # if touching block from left or right
            distance = circleX-(blockX+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                if distance!=0:
                    xDeflect = distance/abs(distance)

        return [xDeflect, yDeflect]

    def checkForCollisions(self, blockPositions):
        # walls
        if (self.x<=(0+self.radius)):
            # east
            self.changeDirection([1,0])
        if (self.x>=(realDimension-self.radius)):
            # west
            self.changeDirection([-1,0])
        if (self.y<=(0+self.radius)):
            # south
            self.changeDirection([0,1])

        # blocks
        for pos in blockPositions:
            collision = self.collisionDetect(pos[0], pos[1], sizeOfOneBlock, self.x, self.y, self.radius)
            self.changeDirection(collision)

class Player():
    def __init__(self, posVector):
        self.posVector = posVector
        self.x = int(self.posVector.x)
        self.y = int(self.posVector.y)

        self.level = 1
        self.numberOfBalls = 3
        self.balls = []

    def resetBalls(self):
        self.balls = []
        for j in range(self.numberOfBalls):
            self.balls.append(Ball(self.posVector, moveVector=Vector(0.0, 0.0)))
            # print(ball)

    def placeBalls(self, separateVector):
        # self.resetBalls()
        for j in range(len(self.balls)):
            ball = self.balls[j]
            for i in range(j):
                ball.posVector.subtract(separateVector)

    def display(self, Surface):
        # possibly change color
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), 20)

    def displayBalls(self, Surface):
        for ball in self.balls:
            ball.display(Surface)

    def updateBalls(self, blockHandler):
        for ball in self.balls:
            ball.move()
            ball.checkForCollisions(blockPositions=blockHandler.blockPositions)

def main():
    pygame.init()
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("Ballz")
    done = False
    clock = pygame.time.Clock()

    blockHandler = BlockHandler()
    blockHandler.addNewLayer(1)

    playerPosition = Vector(realDimension/2, realDimension-10)
    player = Player(posVector=playerPosition)
    player.resetBalls()

    # -------- Main Program Loop -----------
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

            if event.type == pygame.KEYDOWN:
                # JFF
                if event.key == pygame.K_w:
                    blockHandler.addNewLayer(1)

                # for debugging
                if event.key == pygame.K_d:
                    for ball in player.balls:
                        print(ball.posVector.x, ball.posVector.y)
                        print(ball.moveVector.x, ball.moveVector.y)
                        print("")
                if event.key == pygame.K_r:
                    player.resetBalls()

            if event.type == pygame.MOUSEBUTTONUP:
                mousePos = pygame.mouse.get_pos()
                player.shootVector = Vector(mousePos[0]-player.x, mousePos[1]-player.y).shortenTo(1)

                for ball in player.balls:
                    for i in range(player.balls.index(ball)*10):
                        ball.posVector.subtract(player.shootVector)
                    ball.moveVector = player.shootVector

                    # test
                    print(ball.posVector.x, ball.posVector.y)
                    print(ball.moveVector.x, ball.moveVector.y)
                    print("")

        # LOGIC
        player.updateBalls(blockHandler)

        # DRAW
        screen.fill(backgroundColor)

        blockHandler.displayBlocks(screen)
        player.displayBalls(screen)
        player.display(screen)

        pygame.display.flip()
        # 60 frames per second
        clock.tick(60)

    pygame.quit()

if __name__ == "__main__":
    main()

编辑:忘记添加矢量类。

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

    def magnitude(self):
        return ((self.x)**2 + (self.y)**2)**0.5

    def shortenTo(self, radius):
        magnitude = self.magnitude()
        unitX = self.x/magnitude
        unitY = self.y/magnitude

        return Vector(unitX*radius, unitY*radius)

    def add(self, addedVector):
        self.x += addedVector.x
        self.y += addedVector.y

    def subtract(self, subtractedVector):
        self.x -= subtractedVector.x
        self.y -= subtractedVector.y

    def printCoordinates(self):
        print(self.x, self.y)

2 个答案:

答案 0 :(得分:7)

对不起,没有复制品,你的球很好: enter image description here

不,但你遇到的问题是可变对象。 当你设置

ball.moveVector = player.shootVector

您将所有moveVector设置为同一个对象,因此每次碰撞检测都会同时改变所有球的方向。最简单的解决方法:

ball.moveVector = player.shootVector + Vector(x=0, y=0)

修改

我使用了不同的vector模块,您可以使用copy.copy或创建自定义__add__方法:

def __add__(self, other):
    if not isinstance(other, Vector)
        raise ValueError
    return Vector(self.x+other.x, self.y+other.y)

(它来自Vector类,同样适用于减法和mult。)

结束编辑

当球离开图像时你重置的方式也存在一些问题,你应该防止玩家再次点击,直到重置球,但我想这会在以后的开发中出现。

附录

注意:我在Python 3中工作,要么我安装了不同的vector模块,要么他们改变了很多,所以不得不在那里更改一些语法。希望它有所帮助:)

import pygame
import math
import random
from vector import *

backgroundColor = (0, 0, 0)
ballColor = (255, 255, 255)

sizeOfOneBlock = 50.0
realDimension = 600.0
blockNumberInLine = int(realDimension/sizeOfOneBlock)
size = [int(realDimension), int(realDimension)]

# eg. probability(1/3)
def probability(chance):
    return random.random() <= chance

def abs(x):
    if x>=0:
        return x
    else:
        return -x

# the classes used:
# Block, BlockHandler, Ball, Player

class Block():
    def __init__(self, strength, i, j):
        self.strength = strength
        # i and j are numbers between 0 and blockNumberInLine-1
        self.i, self.j = i, j
        self.refreshStats()

    def refreshStats(self):
        self.color = (100, 224, 89)

    def display(self, Surface):
        pygame.draw.rect(Surface, (0, 0, 255), (self.i*sizeOfOneBlock, self.j*sizeOfOneBlock, sizeOfOneBlock, sizeOfOneBlock), 0)

class BlockHandler():
    def __init__(self):
        self.blockList = []
        self.blockPositions = []

    def resetPositionArray(self):
        self.blockPositions = []
        for block in self.blockList:
            self.blockPositions.append([block.i*sizeOfOneBlock, block.j*sizeOfOneBlock])

    def addNewLayer(self, gameLevel):
        # move every existing block down
        for block in self.blockList:
            block.j += 1
        # add new layer
        for i in range(blockNumberInLine):
            if probability(1/3):
                # gameLevel determines the strength of the block
                self.blockList.append(Block(gameLevel, i, 0))
        # after all blocks are loaded, do this
        self.resetPositionArray()

    def displayBlocks(self, Surface):
        for block in self.blockList:
            block.display(Surface)

class Ball():
    def __init__(self, posVector, moveVector):
        self.posVector = posVector
        self.moveVector = moveVector
        self.radius = 2
        self.x = int(self.posVector['x'])
        self.y = int(self.posVector['y'])

    def move(self):
        self.posVector += self.moveVector
        self.x = int(self.posVector['x'])
        self.y = int(self.posVector['y'])

    def display(self, Surface):
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), self.radius)

    def changeDirection(self, tuple):
        # east
        if tuple[0]>0:
            self.moveVector['x'] = abs(self.moveVector['x'])
        # west
        if tuple[0]<0:
            self.moveVector['x'] = -abs(self.moveVector['x'])
        # south
        if tuple[1]>0:
            self.moveVector['y'] = abs(self.moveVector['y'])
        # north
        if tuple[1]<0:
            self.moveVector['y'] = -abs(self.moveVector['y'])

    def collisionDetect(self, blockX, blockY, blockSize, circleX, circleY, circleRadius):
        xDeflect, yDeflect = 0, 0

        # if in the same column
        if (circleX>=blockX) and (circleX<=(blockX+blockSize)):
            # if touching block from above or below
            distance = circleY-(blockY+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                # either 1 or -1
                if distance!=0:
                    yDeflect = distance/abs(distance)
        # if in the same row
        if (circleY>=blockY) and (circleY<=(blockY+blockSize)):
            # if touching block from left or right
            distance = circleX-(blockX+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                if distance!=0:
                    xDeflect = distance/abs(distance)

        return [xDeflect, yDeflect]

    def checkForCollisions(self, blockPositions):
        # walls
        if (self.x<=(0+self.radius)):
            # east
            self.changeDirection([1,0])
        if (self.x>=(realDimension-self.radius)):
            # west
            self.changeDirection([-1,0])
        if (self.y<=(0+self.radius)):
            # south
            self.changeDirection([0,1])

        # blocks
        for pos in blockPositions:
            collision = self.collisionDetect(pos[0], pos[1], sizeOfOneBlock, self.x, self.y, self.radius)
            self.changeDirection(collision)

class Player():
    def __init__(self, posVector):
        self.posVector = posVector
        self.x = int(self.posVector['x'])
        self.y = int(self.posVector['y'])

        self.level = 1
        self.numberOfBalls = 3
        self.balls = []

    def resetBalls(self):
        self.balls = []
        for j in range(self.numberOfBalls):

            x = Vector(x=j, y=j) - Vector(x=j, y=j)
            self.balls.append(Ball(self.posVector, x))
            # print(ball)

    def placeBalls(self, separateVector):
        # self.resetBalls()
        for j in range(len(self.balls)):
            ball = self.balls[j]
            for i in range(j):
                ball.posVector -= separateVector

    def display(self, Surface):
        # possibly change color
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), 20)

    def displayBalls(self, Surface):
        for ball in self.balls:
            ball.display(Surface)

    def updateBalls(self, blockHandler):

        for ball in self.balls:

            ball.move()

            ball.checkForCollisions(blockPositions=blockHandler.blockPositions)

def main():
    pygame.init()
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("Ballz")
    done = False
    clock = pygame.time.Clock()

    blockHandler = BlockHandler()
    blockHandler.addNewLayer(1)

    playerPosition = Vector(x=realDimension/2, y=realDimension-10)
    player = Player(posVector=playerPosition)
    player.resetBalls()

    # -------- Main Program Loop -----------
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

            if event.type == pygame.KEYDOWN:
                # JFF
                if event.rrrr == pygame.K_w:
                    blockHandler.addNewLayer(1)

                # for debugging
                if event.key == pygame.K_d:
                    for ball in player.balls:
                        print(ball.posVector['x'], ball.posVector['y'])
                        print(ball.moveVector['x'], ball.moveVector['y'])
                        print("")
                if event.key == pygame.K_r:
                    player.resetBalls()

            if event.type == pygame.MOUSEBUTTONUP:
                mousePos = pygame.mouse.get_pos()
                player.shootVector = Vector(x=mousePos[0]-player.x, y=mousePos[1]-player.y) / ((mousePos[0]-player.x)**2 + (mousePos[1]-player.y))**.5

                for ball in player.balls:
                    for i in range(player.balls.index(ball)*10):
                        ball.posVector -= player.shootVector
                    ball.moveVector = player.shootVector + Vector(x=0, y=0)

                    # test
                    print(ball.posVector['x'], ball.posVector['y'])
                    print(ball.moveVector['x'], ball.moveVector['y'])
                    print("")

        # LOGIC
        player.updateBalls(blockHandler)

        # DRAW
        screen.fill(backgroundColor)

        blockHandler.displayBlocks(screen)
        player.displayBalls(screen)
        player.display(screen)

        pygame.display.flip()
        # 60 frames per second
        clock.tick(60)
main()

答案 1 :(得分:4)

由于您已将玩家self.posVector传递给球类实例,然后将其分配到posVector属性,因此玩家和球的位置均指内存中相同的Vector对象。您可以改为创建向量的副本,例如使用copy模块,以便每个ball.posVector引用一个单独的向量对象。

首先导入复制功能(它会创建浅拷贝)。

from copy import copy

然后在传递之前复制矢量对象。

def resetBalls(self):
    self.balls = []
    for j in range(self.numberOfBalls):
        self.balls.append(
            Ball(copy(self.posVector), moveVector=Vector(0.0, 0.0)))

# ...

for ball in player.balls:
    for i in range(player.balls.index(ball)*10):
        ball.posVector.subtract(player.shootVector)
    ball.moveVector = copy(player.shootVector)

我还建议使用pygame的Vector2课程,而不是您自己的Vector课程,因为它功能更丰富,效率更高。

abs是一个内置函数。