讲师中心 微信公众号
AI工具推荐 视频效率加速

Pygame游戏开发:实现子弹的发射与显示

云晨姑娘_7275

云晨姑娘_7275

发布时间:2025-11-01 10:29:20

|

819人浏览过

|

来源于php中文网

原创

Pygame游戏开发:实现子弹的发射与显示

在pygame游戏开发中,实现动态对象的精确控制和渲染是核心挑战之一。许多开发者在初次尝试实现玩家发射子弹的功能时,可能会遇到子弹虽然被创建了,但却无法在屏幕上显示的问题。这通常是由于对pygame事件处理机制、对象状态更新以及游戏循环渲染顺序理解不足所导致的。本教程将详细解析如何正确地在pygame中实现子弹的发射、移动与显示,帮助开发者构建功能完善的游戏。

1. 理解子弹不显示的核心问题

子弹无法显示通常源于以下几个关键环节的缺失或错误:

  1. 缺少子弹移动逻辑: 子弹对象被创建后,如果没有明确的指令来更新其位置,它将停留在创建时的坐标,可能被其他对象覆盖或瞬间移出可见区域。
  2. 不正确的输入事件处理: 游戏通常需要实时响应玩家输入(如按下射击键)。如果输入检测逻辑放置不当,或使用了过时的输入状态,子弹创建命令可能永远不会被触发。
  3. 渲染顺序与更新机制: Pygame的游戏循环需要按照“清屏 -> 更新所有对象状态 -> 绘制所有对象 -> 刷新屏幕”的顺序进行。如果子弹的绘制指令在屏幕刷新之前没有被执行,或者被覆盖,它就不会显示。

在提供的代码示例中,具体问题在于:

  • Bullet类缺少一个 move() 方法来更新子弹的 y 坐标。
  • Player类中的 shoot() 方法试图使用一个全局且未实时更新的 userInput 变量来检测按键,导致子弹无法被创建。
  • 子弹的移动和绘制逻辑在Player.draw_bullets()方法中没有正确地结合。

2. 构建可移动的子弹类

首先,我们需要确保 Bullet 类不仅能存储其位置和图像,还能主动更新自身的位置。

class Bullet:
    def __init__(self, x, y, img):
        self.image = img
        # 初始化子弹位置,通常从玩家位置发射
        self.x = x
        self.y = y
        # 定义子弹移动速度
        self.vel = 5 

    def move(self):
        """
        更新子弹的垂直位置,使其向上移动。
        """
        self.y -= self.vel # 假设子弹向上发射,y坐标减小

    def draw_self(self):
        """
        在游戏屏幕上绘制子弹。
        """
        global gameDisplay # 确保能访问到全局的gameDisplay
        gameDisplay.blit(self.image, (self.x, self.y))

关键点:

  • move() 方法是核心,它负责在每一帧更新子弹的 y 坐标,使其向上移动。
  • draw_self() 负责将子弹图像绘制到其当前位置。

3. 优化玩家的射击与子弹管理

Player 类需要能够创建子弹,并管理其发射的所有子弹。

testing-python
testing-python

使用pytest编写和评估有效的Python测试。适用于编写测试、审查测试代码、调试测试失败或提高测试覆盖率。

下载
class Player:
    def __init__(self, x, y, img):
        self.x = x
        self.y = y
        self.image = img
        self.vel = 0
        self.Right = False
        self.Left = False
        self.bullets = [] # 存储玩家发射的所有子弹对象

    def draw_self(self):
        gameDisplay.blit(self.image, (self.x, self.y))

    # ... (movement 和 receive_input 方法保持不变) ...

    def shoot(self):
        """
        创建一个新的子弹对象并添加到玩家的子弹列表中。
        这个方法只负责创建子弹,不负责检测按键。
        """
        # 子弹初始位置可以微调,使其从玩家飞船的中心或前端发射
        bullet = Bullet(self.x + self.image.get_width() // 2 - bullet_sprite.get_width() // 2, 
                        self.y, bullet_sprite)
        self.bullets.append(bullet)

    def draw_bullets(self):
        """
        遍历所有子弹,更新它们的位置并绘制它们。
        """
        # 使用列表切片或逆序遍历,以便安全地移除屏幕外的子弹
        for i, bullet in enumerate(self.bullets[:]): 
            bullet.move() # 先更新子弹位置
            bullet.draw_self() # 再绘制子弹

            # 可选:移除超出屏幕的子弹以节省资源
            if bullet.y < -bullet.image.get_height():
                self.bullets.pop(i) 

    def update(self):
        """
        更新玩家状态,包括移动和绘制自身,以及管理和绘制子弹。
        """
        userInput = pygame.key.get_pressed() # 在此获取玩家输入用于移动
        self.receive_input()
        self.movement(userInput)
        self.draw_self()
        # self.draw_bullets() # 子弹的绘制和更新现在可以在主循环中单独调用,或保持在此处

关键点:

  • shoot() 方法现在只负责实例化 Bullet 对象并将其加入 self.bullets 列表。按键检测的逻辑被移到了主游戏循环中,以确保实时性。
  • draw_bullets() 方法遍历 self.bullets 列表。对于每个子弹,它首先调用 bullet.move() 来更新其位置,然后调用 bullet.draw_self() 来绘制它。这是一个“先更新后绘制”的典型模式。
  • 添加了子弹移除逻辑,防止内存中堆积大量看不见的子弹。

4. 完善主游戏循环

游戏主循环是协调所有游戏对象更新和渲染的核心。

import pygame

pygame.init()

# ... (初始化常量、屏幕、加载图片等保持不变) ...

gameExit = False

# 实例化玩家和敌人
enemy = Enemy(400, -40, enemy_sprite)
player = Player(0, 400, player_sprite)

def draw_game_background():
    """
    负责绘制游戏背景。
    """
    gameDisplay.fill(black)

while not gameExit:
    # 1. 事件处理
    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            gameExit = True
        # 也可以在这里处理一次性按键事件,例如只在按下时发射子弹
        # if event.type == pygame.KEYDOWN:
        #     if event.key == pygame.K_f:
        #         player.shoot()

    # 2. 实时按键状态检测(用于持续按键,如移动或连发)
    keys = pygame.key.get_pressed()
    if keys[pygame.K_f]: # 检测'f'键是否被按下
        player.shoot() # 如果按下,则玩家发射子弹

    # 3. 绘制背景
    draw_game_background()

    # 4. 更新所有游戏对象的状态
    player.update() # 更新玩家位置,并绘制玩家自身
    enemy.uptade()  # 更新敌人位置,并绘制敌人自身

    # 5. 绘制所有子弹 (在玩家和敌人之后绘制,确保它们在背景之上)
    player.draw_bullets() 

    # 6. 刷新屏幕显示所有绘制内容
    pygame.display.update()

    # 7. 控制游戏帧率
    clock.tick(60)

pygame.quit()
quit()

关键改进:

  • 实时按键检测: keys = pygame.key.get_pressed() 必须在主循环内部,每次迭代都调用,以获取当前帧的按键状态。然后 if keys[pygame.K_f]: player.shoot() 确保了子弹在按键按下时被创建。
  • 清晰的渲染顺序:
    1. draw_game_background() 清空屏幕并绘制背景。
    2. player.update() 更新玩家状态并绘制玩家。
    3. enemy.uptade() 更新敌人状态并绘制敌人。
    4. player.draw_bullets() 更新所有子弹位置并绘制它们。
    5. pygame.display.update() 将所有绘制内容呈现在屏幕上。 这种顺序确保了背景在最底层,然后是玩家和敌人,最后是子弹,从而避免了绘制覆盖问题。

5. 完整优化后的代码示例

import pygame

pygame.init()

white = (255, 255, 255)
black = (0, 0, 0)

clock = pygame.time.Clock()

larguraTela = 800
alturaTela = 800

gameDisplay = pygame.display.set_mode((larguraTela, alturaTela))
pygame.display.set_caption('catch the coin')

# 确保图片文件存在于项目目录中
try:
    player_sprite = pygame.image.load('ship_G.png').convert_alpha()
    enemy_sprite = pygame.image.load('enemy_A.png').convert_alpha()
    bullet_sprite = pygame.image.load('spaceMissiles_001.png').convert_alpha()
except pygame.error as e:
    print(f"Error loading image: {e}")
    print("Please ensure 'ship_G.png', 'enemy_A.png', 'spaceMissiles_001.png' are in the same directory.")
    pygame.quit()
    quit()


gameExit = False

class Player:
    def __init__(self, x, y, img):
        self.x = x
        self.y = y
        self.image = img
        self.vel = 0
        self.Right = False
        self.Left = False
        self.bullets = []

    def draw_self(self):
        gameDisplay.blit(self.image, (self.x, self.y))

    def movement(self, userInput):
        if userInput[pygame.K_RIGHT]:
            self.Right = True
            self.Left = False
            # 玩家移动应基于当前速度
            self.x += self.vel
        if userInput[pygame.K_LEFT]:
            self.Left = True
            self.Right = False
            # 玩家移动应基于当前速度
            self.x += self.vel

        # 限制玩家在屏幕边界内
        if self.x < 0:
            self.x = 0
        if self.x > larguraTela - self.image.get_width():
            self.x = larguraTela - self.image.get_width()


    def receive_input(self):
        if self.Right:
            self.vel = 5
        elif self.Left:
            self.vel = -5
        else:
            self.vel = 0

    def shoot(self):
        # 计算子弹的初始位置,使其从玩家飞船中心上方发射
        bullet_x = self.x + self.image.get_width() // 2 - bullet_sprite.get_width() // 2
        bullet_y = self.y 
        bullet = Bullet(bullet_x, bullet_y, bullet_sprite)
        self.bullets.append(bullet)

    def draw_bullets(self):
        # 使用列表切片迭代,以便在循环中安全地移除元素
        for i, bullet in enumerate(self.bullets[:]): 
            bullet.move()
            bullet.draw_self()
            # 移除超出屏幕的子弹
            if bullet.y < -bullet.image.get_height():
                self.bullets.pop(i)

    def update(self):
        # 注意:这里获取的userInput只用于玩家自身的移动,射击输入在主循环中处理
        userInput = pygame.key.get_pressed() 
        self.receive_input()
        self.movement(userInput)
        self.draw_self()
        # draw_bullets() 现在在主循环中单独调用,以明确渲染顺序

class Enemy:
    def __init__(self, x, y, img):
        self.image = img
        self.rect = self.image.get_rect()
        self.rect.y = y
        self.rect.x = x
        self.x = x # 原始x,用于重置
        self.y = y # 原始y,用于重置
        self.vel = 10

    def draw_self(self):
        gameDisplay.blit(self.image, self.rect)

    def movement(self):
        self.rect.y += self.vel
        if self.rect.y > alturaTela:
            self.rect.y = self.y # 重置到顶部
            # 敌人重置后,也可以随机化x坐标
            # self.rect.x = random.randint(0, larguraTela - self.image.get_width()) 

    def uptade(self):
        self.movement() # 先移动
        self.draw_self() # 再绘制

class Bullet:
    def __init__(self, x, y, img):
        self.image = img
        self.x = x
        self.y = y
        self.vel = 5 # 子弹速度

    def move(self):
        self.y -= self.vel # 子弹向上移动

    def draw_self(self):
        gameDisplay.blit(self.image, (self.x, self.y))


enemy = Enemy(400, -40, enemy_sprite)
player = Player(0, 400, player_sprite)


def draw_game_elements():
    """
    统一绘制所有游戏元素,确保正确的渲染顺序。
    """
    gameDisplay.fill(black) # 填充背景
    player.update()        # 更新并绘制玩家
    enemy.uptade()         # 更新并绘制敌人
    player.draw_bullets()  # 更新并绘制子弹

while not gameExit:
    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            gameExit = True

        # 仅在KEYDOWN事件发生时发射子弹,防止按住F键连发
        # 如果需要连发,则使用keys[pygame.K_f]
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_f:
                player.shoot()

    # 如果需要按住F键连发,则使用以下代码代替上面的KEYDOWN事件处理
    # keys = pygame.key.get_pressed()
    # if keys[pygame.K_f]:
    #     player.shoot()

    draw_game_elements() # 统一调用绘制和更新

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

pygame.quit()
quit()

6. 注意事项与总结

  • 图片路径: 确保 player_sprite.png、enemy_sprite.png 和 bullet_sprite.png 文件与你的 Python 脚本位于同一目录下,或者提供正确的相对/绝对路径。
  • 实时输入: 对于需要持续响应的输入(如移动),pygame.key.get_pressed() 是最佳选择,它返回一个布尔值列表,表示每个键的当前状态。对于只需要在按键“按下”瞬间触发一次的事件(如单发子弹),处理 pygame.KEYDOWN 事件更合适。
  • 渲染顺序: 始终遵循“清屏 -> 更新对象状态 -> 绘制对象 -> 刷新屏幕”的顺序。背景应首先绘制,然后是静态或移动较慢的元素,最后是动态或前景元素,以避免被覆盖。
  • 资源管理: 对于超出屏幕的子弹或其他临时游戏对象,应及时从列表中移除,以避免内存泄漏和不必要的计算开销。
  • 模块化: 将游戏逻辑拆分到不同的类和方法中,可以提高代码的可读性、可维护性和复用性。

通过以上优化,你的Pygame游戏将能够正确地实现子弹的发射、移动和显示功能,为更复杂的互动奠定基础。

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

相关专题

更多
堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

5227

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2308

2023.08.10

FrankenPHP集成Laravel详细教程
FrankenPHP集成Laravel详细教程

本专题提供FrankenPHP集成Laravel的详细配置指南,全面解析运行原理、开发环境搭建、Caddyfile配置、Octane工作模式、数据库连接、队列任务、定时任务和生产环境优化,解决部署过程中常见的报错与兼容性问题。

0

2026.10.08

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

120

2026.09.30

LLVM RISC-V参数配置教程
LLVM RISC-V参数配置教程

本专题介绍LLVM对RISC-V基础ISA和扩展的支持方式,涵盖RV32、RV64、标准扩展、实验性扩展、厂商扩展、-menable-experimental-extensions和版本差异。

100

2026.09.30

LLVM IR中间表示入门指南
LLVM IR中间表示入门指南

本专题整理LLVM IR的核心概念,包括中间表示作用、模块结构、函数、基本块、SSA形式、类型系统和常见语法,帮助新手理解LLVM编译流程中的关键层。

80

2026.09.30

PDF转图片方法
PDF转图片方法

需要把 PDF 页面用于上传、预览、分享或图片归档时,PDF 转图片方法专题整理 JPG/PNG 格式选择、逐页导出、清晰度设置、批量下载和结果检查等流程,帮助用户稳定完成 PDF 图片化处理。

80

2026.09.30

PixTV AI视频生成与无限画布创作
PixTV AI视频生成与无限画布创作

PixTV专题整理AI视频与视觉内容创作相关功能使用教程,涵盖AI生图、视频生成、无限画布、多模型创作、素材管理、声音音乐及视频剪辑等功能,帮助用户快速掌握PixTV从创意到成片的完整制作方法。

100

2026.09.29

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

300

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn