为了增强 Space War 系列游戏的趣味性,这里有两种主要途径。一是提高敌机的智能性,比如能有更不可预测的移动轨迹和发射子弹的功能以增加难度和挑战性;二是开发成双人小游戏,双人对战的模式。显然,在 Space War 1.0 的基础上,开发双人版本更为简单(实际上也不简单,很多底层逻辑都要大改,只有一些实现基本功能的代码得以保留),所以我就先开发双人版。
而经过我为期一天的开发与完善,现在我正式宣布Space War2.0已经完成了(在开发过程中也同时对 Space War 1.0 进行了升级,这就有了 Space War 1.1 的诞生)飞机大战系列 Space War 1.1(开源)
在 Space War 2.0 中,plane1通过WSAD键进行移动,JK键进行旋转,SPACE(空格)键进行射击。plane2通过上下左右键进行移动,鼠标进行旋转,左键进行射击。为了维持游戏平衡(用鼠标的肯定简单一点)我设置了plane1血量100,plane2血量50;plane1弹夹容量200,plane2弹夹容量100;plane1装弹时间3秒,plane2装弹时间6秒。下面是打斗示例。
Space War 2.0 2024-07-16
下面给出代码。Space War 2.0 与 Space War 1.0 系列最大的不同就是删去了Enemy_list 和 Enemy_out 列表,因为对plane1和plane2而言都只有一个敌机对象,所以在两个飞机类的方法中,也是将这些设计敌机列表的语句给换掉了。资源就比1.0版多了一个红色的子弹(在最下面),原先的黄色子弹改名为bullet1.png
# -*- coding: utf-8 -*-
"""
Created on Tue Jul 16 11:41:27 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, image, 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.image = self.image1 = image
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): #判断子弹是否击中敌机
flag = False
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
#定义Plane1(飞机)类
class Plane1(pygame.sprite.Sprite):
def __init__(self, screen, image, bullet_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.center = (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 = 1 #角速度
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_image = bullet_image
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 = 6 # 填充子弹所需时间
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, keys): #转动
if keys[K_j]:
self.current_angle += self.angular_speed
if keys[K_k]:
self.current_angle -= self.angular_speed
if self.current_angle > 360:
self.current_angle -= 360
if self.current_angle < 0:
self.current_angle += 360
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.bullet_image,
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): #显示子弹
for bullet in self.bullet_list:
bullet.display() #显示子弹
bullet.move() #移动子弹
if bullet.judge() or bullet.judge1(enemy):#判断子弹是否越界或触碰敌机
self.bullet_list.remove(bullet) #将子弹从bullet_list中删除
# 这里可以添加一些处理子弹消失的操作
if enemy.health <= 0:
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): #判断是否与敌机发生碰撞
flag = False
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
flag = True
return flag
#定义Plane2(飞机)类
class Plane2(pygame.sprite.Sprite):
def __init__(self, screen, image, bullet_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 = 1 #角速度
self.current_angle = 180
self.max_health = max_health
self.health = max_health # 初始生命值等于最大生命值
self.health_bar = HealthBar(screen, 1550, 80, 200, 20, max_health) # 创建血条对象
self.bullet_image = bullet_image
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, 1550, 105, 200, 20, max_bullet) #创建血条对象显示弹夹条
def move(self, keys): #移动
if keys[K_UP] and self.rect.top <= 0:
self.vel_y = 0
elif keys[K_UP] 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_DOWN] and self.rect.bottom >= 1200:
self.vel_y = 0
elif keys[K_DOWN] 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_LEFT] and self.rect.left <= 0:
self.vel_x = 0
elif keys[K_LEFT] 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_RIGHT] and self.rect.right >= 1800:
self.vel_x = 0
elif keys[K_RIGHT] 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.bullet_image,
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): #显示子弹
for bullet in self.bullet_list:
bullet.display() #显示子弹
bullet.move() #移动子弹
if bullet.judge() or bullet.judge1(enemy):#判断子弹是否越界或触碰敌机
self.bullet_list.remove(bullet) #将子弹从bullet_list中删除
# 这里可以添加一些处理子弹消失的操作
if enemy.health <= 0:
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): #判断是否与敌机发生碰撞
flag = False
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
flag = True
return flag
#血条类
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 2.0")
paused = True # 初始化游戏为非暂停状态
#创建Plane1对象plane1
plane1_image = pygame.image.load("歼16.png")
plane1_image = pygame.transform.scale(plane1_image,(72,48))
bullet1_image = pygame.image.load("bullet1.png")
plane1 = Plane1(screen,plane1_image,bullet1_image,200,600,100,200)
#创建Plane2对象plane2
plane2_image = pygame.image.load("enemy.png")
plane2_image = pygame.transform.scale(plane2_image,(51,75))
bullet2_image = pygame.image.load("bullet2.png")
plane2 = Plane2(screen,plane2_image,bullet2_image,1600,600,50,100)
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 plane2.health == 0): # 重新开始游戏
plane1 = Plane1(screen,plane1_image,bullet1_image,200,600,50,300)
plane2 = Plane2(screen,plane2_image,bullet2_image,1600,600,50,300)
paused = False # 重置游戏结束标志
if not paused: # 只有在非暂停状态下才更新游戏逻辑和绘制
screen.fill((0, 204, 255)) # 用纯色填充窗口
#重新开始
if plane1.health > 0 and plane2.health <= 0 :
#显示文字
text_pos = u"Congratulation! Plane1 Win!"
show_text(screen, (450,500), text_pos, (255,0,0), True, 100)
text_pos = u"You can press the 'O' to play again"
show_text(screen, (550,700), text_pos, (0,0,0), True, 50)
#pygame.display.update() #更新屏幕
elif plane2.health > 0 and plane1.health <= 0 :
#显示文字
text_pos = u"Congratulation! Plane2 Win!"
show_text(screen, (450,500), text_pos, (255,0,0), True, 100)
text_pos = u"You can press the 'O' to play again"
show_text(screen, (550,700), text_pos, (0,0,0), True, 50)
#pygame.display.update() #更新屏幕
elif plane1.health <= 0 and plane2.health <= 0 :
#显示文字
text_pos = u"Sorry ,No One Win"
show_text(screen, (550,500), text_pos, (255,0,0), True, 100)
text_pos = u"You can press the 'O' to play again"
show_text(screen, (550,700), text_pos, (0,0,0), True, 50)
#pygame.display.update() #更新屏幕
keys = pygame.key.get_pressed() # 获取键盘状态
if plane1.health > 0:
plane1.move(keys)
if plane2.health > 0:
plane2.move(keys)
mouse_x,mouse_y = pygame.mouse.get_pos() #获取鼠标位置
if plane1.health > 0:
plane1.zhuandong(keys)
if plane2.health > 0:
plane2.zhuandong()
mouse_left, mouse_middle, mouse_right = pygame.mouse.get_pressed()
if plane1.health > 0:
if keys[K_SPACE]:
plane1.fire()
plane1.show_bullet(plane2) #显示子弹
plane1.collision(plane2)
if plane2.health > 0:
if mouse_left:
plane2.fire()
plane2.show_bullet(plane1) #显示子弹
plane2.collision(plane1)
#显示关于plane1的文字
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)
#显示关于plane2的文字
text_pos = u"plane2 score: %d "%(plane2.score)
show_text(screen, (1270,0), text_pos, (255,0,0), True, 40)
#显示飞机坐标
text_pos = u"center:(%d,%d)"%(plane2.rect.centerx, plane2.rect.centery)
show_text(screen, (1270,25), text_pos, (0,0,0), True, 40)
#显示飞机角度
text_pos = u"current_angle:(%f)"%(plane2.current_angle)
show_text(screen, (1270,50), text_pos, (0,0,0), True, 40)
# 绘制飞机的血条
text_pos = u"current_health:(%d)"%(plane2.health)
show_text(screen, (1270,75), text_pos, (0,255,0), True, 40)
plane2.health_bar.draw((255,0,0), (0,255,0), plane2.health)
#显示弹夹容量
text_pos = u"bullet_clip: (%d)"%(plane2.bullets_in_clip)
show_text(screen, (1270,100), text_pos, (255,255,0), True, 40)
plane2.update_reload() # 弹夹填充
plane2.bullet_clip_bar.draw((0,0,0), (255,255,0), plane2.bullets_in_clip)
if plane1.health > 0 :
screen.blit(plane1.image, plane1.rect.topleft) # 绘制图像
if plane2.health > 0 :
screen.blit(plane2.image, plane2.rect.topleft) # 绘制图像
pygame.display.update() #更新屏幕
命名为bullet2.png