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")
问题分析
核心错误点:
self.enemy和self.tower是列表对象,不是单个实例,直接调用.rect会报错,必须遍历列表内的每个元素才能访问对应实例的rect。- 遍历列表时直接删除元素会导致索引混乱,触发异常。
- 依赖全局变量和直接访问类属性的写法不规范,容易引发作用域问题。
解决方案
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
相关产品推荐
相关产品推荐

