在 Python Pygame 中检测线条和图像之间的碰撞

Detecting Collisions Between Line and Image in Python Pygame

我正在 python pygame 中重新创建 Fruit Ninja 游戏。到目前为止,我已经添加了切片功能,它是从鼠标第一次按下时的位置到鼠标按下并移动时的位置绘制的一条线。现在我正致力于检测水果和水果切片线之间的碰撞。我正在尝试为此使用 y=mx+b 方程。使用直线的起点和终点,我计算斜率 (m) 并使用斜率计算 b。最后,我检查线是否穿过水果的坐标。在尝试实施之后,我遇到了一个问题。尝试切片水果时,未检测到碰撞。我想我知道是什么导致了这个问题:水果的宽度和高度如何?毕竟,我只是在检查直线是否穿过水果的 x 和 y 位置,尽管水果不仅仅是一个点,而是一个充满点的图像。现在我的问题是:如何检测线是否穿过水果的 image 而不仅仅是它的位置?我不确定如何使用 y=mx+b 来做到这一点。 代码:

import pygame
import random
import os
pygame.init()

WINDOW_SIZE = (550, 550)
window = pygame.display.set_mode(WINDOW_SIZE)

pygame.display.set_caption("Fruit Ninja")
score_font = pygame.font.SysFont("Arial", 30, "bold")

# List of fruit images
fruitsImages = [os.listdir("Fruits/" + path) for path in os.listdir("Fruits")]

# Images
heartImg = pygame.image.load("heart.png")
game_bg = pygame.image.load("BGs/woodenBG.png")
game_bg = pygame.transform.scale(game_bg, WINDOW_SIZE)
menuBG = pygame.image.load("BGs/menuBG.png")
menuBG = pygame.transform.scale(menuBG, WINDOW_SIZE)
gameLogo = pygame.image.load("fruitNinjaLogo.png")
gameLogo = pygame.transform.scale(gameLogo, (318, 197))
bombImg = pygame.image.load("bomb.png")


class Player:
    def __init__(self):
        self.score = 0
        self.lives = 3
        self.font = score_font
        self.heart = heartImg

    def update(self):
        window.blit(self.font.render(f"SCORE:{self.score}", True, (255, 255, 255)), (10, 10))

        x = 480
        for i in range(self.lives):
            window.blit(self.heart, (x, 10))
            x -= 60

    def init(self):
        self.score = 0
        self.lives = 3


player = Player()


class Fruit:
    def __init__(self, fruit, x, y, upSpeed, downSpeed, fruitsList:list):
        self.fruit = fruit
        self.x = x
        self.y = y
        self.upSpeed = upSpeed
        self.downSpeed = downSpeed
        self.fruitsList = fruitsList

    def spawn(self):
        window.blit(self.fruit, (self.x, self.y))

    def throwUp(self):
        self.y -= self.upSpeed

    def drop(self):
        if self.y <= WINDOW_SIZE[1]:
            self.y += self.downSpeed
        else:
            self.fruitsList.remove(self)

    def checkIfCut(self, starting_point, ending_point):
        try:
            m = (starting_point[1] - ending_point[1]) / (starting_point[0] - ending_point[0])

            if starting_point[0] < 0:
                b = starting_point[1] + starting_point[0] * m
            else:
                b = starting_point[1] - starting_point[0] * m

            if self.y == m * self.x + b:
                print("Fruit Sliced")
            else:
                print("Fruit Not Sliced")

        except ZeroDivisionError:
            pass


def swipe(starting_point, ending_point):
    pygame.draw.line(window, (255, 255, 255), starting_point, ending_point, 5)


def blit_bg():
    window.blit(game_bg, (0, 0))


def main():
    clock = pygame.time.Clock()
    mouseDown = False
    fruit = Fruit(pygame.image.load("Fruits/RedApple/redApple.png"), 200, 200, 20, 50, [])
    while True:
        window.fill((0, 0, 0))
        blit_bg()
        clock.tick(30)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()

            if event.type == pygame.MOUSEBUTTONDOWN:
                mouseDown = True
                starting_point = pygame.mouse.get_pos()

            if mouseDown and event.type == pygame.MOUSEMOTION:
                ending_point = pygame.mouse.get_pos()
                swipe(starting_point, ending_point)
                fruit.checkIfCut(starting_point, ending_point)

            if event.type == pygame.MOUSEBUTTONUP:
                mouseDown = False

            if event.type == pygame.KEYDOWN:
                keys = pygame.key.get_pressed()
                if keys[pygame.K_q]:
                    player.init()
                    menu()

        fruit.spawn()

        player.update()
        pygame.display.update()


def menu():
    font = pygame.font.SysFont("cooperblack", 90)
    while True:
        window.fill((0, 0, 0))
        window.blit(menuBG, (0, 0))
        window.blit(gameLogo, (120, 80))

        playButton = pygame.draw.rect(window, "#ff5c70", (130, 335, 300, 120))
        window.blit(font.render("PLAY", True, (0, 0, 0)), (150, 340))

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()

            if event.type == pygame.MOUSEBUTTONDOWN:
                if playButton.collidepoint(event.pos):
                    main()

        pygame.display.update()



if __name__ == "__main__":
    menu()

我希望我说的很清楚。如果有什么不清楚的地方,请告诉我。

在@user16038533的帮助下,问题解决了!他还链接了 this 页面,这有助于我理解他的解决方案。 代码:

import pygame
import random
import os
pygame.init()

WINDOW_SIZE = (550, 550)
window = pygame.display.set_mode(WINDOW_SIZE)

pygame.display.set_caption("Fruit Ninja")
score_font = pygame.font.SysFont("Arial", 30, "bold")

# List of fruit images
fruitsImages = [os.listdir("Fruits/" + path) for path in os.listdir("Fruits")]

# Images
heartImg = pygame.image.load("heart.png")
game_bg = pygame.image.load("BGs/woodenBG.png")
game_bg = pygame.transform.scale(game_bg, WINDOW_SIZE)
menuBG = pygame.image.load("BGs/menuBG.png")
menuBG = pygame.transform.scale(menuBG, WINDOW_SIZE)
gameLogo = pygame.image.load("fruitNinjaLogo.png")
gameLogo = pygame.transform.scale(gameLogo, (318, 197))
bombImg = pygame.image.load("bomb.png")


class Player:
    def __init__(self):
        self.score = 0
        self.lives = 3
        self.font = score_font
        self.heart = heartImg

    def update(self):
        window.blit(self.font.render(f"SCORE:{self.score}", True, (255, 255, 255)), (10, 10))

        x = 480
        for i in range(self.lives):
            window.blit(self.heart, (x, 10))
            x -= 60

    def init(self):
        self.score = 0
        self.lives = 3


player = Player()


class Fruit:
    def __init__(self, fruit, x, y, upSpeed, downSpeed, fruitsList:list):
        self.fruit = fruit
        self.x = x
        self.y = y
        self.upSpeed = upSpeed
        self.downSpeed = downSpeed
        self.fruitsList = fruitsList
        self.width, self.height = fruit.get_size()

    def spawn(self):
        window.blit(self.fruit, (self.x, self.y))

    def throwUp(self):
        self.y -= self.upSpeed

    def drop(self):
        if self.y <= WINDOW_SIZE[1]:
            self.y += self.downSpeed
        else:
            self.fruitsList.remove(self)

    def checkIfCut(self, starting_point, ending_point):
        x1, y1 = starting_point
        x2, y2 = ending_point

        if self.lineRectIntersecton(x1, y1, x2, y2):
            print("Fruit Sliced")
        else:
            print("Fruit Not Sliced")

    def lineRectIntersecton(self, startingX, startingY, endingX, endingY):
        left = self.lineLineIntersection(startingX, startingY, endingX, endingY, self.x, self.y, self.x,
                                    self.y + self.height)
        right = self.lineLineIntersection(startingX, startingY, endingX, endingY, self.x + self.width,
                                          self.y, self.x + self.width, self.y + self.height)
        top = self.lineLineIntersection(startingX, startingY, endingX, endingY, self.x, self.y,
                                   self.x + self.width, self.y)
        bottom = self.lineLineIntersection(startingX, startingY, endingX, endingY, self.x,
                                           self.y + self.height, self.x + self.width, self.y + self.height)

        return left or right or top or bottom

    @staticmethod
    def lineLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4):
        uAd = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))
        uBd = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))
        if uAd != 0 and uBd != 0:
            uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / uAd
            uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / uBd

            return 0 <= uA <= 1 and 0 <= uB <= 1
        return False


def swipe(starting_point, ending_point):
    pygame.draw.line(window, (255, 255, 255), starting_point, ending_point, 5)


def blit_bg():
    window.blit(game_bg, (0, 0))


def main():
    clock = pygame.time.Clock()
    mouseDown = False
    fruit = Fruit(pygame.image.load("Fruits/RedApple/redApple.png"), 200, 200, 20, 50, [])
    while True:
        window.fill((0, 0, 0))
        blit_bg()
        clock.tick(30)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()

            if event.type == pygame.MOUSEBUTTONDOWN:
                mouseDown = True
                starting_point = pygame.mouse.get_pos()

            if mouseDown and event.type == pygame.MOUSEMOTION:
                ending_point = pygame.mouse.get_pos()
                swipe(starting_point, ending_point)
                fruit.checkIfCut(starting_point, ending_point)

            if event.type == pygame.MOUSEBUTTONUP:
                mouseDown = False

            if event.type == pygame.KEYDOWN:
                keys = pygame.key.get_pressed()
                if keys[pygame.K_q]:
                    player.init()
                    menu()

        fruit.spawn()

        player.update()
        pygame.display.update()


def menu():
    font = pygame.font.SysFont("cooperblack", 90)
    while True:
        window.fill((0, 0, 0))
        window.blit(menuBG, (0, 0))
        window.blit(gameLogo, (120, 80))

        playButton = pygame.draw.rect(window, "#ff5c70", (130, 335, 300, 120))
        window.blit(font.render("PLAY", True, (0, 0, 0)), (150, 340))

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()

            if event.type == pygame.MOUSEBUTTONDOWN:
                if playButton.collidepoint(event.pos):
                    main()

        pygame.display.update()



if __name__ == "__main__":
    menu()

我希望这个问题可以帮助其他正在为同样的问题而苦苦挣扎的人。

好吧,当您不知道如何做事时,您 google 就可以了。您可以检查您的线是否与构成矩形的任何线发生碰撞。 Here 是我获得代码的地方。

def lineLineIntersection(x1,  y1,  x2,  y2,  x3,  y3,  x4,  y4):
    uAd = ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
    uBd = ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
    if uAd != 0 and uBd != 0:
        uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / uAd
        uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / uBd

        return uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1

    return False

def lineRectIntersecton(x1, y1, x2, y2, rx, ry,  rw, rh):
    return lineLineIntersection(x1,y1,x2,y2, rx,ry,rx, ry+rh) \
    or lineLineIntersection(x1,y1,x2,y2, rx+rw,ry, rx+rw,ry+rh) \
    or lineLineIntersection(x1,y1,x2,y2, rx,ry, rx+rw,ry) \
    or lineLineIntersection(x1,y1,x2,y2, rx,ry+rh, rx+rw,ry+rh)

然后您可以将 checkIfCut 函数更改为:

def checkIfCut(self, starting_point, ending_point):
    x1, y1 = starting_point
    x2, y2 = ending_point
    rw, rh = self.fruit.get_size()
    
    return lineRectIntersecton(x1, y1, x2, y2, self.x, self.y, rw, rh)