今天我写完了我的飞机大战游戏,并作出了一些视频中没有的bug修复和程序改进。
那先摆出我写好的代码记录一下这个高兴的时刻吧。(此处代码可以跳过,完全为了记录我第一个游戏而已)
import pygame as pg
import sys
import traceback
import Plane
import Enemy
import Bullet
import Supply
import random
pg.init()
pg.mixer.init()
pm = pg.mixer
pg.display.set_caption('飞机大战') #屏幕设置
running = True
clock = pg.time.Clock()
size = width,height = 480,700
screen = pg.display.set_mode(size)
bg = pg.image.load(r'images\background.png').convert()
RED = (255,0,0)
GREEN = (0,255,0)
BLACK = (0,0,0)
WHITE = (255,255,255)
pm.music.load('sound/game_music.ogg') #载入音乐
pm.music.set_volume(0.1)
bullet_sound = pm.Sound('sound/bullet.wav')
bullet_sound.set_volume(0.2)
bomb_sound = pm.Sound('sound/use_bomb.wav')
bomb_sound.set_volume(0.2)
supply_sound = pm.Sound('sound/supply.wav')
supply_sound.set_volume(0.2)
get_bomb_sound = pm.Sound('sound/get_bomb.wav')
get_bomb_sound.set_volume(0.2)
get_bullet_sound = pm.Sound('sound/get_bullet.wav')
get_bullet_sound.set_volume(0.2)
upgrade_sound = pm.Sound('sound/upgrade.wav')
upgrade_sound.set_volume(0.2)
enemy3_fly_sound = pm.Sound('sound/enemy3_flying.wav')
enemy3_fly_sound.set_volume(0.3)
enemy1_down_sound = pm.Sound('sound/enemy1_down.wav')
enemy1_down_sound.set_volume(0.1)
enemy2_down_sound = pm.Sound('sound/enemy2_down.wav')
enemy2_down_sound.set_volume(0.2)
enemy3_down_sound = pm.Sound('sound/enemy3_down.wav')
enemy3_down_sound.set_volume(0.5)
me_down_sound = pm.Sound('sound/me_down.wav')
me_down_sound.set_volume(0.05)
def add_enemys1(group1,group2,num):
for i in range(num):
e1 = Enemy.enemy1(size)
group1.add(e1)
group2.add(e1)
def add_enemys2(group1,group2,num):
for i in range(num):
e1 = Enemy.enemy2(size)
group1.add(e1)
group2.add(e1)
def add_enemys3(group1,group2,num):
for i in range(num):
e1 = Enemy.enemy3(size)
group1.add(e1)
group2.add(e1)
def speed_up(group,inc):
for each in group:
each.speed += inc
def main():
#补给
triple_supply = Supply.triple_supply(size)
many_bullet_supply = Supply.many_bullet_supply(size)
bo_supply = Supply.bomb_supply(size)
bu_supply = Supply.bullet_supply(size)
SUPPLY = pg.USEREVENT
pg.time.set_timer(SUPPLY,30*1000)
TIME = pg.USEREVENT + 1
second = 0
second_count = pg.time.set_timer(TIME,1*1000) #用于弥补暂停后的时间差对补给发放的影响
#游戏结束选项
Gameover = pg.image.load('images/gameover.png').convert_alpha()
Again = pg.image.load('images/again.png').convert_alpha()
Gameover_rect = Gameover.get_rect()
Again_rect = Again.get_rect()
Gameover_rect.left, Gameover_rect.top = 100,500
Again_rect.left, Again_rect.top = 100,450
#炸弹
bomb_count = 1
bomb_image = pg.image.load('images/bomb.png').convert_alpha()
bomb_rect = bomb_image.get_rect( )
bomb_rect.left,bomb_rect.top = 10,height-bomb_rect.height-10
level = 1
score =0
score_font = pg.font.Font('font/font.ttf',36) #字体设置
gameover_font = pg.font.Font('font/font.ttf',50)
me_switch = True #我方飞机动态切换
INVINCIBLE = pg.USEREVENT + 3 #我方飞机复活后无敌状态
invincible = False
delay = 100
pm.music.play(-1) #-1意为无限循环此音乐
bullets_dictionary = {
} #子弹编排字典:将子弹与每个数字对应,可以用于将子弹分开
dict = [] #子弹容器数组:将每个子弹对象从中取出,编排为字典
bullets_dictionary2 = {
}
dict_2 = []
bullets_dictionary3 = {
}
dict_3 = []
bullets_dictionary4 = {
}
dict_4 = []
x = -1 #子弹索引号
b_num = 4 #子弹数量
is_double = False #超级子弹
is_multiple = False #多重子弹
is_triple = False #三重子弹
#暂停操作
pause = False
pause_nor_image = pg.image.load('images/pause_nor.png').convert_alpha()
pause_pressed_image = pg.image.load('images/pause_pressed.png').convert_alpha()
resume_nor_image = pg.image.load('images/resume_nor.png').convert_alpha()
resume_pressed_image = pg.image.load('images/resume_pressed.png').convert_alpha()
pause_rect = pause_nor_image.get_rect()
pause_rect.left, pause_rect.top = width-pause_rect.width-10,10
pause_image = pause_nor_image #图标初始化
me = Plane.MyPlane(size) #我方飞机实例化
groups = pg.sprite.Group()
enemys1 = pg.sprite.Group() #生成小型敌机
add_enemys1(enemys1,groups,15)
enemys2 = pg.sprite.Group() #生成中型敌机
add_enemys2(enemys2,groups,15)
enemys3 = pg.sprite.Group() #生成大型敌机
add_enemys3(enemys3,groups,15)
bullets_1 = pg.sprite.Group() #生成子弹
for i in range (b_num):
i = Bullet.Bullet_1(size,me)
bullets_1.add(i)
bullets_2 = pg.sprite.Group() #生成超级子弹
for i in range (2*b_num):
i = Bullet.Bullet_2(size,me,((me.rect.left+me.rect.right)/2+30,me.rect.top+30))
bullets_2.add(i)
USETIME = pg.USEREVENT +2 #超级子弹使用时间事件
for each in bullets_1: #引入子弹
dict.append(each)
for i in range(b_num):
bullets_dictionary.setdefault(i,dict[i])
for each in bullets_2:
dict_2.append(each)
for i in range(2*b_num):
bullets_dictionary2.setdefault(i,dict_2[i])
bullets_3 = pg.sprite.Group() #生成多重子弹
for i in range (10*b_num):
i = Bullet.Bullet_3(size,me)
bullets_3.add(i)
for each in bullets_3:
dict_3.append(each)
for i in range(10*b_num):
bullets_dictionary3.setdefault(i,dict_3[i])
bullets_4 = pg.sprite.Group() #生成三重子弹
for i in range (3*b_num):
i = Bullet.Bullet_4(size,me)
bullets_4.add(i)
for each in bullets_4:
dict_4.append(each)
for i in range(3*b_num):
bullets_dictionary4.setdefault(i,dict_4[i])
e1_destory_index = 0 #毁灭图像集合索引
e2_destory_index = 0
e3_destory_index = 0
me_destory_index = 0
running = True
while running:
GameOver = False
record = False
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit() #这个必须在sys.quit前面,否则程序冲突
sys.exit()
elif event.type == SUPPLY: #补给发放
supply_sound.play()
chioces = random.choice(['1','2','3','4'])
if chioces == '1':
bo_supply.reset()
elif chioces == '2':
bu_supply.reset()
elif chioces == '3':
many_bullet_supply.reset()
elif chioces == '4':
triple_supply.reset()
pg.time.set_timer(SUPPLY,30*1000) #将暂停后一次的补给发放时间重置
pg.time.set_timer(TIME,1*1000)
elif event.type == TIME: #弥补暂停对补给的影响
if second < 30:
second += 1
else:
second = 0
elif event.type == pg.MOUSEBUTTONDOWN: #鼠标点击切换暂停
if event.button == 1 and Again_rect.collidepoint(event.pos): #重新开始游戏
puase = False
pm.unpause()
enemy3_fly_sound.stop()
main()
if event.button == 1 and Gameover_rect.collidepoint(event.pos): #结束游戏
pg.quit()
elif event.button == 1 and pause_rect.collidepoint(event.pos):
pause = not pause
if pause:
pm.music.pause() #音乐暂停
pm.pause() #所有音效暂停
pg.time.set_timer(SUPPLY,0) #停止补给计时投放
pg.time.set_timer(TIME,0) #停止计时
else:
pm.music.unpause() #音乐重新开始
pm.unpause() #所有音效重新开始
pg.time.set_timer(SUPPLY,30*1000 - second*1000) #重新开始补给计时投放
elif event.type == pg.MOUSEMOTION:
if pause_rect.collidepoint(event.pos): #暂停图标变换
if pause:
pause_image = resume_pressed_image
else:
pause_image = pause_pressed_image
else:
if pause:
pause_image = resume_nor_image
else:
pause_image = pause_nor_image
elif not pause:
if event.type == pg.KEYDOWN:
if event.key== pg.K_SPACE and bomb_count > 0: #全屏炸弹
bomb_count -= 1
bomb_sound.play()
for each in groups:
if each.rect.bottom > 0:
each.alive = False
if event.type == USETIME: #超级子弹使用时间
is_double = False
b_num = 4
pg.time.set_timer(USETIME,0)
if event.type == USETIME + 1: #多重子弹使用时间
is_multiple = False
b_num = 4
pg.time.set_timer(USETIME+1,0)
if event.type == USETIME + 2: #三重子弹使用时间
is_triple = False
b_num = 4
pg.time.set_timer(USETIME+2,0)
if event.type == INVINCIBLE: #无敌状态
invincible = False
pg.time.set_timer(INVINCIBLE,0)
screen.blit(bg,(0,0)) #把屏幕重新绘制,防止暂停偷看屏幕
if not pause: #游戏暂停
key_pressed = pg.key.get_pressed() #获得哪些按键被长按
if key_pressed[pg.K_w] or key_pressed[pg.K_UP]: #游戏操作
me.moveUp()
if key_pressed[pg.K_s] or key_pressed[pg.K_DOWN]:
me.moveDown()
if key_pressed[pg.K_a] or key_pressed[pg.K_LEFT]:
me.moveLeft()
if key_pressed[pg.K_d] or key_pressed[pg.K_RIGHT]:
me.moveRight()
screen.blit(bg,(0,0)) #绘制背景
if not invincible:
collide = pg.sprite.spritecollide(me,groups,False,pg.sprite.collide_mask) #碰撞检测
if collide:
me.alive = False
for each in collide:
each.alive = False
#判断是否吃到全屏炸弹补给包
if bo_supply.alive:
screen.blit(bo_supply.image,bo_supply.rect)
bo_supply.move()
bo_colide = pg.sprite.collide_mask(bo_supply,me)
if bo_colide:
get_bomb_sound.play()
bo_supply.alive = False
if bomb_count < 3:
bomb_count += 1
#判断是否吃到超级子弹补给包
if bu_supply.alive:
screen.blit(bu_supply.image,bu_supply.rect)
bu_supply.move()
bu_colide = pg.sprite.collide_mask(bu_supply,me)
if bu_colide:
get_bullet_sound.play()
bu_supply.alive = False
is_double = True
b_num = 8
pg.time.set_timer(USETIME,15*1000)
#判断是否吃到多重子弹补给包
if many_bullet_supply.alive:
screen.blit(many_bullet_supply.image,many_bullet_supply.rect)
many_bullet_supply.move()
many_bullet_colide = pg.sprite.collide_mask(many_bullet_supply,me)
if many_bullet_colide :
get_bullet_sound.play()
many_bullet_supply.alive = False
is_multiple = True
b_num = 20
pg.time.set_timer(USETIME+1,5*1000)
#判断是否吃到三重子弹补给包
if triple_supply.alive:
screen.blit(triple_supply.image,triple_supply.rect)
triple_supply.move()
triple_colide = pg.sprite.collide_mask(triple_supply,me)
if triple_colide :
get_bullet_sound.play()
triple_supply.alive = False
is_triple = True
b_num = 12
pg.time.set_timer(USETIME+2,10*1000)
screen.blit(bomb_image,bomb_rect)
bomb_text = score_font.render('× %s'%str(bomb_count),True,WHITE)
#这一句要放在所有可以改变bomb_count的语句后面,并且这里借用了变量score_font
screen.blit(bomb_text,(10+10+bomb_rect.width,height-bomb_rect.height-10))
delay -= 1 #我方飞机动态切换
if me.alive:
if me_switch:
screen.blit(me.image1,me.rect)
else:
screen.blit(me.image2,me.rect)
if not delay % 10:
me_switch = not me_switch
delay = 100
else: #毁灭
if not (delay % 3):
me_down_sound.play()
screen.blit(me.destory_images[me_destory_index],me.rect)
me_destory_index = (me_destory_index+1) % 4
if me_destory_index == 0:
me.reset()
pg.time.set_timer(INVINCIBLE,1*1000)
invincible = True #复活无敌状态打开
if is_double:
bullets = bullets_2
elif is_multiple:
bullets = bullets_3
elif is_triple:
bullets = bullets_4
else:
bullets = bullets_1
if x >= b_num - 1 : #分开索引号的范围控制
x = 0
if not delay%10:
bullet_sound.play()
x += 1
if is_double: #子弹回溯分开
bullets_dictionary2[x].reset(me,((me.rect.left+me.rect.right)/2-33,me.rect.top+30))
bullets_dictionary2[x-1].reset(me,((me.rect.left+me.rect.right)/2-33,me.rect.top+30))
elif is_multiple:
bullets_dictionary3[x+2].reset(me)
bullets_dictionary3[x+1].reset(me)
bullets_dictionary3[x].reset(me)
bullets_dictionary3[x-1].reset(me)
elif is_triple:
bullets_dictionary4[x].reset(me)
bullets_dictionary4[x-1].reset(me)
else:
bullets_dictionary[x].reset(me)
for b in bullets:
if b.alive: #子弹在alive状态下正常飞行
if is_double:
b.Flying(me,((me.rect.left+me.rect.right)/2+30,me.rect.top+30))
else:
b.Flying(me)
bullets_dictionary4[1].leap_left(me)
bullets_dictionary4[2].leap_right(me)
bullets_dictionary4[3].leap_left(me)
bullets_dictionary4[4].leap_right(me)
bullets_dictionary4[5].leap_left(me)
bullets_dictionary4[6].leap_right(me)
bullets_dictionary4[7].leap_left(me)
bullets_dictionary4[8].leap_right(me)
screen.blit(b.image,b.rect)
hit = pg.sprite.spritecollide(b,groups,False,pg.sprite.collide_mask) #子弹的碰撞检测
if hit:
if not is_double:
b.reset(me) #命中敌机后放回我方飞机
else:
b.reset(me,((me.rect.left+me.rect.right)/2+30,me.rect.top+30))
for enemy in hit:
if enemy in enemys1: #对命中不同敌机后的不同效果
enemy.alive = False
else:
if enemy.energy > 0:
enemy.energy -= 1
enemy.hitted =True
else:
enemy.alive = False
for each in enemys3: #引入大型敌机
if each.alive:
if each.hitted == True:
screen.blit(each.image_hit,each.rect)
each.hitted = False
each.moveEnemy3(enemy3_fly_sound)
if each.rect.bottom >= -0.5*each.rect.height:
enemy3_fly_sound.play(-1)
if me_switch: #大型机的动态切换
screen.blit(each.image1,each.rect)
else:
screen.blit(each.image2,each.rect)
#绘制血槽
pg.draw.line(screen,BLACK,(each.rect.left,each.rect.top-5),(each.rect.right,each.rect.top-5),2) #底槽
each.energy_ratio = each.energy/Enemy.enemy3.energy
if each.energy_ratio < 0.3:
color = RED
else:
color = GREEN
pg.draw.line(screen,color,(each.rect.left,each.rect.top-5),\
(each.rect.left+int(each.energy_ratio*each.rect.width),\
each.rect.top-5),2)
else: #毁灭
if not (delay % 3):
enemy3_down_sound.play()
enemy3_fly_sound.stop()
screen.blit(each.destory_images[e3_destory_index],each.rect)
e3_destory_index = (e3_destory_index+1) % 6
if e3_destory_index == 0:
each.reset( )
score += 5
for each in enemys2: #引入中型敌机
if each.alive:
if each.hitted == True:
screen.blit(each.image_hit,each.rect)
each.hitted = False
each.moveEnemy2()
screen.blit(each.image1,each.rect)
#绘制血槽
pg.draw.line(screen,BLACK,(each.rect.left,each.rect.top-5),(each.rect.right,each.rect.top-5),2) #底槽
each.energy_ratio = each.energy/Enemy.enemy2.energy
if each.energy_ratio < 0.3:
color = RED
else:
color = GREEN
pg.draw.line(screen,color,(each.rect.left,each.rect.top-5),\
(each.rect.left+int(each.energy_ratio*each.rect.width),\
each.rect.top-5),2)
else: #毁灭
if not (delay % 3):
enemy2_down_sound.play()
screen.blit(each.destory_images[e2_destory_index],each.rect)
e2_destory_index = (e2_destory_index+1) % 4
if e2_destory_index == 0:
each.reset( )
score += 2
for each in enemys1: #引入小型敌机
if each.alive:
each.moveEnemy1()
screen.blit(each.image1,each.rect)
else: #毁灭
if not (delay % 3):
enemy1_down_sound.play()
screen.blit(each.destory_images[e1_destory_index],each.rect)
e1_destory_index = (e1_destory_index+1) % 4
if e1_destory_index == 0:
each.reset( )
score += 1
#难度升级
if level == 1 and score > 50:
level = 2
upgrade_sound.play()
#增加3架小型敌机,2架中型敌机,1架大型敌机
add_enemys1(enemys1,groups,3)
add_enemys2(enemys2,groups,2)
add_enemys3(enemys3,groups,1)
#提升小型敌机速度
speed_up(enemys1,1)
if level == 2 and score > 200:
level = 3
upgrade_sound.play()
#增加3架小型敌机,2架中型敌机,1架大型敌机
add_enemys1(enemys1,groups,3)
add_enemys2(enemys2,groups,2)
add_enemys3(enemys3,groups,1)
#提升小型敌机速度
speed_up(enemys1,1)
speed_up(enemys2,1)
if level == 3 and score > 500:
level = 4
upgrade_sound.play()
#增加5架小型敌机,3架中型敌机,2架大型敌机
add_enemys1(enemys1,groups,5)
add_enemys2(enemys2,groups,3)
add_enemys3(enemys3,groups,2)
#提升小型敌机速度
speed_up(enemys1,1)
speed_up(enemys2,1)
speed_up(enemys3,1)
if level == 4 and score > 1000:
level = 5
upgrade_sound.play()
#增加5架小型敌机,3架中型敌机,2架大型敌机
add_enemys1(enemys1,groups,5)
add_enemys2(enemys2,groups,3)
add_enemys3(enemys3,groups,2)
#提升小型敌机速度
speed_up(enemys1,1)
speed_up(enemys2,1)
speed_up(enemys3,1)
#分数显示
score_text = score_font.render('Score : %s'%str(score),True,WHITE) #True意为抗锯齿
screen.blit(score_text,(10,3))
screen.blit(pause_image,pause_rect)
#绘制生命数量
for i in range(me.life):
screen.blit(me.image_life,((size[0]-10-(i+1)*me.image_life_rect.width),(size[1]-10-me.image_life_rect.height)))
#存储最高分
if me.life == 0:
if not record: #防止文件多次打开,导致文件或数据丢失
record = True
with open('历史最高分.txt','r') as f:
best_score = int(f.read())
if score > best_score:
with open('历史最高分.txt','w') as f:
f.write(str(score))
#绘制结束界面
screen.blit(bg,(0,0))
best_score_text = gameover_font.render('Best : %s'%str(best_score),True,WHITE)
your_score_text = gameover_font.render('Your Score : ',True,WHITE)
screen.blit(best_score_text,(75,75))
screen.blit(your_score_text,(125,200))
score_text = gameover_font.render('%s'%str(score),True,WHITE)
screen.blit(score_text,(200,300))
screen.blit(Again,(100,450))
screen.blit(Gameover,(100,500))
pm.pause()
pause = True
GameOver = True
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
try:
main( )
except SystemExit:
pass
except :
traceback.print_exc()
pg.quit()
input()
当我自己打完这个代码,我就已然发现,第一个游戏就是第一个游戏果然是又臭又长啊,单单一个main函数就已经接近六百行了,当然他其实不值得这么多行。
那么下面正题开始,小甲鱼的视频只是为了教我们如何使用Pyhton去做一个游戏,那么就必然还有很多可以改进的地方,例如:
1,我方飞机受到碰撞后,不再具有动态效果
(已解决,主要原因:delay赋值过小)
2,大型敌机只要有一架飞出视野,全部的大型敌机的音效都会停止
(改进方案:把该音效变成他的一个属性或方法)
3,子弹只发射一发
(已解决,每多少帧就将随机一颗子弹reset一次就好)
#如果不这样做你的子弹是全部重叠在一起的,表现为一颗子弹
4,计时器出现的问题,暂停后补给发放包会重新开始计时
(已解决,重新设计一个计时器,用于弥补暂停后的补给发放时间)
5,复活后没有明显标准,难以区分是否在无敌状态
(这个太简单了就不讲讲了)
6,可以把子弹的调用用接口封装起来
这是我自己做的小小总结,而括号中是我目前所能想到的解决方案。
第一个问题其实是我自己的错误,我已经作出了解释,如果有同样问题的你可以点击这里
第二个问题其实解决方案有很多,出现这个问题的主要原因就是音效是单一的,即所有的大型敌机共同享有一个敌机飞入视野的音效,那么就难以控制,而有这么一个方法就是把它放入敌机的属性中去,但是这样也是有缺点的,那就是游戏中的音效通道可能会被他们全部暂用,那么不仅其他音效可能无法播放,而且多架大型敌机的声音会十分嘈杂。(如果你有更好的方法希望可以评论区交流一下,毕竟我也是只学了两个月的python而已)
第三个问题是十分常见的问题,因为小甲鱼教的方法对于子弹用的是for循环的方法,那么其中的each可以视为在同时被操作,即子弹同时发射,如果你不人为将一些子弹回溯调回飞机,那么你发出去的子弹就会表现为一颗。解决方法其实也很简单:
bullets_dictionary[x].reset(me)
只要将其中的子弹回调飞机身上,那么就可以实现表现为不同时间发出的子弹了。但是值得注意的是你子弹回调的频率要适当,如果过小可能会变成这样:
你有见识过激光剑吗?就是这样飞机飞到哪,子弹跟到哪。这其实也是一个常见问题,我在视频弹幕上总看到人问子弹怎么会发不出去???其实就是这样。
第四个问题才可以真的算得上是Bug了,因为这一个细节可能新手不会注意到,像小甲鱼那样写代码,一旦暂停,就是你刚开始游戏运行了29s,你重新开始游戏的下一秒也不会看见补给包,因为这时的补给包已经离你30s之远了。
解决这个问题是只要用多一个计时器来记录你游戏运行了多少秒后暂停,这样就可以弥补上那个时间差值了,话不多说,上代码:
TIME = pg.USEREVENT + 1
.................................. 分界线
elif event.type == TIME: #弥补暂停对补给的影响
if second < 30:
second += 1
else:
second = 0
.................................. 分界线
else:
pm.music.unpause() #音乐重新开始
pm.unpause() #所有音效重新开始
pg.time.set_timer(SUPPLY,30*1000 - second*1000)
#重新开始补给计时投放
...................................分界线
pg.time.set_timer(SUPPLY,30*1000) #将暂停后一次的补给发放时间重置
pg.time.set_timer(TIME,1*1000)
修复这个Bug大致流程就是这四步,值得注意的是在暂停后第一次发放补给后应该再把补给事件的时间间隔重置,然后TIME就开始重新计时就好了。
而第五个问题,其实很简单的,只要在这段时间,用切换图片透明度,或者切换图片就可以实现了,或者闪烁效果,这里太简单就不细讲了。重点是第六个!
那么第六个问题来了!不,是改进。用这个方法代码可以变得好看一点,而且十分方便
这里我决定另外写一篇文章来分享一下小甲鱼没有讲到的东西。
Python之多态与接口