前言
昨天我们已经让子弹飞了起来,但是会面临一个和之前小蜜蜂一样的问题,小蜜蜂的行动应该限制在窗口内,那么子弹也是有相同之处,也需要限制一个移动范围,当超出范围之后,就要将其消灭,释放内存,因为子弹飞出屏幕外不是真的消失,只是不显示而已,依然占用内存空间。
一、删除子弹
按照上面的说法,我们只需要判断子弹的位置,如果子弹已经超出窗口就可以删除。这个判断放在哪里合适呢,个人觉得放在刷新窗口前比较合适,在显示窗口内容前判断是否需要删除后,就不用浪费资源了。这样,我们来修改主函数:
import pygame import settings from ship import Ship import game_functions as gf from pygame.sprite import Group def run_game(): pygame.init() new_setting=settings.Settings() screen = pygame.display.set_mode((new_setting.screen_width,new_setting.screen_height)) ship = Ship(screen,new_setting) pygame.display.set_caption("狂敲代码的橘子") bullets = Group() while True: gf.check_events(new_setting,screen,ship,bullets) ship.update() bullets.update() for bullet in bullets: if bullet.rect.bottom<=0: bullets.remove(bullet) print(len(bullets)) gf.update_screen(new_setting,screen,ship,bullets) run_game()
我们再确定了每个子弹的位置后,添加循环判断代码,之前我们说过,窗口的左上角坐标是(0.0),所以只要子弹的最下边小于0就可以判断该子弹已经出了窗口,因为在游戏的显示中通过肉眼是无法判断子弹飞出窗口后有没有删除,所以增加了 print(len(bullets))来测试,当子弹删除后,bullets的长度会减少,直至为0,经运行,确实达到了想要的目的。
这里还有一个问题,我们写的for循环是for bullet in bullets:,但是“大蟒蛇”上写的是for bullet in bullets.copy():,这两者有什么区别呢?当我们在遍历一个列表的同时对其进行修改(如添加、删除元素),可能会导致程序出错或产生意外的结果。这是因为在遍历过程中,Python会不断地更新列表的长度和元素的索引,从而可能导致索引错误或遗漏某些元素。
而使用bullets.copy()创建了一个副本,遍历的是这个副本,对原列表的修改不会影响到副本,从而避免了上述问题。虽然两种写法都能实现我们想要的目的,但是明显第二种更加严谨、稳妥,第一种也许不知道什么时候就会发生错误。
二、限制子弹数量
“大蟒蛇”中认为一般的游戏子弹都有限制,我不记得我玩的游戏子弹有限制,好像都是无限子弹,最多对子弹的发射速度有限制,我们还是按照书上来实践操作。这个功能应该不难实现,只要在创建子弹之前加一个判断,如果存在的子弹小于限制数,就创建一个子弹,并添加到bullets中,如果不是就不做任何操作,这个判断语句代码写在game_functions模块中,但在这之前,要先在settings模块设置一下最大子弹数量属性。
class Settings(): def __init__(self): self.screen_width = 800 self.screen_height = 600 self.bg_color = (220,220,220) self.ship_speed_factor = 0.1 self.bullet_speed_factor = 0.3 self.bullet_width = 2 self.bullet_hight = 5 self.bullet_color = 60,60,60 self.bullets_allowed = 3
import sys import pygame from bullet import Bullet def check_keydown_events(event,new_setting,screen,ship,bullets): if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: if len(bullets)<new_setting.bullets_allowed: new_bullet = Bullet(new_setting,screen,ship) bullets.add(new_bullet) def check_keyup_events(event,ship): if event.key == pygame.K_RIGHT: ship.moving_right = False if event.key == pygame.K_LEFT: ship.moving_left = False def check_events(new_setting,screen,ship,bullets): for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type==pygame.KEYDOWN: check_keydown_events(event,new_setting,screen,ship,bullets) elif event.type==pygame.KEYUP: check_keyup_events(event, ship) def update_screen(new_setting,screen,ship,bullets): screen.fill(new_setting.bg_color) for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() pygame.display.flip()
通过修改这两处代码,每次屏幕上最多只有3发子弹
可以看出,我一直在敲击空格键,但是窗口内最多只有3发子弹。
三、继续重构代码
现在“大蟒蛇”又觉得主程序内容有点多了,决定将判断删除子弹代码转移到game_functions中,并创建函数update_bullets,这样主函数只要调用update_bullets就行了,update_bullets里代码就是照搬过来的,没什么好说的。因为我们在判断按键是否为空格里又加了判断子弹数量,因此我还需单独写一个函数fire_bullet()来存放判断子弹数量代码,需要时直接调用,使代码块看起来更简洁。
import sys import pygame from bullet import Bullet def check_keydown_events(event,new_setting,screen,ship,bullets): if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: fire_bullet(new_setting, screen, ship, bullets) def check_keyup_events(event,ship): if event.key == pygame.K_RIGHT: ship.moving_right = False if event.key == pygame.K_LEFT: ship.moving_left = False def check_events(new_setting,screen,ship,bullets): for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type==pygame.KEYDOWN: check_keydown_events(event,new_setting,screen,ship,bullets) elif event.type==pygame.KEYUP: check_keyup_events(event, ship) def fire_bullet(new_setting,screen,ship,bullets): if len(bullets) < new_setting.bullets_allowed: new_bullet = Bullet(new_setting, screen, ship) bullets.add(new_bullet) def update_bullets(bullets): bullets.update() for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) def update_screen(new_setting,screen,ship,bullets): screen.fill(new_setting.bg_color) for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() pygame.display.flip()
总结
截至目前,玩家操作部分基本完成,后面我将添加敌对的虫族,以及用子弹消灭他们。