Python学习笔记(七)--游戏项目(2)


前篇请见:
Python学习笔记(六)–游戏项目(上)

重构:模块game_functions

在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易拓展。我们将创建一个名为game_functions的新模块,它将存储大量让《外星人入侵》运行的函数。通过创建模块game_functions,可避免alien_invasion.py太长,并使其逻辑更容易理解。

函数check_events()

我们首先把管理时间的代码移动到一个名为check_events()的函数中,以简化`run_ganme()``并隔离事件管理循环。通过隔离时间循环,可将时间管理和游戏的其他方面(如更新屏幕)分离。

check_events()放在一个名为game_functions的新模块中:

game_functions.py文件

import sys
import pygame

def check_events():
    # 监视键盘和鼠标事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

主文件alien_invasion.py:

import pygame
from settings import Settings
from ship import Ship
import game_functions as gf

def run_game():
    # 初始化游戏并创建一个屏幕对象,设置和pygame
    pygame.init()
    #seting类
    ai_settings = Settings()

    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Aline Invasion")

    #创建一艘飞船
    ship = Ship(screen)

    #游戏主循环
    while True:
        gf.check_events()
        # 每次循环时都重绘屏幕
        screen.fill(ai_settings.bg_color)
        ship.blitme()   # 重绘飞船

        #让最近绘制的屏幕可见
        pygame.display.flip()
run_game()

函数update_screen()

为进一步简化run_game(),下面将更新屏幕的代码移动到update_screen()的函数中,并将这个函数放在模块game_functions.py中:

import sys
import pygame

def check_events():
    # 监视键盘和鼠标事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

def update_screen(ai_settings,screen,ship):
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()  # 重绘飞船

    # 让最近绘制的屏幕可见
    pygame.display.flip()

新函数update_screen()包含三个形参,ai_settings,screen,ship。现在需要将alien_invasion.py中的while循环中更新屏幕的代码替换为对函数update_sceen()的调用。

alien_invasion.py

import pygame
from settings import Settings
from ship import Ship
import game_functions as gf

def run_game():
    # 初始化游戏并创建一个屏幕对象,设置和pygame
    pygame.init()
    #seting类
    ai_settings = Settings()

    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Aline Invasion")

    #创建一艘飞船
    ship = Ship(screen)

    #游戏主循环
    while True:
        gf.check_events()
        gf.update_screen(ai_settings,screen,ship)
run_game()

这两个函数让while循环变得更加简单,并让后续开发变得更加容易。(在模块game_functions中而不是run_game中完成大部分工作。)

对代码进行重构使其更容易拓展后,我们可以开始处理游戏的动态方面 了。

驾驶飞船

响应按键

每当用户按键是,都将在Pygame中注册一个事件。事件都是通过方法pygame.event.getf()获取的,因此在函数check_events()中,我们需要指定要检查哪些类型的事件。每次按键都被注册为一个KEYDOWN事件。

检测到KEYDOWN事件时,我们需要检查按下的是否是特定的键。例如,如果按下的是右箭头键,我们就增大飞船的rect.centerx值,将飞船向右移动:

game_ functions.py

import sys
import pygame

def check_events(ship):
    # 监视键盘和鼠标事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                #向右移动飞船
                ship.rect.centerx += 1

def update_screen(ai_settings,screen,ship):
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()  # 重绘飞船

    # 让最近绘制的屏幕可见
    pygame.display.flip()

我们在函数check_events()中包含形参ship,因为玩家按右箭头键时,需要将飞船向右移动。在函数check_events()内部,我们在事件循环中添加了一个elif代码块,以便在Pygame 检测到KEYDOWN事件时作出响应。
我们读取属性event.key,以检查按下的是否是右箭头键。如果按下的是右箭头键,就将ship.rect.centerx的值加1,从而将飞船向右移动。
(这里需要在alien_invasion.py中更新函数的调用)

允许不断移动

玩家按住右箭头键不放时,我们希望飞船不断地向右移动,直到玩家松开为止。我们将让游戏检测pygame.KEYUP事件,以便玩家松开右箭头键时我们能够知道这一点;然后,我们将结合使用KEYDOWN和KEYUP事件,以及一个名为moving_right的标志来实现持续移动。

飞船不动时,标志moving_right将为False。玩家按下右箭头键时,我们将这个标志设置为True;而玩家松开时,我们将这个标志重新设置为False。

飞船的属性都由Ship类控制,因此我们将给这个类添加一个名为moving_right的属性和一个名为update()的方法。方法update()检查标志moving_right的状态,如果这个标志为True,就调整飞船的位置。每当需要调整飞船的位置时,我们都调用这个方法。

更新ship.py:

import pygame

class Ship():
    def __init__(self, screen):
        self.screen = screen

        #加载飞船图片并获取其外接矩形
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部中央
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom
        
        # 移动标志
        self.moving_right = False
    
    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.rect.centerx +=1
            
    def blitme(self):
        """"在指定位置绘制飞船"""
        self.screen.blit(self.image,self.rect)

在方法__init__()中,我们添加了属性self.moving_right,并将其初始值设置为False

接下来,我们添加了方法update(),它在前述标识为True时向右移动飞船。

下面来修改check_events(),使其在玩家按下右箭头键时将moving_right甚至为True,并在玩家松开时将moving_right设置为False。

import sys
import pygame

def check_events(ship):
    # 监视键盘和鼠标事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                # 修改移动参数
                ship.moving_right = True

        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False

def update_screen(ai_settings,screen,ship):
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()  # 重绘飞船

    # 让最近绘制的屏幕可见
    pygame.display.flip()

我们修改了游戏在玩家按下右箭头键时响应的方式:不直接调整飞船的位置,而只是将moving_right设置为True。我们添加了一个新的elif代码块,用于响应KEYUP事件:玩家松开右箭头键(K_RIGHT)时,我们将moving_right设置为False。

最后,我们需要修改alien_invasion.py中的while循环,以便每次执行循环时都调用飞船的方法update()

import pygame
from settings import Settings
from ship import Ship
import game_functions as gf

def run_game():
    # 初始化游戏并创建一个屏幕对象,设置和pygame
    pygame.init()
    # setting类
    ai_settings = Settings()

    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Aline Invasion")

    # 创建一艘飞船
    ship = Ship(screen)

    # 游戏主循环
    while True:
        gf.check_events(ship)
        ship.update()
        gf.update_screen(ai_settings, screen, ship)
run_game()

飞船的位置将在检测到键盘事件后(但在更新屏幕前)更新。这样,玩家输入时,飞船的位置将更新,从而确保使用更新后的位置将飞船绘制到屏幕上。

左右移动

飞船能够不断地向右移动后,添加向左移动的逻辑很容易。我们将再次修改Ship类和函数check_events()

import pygame

class Ship():
    def __init__(self, screen):
        self.screen = screen

        #加载飞船图片并获取其外接矩形
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部中央
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

        # 移动标志
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.rect.centerx +=1
        if self.moving_left:
            self.rect.centerx -=1

    def blitme(self):
        """"在指定位置绘制飞船"""
        self.screen.blit(self.image,self.rect)

在方法__init__()中,我们添加了标志self.moving_left;在方法update()中,我们添加了一个if代码块而不是elif代码块,这样如果玩家同时按下了左右箭头键,将先增大飞船的rect.centerx值,再降低这个值,即飞船的位置保持不变。

import sys
import pygame

def check_events(ship):
    # 监视键盘和鼠标事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                # 修改移动参数
                ship.moving_right = True
            elif event.key == pygame.K_LEFT:
                ship.moving_left = True

        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False
            elif event.key == pygame.K_LEFT:
                ship.moving_left = False

def update_screen(ai_settings,screen,ship):
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()  # 重绘飞船

    # 让最近绘制的屏幕可见
    pygame.display.flip()

调整飞船的速度

当前,每次执行while循环时,飞船最多移动1像素,但我们可以在Settings类中添加属性ship_speed_factor,用于控制飞船的速度。我们将根据这个属性决定飞船在每次循环时最多移动多少距离.

settings.py

class Settings():
    """存储《外星人入侵》的所有设置的类"""
    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_color = (20, 46, 93)

        # 飞船速度的设置
        self.ship_speed_factor = 1.5

本行代码加在__init__()中,但是rectcenterx等属性只能存储整数值,因此我们需要对ship类做些修改

import pygame

class Ship():
    def __init__(self, ai_settings, screen):
        self.screen = screen
        
        self.ai_settings = ai_settings
        
        #加载飞船图片并获取其外接矩形
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部中央
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom
        
        self.center = float(self.rect.centerx)
        
        # 移动标志
        self.moving_right = False
        self.moving_left = False


    def update(self):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的center值而不是rect
        if self.moving_right:
            self.center += self.ai_settings.ship_speed_factor 
        if self.moving_left:
            self.center -= self.ai_settings.ship_speed_factor
            
        # 根据self.center更新rect对象
        self.rect.centerx = self.center

    def blitme(self):
        """"在指定位置绘制飞船"""
        self.screen.blit(self.image,self.rect)

首先,我们在__init__()的形参列表中添加了ai_settings,让飞船能够获取其速度设置。接下来,我们将形参ai_settings的值存储在一个属性中,以便能在update()中使用它。鉴于现在调整飞船的位置时,将增加或减去一个单位为像素的小数值,因此需要将位置存储在一个能够存储小数值的变量中。可以使用小数来设置rect的属性,但rect将只存储这个值的整数部分。为准确地存储飞船的位置,我们定义了一个可存储小数值的新属性self.center。我们使用函数float()self.rect.centerx的值转换为小数,并将结果存储到self.center中。

alien_invasion.py中创建传入实参ai_settings

		# 创建一艘飞船
    ship = Ship(ai_settings, screen)

现在,只要ship_speed_factor的值大于1,飞船的移动速度就会比以前更快。

限制飞船的活动范围

当前,如果玩家按住箭头键的时间足够长,飞船将移到屏幕外面,消失得无影无踪。因此我们要限制飞船的活动范围,让飞船到达屏幕边缘后停止移动。

我们修改Ship类的方法update()

import pygame

class Ship():
    def __init__(self, ai_settings, screen):
        self.screen = screen

        self.ai_settings = ai_settings

        # 加载飞船图片并获取其外接矩形
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        # 将每艘新飞船放在屏幕底部中央
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

        # 在飞船的属性center中存储小数值
        self.center = float(self.rect.centerx)

        # 移动标志
        self.moving_right = False
        self.moving_left = False


    def update(self):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的center值而不是rect
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.center += self.ai_settings.ship_speed_factor
        if self.moving_left and self.rect.left > 0:
            self.center -= self.ai_settings.ship_speed_factor

        # 根据self.center更新rect对象
        self.rect.centerx = self.center

    def blitme(self):
        """"在指定位置绘制飞船"""
        self.screen.blit(self.image,self.rect)

注意update()函数

	def update(self):
		"""根据移动标志调整飞船的位置"""
		# 更新飞船的center值,而不是rect
		if self.moving_right and self.rect.right < self.screen_rect.right:
			self.center += self.ai_settings.ship_speed_factor
		if self.moving_left and self.rect.left > 0:
			self.center -= self.ai_settings.ship_speed_factor
			# 根据self.center更新rect对象
			self.rect.centerx = self.center

重构check_events()

随着我们开发的进行,函数check_event()变得越来越长,我们将其部分代码放在两个函数中:
一个处理KEYDOWN函数,另一个处理KEYUP

import sys
import pygame

def check_keydown_events(event, ship):
    """响应按键"""
    if event.key == pygame.K_RIGHT:
        # 修改移动参数
        ship.moving_right = True
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True

def check_keyup_events(event, ship):
    """响应松开"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False

def check_events(ship):
    # 监视键盘和鼠标事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ship)

        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)

def update_screen(ai_settings,screen,ship):
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()  # 重绘飞船

    # 让最近绘制的屏幕可见
    pygame.display.flip()

我们创建了两个新函数:check_keydown_events()check_keyup_events(),它们都包含形参eventship。这两个函数的代码是从check_events()中复制而来的,因此我们将函数check_events中相应的代码替换成了对这两个函数的调用。现在,函数check_events()更简单,代码结构更清晰。这样,在其中响应其他玩家输入时将更容易。

后续内容详见下节博客。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiaki_0ff

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值