经过我为期一周的开发与完善,现在我正式宣布Space War1.0已经完成了
现在网上能找到的那些飞机大战都是飞机只能左右移动,子弹只能向上发射的无聊版本。
而这一款Space War1.0则与之截然不同。飞机选择了歼16的图片(因为我喜欢歼16),而且机头能跟随鼠标以固定角速度旋转,飞机能上下左右全屏移动,且有微微的加速减速使移动操作更为丝滑。子弹呈长条形,按鼠标左键从机头射出,且子弹角度与飞机一致,弹夹打空后过三秒会自动填满。敌机对象是红色正方形,可以用子弹摧毁或用飞机撞毁(但会使自己收到敌机剩余血量的伤害),若摧毁所有敌机之后,则显示胜利,自身血量为零则显示失败。在游戏开始前,按P键开始,期间仍可用P键实现暂停。按C键退出游戏,当然也可直接叉掉界面以关闭游戏。在游戏胜利或失败后可以按O键重新开始。
Space War1.0 2024-07-14
话不多说,先给代码和资源文件,自己体验吧。之后我会慢慢更新这篇文章,分模块对代码进行解析。(资源文件在最下面)
# -*- coding: utf-8 -*-
"""
Created on Tue May 7 15:33:31 2024
@author: yq1215
"""
import pygame
import sys
import math
import time
import random
from pygame.locals import *
import winsound
#定义Bullet(子弹)类
class Bullet(pygame.sprite.Sprite):
def __init__(self, screen, x, y, angle, damage):
pygame.sprite.Sprite.__init__(self)
#self.image = self.image1 = pygame.Surface((10, 1)) # 用一个简单的矩形代替子弹图片
#self.image.fill((255, 255, 0)) # 设置为黄色
self.image = self.image1 = pygame.image.load("bullet.png")
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.screen = screen
self.angle = angle
self.image = pygame.transform.rotate(self.image1,self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
self.vel = 15
self.x = x
self.y = y
self.damage = damage
def display(self): #显示子弹图片的方法
self.screen.blit(self.image, (self.x, self.y)) #将创建的子弹图片按设定的坐标贴到窗口中
def move(self): #子弹移动方法
self.x += self.vel*math.cos(math.radians(self.angle))
self.y -= self.vel*math.sin(math.radians(self.angle))
def judge(self): #判断子弹是否出界
if self.y<0 or self.y>1200 or self.x<0 or self.x>1800:
return True
else:
return False
def judge1(self, enemy_list): #判断子弹是否击中敌机
flag = False
for enemy in enemy_list:
if (self.x>enemy.rect.centerx-10 and
self.x<enemy.rect.centerx+10 and
self.y>enemy.rect.centery-10 and
self.y<enemy.rect.centery+10):
winsound.Beep(500,1)
enemy.health -= self.damage
flag = True
return flag
#定义Plane(飞机)类
class Plane(pygame.sprite.Sprite):
def __init__(self, screen, image, x, y, max_health, max_bullet):
pygame.sprite.Sprite.__init__(self)
self.image = image
self.image1 = self.image #保留原图
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.screen = screen
self.score = 0 #当前得分
self.acceleration = 0.2
self.max_speed = 8
self.vel_x = 0
self.vel_y = 0
self.angular_speed = 2 #角速度
self.current_angle = 0
self.max_health = max_health
self.health = max_health # 初始生命值等于最大生命值
self.health_bar = HealthBar(screen, 300, 80, 200, 20, max_health) # 创建血条对象
self.bullet_list = [] #存储发射出去的子弹对象引用
self.bullet_clip = max_bullet # 弹夹容量
self.bullets_in_clip = self.bullet_clip # 当前弹夹内子弹数量
self.reloading = False # 是否正在填充子弹
self.reload_start_time = 0 # 开始填充子弹的时间点
self.reload_time = 3 # 填充子弹所需时间
self.bullet_clip_bar = HealthBar(screen, 300, 105, 200, 20, max_bullet) #创建血条对象显示弹夹条
def move(self, keys): #移动
if keys[K_w] and self.rect.top <= 0:
self.vel_y = 0
elif keys[K_w] and self.rect.top > 0:
if self.vel_y > -self.max_speed:
self.vel_y -= self.acceleration
elif self.vel_y < 0:
self.vel_y += self.acceleration
if self.vel_y > -self.acceleration:
self.vel_y = 0
if keys[K_s] and self.rect.bottom >= 1200:
self.vel_y = 0
elif keys[K_s] and self.rect.bottom < 1200:
if self.vel_y < self.max_speed:
self.vel_y += self.acceleration
elif self.vel_y > 0:
self.vel_y -= self.acceleration
if self.vel_y < self.acceleration:
self.vel_y = 0
if keys[K_a] and self.rect.left <= 0:
self.vel_x = 0
elif keys[K_a] and self.rect.left > 0:
if self.vel_x > -self.max_speed:
self.vel_x -= self.acceleration
elif self.vel_x < 0:
self.vel_x += self.acceleration
if self.vel_x > -self.acceleration:
self.vel_x = 0
if keys[K_d] and self.rect.right >= 1800:
self.vel_x = 0
elif keys[K_d] and self.rect.right < 1800:
if self.vel_x < self.max_speed:
self.vel_x += self.acceleration
elif self.vel_x > 0:
self.vel_x -= self.acceleration
if self.vel_x < self.acceleration:
self.vel_x = 0
self.rect.x += int(self.vel_x)
self.rect.y += int(self.vel_y)
def zhuandong(self): #转动
target_angle = get_angle(self.rect.centerx,self.rect.centery,mouse_x,mouse_y)
self.current_angle = rotate_towards_mouse(self.current_angle,target_angle,self.angular_speed)
self.image = rotated_img = pygame.transform.rotate(self.image1,self.current_angle)
self.rect = new_rect = rotated_img.get_rect(center=self.rect.center)
def fire(self): #存储发射子弹的方法
if not self.reloading and self.bullets_in_clip > 0:
winsound.Beep(5000,1)
self.bullet_list.append(Bullet(self.screen,
self.rect.centerx+24*math.cos(math.radians(self.current_angle)),
self.rect.centery-24*math.sin(math.radians(self.current_angle)),
self.current_angle, 1)) #将发射的子弹对象存储到bullet_list中
self.bullets_in_clip -= 1
if self.bullets_in_clip == 0:
self.start_reload()
def show_bullet(self, enemy_list, enemy_out): #显示子弹
for bullet in self.bullet_list:
bullet.display() #显示子弹
bullet.move() #移动子弹
if bullet.judge() or bullet.judge1(enemy_list):#判断子弹是否越界或触碰敌机
self.bullet_list.remove(bullet) #将子弹从bullet_list中删除
# 这里可以添加一些处理子弹消失的操作
for enemy in enemy_list:
if enemy.health <= 0:
enemy_out.append(enemy)
enemy_list.remove(enemy)
self.score += 1 #用子弹摧毁敌机才会加分,撞毁不加分
def start_reload(self): #开始装弹的时间
self.reloading = True
self.reload_start_time = time.time()
def update_reload(self): #装弹
if self.reloading:
current_time = time.time()
if current_time - self.reload_start_time >= self.reload_time:
self.bullets_in_clip = self.bullet_clip
self.reloading = False
def take_damage(self, damage): #受到伤害
self.health -= damage
if self.health < 0:
self.health = 0 # 生命值不会小于0
def heal(self, heal_amount): #治愈回血
self.health += heal_amount
if self.health > self.max_health:
self.health = self.max_health # 生命值不会超过最大生命值
def collision(self, enemy_list, enemy_out): #判断是否与敌机发生碰撞
flag = False
for enemy in enemy_list:
if (self.rect.centerx>enemy.rect.centerx-20 and
self.rect.centerx<enemy.rect.centerx+20 and
self.rect.centery>enemy.rect.centery-20 and
self.rect.centery<enemy.rect.centery+20):
winsound.Beep(300,2)
self.take_damage(enemy.health)
enemy.health = 0
enemy_out.append(enemy)
enemy_list.remove(enemy)
flag = True
return flag
#定义Enemy(敌机)类
class Enemy(pygame.sprite.Sprite):
def __init__(self, screen, image, x, y, max_health):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.image = image
self.image1 = self.image #保留原图
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.acceleration = 0.2
self.max_speed = 5
self.vel_x = 5
self.vel_y = 5
self.max_health = max_health
self.health = max_health # 初始生命值等于最大生命值
def update(self):
if self.rect.left < 0 or self.rect.right > 1800:
self.vel_x = -self.vel_x
if self.rect.top < 0 or self.rect.bottom > 1200:
self.vel_y = - self.vel_y
self.rect.left += self.vel_x
self.rect.top -= self.vel_y
self.screen.blit(enemy.image, enemy.rect.topleft)
# 创建并实时更新血条对象位置
self.health_bar = HealthBar(screen, self.rect.left-5, self.rect.top-10, 30, 3, self.max_health)
self.health_bar.draw((0,0,0),(0,255,0),self.health)
#血条类
class HealthBar(pygame.sprite.Sprite):
def __init__(self, screen, x, y, width, height, max_health):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.x = x
self.y = y
self.width = width
self.height = height
self.max_health = max_health
def draw(self, color_bottom, color_up, current_health):
# 计算血条长度
bar_length = (current_health / self.max_health) * self.width
# 绘制红色底条
pygame.draw.rect(self.screen, color_bottom, (self.x, self.y, self.width, self.height))
# 绘制绿色血条
pygame.draw.rect(self.screen, color_up, (self.x, self.y, bar_length, self.height))
#获取角度
def get_angle(x1,y1,x2,y2):
dx = x2 - x1
dy = y2 - y1
return math.degrees(math.atan2(-dy,dx))
#更新当前角度 current_angle
def rotate_towards_mouse(current_angle,target_angle,angular_speed):
angle_diff = target_angle - current_angle
if angle_diff > 180:
angle_diff -= 360
elif angle_diff < -180:
angle_diff += 360
if angle_diff > 0:
return (current_angle + min(angular_speed,angle_diff)) % 360
elif angle_diff < 0:
return (current_angle - min(angular_speed,-angle_diff)) % 360
else:
return current_angle
#显示文字
def show_text(surface_handle, pos, text, color, font_bold=False, font_size=13, font_italic=False):
cur_font = pygame.font.SysFont("宋体",font_size) #获取系统字体
#cur_font = pygame.font.Font('simhei.ttf',30)
cur_font.set_bold(font_bold) #设置是否加粗
cur_font.set_italic(font_italic) #设置是否斜体
text_fmt = cur_font.render(text, 1, color) #设置文字内容
surface_handle.blit(text_fmt, pos) #绘制文字
#初始化
pygame.init()
clock = pygame.time.Clock()
pygame.mouse.set_visible(True) # 显示鼠标
screen = pygame.display.set_mode((1800, 1200)) #创建屏幕对象
pygame.display.set_caption("Space War 1.0")
paused = True # 初始化游戏为非暂停状态
#创建Plane对象plane1
plane_image = pygame.image.load("歼16.png")
plane_image = pygame.transform.scale(plane_image,(72,48))
plane1 = Plane(screen,plane_image,800,600,50,300)
#plane1.image = plane1.image1 = pygame.transform.scale(plane1.image,(72,48)) #可以将角色缩放
# 敌机总数
enemy_number = 20
# 被击毁和撞毁的敌机列表
enemy_out = []
# 当前屏幕最大敌机数量
max_enemy_count = 8
# 创建一个空的敌机列表
Enemy_list = []
# 控制敌机对象添加的帧数间隔
frame_count = 0
add_enemy_interval = 60 # 每隔60帧添加一个敌机对象
screen.fill((0, 204, 255)) # 用纯色填充窗口
# 显示文字
text_pos = u"WELCOME TO SPACE WAR"
show_text(screen, (450,500), text_pos, (255,0,0), True, 100)
text_pos = u"Please press the 'P' to start or stop the game"
show_text(screen, (550,600), text_pos, (0,0,0), True, 50)
text_pos = u"Press the 'C' will close the game"
show_text(screen, (640,650), text_pos, (0,0,0), True, 50)
pygame.display.update() #更新屏幕
#主循环
while True:
clock.tick(60) # 每秒不超过60帧的速度运行
for event in pygame.event.get():
if event.type == QUIT:
screen.fill((0, 204, 255)) # 用纯色填充窗口
text_pos = u"GOOD BYE"
show_text(screen, (700,500), text_pos, (255,0,0), True, 100)
pygame.display.update() #更新屏幕
time.sleep(1)
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c: # 按下 "C" 键关闭游戏
screen.fill((0, 204, 255)) # 用纯色填充窗口
text_pos = u"GOOD BYE"
show_text(screen, (700,500), text_pos, (255,0,0), True, 100)
pygame.display.update() #更新屏幕
time.sleep(1)
pygame.quit()
sys.exit()
if event.key == pygame.K_p:
paused = not paused # 按下 "P" 键时切换暂停状态
if (event.key == pygame.K_o and plane1.health == 0 or
event.key == pygame.K_o and len(enemy_out)==enemy_number): # 重新开始游戏
Enemy_list.clear() # 清空敌机列表
frame_count = 0 # 重置帧数计数器
enemy_out = [] # 重置剩余敌机数量
plane1 = Plane(screen,plane1.image1,800,600,50,300)
paused = False # 重置游戏结束标志
if not paused: # 只有在非暂停状态下才更新游戏逻辑和绘制
screen.fill((0, 204, 255)) # 用纯色填充窗口
#重新开始
if plane1.health == 0: #如果飞机生命值为 0
#显示文字
text_pos = u"You Out"
show_text(screen, (750,500), text_pos, (255,0,0), True, 100)
text_pos = u"You can press the 'O' to restart the game"
show_text(screen, (550,600), text_pos, (0,0,0), True, 50)
pygame.display.update() #更新屏幕
paused = not paused
if len(enemy_out)==enemy_number and plane1.health > 0: # 如果敌机全部消灭且生命值仍大于零
#显示文字
text_pos = u"Congratulation! You Win!"
show_text(screen, (450,500), text_pos, (255,0,0), True, 100)
text_pos = u"Your Score Is: %d"%(plane1.score)
show_text(screen, (600,600), text_pos, (255,0,0), True, 100)
text_pos = u"You can press the 'O' to renew your record"
show_text(screen, (550,700), text_pos, (0,0,0), True, 50)
pygame.display.update() #更新屏幕
paused = not paused
# 随机生成敌机
frame_count += 1 # 帧数递增
if (len(enemy_out)+len(Enemy_list)<enemy_number
and len(Enemy_list)<max_enemy_count
and frame_count % add_enemy_interval==0):
# 随机生成敌机的初始位置
x = random.choice([0, 1780]) # 在屏幕左右边缘随机选择
y = random.randint(0, 1180) # 在屏幕上随机选择纵向位置
# 加载敌机图像
enemy_image = pygame.image.load("enemy1.png")
# 创建敌机对象
enemy = Enemy(screen, enemy_image, x, y, 10)
# 将敌机对象添加到敌机列表
Enemy_list.append(enemy)
# 用键盘控制角色移动
keys = pygame.key.get_pressed() # 获取键盘状态
plane1.move(keys) # 更新飞机对象
#用鼠标控制角色转动
mouse_x,mouse_y = pygame.mouse.get_pos()
plane1.zhuandong()
#检测鼠标点击,发射子弹
mouse_left, mouse_middle, mouse_right = pygame.mouse.get_pressed()
if mouse_left:
plane1.fire()
plane1.show_bullet(Enemy_list, enemy_out) #显示子弹
plane1.collision(Enemy_list, enemy_out)
#显示文字
text_pos = u"plane1 score: %d "%(plane1.score)
show_text(screen, (20,0), text_pos, (255,0,0), True, 40)
#显示飞机坐标
text_pos = u"center:(%d,%d)"%(plane1.rect.centerx, plane1.rect.centery)
show_text(screen, (20,25), text_pos, (0,0,0), True, 40)
#显示飞机角度
text_pos = u"current_angle:(%f)"%(plane1.current_angle)
show_text(screen, (20,50), text_pos, (0,0,0), True, 40)
# 绘制飞机的血条
text_pos = u"current_health:(%d)"%(plane1.health)
show_text(screen, (20,75), text_pos, (0,255,0), True, 40)
plane1.health_bar.draw((255,0,0), (0,255,0), plane1.health)
#显示弹夹容量
text_pos = u"bullet_clip: (%d)"%(plane1.bullets_in_clip)
show_text(screen, (20,100), text_pos, (255,255,0), True, 40)
plane1.update_reload() # 弹夹填充
plane1.bullet_clip_bar.draw((0,0,0), (255,255,0), plane1.bullets_in_clip)
# 显示剩余敌机数量
text_pos = u"remanent enemy number:(%d)"%(enemy_number-len(enemy_out))
show_text(screen, (1400,20), text_pos, (255,0,0), True, 40)
screen.blit(plane1.image, plane1.rect.topleft) # 绘制图像
# 更新敌机
for enemy in Enemy_list:
enemy.update()
pygame.display.update() #更新屏幕
子弹图片(命名为bullet.png):
敌机图片(命名为enemy1.png):
我机图片(命名为歼16.png):