You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pygame塔防游戏:Tower与Enemy类矩形碰撞检测问题求助

塔防游戏碰撞检测问题解决

问题描述

我的塔防游戏代码大体能运行,但不知道怎么在Tower和Enemy类外部访问它们的rect矩形。我想用碰撞检测来删除Enemy对象,之后会改成血条系统。已经卡这个碰撞检测问题好久了,希望有人能帮忙,这样我才能继续做关卡、血条和登录系统。谢谢!

原始代码

def wave(quantity, size, distance):
    global saiba
    hh = True
    for i in range(quantity):
        saiba = Enemy(800 + (distance + size)*i, 100- size/2, size, size)
        MainWindow.enemy.append(saiba)

def button_tower(x, y, width, height, mouse, click, image, action = None):
    if x+width > mouse[0] > x and y+height > mouse[1] > y:
        if click[0] == 1 and action != None:
            MainWindow.action_box = action

def tower(width=30, height=30):
    global goku
    goku = Tower(MainWindow.mouse[0], MainWindow.mouse[1], width, height)
    MainWindow.tower.append(goku)

class Main:
    def __init__(self, width = WIDTH+100, height = HEIGHT + 100):
        pygame.display.set_caption('DBZ Tower Defense')
        self.startwave = False
        self.width = width
        self.height = height
        self.Gamewindow = pygame.display.set_mode((self.width, self.height))

    def wave(self):
        self.startwave = True

    def Intro(self):
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()

            self.Gamewindow.fill(BLACK)
            largeText = pygame.font.Font('freesansbold.ttf', 30)
            TextSurf, TextRect = text_objects("simple tower defense game", largeText)
            TextRect = (100, 100)
            self.Gamewindow.blit(TextSurf, TextRect)

            button_text("New game", 100, 200, 400, 50, GREEN, LGREEN, MainWindow.MainLoop)
            button_text("Continue", 100, 300, 400, 50, RED, LRED)
            button_text("Exit", 100, 400, 400, 50, BLUE, LBLUE, quit)
            pygame.display.update()

    def MainLoop(self):
        self.enemy = []
        self.tower = []
        self.action_box = None
        while True:
            Mainclock.tick(60)
            self.mouse = pygame.mouse.get_pos()
            self.click = pygame.mouse.get_pressed()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == ord('t'):
                        tower()
            
            if self.startwave == True and len(self.enemy)==0:
                wave(10, 20, 8)
                self.startwave = False
        
            # 错误的碰撞检测代码
            tt = self.enemy.rect.colliderect(self.tower.rect)
            if tt:
                del self.enemy[index]
        
            for index,ene in enumerate(self.enemy):
                ene.update()
                ene.move()
                if ene.rect.left <= 0:
                    del self.enemy[index]

            self.Gamewindow.fill(CYAN)
            self.Gamewindow.blit(light_image_map1, background_rectangle)
            button_tower(800, 0, 50, 50, self.mouse, self.click, fantower_image, tower)

            if pygame.mouse.get_pressed()[0] == 1 and self.action_box != None:
                rectangle30 = pygame.Rect(self.mouse[0]-15, self.mouse[1]-15, 30, 30)
                self.Gamewindow.blit(action_box_image, rectangle30)
            elif self.action_box != None:
                self.action_box()
                self.action_box = None
            for object_enemy in self.enemy:
                self.Gamewindow.blit(fantower_image, object_enemy.rect)
            for object_tower in self.tower:
                self.Gamewindow.blit(object_tower.image, object_tower.rect)

            button_text("Start next wave", 0, 600, WIDTH, 100, PURPLE, LPURPLE, MainWindow.wave)
            pygame.display.update()

class Enemy:
    def __init__(self, x, y, width, height):
        self.rect = pygame.Rect(x, y, width, height)
        self.dir = 4
        self.movement = [(810, 100, 2), (810, 300, 4), (575, 300, 8), (575, 230, 4), (385, 230, 2), (385, 300, 4), (276, 300, 2), (276, 392, 4), (70, 392, 8), (70, 300, 4)]

    def move(self): 
        if self.dir == 8:
            self.rect.centery -= 1
        if self.dir == 4:
            self.rect.centerx -= 1
        if self.dir == 6:
            self.rect.centerx += 1
        if self.dir == 2:
            self.rect.centery += 1

    def update(self):
        for pos in self.movement:
             if self.rect.center == (pos[0], pos[1]):
                self.dir = pos[2]

    def color(self, colorid):
        return COLORS[colorid]

class Tower:
    def __init__(self, x, y, width, height):
        self.rect = pygame.Rect(x-15, y-15, width, height)
        self.image = pygame.transform.scale(tower1, (50, 50))

        def colliderec(self):
            if self.rect.collidedict(self.rect):
                print("true")

问题分析

核心错误点:

  1. self.enemy和self.tower是列表对象,不是单个实例,直接调用.rect会报错,必须遍历列表内的每个元素才能访问对应实例的rect。
  2. 遍历列表时直接删除元素会导致索引混乱,触发异常。
  3. 依赖全局变量和直接访问类属性的写法不规范,容易引发作用域问题。

解决方案

1. 修正碰撞检测逻辑

替换MainLoop中错误的碰撞检测代码,改用嵌套遍历+批量删除的方式:

# 替换原来错误的碰撞检测代码
enemies_to_remove = []
for enemy in self.enemy:
    enemy.update()
    enemy.move()
    # 判断敌人是否到达终点
    if enemy.rect.left <= 0:
        enemies_to_remove.append(enemy)
        continue
    # 遍历所有塔,检测碰撞
    for tower in self.tower:
        if enemy.rect.colliderect(tower.rect):
            enemies_to_remove.append(enemy)
            break  # 一个敌人被检测到碰撞后,无需再检测其他塔
# 批量删除标记的敌人,避免遍历中删除的索引问题
self.enemy = [e for e in self.enemy if e not in enemies_to_remove]

2. 规范全局变量与函数调用

去掉wave和tower函数的全局变量依赖,直接在Main类内处理:

  • 删除全局wave函数,在MainLoop的wave触发逻辑里直接创建敌人:
if self.startwave == True and len(self.enemy)==0:
    # 直接在类内生成敌人,无需全局函数
    for i in range(10):
        saiba = Enemy(800 + (8 + 20)*i, 100 - 20/2, 20, 20)
        self.enemy.append(saiba)
    self.startwave = False
  • 修改键盘事件处理,直接创建塔:
if event.type == pygame.KEYDOWN:
    if event.key == ord('t'):
        # 直接创建塔并添加到列表,无需全局函数
        goku = Tower(self.mouse[0], self.mouse[1], 30, 30)
        self.tower.append(goku)

3. 修正Tower类的方法缩进

Tower类内的colliderec方法缩进错误,应与__init__同级,若需要单独的碰撞检测方法,可改为:

class Tower:
    def __init__(self, x, y, width, height):
        self.rect = pygame.Rect(x-15, y-15, width, height)
        self.image = pygame.transform.scale(tower1, (50, 50))

    def collides_with(self, enemy):
        # 检测当前塔与指定敌人的碰撞
        return self.rect.colliderect(enemy.rect)

后续优化建议

  • 将碰撞检测逻辑封装成单独方法,让Main类代码更清晰。
  • 提前规划血条系统:给Enemy类添加hp属性,碰撞时减少血量而非直接删除。
  • 避免直接操作类属性(如MainWindow.enemy),改用类实例的属性访问。

内容的提问来源于stack exchange,提问作者omzy54

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 01:01:13