实时同步文件:Python制作流浪气球游戏(射击类)
B站视频教学地址:浪淘三千
(代码和素材是完整的,对应视频也已更新完毕)
以下是静态样式展示:
"""
本游戏制作了导弹拦截流浪气球的基本代码 有 音效、音乐、精灵、碰撞、轨迹计算 等知识的应用
当基础功能学会以后,可以教大家平面地图文件的绘制和使用 制作更丰富精彩的游戏场景
视频教学地址,会陆续更新 https://space.bilibili.com/455954948
欢迎积极交流更好的改进建议,一起升级游戏 (代码已同步 链接见视频评论区)
"""
import arcade
# 常量 窗口大小和标题
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Arcade 游戏教学 流浪气球"
# arcade_game_202302 流浪气球Ⅰ 教学版
class LiuLangQiQiu(arcade.View):
""" 视图程序,用于在窗体内展示. """
def __init__(self):
# 初始化父类的属性
super().__init__()
self.background_image = None
# 鼠标的图片
self.couser_pic = None
# 游戏背景音乐
self.background_sound = arcade.load_sound("../声音文件/忍者神龟背景音乐.mp3")
# 在初始化时调用的一些准备工作代码
self.set_up()
def set_up(self):
""" 进行一些游戏准备工作,让游戏主逻辑从这开始,以便于在有需要时重开游戏. """
# 背景图片 当作精灵加载进来
self.background_image = arcade.Sprite("../图片文件/世界卫星地图.png")
# 播放并记录游戏背景音乐
self.current_play = self.background_sound.play(volume=0.4, loop=True)
# 设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 3
self.background_image.center_y = self.window.height // 2
# 鼠标图片加载
self.couser_pic = arcade.Sprite('../图片文件/瞄准.png', scale=0.2)
# 画面渲染绘制
def on_draw(self):
""" 负责渲染画面 速率: 60帧/秒. """
# 清除上一帧绘制的画面
self.clear()
# 绘制背景图片
self.background_image.draw()
# 绘制鼠标
self.couser_pic.draw()
# 控制每次刷新时的变化
def on_update(self, delta_time: float):
""" 负责逻辑变化 速率: 60帧/秒. """
pass
def on_mouse_press(self, x, y, button, modifiers):
""" 监听鼠标点击事件. """
mouse_buttons = {1: "左键", 2: "中键", 4: "右键"}
print(f"当前点击的坐标是{x,y}")
def on_mouse_release(self, x: float, y: float, button: int, modifiers: int):
""" 监听鼠标释放事件. """
pass
def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
""" 监听鼠标移动事件 """
self.couser_pic.center_x = x
self.couser_pic.center_y = y
def on_key_press(self,key, modifiers):
"""监听键盘按键点击事件
modifiers:None = 16,shift= 1(17) ctrl= 2(18) alt= 4(20) """
print("---键盘按键按下了:--",key,"--修饰键编号的和是:---", modifiers)
def on_key_release(self, key, modifiers):
print("---键盘按键抬起了:--",key,"--剩余修饰键编号的和是:---", modifiers)
def on_resize(self, width: int, height: int):
# 重新设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 2
self.background_image.center_y = self.window.height // 2
# 重新设置精灵素材宽度和高度
self.background_image.width = self.window.width
self.background_image.height = self.window.height
def main():
# 设置窗体的宽、高、名字、是否支持缩放
window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, resizable=True)
# 设置窗体的 logo 图标
import pyglet
window.set_icon(pyglet.image.load('../图片文件/浪淘三千.png'))
# 设置鼠标形状
cursor = window.get_system_mouse_cursor(window.CURSOR_HAND)
window.set_mouse_cursor(cursor)
# 实例化定义的某个窗体
start_view = LiuLangQiQiu()
# 设置当前应该显示哪个窗体
window.show_view(start_view)
# 保持程序持续运行
arcade.run()
if __name__ == "__main__":
main()
"""
本游戏制作了导弹拦截流浪气球的基本代码 有 音效、音乐、精灵、碰撞、轨迹计算 等知识的应用
当基础功能学会以后,可以教大家平面地图文件的绘制和使用 制作更丰富精彩的游戏场景
视频教学地址,会陆续更新 https://space.bilibili.com/455954948
欢迎积极交流更好的改进建议,一起升级游戏 (代码已同步 链接见视频评论区)
"""
import arcade
import os,random
# 常量 窗口大小和标题
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Arcade 游戏教学 流浪气球"
class QiQiu(arcade.Sprite):
def __init__(self, filename, change_x, change_y, init_x, init_y):
super(QiQiu, self).__init__(filename,hit_box_algorithm = "Simple")
# 导弹精灵的飞行变化速度
self.change_x = change_x
self.change_y = change_y
# 导弹精灵的初始中心点(init_x,init_y)
self.center_x = init_x
self.center_y = init_y
# 记录气球单次持续垂直移动的距离
self.total_change_y = 0
# 是否被追踪 一旦被追踪则不再被别的导弹追踪
self.is_tracked = False
# 初始中心点不在界面左侧,则是从右向左飞行的,因为原始图片都朝向右侧,向左飞行的要水平翻转
if self.center_x > 0:
self.append_texture((arcade.load_texture(filename, mirrored=True)))
# texture_right_forward = arcade.load_texture(filename, mirrored=True)
self.set_texture(1)
def update(self, window_height=800):
self.total_change_y += self.change_y
# 当单次垂直移动距离超过50就重新随机选择方向 这个数字通过自己测试调整得到
if abs(self.total_change_y) > 50:
# 随机改变气球纵向移动方向
self.change_y *= random.choice([-1, 1])
# 再次重新计算持续垂直移动的距离
self.total_change_y = 0
if self.center_y > window_height- self.height or self.center_y < self.height:
self.change_y = 0
self.total_change_y = 0
self.center_x += self.change_x
self.center_y += self.change_y
# arcade_game_202302 流浪气球Ⅰ教学版
class LiuLangQiQiu(arcade.View):
""" 视图程序,用于在窗体内展示. """
def __init__(self):
# 初始化父类的属性
super().__init__()
self.background_image = None
# 气球类
self.qi_qiu = None
self.qi_qiu_list = None
# 所有气球文件
self.all_qi_qiu = None
# 游戏背景音乐
self.background_sound = arcade.load_sound("../声音文件/忍者神龟背景音乐.mp3")
# 鼠标的图片
self.couser_pic = None
# 在初始化时调用的一些准备工作代码
self.set_up()
def set_up(self,init_qi_qiu_num=3):
""" 进行一些游戏准备工作,让游戏主逻辑从这开始,以便于在有需要时重开游戏. """
# 背景图片 当作精灵加载进来
self.background_image = arcade.Sprite("../图片文件/世界卫星地图.png")
# 播放并记录游戏背景音乐
self.current_play = self.background_sound.play(volume=0.4, loop=True)
# 气球精灵列表
self.qi_qiu_list = arcade.SpriteList()
# 获取文件夹中所有的飞艇图片
self.all_qi_qiu = os.listdir("../图片文件/气球飞艇")
print(self.all_qi_qiu)
# 设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 3
self.background_image.center_y = self.window.height // 2
# 初始状态时创建自定义个气球(默认3个)
self.creat_qi_qiu(init_qi_qiu_num)
self.couser_pic = arcade.Sprite('../图片文件/瞄准.png', scale=0.2)
def creat_qi_qiu(self, num):
"""创建气球"""
for i in range(1, num+1):
self.qi_qiu = QiQiu(filename=f"../图片文件/气球飞艇/{random.choice(self.all_qi_qiu)}",
change_x=random.choice([0.5,0.5,1,2,2]), change_y=random.choice([1,2]),
init_x=random.choice([-80, self.window.width + 200]),
init_y=random.randint(200, self.window.height - num*50))
self.qi_qiu.width = 60
self.qi_qiu.height = 40
# self.qi_qiu.scale = 0.5
if self.qi_qiu.center_x > self.window.width//2:
self.qi_qiu.change_x = -self.qi_qiu.change_x
self.qi_qiu_list.append(self.qi_qiu)
# 画面渲染绘制
def on_draw(self):
""" 负责渲染画面 速率: 60帧/秒. """
# 清除上一帧绘制的画面
self.clear()
# 绘制背景图片
self.background_image.draw()
# 绘制气球精灵组
self.qi_qiu_list.draw()
# 绘制鼠标
self.couser_pic.draw()
# 控制每次刷新时的变化
def on_update(self, delta_time: float):
""" 负责逻辑变化 速率: 60帧/秒. """
for qi_qiu_id, qi_qiu in enumerate(self.qi_qiu_list):
# 气球移动
qi_qiu.update(self.window.height)
if qi_qiu.center_x not in range(-230, self.window.width+230):
# 气球飞出屏幕一定距离后 则消失
qi_qiu.remove_from_sprite_lists()
# 再次创建气球
self.creat_qi_qiu(1)
def on_mouse_press(self, x, y, button, modifiers):
""" 监听鼠标点击事件. """
mouse_buttons = {1: "左键", 2: "中键", 4: "右键"}
print(f"当前点击的坐标是{x,y}")
def on_mouse_release(self, x: float, y: float, button: int, modifiers: int):
""" 监听鼠标释放事件. """
pass
def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
""" 监听鼠标移动事件 """
self.couser_pic.center_x = x
self.couser_pic.center_y = y
def on_key_press(self,key, modifiers):
"""监听键盘按键点击事件
modifiers:None = 16,shift= 1(17) ctrl= 2(18) alt= 4(20) """
print("---键盘按键按下了:--",key,"--修饰键编号的和是:---", modifiers)
def on_key_release(self, key, modifiers):
print("---键盘按键抬起了:--",key,"--剩余修饰键编号的和是:---", modifiers)
def on_resize(self, width: int, height: int):
# 重新设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 2
self.background_image.center_y = self.window.height // 2
# 重新设置精灵素材宽度和高度
self.background_image.width = self.window.width
self.background_image.height = self.window.height
def main():
# 设置窗体的宽、高、名字、是否支持缩放
window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, resizable=True)
# 设置窗体的 logo 图标
import pyglet
window.set_icon(pyglet.image.load('../图片文件/浪淘三千.png'))
# 设置鼠标形状
cursor = window.get_system_mouse_cursor(window.CURSOR_HAND)
window.set_mouse_cursor(cursor)
# 实例化定义的某个窗体
start_view = LiuLangQiQiu()
# 设置当前应该显示哪个窗体
window.show_view(start_view)
# 保持程序持续运行
arcade.run()
if __name__ == "__main__":
main()
"""
本游戏制作了导弹拦截流浪气球的基本代码 有 音效、音乐、精灵、碰撞、轨迹计算 等知识的应用
当基础功能学会以后,可以教大家平面地图文件的绘制和使用 制作更丰富精彩的游戏场景
视频教学地址,会陆续更新 https://space.bilibili.com/455954948
欢迎积极交流更好的改进建议,一起升级游戏 (代码已同步 链接见视频评论区)
"""
import arcade
import os,random,math
# 常量 窗口大小和标题
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Arcade 游戏教学 流浪气球"
class QiQiu(arcade.Sprite):
def __init__(self, filename, change_x, change_y, init_x, init_y):
super(QiQiu, self).__init__(filename,hit_box_algorithm = "Simple")
# 导弹精灵的飞行变化速度
self.change_x = change_x
self.change_y = change_y
# 导弹精灵的初始中心点(init_x,init_y)
self.center_x = init_x
self.center_y = init_y
# 记录气球单次持续垂直移动的距离
self.total_change_y = 0
# 是否被追踪 一旦被追踪则不再被别的导弹追踪
self.is_tracked = False
# 初始中心点不在界面左侧,则是从右向左飞行的,因为原始图片都朝向右侧,向左飞行的要水平翻转
if self.center_x > 0:
self.append_texture((arcade.load_texture(filename, mirrored=True)))
# texture_right_forward = arcade.load_texture(filename, mirrored=True)
self.set_texture(1)
def update(self, window_height=800):
self.total_change_y += self.change_y
# 当单次垂直移动距离超过50就重新随机选择方向 这个数字通过自己测试调整得到
if abs(self.total_change_y) > 50:
# 随机改变气球纵向移动方向
self.change_y *= random.choice([-1, 1])
# 再次重新计算持续垂直移动的距离
self.total_change_y = 0
if self.center_y > window_height- self.height or self.center_y < self.height:
self.change_y = 0
self.total_change_y = 0
self.center_x += self.change_x
self.center_y += self.change_y
class DaoDan(arcade.Sprite):
"""目前是设计成了自动搜寻目标 然后攻击的模式"""
def __init__(self, filename, change_x, change_y, init_x, init_y, target_x=None, target_y=None):
super(DaoDan, self).__init__(filename,hit_box_algorithm = "Simple")
# self.load_animated_gif(filename)
# 导弹精灵的飞行变化速度
self.change_x = change_x
self.change_y = change_y
self.change_y_init = change_y
# 记录导弹精灵的初始点
self.init_x = init_x
self.init_y = init_y
# 导弹精灵的初始中心点(init_x,init_y)
self.center_x = init_x
self.center_y = init_y
# 要追踪的目标
self.target_x = target_x
self.target_y = target_y
# 导弹是否还在地面上(没有发射)
self.is_on_ground = True
# 是否正在追踪 初始是未开始追踪
self.is_tracking = False
# 导弹点火发射音效
self.fire_sound = arcade.load_sound("../声音文件/火焰推进器.mp3")
# 导弹爆炸音效
self.explode_sound = arcade.load_sound("../声音文件/中距离爆炸.mp3")
def track_target(self, target_x, target_y, change_angle=0.1):
# 当被调用的时候,开始追踪目标气球
# 计算导弹和气球飞艇之间的弧度值 Π = 3.14(弧度) = 180°(角度) 向左转角度要加 向右转角度要减
target_radians_angle = math.atan2(target_y-self.center_y,target_x-self.center_x)
# 将上一步计算出的弧度转换为角度值
# 都要减去90 因为导弹的初始方向是向上的,所以有一个默认的90°角,减去后恢复到 0°再计算。
target_degree_angle = math.degrees(target_radians_angle)-90
# print(f"角度是:{target_degree_angle}")
self.center_x += self.change_x if self.center_x < target_x else -self.change_x
self.center_y += self.change_y if self.center_y < target_y else -self.change_y
self.change_y = 0 if abs(self.center_y - target_y) < 5 else self.change_y_init
# print("2222", self.center_y, target_y, self.change_y)
if abs(target_degree_angle) in range(0,8):
self.angle = 0
elif abs(target_degree_angle) in range(172,188):
self.angle = 180
else:
self.angle = target_degree_angle
# 定义一个弹道导弹类 继承导弹基类 ,因为追踪逻辑不一样,所以需要重写其追踪方法
class DanDaoDaoDan(DaoDan):
# def __init__(self):
# super().__init__()
def track_target(self):
"""开始计算固定飞行轨迹 y = a(x-h)²+k h 和 k 为抛物线的顶点横纵坐标
将鼠标点击的(self.target_x,self.target_y)点作为顶点,则有
【抛物线函数 y = a(x-self.target_x)²+self.target_y 】
将导弹的起点坐标带入上述方程 可以求出 a 值 于是就可以求出抛物线的函数式
【抛物线函数 init_y = a(init_x-self.target_x)²+self.target_y 】
a = (init_y - self.target_y)/(init_x-self.target_x)²
"""
# 如果开口向下,a小于0;开口向上,a大于0.
a = (self.init_y - self.target_y)/(self.init_x-self.target_x)**2
# print(f"a的值是:{a}")
self.center_x += self.change_x
# 将横坐标带入方程求纵坐标
self.center_y = a*(int(self.center_x - self.target_x)**2)+self.target_y
# print(f"{self.center_y} = {a}*({int(self.center_x - self.target_x)})**2+{self.target_y}")
# 计算导弹和气球飞艇之间的弧度值 Π = 3.14(弧度) = 180°(角度)
target_radians_angle = math.atan2(self.target_y-self.center_y,self.target_x-self.center_x)
# 将上一步计算出的弧度转换为角度值
# 都要减去90 因为导弹的初始方向是向上的,所以有一个默认的90°角,减去后恢复到 0°再计算。
target_degree_angle = math.degrees(target_radians_angle)-90
if (self.target_x-self.init_x)*(self.target_x-self.center_x) < 0:
# 值小于0 的时候说明导弹飞行中已经超过了设定好的最高点
target_degree_angle = target_degree_angle + 180
# print(f"角度是:{target_degree_angle}")
if abs(target_degree_angle) in range(0,8):
self.angle = 0
elif abs(target_degree_angle) in range(172,188):
self.angle = 180
else:
self.angle = target_degree_angle
class BaoZhaYanWu(arcade.Sprite):
def __init__(self):
super(BaoZhaYanWu, self).__init__()
# 初始中心点不在界面左侧,则是从右向左飞行的,因为原始图片都朝向右侧,向左飞行的要水平翻转
all_explore_pic = os.listdir("../图片文件/images11")
for pic_name in all_explore_pic:
self.append_texture((arcade.load_texture(f"../图片文件/images11/{pic_name}")))
# 记下一共有多少纹理图
self.textures_num_len = len(all_explore_pic)
self.texture_index_now = 0
def update(self, texture_index: int = None):
# 如果状态是 True 则开始计算绘制位置
if self.texture_index_now < self.textures_num_len:
self.set_texture(self.texture_index_now)
self.texture_index_now += 1
return False
else:
# 收到为True的返回值时 销毁烟雾精灵
return True
# arcade_game_202302 流浪气球Ⅰ教学版
class LiuLangQiQiu(arcade.View):
""" 视图程序,用于在窗体内展示. """
def __init__(self):
# 初始化父类的属性
super().__init__()
self.background_image = None
self.dao_dan = None
# 气球类
self.qi_qiu = None
self.qi_qiu_list = None
# 导弹类
self.dao_dan = None
self.dao_dan_list = None
# 弹道导弹类
self.dan_dao_dao_dan = None
self.dan_dao_dao_dan_list = None
# 所有气球文件
self.all_qi_qiu = None
# 游戏背景音乐
self.background_sound = arcade.load_sound("../声音文件/忍者神龟背景音乐.mp3")
# 导弹发射车
self.dao_dan_che_image = None
# 爆炸烟雾
self.explode_smoke = None
self.explode_smoke_list = None
# 鼠标的图片
self.couser_pic = None
# 在初始化时调用的一些准备工作代码
self.set_up()
def set_up(self,init_qi_qiu_num=3):
""" 进行一些游戏准备工作,让游戏主逻辑从这开始,以便于在有需要时重开游戏. """
# 背景图片 当作精灵加载进来
self.background_image = arcade.Sprite("../图片文件/世界卫星地图.png")
# 游戏背景音乐
self.current_play = self.background_sound.play(volume=0.4, loop=True)
# 气球精灵列表
self.qi_qiu_list = arcade.SpriteList()
# 导弹精灵列表
self.dao_dan_list = arcade.SpriteList()
# 弹道导弹精灵列表
self.dan_dao_dao_dan_list = arcade.SpriteList()
# 爆炸烟雾精灵
self.explode_smoke_list = arcade.SpriteList()
# 获取文件夹中所有的飞艇图片
self.all_qi_qiu = os.listdir("../图片文件/气球飞艇")
print(self.all_qi_qiu)
# 设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 3
self.background_image.center_y = self.window.height // 2
# 导弹发射车 在on_resize()里设置绘制位置
self.dao_dan_che_image = arcade.Sprite("../图片文件/东风41.png")
self.dao_dan_che_image.width = 70
self.dao_dan_che_image.height = 70
# 初始状态时创建自定义个气球(默认3个)
self.creat_qi_qiu(init_qi_qiu_num)
self.couser_pic = arcade.Sprite('../图片文件/瞄准.png', scale=0.2)
def creat_qi_qiu(self, num):
"""创建气球"""
for i in range(1, num+1):
self.qi_qiu = QiQiu(filename=f"../图片文件/气球飞艇/{random.choice(self.all_qi_qiu)}",
change_x=random.choice([0.5,0.5,1,2,2]), change_y=random.choice([1,2]),
init_x=random.choice([-80, self.window.width + 200]),
init_y=random.randint(200, self.window.height - num*50))
self.qi_qiu.width = 60
self.qi_qiu.height = 40
# self.qi_qiu.scale = 0.5
if self.qi_qiu.center_x > self.window.width//2:
self.qi_qiu.change_x = -self.qi_qiu.change_x
self.qi_qiu_list.append(self.qi_qiu)
def creat_zhui_zong_dao_dan(self):
dao_dan = DaoDan("../图片文件/导弹.gif", change_x=4, change_y=3,
init_x=self.window.width // 3, init_y=self.window.height // 2)
self.dao_dan_list.append(dao_dan)
dao_dan.fire_sound.play()
def creat_dan_dao_dao_dan(self,x,y):
# 弹道导弹 如果剩余的弹道弹数量大于0 就接受命令进行创建
init_x = self.window.width // 3
init_y = self.window.height // 2
speed = 1 if abs(x - init_x) < 200 else 3
# 如果目标点在右侧 则导弹向右飞行 横坐标变化为正数 否则为负数
change_x = speed if x > self.window.width // 3 else - speed
dan_dao_dao_dan = DanDaoDaoDan("../图片文件/弹道导弹.png", change_x=change_x, change_y=3,
init_x=init_x, init_y=init_y,
target_x=x, target_y=y)
dan_dao_dao_dan.scale = 0.7
self.dan_dao_dao_dan_list.append(dan_dao_dao_dan)
dan_dao_dao_dan.fire_sound.play()
def creat_yan_wu(self,center_x, center_y):
yan_wu = BaoZhaYanWu()
yan_wu.center_x = center_x
yan_wu.center_y = center_y
self.explode_smoke_list.append(yan_wu)
# 画面渲染绘制
def on_draw(self):
""" 负责渲染画面 速率: 60帧/秒. """
# 清除上一帧绘制的画面
self.clear()
# 绘制背景图片
self.background_image.draw()
# 导弹发射车
self.dao_dan_che_image.draw()
# 绘制气球精灵组
self.qi_qiu_list.draw()
# 绘制导弹精灵组
self.dao_dan_list.draw()
# 绘制弹道导弹精灵组
self.dan_dao_dao_dan_list.draw()
# 绘制爆炸烟雾
self.explode_smoke_list.draw()
# 绘制鼠标
self.couser_pic.draw()
# 控制每次刷新时的变化
def on_update(self, delta_time: float):
""" 负责逻辑变化 速率: 60帧/秒. """
for qi_qiu_id, qi_qiu in enumerate(self.qi_qiu_list):
# 处理气球和自动追踪导弹的移动
qi_qiu.update(self.window.height)
if qi_qiu_id <= len(self.dao_dan_list)-1:
dao_dan = self.dao_dan_list[qi_qiu_id]
dao_dan.track_target(qi_qiu.center_x, qi_qiu.center_y, change_angle=-0.5)
# else:
# print("请尽快发射导弹")
if qi_qiu.center_x not in range(-230, self.window.width+230):
# 气球飞出屏幕一定距离后 则消失
qi_qiu.remove_from_sprite_lists()
# 再次创建气球
self.creat_qi_qiu(1)
# 处理按下鼠标发射的弹道导弹
for dan_dao_dao_dan in self.dan_dao_dao_dan_list:
# 处理气球和弹道导弹的移动
dan_dao_dao_dan.track_target()
# 如果导弹飞出了屏幕 则销毁
if dan_dao_dao_dan.center_x not in range(-230, self.window.width+230):
dan_dao_dao_dan.remove_from_sprite_lists()
def on_mouse_press(self, x, y, button, modifiers):
""" 监听鼠标点击事件. """
mouse_buttons = {1: "左键", 2: "中键", 4: "右键"}
# print(f"当前点击的坐标是{x,y}")
# 创建弹道导弹
self.creat_dan_dao_dao_dan(x,y)
def on_mouse_release(self, x: float, y: float, button: int, modifiers: int):
""" 监听鼠标释放事件. """
pass
def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
""" 监听鼠标移动事件 """
self.couser_pic.center_x = x
self.couser_pic.center_y = y
def on_key_press(self,key, modifiers):
"""监听键盘按键点击事件
modifiers:None = 16,shift= 1(17) ctrl= 2(18) alt= 4(20) """
print("---键盘按键按下了:--",key,"--修饰键编号的和是:---", modifiers)
if key == arcade.key.SPACE:
self.creat_zhui_zong_dao_dan()
def on_key_release(self, key, modifiers):
print("---键盘按键抬起了:--",key,"--剩余修饰键编号的和是:---", modifiers)
def on_resize(self, width: int, height: int):
# 重新设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 2
self.background_image.center_y = self.window.height // 2
# 重新设置精灵素材宽度和高度
self.background_image.width = self.window.width
self.background_image.height = self.window.height
# 设置导弹车精灵素材中心点的位置
self.dao_dan_che_image.center_x = self.window.width // 3 - 30
self.dao_dan_che_image.center_y = self.window.height // 2
def main():
# 设置窗体的宽、高、名字、是否支持缩放
window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, resizable=True)
# 设置窗体的 logo 图标
import pyglet
window.set_icon(pyglet.image.load('../图片文件/浪淘三千.png'))
# 设置鼠标形状
cursor = window.get_system_mouse_cursor(window.CURSOR_HAND)
window.set_mouse_cursor(cursor)
# 实例化定义的某个窗体
start_view = LiuLangQiQiu()
# 设置当前应该显示哪个窗体
window.show_view(start_view)
# 保持程序持续运行
arcade.run()
if __name__ == "__main__":
main()
"""
本游戏制作了导弹拦截流浪气球的基本代码 有 音效、音乐、精灵、碰撞、轨迹计算 等知识的应用
当基础功能学会以后,可以教大家平面地图文件的绘制和使用 制作更丰富精彩的游戏场景
视频教学地址,会陆续更新 https://space.bilibili.com/455954948
欢迎积极交流更好的改进建议,一起升级游戏 (代码已同步 链接见视频评论区)
"""
import arcade
import os,random,math
# 常量 窗口大小和标题
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Arcade 游戏教学 流浪气球"
class QiQiu(arcade.Sprite):
def __init__(self, filename, change_x, change_y, init_x, init_y):
super(QiQiu, self).__init__(filename,hit_box_algorithm = "Simple")
# 导弹精灵的飞行变化速度
self.change_x = change_x
self.change_y = change_y
# 导弹精灵的初始中心点(init_x,init_y)
self.center_x = init_x
self.center_y = init_y
# 记录气球单次持续垂直移动的距离
self.total_change_y = 0
# 是否被追踪 一旦被追踪则不再被别的导弹追踪
self.is_tracked = False
# 初始中心点不在界面左侧,则是从右向左飞行的,因为原始图片都朝向右侧,向左飞行的要水平翻转
if self.center_x > 0:
self.append_texture((arcade.load_texture(filename, mirrored=True)))
# texture_right_forward = arcade.load_texture(filename, mirrored=True)
self.set_texture(1)
def update(self, window_height=800):
self.total_change_y += self.change_y
# 当单次垂直移动距离超过50就重新随机选择方向 这个数字通过自己测试调整得到
if abs(self.total_change_y) > 50:
# 随机改变气球纵向移动方向
self.change_y *= random.choice([-1, 1])
# 再次重新计算持续垂直移动的距离
self.total_change_y = 0
if self.center_y > window_height- self.height or self.center_y < self.height:
self.change_y = 0
self.total_change_y = 0
self.center_x += self.change_x
self.center_y += self.change_y
class DaoDan(arcade.Sprite):
"""目前是设计成了自动搜寻目标 然后攻击的模式"""
def __init__(self, filename, change_x, change_y, init_x, init_y, target_x=None, target_y=None):
super(DaoDan, self).__init__(filename,hit_box_algorithm = "Simple")
# self.load_animated_gif(filename)
# 导弹精灵的飞行变化速度
self.change_x = change_x
self.change_y = change_y
self.change_y_init = change_y
# 记录导弹精灵的初始点
self.init_x = init_x
self.init_y = init_y
# 导弹精灵的初始中心点(init_x,init_y)
self.center_x = init_x
self.center_y = init_y
# 要追踪的目标
self.target_x = target_x
self.target_y = target_y
# 导弹是否还在地面上(没有发射)
self.is_on_ground = True
# 是否正在追踪 初始是未开始追踪
self.is_tracking = False
# 导弹点火发射音效
self.fire_sound = arcade.load_sound("../声音文件/火焰推进器.mp3")
# 导弹爆炸音效
self.explode_sound = arcade.load_sound("../声音文件/中距离爆炸.mp3")
def track_target(self, target_x, target_y, change_angle=0.1):
# 当被调用的时候,开始追踪目标气球
# 计算导弹和气球飞艇之间的弧度值 Π = 3.14(弧度) = 180°(角度) 向左转角度要加 向右转角度要减
target_radians_angle = math.atan2(target_y-self.center_y,target_x-self.center_x)
# 将上一步计算出的弧度转换为角度值
# 都要减去90 因为导弹的初始方向是向上的,所以有一个默认的90°角,减去后恢复到 0°再计算。
target_degree_angle = math.degrees(target_radians_angle)-90
# print(f"角度是:{target_degree_angle}")
self.center_x += self.change_x if self.center_x < target_x else -self.change_x
self.center_y += self.change_y if self.center_y < target_y else -self.change_y
self.change_y = 0 if abs(self.center_y - target_y) < 5 else self.change_y_init
# print("2222", self.center_y, target_y, self.change_y)
if abs(target_degree_angle) in range(0,8):
self.angle = 0
elif abs(target_degree_angle) in range(172,188):
self.angle = 180
else:
self.angle = target_degree_angle
# 定义一个弹道导弹类 继承导弹基类 ,因为追踪逻辑不一样,所以需要重写其追踪方法
class DanDaoDaoDan(DaoDan):
# def __init__(self):
# super().__init__()
def track_target(self):
"""开始计算固定飞行轨迹 y = a(x-h)²+k h 和 k 为抛物线的顶点横纵坐标
将鼠标点击的(self.target_x,self.target_y)点作为顶点,则有
【抛物线函数 y = a(x-self.target_x)²+self.target_y 】
将导弹的起点坐标带入上述方程 可以求出 a 值 于是就可以求出抛物线的函数式
【抛物线函数 init_y = a(init_x-self.target_x)²+self.target_y 】
a = (init_y - self.target_y)/(init_x-self.target_x)²
"""
# 如果开口向下,a小于0;开口向上,a大于0.
a = (self.init_y - self.target_y)/(self.init_x-self.target_x)**2
# print(f"a的值是:{a}")
self.center_x += self.change_x
# 将横坐标带入方程求纵坐标
self.center_y = a*(int(self.center_x - self.target_x)**2)+self.target_y
# print(f"{self.center_y} = {a}*({int(self.center_x - self.target_x)})**2+{self.target_y}")
# 计算导弹和气球飞艇之间的弧度值 Π = 3.14(弧度) = 180°(角度)
target_radians_angle = math.atan2(self.target_y-self.center_y,self.target_x-self.center_x)
# 将上一步计算出的弧度转换为角度值
# 都要减去90 因为导弹的初始方向是向上的,所以有一个默认的90°角,减去后恢复到 0°再计算。
target_degree_angle = math.degrees(target_radians_angle)-90
if (self.target_x-self.init_x)*(self.target_x-self.center_x) < 0:
# 值小于0 的时候说明导弹飞行中已经超过了设定好的最高点
target_degree_angle = target_degree_angle + 180
# print(f"角度是:{target_degree_angle}")
if abs(target_degree_angle) in range(0,8):
self.angle = 0
elif abs(target_degree_angle) in range(172,188):
self.angle = 180
else:
self.angle = target_degree_angle
class BaoZhaYanWu(arcade.Sprite):
def __init__(self):
super(BaoZhaYanWu, self).__init__()
# 初始中心点不在界面左侧,则是从右向左飞行的,因为原始图片都朝向右侧,向左飞行的要水平翻转
all_explore_pic = os.listdir("../图片文件/images11")
for pic_name in all_explore_pic:
self.append_texture((arcade.load_texture(f"../图片文件/images11/{pic_name}")))
# 记下一共有多少纹理图
self.textures_num_len = len(all_explore_pic)
self.texture_index_now = 0
def update(self, texture_index: int = None):
# 如果状态是 True 则开始计算绘制位置
if self.texture_index_now < self.textures_num_len:
self.set_texture(self.texture_index_now)
self.texture_index_now += 1
return False
else:
# 收到为True的返回值时 销毁烟雾精灵
return True
# arcade_game_202302 流浪气球Ⅰ教学版
class LiuLangQiQiu(arcade.View):
""" 视图程序,用于在窗体内展示. """
def __init__(self):
# 初始化父类的属性
super().__init__()
self.background_image = None
self.dao_dan = None
# 气球类
self.qi_qiu = None
self.qi_qiu_list = None
# 导弹类
self.dao_dan = None
self.dao_dan_list = None
# 弹道导弹类
self.dan_dao_dao_dan = None
self.dan_dao_dao_dan_list = None
# 所有气球文件
self.all_qi_qiu = None
# 游戏背景音乐
self.background_sound = arcade.load_sound("../声音文件/忍者神龟背景音乐.mp3")
# 导弹发射车
self.dao_dan_che_image = None
# 爆炸烟雾
self.explode_smoke = None
self.explode_smoke_list = None
# 为了有节奏的控制游戏刷新时个别元素的刷新速度 设置一个记录时间间隔变量
self.interval_time = 0
# 鼠标的图片
self.couser_pic = None
# 在初始化时调用的一些准备工作代码
self.set_up()
def set_up(self,init_qi_qiu_num=3):
""" 进行一些游戏准备工作,让游戏主逻辑从这开始,以便于在有需要时重开游戏. """
# 背景图片 当作精灵加载进来
self.background_image = arcade.Sprite("../图片文件/世界卫星地图.png")
# 游戏背景音乐
self.current_play = self.background_sound.play(volume=0.4, loop=True)
# 气球精灵列表
self.qi_qiu_list = arcade.SpriteList()
# 导弹精灵列表
self.dao_dan_list = arcade.SpriteList()
# 弹道导弹精灵列表
self.dan_dao_dao_dan_list = arcade.SpriteList()
# 爆炸烟雾精灵
self.explode_smoke_list = arcade.SpriteList()
# 获取文件夹中所有的飞艇图片
self.all_qi_qiu = os.listdir("../图片文件/气球飞艇")
print(self.all_qi_qiu)
# 设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 3
self.background_image.center_y = self.window.height // 2
# 导弹发射车 在on_resize()里设置绘制位置
self.dao_dan_che_image = arcade.Sprite("../图片文件/东风41.png")
self.dao_dan_che_image.width = 70
self.dao_dan_che_image.height = 70
# 初始状态时创建自定义个气球(默认3个)
self.creat_qi_qiu(init_qi_qiu_num)
self.couser_pic = arcade.Sprite('../图片文件/瞄准.png', scale=0.2)
def creat_qi_qiu(self, num):
"""创建气球"""
for i in range(1, num+1):
self.qi_qiu = QiQiu(filename=f"../图片文件/气球飞艇/{random.choice(self.all_qi_qiu)}",
change_x=random.choice([0.5,0.5,1,2,2]), change_y=random.choice([1,2]),
init_x=random.choice([-80, self.window.width + 200]),
init_y=random.randint(200, self.window.height - num*50))
self.qi_qiu.width = 60
self.qi_qiu.height = 40
# self.qi_qiu.scale = 0.5
if self.qi_qiu.center_x > self.window.width//2:
self.qi_qiu.change_x = -self.qi_qiu.change_x
self.qi_qiu_list.append(self.qi_qiu)
def creat_zhui_zong_dao_dan(self):
dao_dan = DaoDan("../图片文件/导弹.gif", change_x=4, change_y=3,
init_x=self.window.width // 3, init_y=self.window.height // 2)
self.dao_dan_list.append(dao_dan)
dao_dan.fire_sound.play()
def creat_dan_dao_dao_dan(self,x,y):
# 弹道导弹 如果剩余的弹道弹数量大于0 就接受命令进行创建
init_x = self.window.width // 3
init_y = self.window.height // 2
speed = 1 if abs(x - init_x) < 200 else 3
# 如果目标点在右侧 则导弹向右飞行 横坐标变化为正数 否则为负数
change_x = speed if x > self.window.width // 3 else - speed
dan_dao_dao_dan = DanDaoDaoDan("../图片文件/弹道导弹.png", change_x=change_x, change_y=3,
init_x=init_x, init_y=init_y,
target_x=x, target_y=y)
dan_dao_dao_dan.scale = 0.7
self.dan_dao_dao_dan_list.append(dan_dao_dao_dan)
dan_dao_dao_dan.fire_sound.play()
def creat_yan_wu(self,center_x, center_y):
yan_wu = BaoZhaYanWu()
yan_wu.center_x = center_x
yan_wu.center_y = center_y
self.explode_smoke_list.append(yan_wu)
# 画面渲染绘制
def on_draw(self):
""" 负责渲染画面 速率: 60帧/秒. """
# 清除上一帧绘制的画面
self.clear()
# 绘制背景图片
self.background_image.draw()
# 导弹发射车
self.dao_dan_che_image.draw()
# 绘制气球精灵组
self.qi_qiu_list.draw()
# 绘制导弹精灵组
self.dao_dan_list.draw()
# 绘制弹道导弹精灵组
self.dan_dao_dao_dan_list.draw()
# 绘制爆炸烟雾
self.explode_smoke_list.draw()
# 绘制鼠标
self.couser_pic.draw()
# 控制每次刷新时的变化
def on_update(self, delta_time: float):
""" 负责逻辑变化 速率: 60帧/秒. """
for qi_qiu_id, qi_qiu in enumerate(self.qi_qiu_list):
# 处理气球和自动追踪导弹的碰撞及逻辑
qi_qiu.update(self.window.height)
if qi_qiu_id <= len(self.dao_dan_list)-1:
dao_dan = self.dao_dan_list[qi_qiu_id]
dao_dan.track_target(qi_qiu.center_x, qi_qiu.center_y, change_angle=-0.5)
# else:
# print("请尽快发射导弹")
collision = arcade.check_for_collision_with_list(qi_qiu, self.dao_dan_list)
if len(collision) > 0:
print(f"发生碰撞了,相撞的{qi_qiu_id}号导弹和气球都被移除")
# 移除碰到的气球
qi_qiu.remove_from_sprite_lists()
# 创建气球
self.creat_qi_qiu(1)
# 移除碰到的追踪导弹
for collision_obj in collision:
collision_obj.remove_from_sprite_lists()
collision_obj.explode_sound.play()
# 创建烟雾 坐标为所碰撞气球的坐标
self.creat_yan_wu(qi_qiu.center_x,qi_qiu.center_y)
if qi_qiu.center_x not in range(-230, self.window.width+230):
# 气球飞出屏幕一定距离后 则消失
qi_qiu.remove_from_sprite_lists()
# 再次创建气球
self.creat_qi_qiu(1)
# 处理按下鼠标发射的弹道导弹
for dan_dao_dao_dan in self.dan_dao_dao_dan_list:
# 处理气球和弹道导弹的碰撞及逻辑
dan_dao_dao_dan.track_target()
collision2 = arcade.check_for_collision_with_list(dan_dao_dao_dan, self.qi_qiu_list)
for collision_obj2 in collision2:
collision_obj2.remove_from_sprite_lists()
dan_dao_dao_dan.remove_from_sprite_lists()
dan_dao_dao_dan.explode_sound.play()
# 创建气球
self.creat_qi_qiu(1)
# 创建烟雾
self.creat_yan_wu(collision_obj2.center_x, collision_obj2.center_y)
# 如果导弹飞出了屏幕 则销毁
if dan_dao_dao_dan.center_x not in range(-230, self.window.width+230):
dan_dao_dao_dan.remove_from_sprite_lists()
# 烟雾的绘制
for smoke in self.explode_smoke_list:
self.interval_time += delta_time
if self.interval_time > 0.08:
res = smoke.update()
self.interval_time = 0
if res:
smoke.remove_from_sprite_lists()
def on_mouse_press(self, x, y, button, modifiers):
""" 监听鼠标点击事件. """
mouse_buttons = {1: "左键", 2: "中键", 4: "右键"}
# print(f"当前点击的坐标是{x,y}")
# 创建弹道导弹
self.creat_dan_dao_dao_dan(x,y)
def on_mouse_release(self, x: float, y: float, button: int, modifiers: int):
""" 监听鼠标释放事件. """
pass
def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
""" 监听鼠标移动事件 """
self.couser_pic.center_x = x
self.couser_pic.center_y = y
def on_key_press(self,key, modifiers):
"""监听键盘按键点击事件
modifiers:None = 16,shift= 1(17) ctrl= 2(18) alt= 4(20) """
print("---键盘按键按下了:--",key,"--修饰键编号的和是:---", modifiers)
if key == arcade.key.SPACE:
self.creat_zhui_zong_dao_dan()
def on_key_release(self, key, modifiers):
print("---键盘按键抬起了:--",key,"--剩余修饰键编号的和是:---", modifiers)
def on_resize(self, width: int, height: int):
# 重新设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 2
self.background_image.center_y = self.window.height // 2
# 重新设置精灵素材宽度和高度
self.background_image.width = self.window.width
self.background_image.height = self.window.height
# 设置导弹车精灵素材中心点的位置
self.dao_dan_che_image.center_x = self.window.width // 3 - 30
self.dao_dan_che_image.center_y = self.window.height // 2
def main():
# 设置窗体的宽、高、名字、是否支持缩放
window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, resizable=True)
# 设置窗体的 logo 图标
import pyglet
window.set_icon(pyglet.image.load('../图片文件/浪淘三千.png'))
# 设置鼠标形状
cursor = window.get_system_mouse_cursor(window.CURSOR_HAND)
window.set_mouse_cursor(cursor)
# 实例化定义的某个窗体
start_view = LiuLangQiQiu()
# 设置当前应该显示哪个窗体
window.show_view(start_view)
# 保持程序持续运行
arcade.run()
if __name__ == "__main__":
main()
"""
本游戏制作了导弹拦截流浪气球的基本代码 有 音效、音乐、精灵、碰撞、轨迹计算 等知识的应用
当基础功能学会以后,可以教大家平面地图文件的绘制和使用 制作更丰富精彩的游戏场景
视频教学地址,会陆续更新 https://space.bilibili.com/455954948
欢迎积极交流更好的改进建议,一起升级游戏 (代码已同步 链接见视频评论区)
"""
import arcade
import os,random,math
# 常量 窗口大小和标题
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Arcade 游戏教学 流浪气球"
class QiQiu(arcade.Sprite):
def __init__(self, filename, change_x, change_y, init_x, init_y):
super(QiQiu, self).__init__(filename,hit_box_algorithm = "Simple")
# 导弹精灵的飞行变化速度
self.change_x = change_x
self.change_y = change_y
# 导弹精灵的初始中心点(init_x,init_y)
self.center_x = init_x
self.center_y = init_y
# 记录气球单次持续垂直移动的距离
self.total_change_y = 0
# 是否被追踪 一旦被追踪则不再被别的导弹追踪
self.is_tracked = False
# 初始中心点不在界面左侧,则是从右向左飞行的,因为原始图片都朝向右侧,向左飞行的要水平翻转
if self.center_x > 0:
self.append_texture((arcade.load_texture(filename, mirrored=True)))
# texture_right_forward = arcade.load_texture(filename, mirrored=True)
self.set_texture(1)
def update(self, window_height=800):
self.total_change_y += self.change_y
# 当单次垂直移动距离超过50就重新随机选择方向 这个数字通过自己测试调整得到
if abs(self.total_change_y) > 50:
# 随机改变气球纵向移动方向
self.change_y *= random.choice([-1, 1])
# 再次重新计算持续垂直移动的距离
self.total_change_y = 0
if self.center_y > window_height- self.height or self.center_y < self.height:
self.change_y = 0
self.total_change_y = 0
self.center_x += self.change_x
self.center_y += self.change_y
class DaoDan(arcade.Sprite):
"""目前是设计成了自动搜寻目标 然后攻击的模式"""
def __init__(self, filename, change_x, change_y, init_x, init_y, target_x=None, target_y=None):
super(DaoDan, self).__init__(filename,hit_box_algorithm = "Simple")
# self.load_animated_gif(filename)
# 导弹精灵的飞行变化速度
self.change_x = change_x
self.change_y = change_y
self.change_y_init = change_y
# 记录导弹精灵的初始点
self.init_x = init_x
self.init_y = init_y
# 导弹精灵的初始中心点(init_x,init_y)
self.center_x = init_x
self.center_y = init_y
# 要追踪的目标
self.target_x = target_x
self.target_y = target_y
# 导弹是否还在地面上(没有发射)
self.is_on_ground = True
# 是否正在追踪 初始是未开始追踪
self.is_tracking = False
# 导弹点火发射音效
self.fire_sound = arcade.load_sound("../声音文件/火焰推进器.mp3")
# 导弹爆炸音效
self.explode_sound = arcade.load_sound("../声音文件/中距离爆炸.mp3")
def track_target(self, target_x, target_y, change_angle=0.1):
# 当被调用的时候,开始追踪目标气球
# 计算导弹和气球飞艇之间的弧度值 Π = 3.14(弧度) = 180°(角度) 向左转角度要加 向右转角度要减
target_radians_angle = math.atan2(target_y-self.center_y,target_x-self.center_x)
# 将上一步计算出的弧度转换为角度值
# 都要减去90 因为导弹的初始方向是向上的,所以有一个默认的90°角,减去后恢复到 0°再计算。
target_degree_angle = math.degrees(target_radians_angle)-90
# print(f"角度是:{target_degree_angle}")
self.center_x += self.change_x if self.center_x < target_x else -self.change_x
self.center_y += self.change_y if self.center_y < target_y else -self.change_y
self.change_y = 0 if abs(self.center_y - target_y) < 5 else self.change_y_init
# print("2222", self.center_y, target_y, self.change_y)
if abs(target_degree_angle) in range(0,8):
self.angle = 0
elif abs(target_degree_angle) in range(172,188):
self.angle = 180
else:
self.angle = target_degree_angle
# 定义一个弹道导弹类 继承导弹基类 ,因为追踪逻辑不一样,所以需要重写其追踪方法
class DanDaoDaoDan(DaoDan):
# def __init__(self):
# super().__init__()
def track_target(self):
"""开始计算固定飞行轨迹 y = a(x-h)²+k h 和 k 为抛物线的顶点横纵坐标
将鼠标点击的(self.target_x,self.target_y)点作为顶点,则有
【抛物线函数 y = a(x-self.target_x)²+self.target_y 】
将导弹的起点坐标带入上述方程 可以求出 a 值 于是就可以求出抛物线的函数式
【抛物线函数 init_y = a(init_x-self.target_x)²+self.target_y 】
a = (init_y - self.target_y)/(init_x-self.target_x)²
"""
# 如果开口向下,a小于0;开口向上,a大于0.
a = (self.init_y - self.target_y)/(self.init_x-self.target_x)**2
# print(f"a的值是:{a}")
self.center_x += self.change_x
# 将横坐标带入方程求纵坐标
self.center_y = a*(int(self.center_x - self.target_x)**2)+self.target_y
# print(f"{self.center_y} = {a}*({int(self.center_x - self.target_x)})**2+{self.target_y}")
# 计算导弹和气球飞艇之间的弧度值 Π = 3.14(弧度) = 180°(角度)
target_radians_angle = math.atan2(self.target_y-self.center_y,self.target_x-self.center_x)
# 将上一步计算出的弧度转换为角度值
# 都要减去90 因为导弹的初始方向是向上的,所以有一个默认的90°角,减去后恢复到 0°再计算。
target_degree_angle = math.degrees(target_radians_angle)-90
if (self.target_x-self.init_x)*(self.target_x-self.center_x) < 0:
# 值小于0 的时候说明导弹飞行中已经超过了设定好的最高点
target_degree_angle = target_degree_angle + 180
# print(f"角度是:{target_degree_angle}")
if abs(target_degree_angle) in range(0,8):
self.angle = 0
elif abs(target_degree_angle) in range(172,188):
self.angle = 180
else:
self.angle = target_degree_angle
class BaoZhaYanWu(arcade.Sprite):
def __init__(self):
super(BaoZhaYanWu, self).__init__()
# 初始中心点不在界面左侧,则是从右向左飞行的,因为原始图片都朝向右侧,向左飞行的要水平翻转
all_explore_pic = os.listdir("../图片文件/images11")
for pic_name in all_explore_pic:
self.append_texture((arcade.load_texture(f"../图片文件/images11/{pic_name}")))
# 记下一共有多少纹理图
self.textures_num_len = len(all_explore_pic)
self.texture_index_now = 0
def update(self, texture_index: int = None):
# 如果状态是 True 则开始计算绘制位置
if self.texture_index_now < self.textures_num_len:
self.set_texture(self.texture_index_now)
self.texture_index_now += 1
return False
else:
# 收到为True的返回值时 销毁烟雾精灵
return True
class InfoPanel:
"""信息板 以后可以优化为游戏公共信息类"""
def __init__(self):
super(InfoPanel, self).__init__()
self.game_info = {}
self.set_up()
def set_up(self,init_qi_qiu_num=0,game_limit_time=10,
zhui_zong_dao_dan_num=10,dan_dao_dao_dan_num=20,difficulty_level=2):
self.game_info.clear()
self.game_info["game_status"] = True # 游戏状态(能玩和不能玩)
self.game_info["game_pause"] = False # 游戏暂停状态
self.game_info["player_score"] = 0 # 玩家分数
self.game_info["player_use_time"] = 0 # 玩家用时
self.game_info["game_limit_time"] = game_limit_time # 游戏限时 单位秒
self.game_info["difficulty_level"] = difficulty_level # 游戏难度(平均几个弹道导弹 VS 一个气球)
self.game_info["surplus_dan_dao_dao_dan_num"] = dan_dao_dao_dan_num # surplus:剩余 弹道导弹数量
self.game_info["surplus_zhui_zong_dao_dan_num"] = zhui_zong_dao_dan_num # 剩余追踪弹的数量
# 得分要求为 追踪导弹的数量个数 加上 弹道导弹数量个数的一半
self.game_info["player_target_score"] = zhui_zong_dao_dan_num + dan_dao_dao_dan_num//max(1,difficulty_level) # 玩家目标分数
self.game_info["init_qi_qiu_num"] = init_qi_qiu_num # 初始气球数量设置为
self.game_info["surplus_qi_qiu_num"] = 0 # 剩余气球数量
self.game_info["all_qi_qiu_num"] = 0 # 全部出现过的气球数量
self.game_info["zhui_zong_dao_dan_pic"] = arcade.Sprite("../图片文件/导弹.gif")
self.game_info["dan_dao_dao_dan_pic"] = arcade.Sprite("../图片文件/弹道导弹.png")
# 游戏胜利图片
self.game_info["game_win_pic"] = arcade.Sprite("../图片文件/win.png")
# 游戏失败图片
self.game_info["game_over_pic"] = arcade.Sprite("../图片文件/game_over.png")
# 游戏暂停图片
self.game_info["game_pause_pic"] = arcade.Sprite("../图片文件/游戏暂停.png")
# 游戏胜利音乐
self.game_info["game_win_sound"] = arcade.sound.load_sound("../声音文件/成功.mp3")
# 游戏失败音乐
self.game_info["game_over_sound_1"] = arcade.sound.load_sound("../声音文件/马里奥游戏结束.mp3")
# 游戏失败嘲讽
self.game_info["game_over_sound_2"] = arcade.sound.load_sound("../声音文件/马里奥哈哈嘲讽.mp3")
# 游戏暂停音效
self.game_info["game_pause_sound"] = arcade.sound.load_sound("../声音文件/暂停.mp3")
# # 游戏暂停后重新开始音效
# self.game_info["game_continue_sound"] = arcade.sound.load_sound("../声音文件/暂停后开始.mp3")
def draw_game_info(self,x,y):
# x: 屏幕宽度 y:屏幕高度
font_size = 20
# 第一行
# 描述游戏信息的字符串
self.game_info["game_info_txt"] = f"难度:{self.game_info['difficulty_level']} vs 1" \
f" 得分:{self.game_info['player_score']} /{self.game_info['player_target_score']}"\
f" 命中率:{self.game_info['player_score']}/{self.game_info['all_qi_qiu_num']}"\
f" 计时:{self.game_info['player_use_time']}/{self.game_info['game_limit_time']}"
arcade.Text(
self.game_info["game_info_txt"], 10, y - font_size*2,
color=arcade.color.SAE, font_size=font_size, width=x, font_name="站酷快乐体2016修订版",
bold=True, align="left", anchor_x="left", multiline=False).draw()
# 第二行
# 绘制追踪导弹信息
self.game_info["zhui_zong_dao_dan_pic"].width = font_size*2
self.game_info["zhui_zong_dao_dan_pic"].height = font_size*2
self.game_info["zhui_zong_dao_dan_pic"].center_x = 10 + font_size // 2
self.game_info["zhui_zong_dao_dan_pic"].center_y = y - font_size * 3.5
self.game_info["zhui_zong_dao_dan_pic"].draw()
arcade.Text(
f"X {self.game_info['surplus_zhui_zong_dao_dan_num']}", 10 + font_size*2 , y - font_size *4,
color=arcade.color.SAE, font_size=font_size, width=x, font_name="站酷快乐体2016修订版",
bold=True, align="left", anchor_x="left", multiline=True).draw()
# 绘制弹道导弹信息 (第二行 追踪导弹后面的内容)
self.game_info["dan_dao_dao_dan_pic"].width = font_size*2
self.game_info["dan_dao_dao_dan_pic"].height = font_size*2
self.game_info["dan_dao_dao_dan_pic"].center_x = 10 + font_size * 2 + 5*font_size
self.game_info["dan_dao_dao_dan_pic"].center_y = y - font_size * 3.5
self.game_info["dan_dao_dao_dan_pic"].draw()
arcade.Text(
f"X {self.game_info['surplus_dan_dao_dao_dan_num']}", 10 + font_size * 2 + 6*font_size, y - font_size *4,
color=arcade.color.SAE, font_size=font_size, width=x, font_name="站酷快乐体2016修订版",
bold=True, align="left", anchor_x="left", multiline=True).draw()
# 游戏失败时持续执行
if not self.game_info["game_status"] and self.game_info["player_use_time"] >= self.game_info["game_limit_time"]:
self.game_info["game_over_pic"].center_x = x // 2
self.game_info["game_over_pic"].center_y = y // 2
self.game_info["game_over_pic"].draw()
arcade.Text(
f"~~按右侧 SHIFT 键重新开始~~", x//2,y//2 - 200,
color=arcade.color.WHITE, font_size=font_size*2, width=x, font_name="站酷快乐体2016修订版",
bold=True, align="left", anchor_x="center", multiline=False).draw()
# 游戏胜利时持续执行
if not self.game_info["game_status"] and (
self.game_info["player_score"] >= self.game_info['player_target_score']):
self.game_info["game_win_pic"].center_x = x // 2
self.game_info["game_win_pic"].center_y = y // 2
self.game_info["game_win_pic"].draw()
# 触发游戏暂停时持续执行
if self.game_info["game_status"] and self.game_info["game_pause"]:
self.game_info["game_pause_pic"].center_x = x // 2
self.game_info["game_pause_pic"].center_y = y // 2
self.game_info["game_pause_pic"].draw()
arcade.Text(
f"~~按 Enter 键继续~~", x // 2, y // 2 - 200,
color=arcade.color.WHITE, font_size=font_size*2, width=x, font_name="站酷快乐体2016修订版",
bold=True, align="left", anchor_x="center", multiline=False).draw()
def check_game_status(self, bgm):
# 游戏失败时执行
if self.game_info["game_status"] and self.game_info["player_use_time"] >= self.game_info["game_limit_time"]:
self.game_info["game_status"] = False
arcade.stop_sound(bgm)
self.game_info["game_over_sound_1"].play()
self.game_info["game_over_sound_2"].play()
# 游戏胜利时执行
if self.game_info["game_status"] and (
self.game_info["player_score"] >= self.game_info['player_target_score']):
self.game_info["game_status"] = False
arcade.stop_sound(bgm)
self.game_info["game_win_sound"].play()
# 游戏暂停时执行
if self.game_info["game_pause"]:
return False
return self.game_info["game_status"]
# arcade_game_202302 流浪气球Ⅰ
class LiuLangQiQiu(arcade.View):
""" 视图程序,用于在窗体内展示. """
def __init__(self):
# 初始化父类的属性
super().__init__()
self.background_image = None
self.dao_dan = None
# 气球类
self.qi_qiu = None
self.qi_qiu_list = None
# 导弹类
self.dao_dan = None
self.dao_dan_list = None
# 弹道导弹类
self.dan_dao_dao_dan = None
self.dan_dao_dao_dan_list = None
# 所有气球文件
self.all_qi_qiu = None
# 游戏背景音乐
self.background_sound = arcade.load_sound("../声音文件/忍者神龟背景音乐.mp3")
# 导弹发射车
self.dao_dan_che_image = None
# 爆炸烟雾
self.explode_smoke = None
self.explode_smoke_list = None
# 为了有节奏的控制游戏刷新时个别元素的刷新速度 设置一个记录时间间隔变量
self.interval_time = 0
# 游戏信息面板
self.info_panel = None
# 鼠标的图片
self.couser_pic = None
# 在初始化时调用的一些准备工作代码
self.set_up()
# 有规律的调用time_clock函数,每次调用间隔设置为1秒
arcade.schedule(self.time_clock, 1)
def set_up(self,init_qi_qiu_num = 5,zhui_zong_dao_dan_num = 20,dan_dao_dao_dan_num=30,difficulty_level=3):
"difficulty_level:难度等级 代表平均至少用几个弹道导弹就击中一个气球 ,会以此来计算玩家的目标分数 数字越大越简单 最小是1"
""" 进行一些游戏准备工作,让游戏主逻辑从这开始,以便于在有需要时重开游戏. """
# 实例化游戏信息类
self.info_panel = InfoPanel()
self.info_panel.set_up(init_qi_qiu_num=init_qi_qiu_num,
game_limit_time=zhui_zong_dao_dan_num * 1 + dan_dao_dao_dan_num*2,
zhui_zong_dao_dan_num=zhui_zong_dao_dan_num, dan_dao_dao_dan_num=dan_dao_dao_dan_num,
difficulty_level=difficulty_level)
# 游戏背景音乐
self.current_play = self.background_sound.play(volume=0.4, loop=True)
# 气球精灵列表
self.qi_qiu_list = arcade.SpriteList()
# 导弹精灵列表
self.dao_dan_list = arcade.SpriteList()
# 弹道导弹精灵列表
self.dan_dao_dao_dan_list = arcade.SpriteList()
# 爆炸烟雾精灵
self.explode_smoke_list = arcade.SpriteList()
# 获取文件夹中所有的飞艇图片
self.all_qi_qiu = os.listdir("../图片文件/气球飞艇")
print(self.all_qi_qiu)
# 背景图片 当作精灵加载进来
self.background_image = arcade.Sprite("../图片文件/世界卫星地图.png")
# 设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 3
self.background_image.center_y = self.window.height // 2
# 导弹发射车 在on_resize()里设置绘制位置
self.dao_dan_che_image = arcade.Sprite("../图片文件/东风41.png")
self.dao_dan_che_image.width = 70
self.dao_dan_che_image.height = 70
# 初始状态时创建自定义个气球(默认3个)
self.creat_qi_qiu(init_qi_qiu_num)
self.couser_pic = arcade.Sprite('../图片文件/瞄准.png', scale=0.2)
# 括号里的这个参数必须写,他是一个时间间隔,代表上次调用 到本次调用之间的时间差 不严格等于我们设置的1秒
def time_clock(self, delta_time):
# print(delta_time)
# 因为我们要更改TIME_CLOCK的值,所以使用global 让函数可以改变这个全局变量
if self.info_panel.game_info["game_status"] and not self.info_panel.game_info["game_pause"]:
self.info_panel.game_info["player_use_time"] += 1
def creat_qi_qiu(self, num):
"""创建气球"""
for i in range(1, num+1):
self.qi_qiu = QiQiu(filename=f"../图片文件/气球飞艇/{random.choice(self.all_qi_qiu)}",
change_x=random.choice([0.5,0.5,1,2,2]), change_y=random.choice([1,2]),
init_x=random.choice([-80, self.window.width + 200]),
init_y=random.randint(200, self.window.height - num*50))
self.qi_qiu.width = 60
self.qi_qiu.height = 40
# self.qi_qiu.scale = 0.5
if self.qi_qiu.center_x > self.window.width//2:
self.qi_qiu.change_x = -self.qi_qiu.change_x
self.qi_qiu_list.append(self.qi_qiu)
# 每次创建过气球后 都将统计到游戏信息面板中
self.info_panel.game_info["all_qi_qiu_num"] += num
def creat_zhui_zong_dao_dan(self):
# 追踪导弹 如果剩余的追踪弹数量大于0 就接受命令进行创建
if self.info_panel.game_info["surplus_zhui_zong_dao_dan_num"] > 0:
dao_dan = DaoDan("../图片文件/导弹.gif", change_x=4, change_y=3,
init_x=self.window.width // 3, init_y=self.window.height // 2)
self.dao_dan_list.append(dao_dan)
dao_dan.fire_sound.play()
self.info_panel.game_info["surplus_zhui_zong_dao_dan_num"] -= 1
def creat_dan_dao_dao_dan(self,x,y):
# 弹道导弹 如果剩余的弹道弹数量大于0 就接受命令进行创建
if self.info_panel.game_info["surplus_dan_dao_dao_dan_num"] > 0:
init_x = self.window.width // 3
init_y = self.window.height // 2
speed = 1 if abs(x - init_x) < 200 else 3
# 如果目标点在右侧 则导弹向右飞行 横坐标变化为正数 否则为负数
change_x = speed if x > self.window.width // 3 else - speed
dan_dao_dao_dan = DanDaoDaoDan("../图片文件/弹道导弹.png", change_x=change_x, change_y=3,
init_x=init_x, init_y=init_y,
target_x=x, target_y=y)
dan_dao_dao_dan.scale = 0.7
self.dan_dao_dao_dan_list.append(dan_dao_dao_dan)
dan_dao_dao_dan.fire_sound.play()
# 创建完毕后 剩余弹道导弹数量 -1
self.info_panel.game_info["surplus_dan_dao_dao_dan_num"] -= 1
def creat_yan_wu(self,center_x, center_y):
yan_wu = BaoZhaYanWu()
yan_wu.center_x = center_x
yan_wu.center_y = center_y
self.explode_smoke_list.append(yan_wu)
# 画面渲染绘制
def on_draw(self):
""" 负责渲染画面 速率: 60帧/秒. """
# 清除上一帧绘制的画面
self.clear()
# 绘制背景图片
self.background_image.draw()
# 导弹发射车
self.dao_dan_che_image.draw()
# 绘制气球精灵组
self.qi_qiu_list.draw()
# 绘制导弹精灵组
self.dao_dan_list.draw()
# 绘制弹道导弹精灵组
self.dan_dao_dao_dan_list.draw()
# 绘制爆炸烟雾
self.explode_smoke_list.draw()
# 绘制游戏信息
self.info_panel.draw_game_info(self.window.width, self.window.height)
# 绘制鼠标
self.couser_pic.draw()
# 控制每次刷新时的变化
def on_update(self, delta_time: float):
""" 负责逻辑变化 速率: 60帧/秒. """
if self.info_panel.check_game_status(self.current_play):
# 如果检测游戏的状态为True 则继续刷新 否则不再执行后续逻辑
for qi_qiu_id, qi_qiu in enumerate(self.qi_qiu_list):
# 处理气球和自动追踪导弹的碰撞及逻辑
qi_qiu.update(self.window.height)
if qi_qiu_id <= len(self.dao_dan_list)-1:
dao_dan = self.dao_dan_list[qi_qiu_id]
dao_dan.track_target(qi_qiu.center_x, qi_qiu.center_y, change_angle=-0.5)
# else:
# print("请尽快发射导弹")
collision = arcade.check_for_collision_with_list(qi_qiu, self.dao_dan_list)
if len(collision) > 0:
print(f"发生碰撞了,相撞的{qi_qiu_id}号导弹和气球都被移除")
# 移除碰到的气球
qi_qiu.remove_from_sprite_lists()
# 剩余气球数量 -1
self.info_panel.game_info["surplus_qi_qiu_num"] -= 1
# 玩家分数 +1
self.info_panel.game_info["player_score"] += 1
# 创建气球
self.creat_qi_qiu(1)
# 移除碰到的追踪导弹
for collision_obj in collision:
collision_obj.remove_from_sprite_lists()
collision_obj.explode_sound.play()
# 追踪导弹数量-1 因为在创建时候就减去了1 所以这里不再减
# self.info_panel.game_info["surplus_zhui_zong_dao_dan_num"] -= 1
# 创建烟雾 坐标为所碰撞气球的坐标
self.creat_yan_wu(qi_qiu.center_x,qi_qiu.center_y)
if qi_qiu.center_x not in range(-230, self.window.width+230):
# 气球飞出屏幕一定距离后 则消失
qi_qiu.remove_from_sprite_lists()
# 剩余气球数量 -1
self.info_panel.game_info["surplus_qi_qiu_num"] -= 1
# 再次创建气球
self.creat_qi_qiu(1)
# 处理按下鼠标发射的弹道导弹
for dan_dao_dao_dan in self.dan_dao_dao_dan_list:
# 处理气球和弹道导弹的碰撞及逻辑
dan_dao_dao_dan.track_target()
collision2 = arcade.check_for_collision_with_list(dan_dao_dao_dan, self.qi_qiu_list)
for collision_obj2 in collision2:
collision_obj2.remove_from_sprite_lists()
dan_dao_dao_dan.remove_from_sprite_lists()
dan_dao_dao_dan.explode_sound.play()
# 剩余气球数量 -1
self.info_panel.game_info["surplus_qi_qiu_num"] -= 1
# 玩家分数 +1
self.info_panel.game_info["player_score"] += 1
# 创建气球
self.creat_qi_qiu(1)
# 创建烟雾
self.creat_yan_wu(collision_obj2.center_x, collision_obj2.center_y)
# 如果导弹飞出了屏幕 则销毁
if dan_dao_dao_dan.center_x not in range(-230, self.window.width+230):
dan_dao_dao_dan.remove_from_sprite_lists()
# 将原来创建气球的时机改为每当有一个气球消失 就创建一个 下面代码不再使用
# if len(self.qi_qiu_list) <= 3:
# self.creat_qi_qiu(3)
for smoke in self.explode_smoke_list:
self.interval_time += delta_time
if self.interval_time > 0.08:
res = smoke.update()
self.interval_time = 0
if res:
smoke.remove_from_sprite_lists()
def on_mouse_press(self, x, y, button, modifiers):
""" 监听鼠标点击事件. """
mouse_buttons = {1: "左键", 2: "中键", 4: "右键"}
# print(f"当前点击的坐标是{x,y}")
if self.info_panel.game_info["game_status"] and not self.info_panel.game_info["game_pause"]:
# 创建弹道导弹
self.creat_dan_dao_dao_dan(x,y)
def on_mouse_release(self, x: float, y: float, button: int, modifiers: int):
""" 监听鼠标释放事件. """
pass
def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
""" 监听鼠标移动事件 """
self.couser_pic.center_x = x
self.couser_pic.center_y = y
def on_key_press(self,key, modifiers):
"""监听键盘按键点击事件
modifiers:None = 16,shift= 1(17) ctrl= 2(18) alt= 4(20) """
print("---键盘按键按下了:--",key,"--修饰键编号的和是:---", modifiers)
if key == arcade.key.SPACE and self.info_panel.game_info["game_status"] \
and not self.info_panel.game_info["game_pause"]:
self.creat_zhui_zong_dao_dan()
if key == arcade.key.RSHIFT and not self.info_panel.game_info["game_status"]:
# 让游戏重新开始 并且重置屏幕大小为上一次的大小
self.set_up()
self.on_resize(self.window.width,self.window.height)
def on_key_release(self, key, modifiers):
print("---键盘按键抬起了:--",key,"--剩余修饰键编号的和是:---", modifiers)
print("---键盘按下之前:--",self.info_panel.game_info["game_pause"])
if key == arcade.key.ENTER and self.info_panel.game_info["game_status"]:
self.info_panel.game_info["game_pause"] = not self.info_panel.game_info["game_pause"]
self.info_panel.game_info["game_pause_sound"].play()
print("---键盘按下之后:--", self.info_panel.game_info["game_pause"])
def on_resize(self, width: int, height: int):
# 重新设置精灵素材中心点的位置
self.background_image.center_x = self.window.width // 2
self.background_image.center_y = self.window.height // 2
# 重新设置精灵素材宽度和高度
self.background_image.width = self.window.width
self.background_image.height = self.window.height
# 设置导弹车精灵素材中心点的位置
self.dao_dan_che_image.center_x = self.window.width // 3 - 30
self.dao_dan_che_image.center_y = self.window.height // 2
for jing_ling in (self.dan_dao_dao_dan_list,self.dao_dan_list,self.qi_qiu_list):
# 界面在玩耍时侯如果缩放,会使导弹和气球位置发生相对于地图的偏移
pass
def main():
# 设置窗体的宽、高、名字、是否支持缩放
window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, resizable=True)
# 设置窗体的 logo 图标
import pyglet
window.set_icon(pyglet.image.load('../图片文件/浪淘三千.png'))
# 设置鼠标形状
cursor = window.get_system_mouse_cursor(window.CURSOR_HAND)
window.set_mouse_cursor(cursor)
# 实例化定义的某个窗体
start_view = LiuLangQiQiu()
# 设置当前应该显示哪个窗体
window.show_view(start_view)
# 保持程序持续运行
arcade.run()
if __name__ == "__main__":
main()