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

如何解决Pygame游戏中敌人扎堆生成的问题?

解决敌人扎堆生成问题

需求与现状

  • 目标:使敌人之间保持间距,为玩家提供躲避机会
  • 问题:敌人出现扎堆生成的情况
    敌人扎堆示例
  • 限制条件:
    1. 敌人单向移动
    2. 背景为导入图像,无法使用额外的screen.blit()操作
  • 已尝试无效方法:
    1. 扩大randint的取值范围
    2. 使用delay = random.random()
    3. 增大obstacle_timer的毫秒值,无任何变化

解决思路

原代码用固定定时器每隔固定时间生成敌人,完全不考虑已有敌人的位置,必然导致扎堆。正确的做法是动态控制生成时机与位置:

  1. 取消固定定时器,改用帧计数追踪距离上次生成敌人的时间
  2. 每次生成敌人时,参考当前最靠右的敌人位置,确保新敌人和它保持足够的最小间距
  3. 生成间隔用随机值,让敌人出现节奏更自然

修改后的完整代码

import pygame 
from sys import exit
from random import randint, choice, random

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        walk1 = pygame.image.load('').convert_alpha()
        walk2 = pygame.image.load('').convert_alpha()
        
        self.player_walk = [walk1, walk2]
        self.player_index = 0
        
        jump = pygame.image.load('').convert_alpha()
        fall1 = pygame.image.load('').convert_alpha()
        fall2 = pygame.image.load('').convert_alpha()
        fall3 = pygame.image.load('').convert_alpha()
        
        self.player_jump = [jump, fall1, fall2, fall3]
        
        self.image = self.player_walk[self.player_index]
        self.rect = self.image.get_rect(midbottom = (80, 300))
        self.gravity = 0
        self.jump_sound = pygame.mixer.Sound('')
        

    def player_input(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_SPACE] and self.rect.bottom >= 300:
            self.gravity = -20
            self.jump_sound.play()
    
    def apply_gravity(self):
        self.gravity += 1
        self.rect.y += self.gravity
        if self.rect.bottom >= 300: self.rect.bottom = 300 

    def animation_state(self):
        if self.rect.bottom < 300: 
            self.player_index += 0.07
            if self.player_index >= len(self.player_jump): self.player_index = 0
            self.image = self.player_jump[int(self.player_index)]
        else:
            self.player_index += 0.1
            if self.player_index >= len(self.player_walk): self.player_index = 0
            self.image = self.player_walk[int(self.player_index)]

    def update(self):
        self.player_input()
        self.apply_gravity()
        self.animation_state()

class Obstacle(pygame.sprite.Sprite):
    def __init__(self, type, spawn_x):
        super().__init__()

        if type == 'Obstacle1':
            obstacle1 = pygame.image.load('').convert_alpha()
            obstacle1a = pygame.image.load('').convert_alpha()
            obstacle1b = pygame.image.load('').convert_alpha()
            self.anim = [obstacle1, obstacle1a, obstacle1b]
            y_pos = 150

        else: 
            obstacle2 = pygame.image.load('').convert_alpha()
            obstacle2a = pygame.image.load('').convert_alpha()
            obstacle2b = pygame.image.load('').convert_alpha()
            self.anim = [obstacle2, obstacle2a, obstacle2b]
            y_pos = 300

        self.animation_index = 0
        self.image = self.anim[self.animation_index]
        # 使用传入的spawn_x作为生成位置,不再随机大范围
        self.rect = self.image.get_rect(midbottom = (spawn_x, y_pos))
    
    def animation_state(self):
        self.animation_index += 0.1
        if self.animation_index >= len(self.anim): self.animation_index = 0
        self.image = self.anim[int(self.animation_index)]
     
    def destroy(self):
        if self.rect.x <= -100: self.kill()
    
    def update(self):
        self.animation_state()
        self.rect.x -= 6
        self.destroy()

def display_score():
    current_time = int(pygame.time.get_ticks()/1000) - start_time
    score_surf = font.render(f'Score: {current_time}', False, "white")
    score_rect = score_surf.get_rect(center = (400, 50))
    screen.blit(score_surf, score_rect)
    return current_time 

def collision():
    if pygame.sprite.spritecollide(player.sprite,obstacle_group,False):
        obstacle_group.empty()
        bg_rect.x = 0
        return False
    else: 
        return True

pygame.init()
screen = pygame.display.set_mode((800, 350))
pygame.display.set_caption('Game by @xxRookiexx')
clock = pygame.time.Clock()
game_active = False
score = 0
start_time = 0
during = pygame.mixer.Sound('')
during.play(loops = -1)

# Groups
player = pygame.sprite.GroupSingle()
player.add(Player())

obstacle_group = pygame.sprite.Group()

# Variables
font = pygame.font.Font('', 20)
# 新增:控制敌人生成的变量
enemy_spawn_timer = 0
# 随机生成间隔:120到300帧(2-5秒,基于60帧/秒)
min_spawn_frames = 120
max_spawn_frames = 300
next_spawn_frames = randint(min_spawn_frames, max_spawn_frames)
# 敌人最小间距:屏幕宽度的1/2,保证不会扎堆
min_enemy_distance = 400

# Backgrounds
bg = pygame.image.load('').convert_alpha()
bg_rect = bg.get_rect(topleft = (0,0))
land = pygame.image.load('').convert_alpha()
shadow = pygame.image.load('').convert_alpha()

# Home screen
player_stand = pygame.image.load('')
player_stand_rect = player_stand.get_rect(center = (400, 225))
intro = pygame.image.load('')
intro_rect = intro.get_rect(topleft = (0,0))

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

        if not game_active:
            if event.type == pygame.MOUSEBUTTONDOWN and player_stand_rect.collidepoint(pygame.mouse.get_pos()): 
                start_game = pygame.mixer.Sound('')
                start_game.play()
                game_active = True
                start_time = int(pygame.time.get_ticks()/1000)
                # 重置生成计时器
                enemy_spawn_timer = 0
                next_spawn_frames = randint(min_spawn_frames, max_spawn_frames)
    
    if game_active:
        bg_rect.x -= 0.5
        if bg_rect.x == -1000: bg_rect.x = 0
        screen.blit(bg,bg_rect)
        screen.blit(land, (0,300))
        score = display_score()
        player.draw(screen)
        player.update() 

        # 处理敌人生成逻辑
        enemy_spawn_timer += 1
        if enemy_spawn_timer >= next_spawn_frames:
            # 获取最后一个敌人的位置,没有的话用屏幕右侧作为基准
            last_enemy_x = screen.get_width()
            if obstacle_group:
                # 取所有敌人中X最大的那个(最靠右的)
                last_enemy = max(obstacle_group.sprites(), key=lambda s: s.rect.x)
                last_enemy_x = last_enemy.rect.x + last_enemy.rect.width
            
            # 计算新敌人的生成位置,确保和最后一个敌人间距足够
            spawn_x = last_enemy_x + min_enemy_distance + randint(100, 300)
            # 生成敌人
            obstacle_group.add(Obstacle(choice(['Obstacle1', 'Obstacle2', 'Obstacle2']), spawn_x))
            # 重置计时器和下一次生成间隔
            enemy_spawn_timer = 0
            next_spawn_frames = randint(min_spawn_frames, max_spawn_frames)

        obstacle_group.draw(screen)
        obstacle_group.update()
        
        game_active = collision()
    else:
        screen.blit(intro, intro_rect)
        screen.blit(player_stand,player_stand_rect)
        score_message = font.render(f'Your score: {score}',False,"white")
        score_message_rect = score_message.get_rect(center = (400,330))
        screen.blit(score_message,score_message_rect)

    pygame.display.update()
    clock.tick(60)

关键修改点

  1. 移除固定定时器:删掉obstacle_timer相关代码,改用帧计数enemy_spawn_timer控制生成时机
  2. 动态计算生成位置:每次生成新敌人时,取当前最靠右的敌人位置,加上最小间距和随机偏移量,确保敌人之间不会扎堆
  3. 随机生成间隔:每次生成后随机设置下一次生成的帧间隔,让敌人出现节奏更自然
  4. 修改Obstacle类初始化:新增spawn_x参数,接收计算好的生成位置,不再用大范围随机X坐标

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 20:07:01