前言
在上一篇中,我们初步熟悉了pygame的控制流程,但这对于一个游戏而言是远远不够的。所以在这一篇中,我们的任务是添加一架飞机(玩家),并且能够控制它进行移动,这样我们就又离目标进了一步了~ε=ε=(ノ≧∇≦)ノ
正片开始!
1. 把我们的战斗机搬上屏幕
在正式上代码以前,有一点需要说明的,我们可以看到,所有屏幕上出现的元素都在资源文件(resources/image/shoot.png)中,那我们要怎么做才能把一只飞机给裁剪出来呢?在pygame中,所有在屏幕上显示的元素都可以视为一个surface,利用这个特点,我们可以把资源文件导入(load()函数),然后用surface.subsurface()函数把shoot.png中我们想要的元素裁剪出来,这样问题就解决了~(tips:怎样才知道shoot.png中飞机的准确坐标呢?在resources/image/shoot.pack文件中已经详细记录了每个屏幕元素的左上角坐标以及它的宽和高了)
先上代码:(在注释# == new add ==之间的代码为新加入代码)
1 import pygame # 导入pygame库 2 from pygame.locals import * # 导入pygame库中的一些常量 3 from sys import exit # 导入sys库中的exit函数 4 5 # 定义窗口的分辨率 6 SCREEN_WIDTH = 480 7 SCREEN_HEIGHT = 640 8 9 # 计数ticks == new add == 10 ticks = 0 11 # 计数ticks == new add == 12 13 # 初始化游戏 14 pygame.init() # 初始化pygame 15 screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT]) # 初始化窗口 16 pygame.display.set_caption('This is my first pygame-program') # 设置窗口标题 17 18 # 载入背景图 19 background = pygame.image.load('resources/image/background.png') 20 21 # 载入资源图片 == new add == 22 shoot_img = pygame.image.load('resources/image/shoot.png') 23 # 用subsurface剪切读入的图片 24 hero1_rect = pygame.Rect(0, 99, 102, 126) 25 hero2_rect = pygame.Rect(165, 360, 102, 126) 26 hero1 = shoot_img.subsurface(hero1_rect) 27 hero2 = shoot_img.subsurface(hero2_rect) 28 hero_pos = [200, 500] 29 # 载入资源图片 == new add == 30 31 # 事件循环(main loop) 32 while True: 33 34 # 绘制背景 35 screen.blit(background, (0, 0)) 36 37 # 绘制飞机 == new add == 38 if ticks % 50 < 25: 39 screen.blit(hero1, hero_pos) 40 else: 41 screen.blit(hero2, hero_pos) 42 ticks += 1 43 # 绘制飞机 == new add == 44 45 # 更新屏幕 46 pygame.display.update() 47 48 # 处理游戏退出 49 # 从消息队列中循环取 50 for event in pygame.event.get(): 51 if event.type == pygame.QUIT: 52 pygame.quit() 53 exit()
不妨运行一下:
可以看到,我们的飞机动了,究竟是怎样实现动画效果的呢?在代码中,新加入的代码一共有三处,显示玩家飞机的那一段已经解释过了,现在我们来解释其余的两段新加入的代码。首先增加了一个计数变量ticks,在消息循环中,每循环一次就累加一次,可以理解为每一个周期就是1 tick,我们可以利用周期数来分隔不同的显示效果;我们读入了两张不同的玩家飞机图片,利用周期数实现每50个周期,前25个周期显示hero1,后25个周期显示hero2,这样就有了缓慢变化的动态效果。不过值得注意的是,这样每一次循环就计算并判断一次ticks的做法明显不是好方法。还有!(╯°口°)╯(┴—┴,Python是木有自增表达式的,我试图++ticks结果报错了。
战斗机已经准备就绪,该教会飞行员怎样操作了~(`・ω・´)
2. 用键盘控制飞机移动
我们知道,每敲一下键盘都会产生一个事件,而这个事件能被Python检测到,既然知道了这个大前提,那控制飞机就很容易了。
come on code~ ( °∀°)ノ
1 import pygame # 导入pygame库 2 from pygame.locals import * # 导入pygame库中的一些常量 3 from sys import exit # 导入sys库中的exit函数 4 5 # 定义窗口的分辨率 6 SCREEN_WIDTH = 480 7 SCREEN_HEIGHT = 640 8 9 ticks = 0 10 # dict == new add == 11 offset = {pygame.K_LEFT:0, pygame.K_RIGHT:0, pygame.K_UP:0, pygame.K_DOWN:0} 12 # dict == new add == 13 14 # 初始化游戏 15 pygame.init() # 初始化pygame 16 screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT]) # 初始化窗口 17 pygame.display.set_caption('This is my first pygame-program') # 设置窗口标题 18 19 # 载入背景图 20 background = pygame.image.load('resources/image/background.png') 21 22 # 载入飞机图片 23 shoot_img = pygame.image.load('resources/image/shoot.png') 24 # 用subsurface剪切读入的图片 25 hero1_rect = pygame.Rect(0, 99, 102, 126) 26 hero2_rect = pygame.Rect(165, 360, 102, 126) 27 hero1 = shoot_img.subsurface(hero1_rect) 28 hero2 = shoot_img.subsurface(hero2_rect) 29 hero_pos = [200, 500] 30 31 # 事件循环(main loop) 32 while True: 33 34 # 绘制背景 35 screen.blit(background, (0, 0)) 36 37 # 绘制飞机 38 if ticks % 50 < 25: 39 screen.blit(hero1, hero_pos) 40 else: 41 screen.blit(hero2, hero_pos) 42 ticks += 1 # python已略去自增运算符 43 44 # 更新屏幕 45 pygame.display.update() 46 47 # 处理游戏退出 48 # 从消息队列中循环取 49 for event in pygame.event.get(): 50 if event.type == pygame.QUIT: 51 pygame.quit() 52 exit() 53 54 # Python中没有switch-case 多用字典类型替代 55 # 控制方向 == new add == 56 if event.type == pygame.KEYDOWN: 57 if event.key in offset: 58 offset[event.key] = 3 59 elif event.type == pygame.KEYUP: 60 if event.key in offset: 61 offset[event.key] = 0 62 63 # part 1 64 #offset_x = offset[pygame.K_RIGHT] - offset[pygame.K_LEFT] 65 #offset_y = offset[pygame.K_DOWN] - offset[pygame.K_UP] 66 #hero_pos = [hero_pos[0] + offset_x, hero_pos[1] + offset_y] 67 # part 2 68 offset_x = offset[pygame.K_RIGHT] - offset[pygame.K_LEFT] 69 offset_y = offset[pygame.K_DOWN] - offset[pygame.K_UP] 70 hero_pos = [hero_pos[0] + offset_x, hero_pos[1] + offset_y] 71 # 控制方向 == new add ==
新加入了两处,一个是字典类型的变量,一个是控制方向的代码。跟之前控制程序退出的代码一样,依然是从事件队列中取事件;当event.type为按键事件时,再判断event.key是否属于上下左右四个键位中的其中一个;最后在其相应方向上给一个偏移量,即完成判断的过程;松键的话该方向上的偏移量赋值为0,这样该键方向上就没有位移了;最后将偏移量加到飞机的pos上,下一轮刷新时自然就到移动到新地方了。(这里插播一个事,我以前写过c++和java,所以写到判断键盘键位时,很自然就想用switch-case语句,没想到竟然出错了(╯°口°)╯(┴—┴,后来才发现原来Python是没有switch-case语句的,switch-case语句多用字典数据结构代替,感觉这样的写法就更灵活了)
上面讲得都比较简单,现在我们要思考一个问题,上面的代码中,line 63-66和line 67-70的代码是一样的,但两部分代码的效果却是很不一样。part1代码使得我们要按一次键飞机才会动一次,而在part2代码中我们可以长按方向键来控制飞机,不禁感慨一下Python的缩进。part1是在for循环的影响下的,也就是说,事件队列中有事件才会执行part1的代码,假设我们长按方向左键(注意,击键一次只会产生一个pygame.KEYDOWN事件,所以长按也只触发一次),飞机也只会往左移3个像素点;而part2代码由于不在for循环内,所以每一tick就向左移3个像素点直到松开方向左键。
演示一下效果:
好的,控制飞机已经不成问题了,不过大家发现飞机有一个小问题了吗?它会穿到窗口的“外面”去!不过我们只要添加一个小小的限制就可以解决问题了,只要把line68-70换成以下边界判断代码就可以了。
1 hero_x = hero_pos[0] + offset[pygame.K_RIGHT] - offset[pygame.K_LEFT] 2 hero_y = hero_pos[1] + offset[pygame.K_DOWN] - offset[pygame.K_UP] 3 if hero_x < 0: 4 hero_pos[0] = 0 5 elif hero_x > SCREEN_WIDTH - hero1_rect.width: 6 hero_pos[0] = SCREEN_WIDTH - hero1_rect.width 7 else: 8 hero_pos[0] = hero_x 9 10 if hero_y < 0: 11 hero_pos[1] = 0 12 elif hero_y > SCREEN_HEIGHT - hero1_rect.height: 13 hero_pos[1] = SCREEN_HEIGHT - hero1_rect.height 14 else: 15 hero_pos[1] = hero_y
今天功能就完成到这里吧~(`・ω・´)