独此一家,建议收藏

  • 前言
  • 一、创建一个空白窗口
    • step001.py代码示例
  • 二、创建很多全局能用的常量
    • step002.py代码示例
  • 三、创建实例变量即代表各种精灵等的变量
    • step003.py代码和效果示例
  • 四、加载并显示地图编辑器设计的地图
    • 1.打开地图编辑器 并新建地图文件
    • 2.设置地图大小并命名保存
    • 3.加载图片素材用于绘制地图
    • 4.在Platforms图层绘制墙体和地面等元素
    • 5.在代码中显示地图
    • step004.py代码和效果示例
  • 五、添加物理引擎,使物体具有物理属性
    • step005.py代码示例
  • 六、添加玩家移动功能
    • step006.py代码示例
  • 七、让玩家跳跃
    • step007.py效果示例
    • step007.py代码示例
  • 八、添加角色移动动画
    • step008.py效果示例
    • step008.py代码示例
  • 九、增加射击功能
    • step009.py效果示例
    • step009.py代码示例
  • 十、让子弹消失
    • step010.py效果示例(1)
    • step010.py效果示例(2)
    • step010.py代码示例
  • 十一、添加自动移动的物体
    • 1.在对象层加入物体图块并显示
    • 2.给对象层物体添加属性
    • step011.py效果示例
    • step011.py代码示例
  • 十二、创建可以上下爬动的梯子
    • 1.在地图编辑器创建梯子的图层
    • 2.在代码中加载图块层并显示
    • 3.让玩家能够爬上梯子并相应移动
    • step012.py效果示例
    • step012.py代码示例
  • 十三、 打包成exe软件包
    • 1.首先在入口文件 的代码里加上三行代码
    • 2.安装打包工具库pyinstaller
    • 3.修改arcade库源文件的一行代码
    • 4.进行打包
    • 5.把素材文件加入软件包
    • 6.再次双击exe程序 搞定
  • 十四、拓展(1):屏幕移动(随玩家)
    • 1.在原来地图基础上加入更多元素
    • 2.定义内边框的范围 和 当前边框数值
    • 3.改保存地图的变量 为 类属性(也就是加个self)
    • 4.在update()方法里计算屏幕的滑动量,并让屏幕滑动
    • step013.py效果示例
    • step013.py代码示例
  • 十五、拓展(2):优化玩家速度
    • 1. 让玩家不再逆天弹跳
  • 十六、拓展(3):添加背景层和前景层
    • 1.地图编辑器里创建新层并绘制相应图块
    • 2、在代码中添加相关图层并显示
  • 十七、拓展(4):添加音效和背景音乐
    • 1.添加音效
    • 2.添加背景音乐
    • 代码示例
  • 十八、拓展(5):添加分数文字等
  • 十九、拓展(6):定时器功能
  • 终结

前言

  • 本文档参照了arcade官网示例,并且做了相关补充,仅作初次掌握使用
  • 本文图片和声音素材均为arcade内置素材。
  • 用arcade写2D游戏,是因为他更加面向对象化,代码清晰,比pygame更容易阅读和理解。
  • 首先确保你安装了arcade库和tiled map editor地图编辑器
  • 在终端使用 pip install arcade 或者你常用的方式安装arcade游戏库
  • 每块内容的代码都是递进的,可以直接复制使用,不懂的地方欢迎留言
  • 内容较长,耐心些你就是班里最靓的仔
  • arcade官方地址https://arcade.academy/tutorials/pymunk_platformer/index.html
  • 游戏地图编辑器下载地址 https://thorbjorn.itch.io/tiled
  • 后期应该会出视频讲解,到时候会陆续链接。B站 浪淘三千
  • 第一个视频

一、创建一个空白窗口

窗口打开后 暂时是黑色背景 后续我们一步步完善

step001.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# 屏幕尺寸 大小以像素为单位 此处是800宽 600高
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)def setup(self):""" Set up everything with the game 设置游戏的各种起始数据"""passdef on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""passdef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""passdef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""passdef on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

效果示例 是一个黑色的界面

二、创建很多全局能用的常量

常量定义很多固定的属性,例如子弹的速度,大小等,在全局都可以直接使用
这里新引入的math 数学库 和用于确定数据类型的 typing的 Optional方法

step002.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 128# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHTclass GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)def setup(self):""" Set up everything with the game 设置游戏的各种起始数据"""passdef on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""passdef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""passdef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""passdef on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

运行效果同上一步 只不过屏幕更大了 掌握方法也可以自由调整
接下来学习背景颜色的设置

三、创建实例变量即代表各种精灵等的变量

step003.py代码和效果示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 128# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHTclass GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型self.player_sprite: Optional[arcade.Sprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置游戏的各种起始数据"""passdef on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""passdef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""passdef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""passdef on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

屏幕颜色变成了绿色

四、加载并显示地图编辑器设计的地图

这里暂时无法放置地图文件链接,以后补百度网盘的

1.打开地图编辑器 并新建地图文件

左上角 文件> 新建 > 创建新地图> 之后会让设置组成地图的格子的大小和个数
我们还是用32*32像素的 可以相对

2.设置地图大小并命名保存


下面这个图片请忽略

命名并保存

初始地图界面 这是我二次创建的 原来100*100格子太多了

3.加载图片素材用于绘制地图

图片素材:这里称为图块集 或者图块 就是把一张具有各种界面元素的图片分解为许多小块后的集合
地图文件命名:pymunk_test_map
不同地图块层命名:
地形层 Platforms 创建墙体和地面
可被移动物体层 Dynamic Items 例如一些小箱子之类的物体

选择另存为,然后保存到当前代码所在文件夹

保存到当前代码所在文件夹

预览图块
重新加载图块时 还要重新命名 可以用同样的名字覆盖掉之前文件就行

重新创建了地图和图块集 看起来更加舒适
修改当前图层名字为 Platforms

4.在Platforms图层绘制墙体和地面等元素

绘制完记得ctrl+s保存

右键空白区域 再创建一个可被移动物体层 Dynamic Items

5.在代码中显示地图


运行发现界面空白部分太大
我们稍微调一下画面尺寸就好 让一个基础物体大小等于一个格子大小 下面有讲


然后效果如下 玩家的初始位置似乎还要再上移一格 那我们再调整初始位置

稍作优化 即可更改玩家位置

step004.py代码和效果示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHTclass GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型self.player_sprite: Optional[arcade.Sprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",SPRITE_SCALING_PLAYER)# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""passdef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""passdef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""passdef on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.bullet_list.draw()self.item_list.draw()self.player_list.draw()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

五、添加物理引擎,使物体具有物理属性

物理属性主要为:

属性名 介绍
质量Mass 自定义 质量影响其他属性,质量大的可以推动质量小的物体
阻尼damping 0~1之间 数字越大,阻力越大,失速越快,1没力施加时瞬间时速,0不失速。
重力gravity 一个加速度常数 跳起来的时候可以落下
摩擦力 0~1之间 0是冰,1是胶水




效果展示

step005.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型self.player_sprite: Optional[arcade.Sprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",SPRITE_SCALING_PLAYER)# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""passdef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""passdef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.bullet_list.draw()self.item_list.draw()self.player_list.draw()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

六、添加玩家移动功能

补充常量都在程序一开始的地方定义就好 大写更容易明白是常量

监听按键状态

在刷新方法里 持续监听按键事件 并给物理以移动需要的力量

效果示例 可以使用键盘的左右键移动了

step006.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型self.player_sprite: Optional[arcade.Sprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",SPRITE_SCALING_PLAYER)# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Truedef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个左或右按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.bullet_list.draw()self.item_list.draw()self.player_list.draw()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

七、让玩家跳跃

再次定义新的常量 控制空中受力 和起跳力

引擎的 is_on_ground()方法 检测到在地面上时返回true 否则返回false

设置不同状态时 左右移动的力 也就是速度

step007.py效果示例

step007.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1800class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型self.player_sprite: Optional[arcade.Sprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",SPRITE_SCALING_PLAYER)# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)def on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行if is_on_ground:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个左或右按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.bullet_list.draw()self.item_list.draw()self.player_list.draw()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

八、添加角色移动动画

让角色移动时候可以有连续的动作 看起来就像在跑步或者走路 而不是静止飘移
还要考虑角色的左右朝向问题 因此 我们再加些新的常量

添加新的类 单独定义玩家的一些属性和方法

定义了新的玩家类 那个在主程序类中 创建玩家的方法也相应要做改变
init 和 setup 方法里都要改 改完后运行 精灵就可以在移动时切换造型了

step008.py效果示例

step008.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1800# Close enough to not-moving to have the animation go to idle.
# 设置这个常量 当玩家角色移动的距离小于0.1时 不让他有动画的走路表现 而是静止 比如在挨着墙的时候按方向键 不再有走路动画
DEAD_ZONE = 0.1# Constants used to track if the player is facing left or right
# 设置面向右侧 为 0  面向左 为 1
RIGHT_FACING = 0
LEFT_FACING = 1# How many pixels to move before we change the texture in the walking animation
# 动作切换是很快的事情,那怎么控制切换频率或者速度呢,我们设置这个变量,使角色移动量每大于20像素时再进行动画 切换
DISTANCE_TO_CHANGE_TEXTURE = 20class PlayerSprite(arcade.Sprite):""" Player Sprite """def __init__(self):""" Init """# Let parent initialize 初始化父类方法super().__init__()# Set our scale 设置缩放系数self.scale = SPRITE_SCALING_PLAYER# Images from Kenney.nl's Character pack 图片来自 Kenney网站的素材 aracade 以默认内置# 以下 图片都可以用 选一个喜欢的造型集即可 名字是一样的 后缀不一样则造型不一样 所以main_path 只定义名字# main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"main_path = ":resources:images/animated_characters/female_person/femalePerson"# main_path = ":resources:images/animated_characters/male_person/malePerson"# main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"# main_path = ":resources:images/animated_characters/zombie/zombie"# main_path = ":resources:images/animated_characters/robot/robot"# Load textures for idle standing 使用load_texture_pair加载不同状态下的造型图片self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")  # 站立造型self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")  # 跳跃造型self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")  # 下落造型# Load textures for walking 由于走路的造型较多 这里用循环去加载 然后放入列表中备用self.walk_textures = []for i in range(8):texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")self.walk_textures.append(texture)# Set the initial texture 默认的站立造型 (0,代表造型里的第一张图片,因为以后还会有左右不同方向的造型)self.texture = self.idle_texture_pair[0]# Hit box will be set based on the first image used. 角色边框由第一张造型图片定义 以便后续碰撞检测self.hit_box = self.texture.hit_box_points# Default to face-right 默认觉得是脸朝右的self.character_face_direction = RIGHT_FACING# Index of our current texture  当前造型图片在图片组中的索引值self.cur_texture = 0# How far have we traveled horizontally since changing the texture# 定义一个 记录上次变换造型以后 角色的移动距离  后续用于比较当他大于我们上面常量设置的值时 执行造型切换self.x_odometer = 0#  这是物理引擎一个处理移动的方法,只要这个精灵是由物理引擎创建的,那么物理引擎自己就会去调用这个固定方法的#  dx 代表每一帧之间 物体横向变化的距离  dy 代表每一帧之间 物体纵向变化的距离#  d_angle则代表每一帧之间 物体角度朝向的变化量def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle being moved by the pymunk engine 这是物理引擎一个处理移动的方法"""# Figure out if we need to face left or right  判断我们是否要改变角色的朝向if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:self.character_face_direction = LEFT_FACINGelif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:self.character_face_direction = RIGHT_FACING# Are we on the ground? 判断当前对象是否在地面上 self代表当前对象 谁用这个类创建的 那么self就是谁is_on_ground = physics_engine.is_on_ground(self)# Add to the odometer how far we've moved  将横向的变化量累加起来self.x_odometer += dx# Jumping animationif not is_on_ground:if dy > DEAD_ZONE:self.texture = self.jump_texture_pair[self.character_face_direction]returnelif dy < -DEAD_ZONE:self.texture = self.fall_texture_pair[self.character_face_direction]return# Idle animationif abs(dx) <= DEAD_ZONE:self.texture = self.idle_texture_pair[self.character_face_direction]return# Have we moved far enough to change the texture?if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.x_odometer = 0# Advance the walking animationself.cur_texture += 1if self.cur_texture > 7:self.cur_texture = 0self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型# self.player_sprite: Optional[arcade.Sprite] = Noneself.player_sprite: Optional[PlayerSprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数# self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.p# ng",SPRITE_SCALING_PLAYER)self.player_sprite = PlayerSprite()# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)def on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行if is_on_ground:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个左或右按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.bullet_list.draw()self.item_list.draw()self.player_list.draw()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

九、增加射击功能

老规矩 在增加新的物体的时候 我们一般都会去创建一些关于这个物体的常量

创建一个鼠标监听事件 (arcade库默认会调用他)子弹暂时使用创建的矩形来表示

step009.py效果示例

点击鼠标时 子弹可以朝鼠标位置发射出来 并且碰到别的物体时候 可以反弹 具备正常物理属性

step009.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity 重力
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1800# Close enough to not-moving to have the animation go to idle.
# 设置这个常量 当玩家角色移动的距离小于0.1时 不让他有动画的走路表现 而是静止 比如在挨着墙的时候按方向键 不再有走路动画
DEAD_ZONE = 0.1# Constants used to track if the player is facing left or right
# 设置面向右侧 为 0  面向左 为 1
RIGHT_FACING = 0
LEFT_FACING = 1# How many pixels to move before we change the texture in the walking animation
# 动作切换是很快的事情,那怎么控制切换频率或者速度呢,我们设置这个变量,使角色移动量每大于20像素时再进行动画 切换
DISTANCE_TO_CHANGE_TEXTURE = 20# step009.py 添加
# How much force to put on the bullet  给子弹施加的初始动力
BULLET_MOVE_FORCE = 4500# Mass of the bullet  子弹的质量
BULLET_MASS = 0.1# Make bullet less affected by gravity 给子弹一个更小的重力值
BULLET_GRAVITY = 300class PlayerSprite(arcade.Sprite):""" Player Sprite """def __init__(self):""" Init """# Let parent initialize 初始化父类方法super().__init__()# Set our scale 设置缩放系数self.scale = SPRITE_SCALING_PLAYER# Images from Kenney.nl's Character pack 图片来自 Kenney网站的素材 aracade 以默认内置# 以下 图片都可以用 选一个喜欢的造型集即可 名字是一样的 后缀不一样则造型不一样 所以main_path 只定义名字# main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"main_path = ":resources:images/animated_characters/female_person/femalePerson"# main_path = ":resources:images/animated_characters/male_person/malePerson"# main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"# main_path = ":resources:images/animated_characters/zombie/zombie"# main_path = ":resources:images/animated_characters/robot/robot"# Load textures for idle standing 使用load_texture_pair加载不同状态下的造型图片self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")  # 站立造型self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")  # 跳跃造型self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")  # 下落造型# Load textures for walking 由于走路的造型较多 这里用循环去加载 然后放入列表中备用self.walk_textures = []for i in range(8):texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")self.walk_textures.append(texture)# Set the initial texture 默认的站立造型 (0,代表造型里的第一张图片,因为以后还会有左右不同方向的造型)self.texture = self.idle_texture_pair[0]# Hit box will be set based on the first image used. 角色边框由第一张造型图片定义 以便后续碰撞检测self.hit_box = self.texture.hit_box_points# Default to face-right 默认觉得是脸朝右的self.character_face_direction = RIGHT_FACING# Index of our current texture  当前造型图片在图片组中的索引值self.cur_texture = 0# How far have we traveled horizontally since changing the texture# 定义一个 记录上次变换造型以后 角色的移动距离  后续用于比较当他大于我们上面常量设置的值时 执行造型切换self.x_odometer = 0#  这是物理引擎一个处理移动的方法,只要这个精灵是由物理引擎创建的,那么物理引擎自己就会去调用这个固定方法的#  dx 代表每一帧之间 物体横向变化的距离  dy 代表每一帧之间 物体纵向变化的距离#  d_angle则代表每一帧之间 物体角度朝向的变化量def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle being moved by the pymunk engine 这是物理引擎一个处理移动的方法"""# Figure out if we need to face left or right  判断我们是否要改变角色的朝向if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:self.character_face_direction = LEFT_FACINGelif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:self.character_face_direction = RIGHT_FACING# Are we on the ground? 判断当前对象是否在地面上 self代表当前对象 谁用这个类创建的 那么self就是谁is_on_ground = physics_engine.is_on_ground(self)# Add to the odometer how far we've moved  将横向的变化量累加起来self.x_odometer += dx# Jumping animationif not is_on_ground:if dy > DEAD_ZONE:self.texture = self.jump_texture_pair[self.character_face_direction]returnelif dy < -DEAD_ZONE:self.texture = self.fall_texture_pair[self.character_face_direction]return# Idle animationif abs(dx) <= DEAD_ZONE:self.texture = self.idle_texture_pair[self.character_face_direction]return# Have we moved far enough to change the texture?if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.x_odometer = 0# Advance the walking animationself.cur_texture += 1if self.cur_texture > 7:self.cur_texture = 0self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型# self.player_sprite: Optional[arcade.Sprite] = Noneself.player_sprite: Optional[PlayerSprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数# self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.p# ng",SPRITE_SCALING_PLAYER)self.player_sprite = PlayerSprite()# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)def on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行if is_on_ground:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个左或右按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.bullet_list.draw()self.item_list.draw()self.player_list.draw()# 当鼠标点击屏幕时 会给该方法传入以下参数 x :横坐标 y:纵坐标 button:鼠标左中右按的哪个键 modifiers:修饰键 alt shift 等这些def on_mouse_press(self, x, y, button, modifiers):""" Called whenever the mouse button is clicked.当鼠标点击时 本方法被调用"""# 使用创建据矩形的方法 创建一个子弹bullet = arcade.SpriteSolidColor(20, 5, arcade.color.DARK_YELLOW)self.bullet_list.append(bullet)  # 把子弹加入上面就定义过的子弹精灵组# Position the bullet at the player's current location# 定义子弹的初始位置 也就是精灵的位置start_x = self.player_sprite.center_xstart_y = self.player_sprite.center_ybullet.position = self.player_sprite.position# Get from the mouse the destination location for the bullet# IMPORTANT! If you have a scrolling screen, you will also need# to add in self.view_bottom and self.view_left.# 记录点击位置的坐标dest_x = xdest_y = y# Do math to calculate how to get the bullet to the destination.# Calculation the angle in radians between the start points# and end points. This is the angle the bullet will travel.# 使用高一会学习的三角函数知识 计算 玩家相对于平面和鼠标点击位置之间的角度x_diff = dest_x - start_xy_diff = dest_y - start_yangle = math.atan2(y_diff, x_diff)# What is the 1/2 size of this sprite, so we can figure out how far# away to spawn the bulletsize = max(self.player_sprite.width, self.player_sprite.height) / 2# Use angle to to spawn bullet away from player in proper direction# 加上size的好处是 让子弹从玩家图片外部发射出去,避免内部发射导致物理引擎误判bullet.center_x += size * math.cos(angle)bullet.center_y += size * math.sin(angle)# Set angle of bullet 设置子弹的朝向bullet.angle = math.degrees(angle)# Gravity to use for the bullet# If we don't use custom gravity, bullet drops too fast, or we have# to make it go too fast.# Force is in relation to bullet's angle.# 专门定义以下子弹垂直方向受到的重力 而不是默认的bullet_gravity = (0, -BULLET_GRAVITY)# Add the sprite. This needs to be done AFTER setting the fields above.# 使用物理引擎管理子弹 这个子弹是具有弹性的self.physics_engine.add_sprite(bullet,mass=BULLET_MASS,damping=1.0,friction=0.6,collision_type="bullet",gravity=bullet_gravity,elasticity=0.9)# Add force to bullet# 最后 让物理引擎给子弹施加一个力量,让他飞走force = (BULLET_MOVE_FORCE, 0)self.physics_engine.apply_force(bullet, force)def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

十、让子弹消失

为了减轻程序的压力 我们规定子弹在小于某一个高度时即可消失
或者在碰到某物体时消失
首先 专门再定一个子弹类 让物理引擎来时时监测他

然后

step010.py效果示例(1)


当撞击某规定物体时候,和物体一起消失

step010.py效果示例(2)

step010.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity 重力
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1800# Close enough to not-moving to have the animation go to idle.
# 设置这个常量 当玩家角色移动的距离小于0.1时 不让他有动画的走路表现 而是静止 比如在挨着墙的时候按方向键 不再有走路动画
DEAD_ZONE = 0.1# Constants used to track if the player is facing left or right
# 设置面向右侧 为 0  面向左 为 1
RIGHT_FACING = 0
LEFT_FACING = 1# How many pixels to move before we change the texture in the walking animation
# 动作切换是很快的事情,那怎么控制切换频率或者速度呢,我们设置这个变量,使角色移动量每大于20像素时再进行动画 切换
DISTANCE_TO_CHANGE_TEXTURE = 20# step009.py 添加
# How much force to put on the bullet  给子弹施加的初始动力
BULLET_MOVE_FORCE = 4500# Mass of the bullet  子弹的质量
BULLET_MASS = 0.1# Make bullet less affected by gravity 给子弹一个更小的重力值
BULLET_GRAVITY = 300class PlayerSprite(arcade.Sprite):""" Player Sprite """def __init__(self):""" Init """# Let parent initialize 初始化父类方法super().__init__()# Set our scale 设置缩放系数self.scale = SPRITE_SCALING_PLAYER# Images from Kenney.nl's Character pack 图片来自 Kenney网站的素材 aracade 以默认内置# 以下 图片都可以用 选一个喜欢的造型集即可 名字是一样的 后缀不一样则造型不一样 所以main_path 只定义名字# main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"main_path = ":resources:images/animated_characters/female_person/femalePerson"# main_path = ":resources:images/animated_characters/male_person/malePerson"# main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"# main_path = ":resources:images/animated_characters/zombie/zombie"# main_path = ":resources:images/animated_characters/robot/robot"# Load textures for idle standing 使用load_texture_pair加载不同状态下的造型图片self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")  # 站立造型self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")  # 跳跃造型self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")  # 下落造型# Load textures for walking 由于走路的造型较多 这里用循环去加载 然后放入列表中备用self.walk_textures = []for i in range(8):texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")self.walk_textures.append(texture)# Set the initial texture 默认的站立造型 (0,代表造型里的第一张图片,因为以后还会有左右不同方向的造型)self.texture = self.idle_texture_pair[0]# Hit box will be set based on the first image used. 角色边框由第一张造型图片定义 以便后续碰撞检测self.hit_box = self.texture.hit_box_points# Default to face-right 默认觉得是脸朝右的self.character_face_direction = RIGHT_FACING# Index of our current texture  当前造型图片在图片组中的索引值self.cur_texture = 0# How far have we traveled horizontally since changing the texture# 定义一个 记录上次变换造型以后 角色的移动距离  后续用于比较当他大于我们上面常量设置的值时 执行造型切换self.x_odometer = 0#  这是物理引擎一个处理移动的方法,只要这个精灵是由物理引擎创建的,那么物理引擎自己就会去调用这个固定方法的#  dx 代表每一帧之间 物体横向变化的距离  dy 代表每一帧之间 物体纵向变化的距离#  d_angle则代表每一帧之间 物体角度朝向的变化量def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle being moved by the pymunk engine 这是物理引擎一个处理移动的方法"""# Figure out if we need to face left or right  判断我们是否要改变角色的朝向if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:self.character_face_direction = LEFT_FACINGelif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:self.character_face_direction = RIGHT_FACING# Are we on the ground? 判断当前对象是否在地面上 self代表当前对象 谁用这个类创建的 那么self就是谁is_on_ground = physics_engine.is_on_ground(self)# Add to the odometer how far we've moved  将横向的变化量累加起来self.x_odometer += dx# Jumping animationif not is_on_ground:if dy > DEAD_ZONE:self.texture = self.jump_texture_pair[self.character_face_direction]returnelif dy < -DEAD_ZONE:self.texture = self.fall_texture_pair[self.character_face_direction]return# Idle animationif abs(dx) <= DEAD_ZONE:self.texture = self.idle_texture_pair[self.character_face_direction]return# Have we moved far enough to change the texture?if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.x_odometer = 0# Advance the walking animationself.cur_texture += 1if self.cur_texture > 7:self.cur_texture = 0self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]class BulletSprite(arcade.SpriteSolidColor):""" Bullet Sprite """def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle when the sprite is moved by the physics engine. """# If the bullet falls below the screen, remove it# 当子弹下落并小于某一个值时 令其消失 这里设置为50 容易观察 负数则在掉出屏幕底部时消失(一般也就设置为负数)if self.center_y < 50:self.remove_from_sprite_lists()class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型# self.player_sprite: Optional[arcade.Sprite] = Noneself.player_sprite: Optional[PlayerSprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数# self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.p# ng",SPRITE_SCALING_PLAYER)self.player_sprite = PlayerSprite()# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")# step010.py添加 在setup 方法里面定义两个方法并调用def wall_hit_handler(bullet_sprite, _wall_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞墙时消失"""bullet_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "wall", post_handler=wall_hit_handler)def item_hit_handler(bullet_sprite, item_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞到物体时消失 """bullet_sprite.remove_from_sprite_lists()item_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "item", post_handler=item_hit_handler)def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)def on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行if is_on_ground:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个左或右按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.bullet_list.draw()self.item_list.draw()self.player_list.draw()# 当鼠标点击屏幕时 会给该方法传入以下参数 x :横坐标 y:纵坐标 button:鼠标左中右按的哪个键 modifiers:修饰键 alt shift 等这些def on_mouse_press(self, x, y, button, modifiers):""" Called whenever the mouse button is clicked.当鼠标点击时 本方法被调用"""# 使用创建据矩形的方法 创建一个子弹# bullet = arcade.SpriteSolidColor(20, 5, arcade.color.DARK_YELLOW)bullet = BulletSprite(20, 5, arcade.color.DARK_YELLOW)self.bullet_list.append(bullet)   #  把子弹加入上面就定义过的子弹精灵组# Position the bullet at the player's current location# 定义子弹的初始位置 也就是精灵的位置start_x = self.player_sprite.center_xstart_y = self.player_sprite.center_ybullet.position = self.player_sprite.position# Get from the mouse the destination location for the bullet# IMPORTANT! If you have a scrolling screen, you will also need# to add in self.view_bottom and self.view_left.# 记录点击位置的坐标dest_x = xdest_y = y# Do math to calculate how to get the bullet to the destination.# Calculation the angle in radians between the start points# and end points. This is the angle the bullet will travel.# 使用高一会学习的三角函数知识 计算 玩家相对于平面和鼠标点击位置之间的角度x_diff = dest_x - start_xy_diff = dest_y - start_yangle = math.atan2(y_diff, x_diff)# What is the 1/2 size of this sprite, so we can figure out how far# away to spawn the bulletsize = max(self.player_sprite.width, self.player_sprite.height) / 2# Use angle to to spawn bullet away from player in proper direction# 加上size的好处是 让子弹从玩家图片外部发射出去,避免内部发射导致物理引擎误判bullet.center_x += size * math.cos(angle)bullet.center_y += size * math.sin(angle)# Set angle of bullet 设置子弹的朝向bullet.angle = math.degrees(angle)# Gravity to use for the bullet# If we don't use custom gravity, bullet drops too fast, or we have# to make it go too fast.# Force is in relation to bullet's angle.# 专门定义以下子弹垂直方向受到的重力 而不是默认的bullet_gravity = (0, -BULLET_GRAVITY)# Add the sprite. This needs to be done AFTER setting the fields above.# 使用物理引擎管理子弹 这个子弹是具有弹性的self.physics_engine.add_sprite(bullet,mass=BULLET_MASS,damping=1.0,friction=0.6,collision_type="bullet",gravity=bullet_gravity,elasticity=0.9)# Add force to bullet# 最后 让物理引擎给子弹施加一个力量,让他飞走force = (BULLET_MOVE_FORCE, 0)self.physics_engine.apply_force(bullet, force)def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

十一、添加自动移动的物体

1.在对象层加入物体图块并显示

回到地图编辑器 添加对象层 命名为 Moving Platforms

添加完 记得ctrl+s保存文件

先初始化这个层 然后再在setup()方法里加载

加载并放入引擎

此时运行,发现界面已经成功展示,不过这个可移动物体还不能移动,我们需要额外对他添加属性。而这个可以在 Tiled 地图编辑器中设置

2.给对象层物体添加属性

先来说下精灵可以设置的基本属性

添加完 依旧记得先保存文件哦

然后我们继续补全代码,通过检测图块到达边界时的状态,进而调整改变量的正负来达到 让图块可以来回移动的目的

step011.py效果示例

step011.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity 重力
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1800# Close enough to not-moving to have the animation go to idle.
# 设置这个常量 当玩家角色移动的距离小于0.1时 不让他有动画的走路表现 而是静止 比如在挨着墙的时候按方向键 不再有走路动画
DEAD_ZONE = 0.1# Constants used to track if the player is facing left or right
# 设置面向右侧 为 0  面向左 为 1
RIGHT_FACING = 0
LEFT_FACING = 1# How many pixels to move before we change the texture in the walking animation
# 动作切换是很快的事情,那怎么控制切换频率或者速度呢,我们设置这个变量,使角色移动量每大于20像素时再进行动画 切换
DISTANCE_TO_CHANGE_TEXTURE = 20# step009.py 添加
# How much force to put on the bullet  给子弹施加的初始动力
BULLET_MOVE_FORCE = 4500# Mass of the bullet  子弹的质量
BULLET_MASS = 0.1# Make bullet less affected by gravity 给子弹一个更小的重力值
BULLET_GRAVITY = 300class PlayerSprite(arcade.Sprite):""" Player Sprite """def __init__(self):""" Init """# Let parent initialize 初始化父类方法super().__init__()# Set our scale 设置缩放系数self.scale = SPRITE_SCALING_PLAYER# Images from Kenney.nl's Character pack 图片来自 Kenney网站的素材 aracade 以默认内置# 以下 图片都可以用 选一个喜欢的造型集即可 名字是一样的 后缀不一样则造型不一样 所以main_path 只定义名字# main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"main_path = ":resources:images/animated_characters/female_person/femalePerson"# main_path = ":resources:images/animated_characters/male_person/malePerson"# main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"# main_path = ":resources:images/animated_characters/zombie/zombie"# main_path = ":resources:images/animated_characters/robot/robot"# Load textures for idle standing 使用load_texture_pair加载不同状态下的造型图片self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")  # 站立造型self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")  # 跳跃造型self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")  # 下落造型# Load textures for walking 由于走路的造型较多 这里用循环去加载 然后放入列表中备用self.walk_textures = []for i in range(8):texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")self.walk_textures.append(texture)# Set the initial texture 默认的站立造型 (0,代表造型里的第一张图片,因为以后还会有左右不同方向的造型)self.texture = self.idle_texture_pair[0]# Hit box will be set based on the first image used. 角色边框由第一张造型图片定义 以便后续碰撞检测self.hit_box = self.texture.hit_box_points# Default to face-right 默认觉得是脸朝右的self.character_face_direction = RIGHT_FACING# Index of our current texture  当前造型图片在图片组中的索引值self.cur_texture = 0# How far have we traveled horizontally since changing the texture# 定义一个 记录上次变换造型以后 角色的移动距离  后续用于比较当他大于我们上面常量设置的值时 执行造型切换self.x_odometer = 0#  这是物理引擎一个处理移动的方法,只要这个精灵是由物理引擎创建的,那么物理引擎自己就会去调用这个固定方法的#  dx 代表每一帧之间 物体横向变化的距离  dy 代表每一帧之间 物体纵向变化的距离#  d_angle则代表每一帧之间 物体角度朝向的变化量def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle being moved by the pymunk engine 这是物理引擎一个处理移动的方法"""# Figure out if we need to face left or right  判断我们是否要改变角色的朝向if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:self.character_face_direction = LEFT_FACINGelif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:self.character_face_direction = RIGHT_FACING# Are we on the ground? 判断当前对象是否在地面上 self代表当前对象 谁用这个类创建的 那么self就是谁is_on_ground = physics_engine.is_on_ground(self)# Add to the odometer how far we've moved  将横向的变化量累加起来self.x_odometer += dx# Jumping animationif not is_on_ground:if dy > DEAD_ZONE:self.texture = self.jump_texture_pair[self.character_face_direction]returnelif dy < -DEAD_ZONE:self.texture = self.fall_texture_pair[self.character_face_direction]return# Idle animationif abs(dx) <= DEAD_ZONE:self.texture = self.idle_texture_pair[self.character_face_direction]return# Have we moved far enough to change the texture?if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.x_odometer = 0# Advance the walking animationself.cur_texture += 1if self.cur_texture > 7:self.cur_texture = 0self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]class BulletSprite(arcade.SpriteSolidColor):""" Bullet Sprite """def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle when the sprite is moved by the physics engine. """# If the bullet falls below the screen, remove it# 当子弹下落并小于某一个值时 令其消失 这里设置为50 容易观察 负数则在掉出屏幕底部时消失(一般也就设置为负数)if self.center_y < 50:self.remove_from_sprite_lists()class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型# self.player_sprite: Optional[arcade.Sprite] = Noneself.player_sprite: Optional[PlayerSprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# step011.py添加 可移动物体层 而不是动态物体层self.moving_sprites_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# step011.py添加 移动物体层self.moving_sprites_list = arcade.tilemap.process_layer(my_map,'Moving Platforms',SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数# self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.p# ng",SPRITE_SCALING_PLAYER)self.player_sprite = PlayerSprite()# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add kinematic spritesself.physics_engine.add_sprite_list(self.moving_sprites_list,body_type=arcade.PymunkPhysicsEngine.KINEMATIC)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")# step010.py添加 在setup 方法里面定义两个方法并调用def wall_hit_handler(bullet_sprite, _wall_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞墙时消失"""bullet_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "wall", post_handler=wall_hit_handler)def item_hit_handler(bullet_sprite, item_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞到物体时消失 """bullet_sprite.remove_from_sprite_lists()item_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "item", post_handler=item_hit_handler)def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)def on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行if is_on_ground:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个左或右按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# step011.py添加 当移动物体层的物体移动到给他定义的边缘时 让他再原路返回# For each moving sprite, see if we've reached a boundary and need to# reverse course.for moving_sprite in self.moving_sprites_list:# print(moving_sprite.top,moving_sprite.bottom,moving_sprite.boundary_top,moving_sprite.boundary_bottom,moving_sprite.change_y)if moving_sprite.boundary_right and moving_sprite.change_x > 0 and moving_sprite.right > moving_sprite.boundary_right:moving_sprite.change_x *= -1elif moving_sprite.boundary_left and moving_sprite.change_x < 0 and moving_sprite.left < moving_sprite.boundary_left:moving_sprite.change_x *= -1if moving_sprite.boundary_top and moving_sprite.change_y > 0 and moving_sprite.top > moving_sprite.boundary_top:moving_sprite.change_y *= -1elif moving_sprite.boundary_bottom and moving_sprite.change_y < 0 and moving_sprite.bottom < moving_sprite.boundary_bottom:moving_sprite.change_y *= -1# velocity 是一个速度列表,第一个时横向速度,第二个是纵向速度(是每秒的改变量,因此下面我们要变成每帧的变化量再用)# Figure out and set our moving platform velocity.# Pymunk uses velocity is in pixels per second. If we instead have# pixels per frame, we need to convert.velocity = (moving_sprite.change_x * 1 / delta_time, moving_sprite.change_y * 1 / delta_time)# 注意:官方文档少了下面这一行代码self.physics_engine.set_velocity(moving_sprite, velocity)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面"""arcade.start_render()self.wall_list.draw()self.moving_sprites_list.draw()  # step011.py添加self.bullet_list.draw()self.item_list.draw()self.player_list.draw()# 当鼠标点击屏幕时 会给该方法传入以下参数 x :横坐标 y:纵坐标 button:鼠标左中右按的哪个键 modifiers:修饰键 alt shift 等这些def on_mouse_press(self, x, y, button, modifiers):print(x,y)""" Called whenever the mouse button is clicked.当鼠标点击时 本方法被调用"""# 使用创建据矩形的方法 创建一个子弹# bullet = arcade.SpriteSolidColor(20, 5, arcade.color.DARK_YELLOW)bullet = BulletSprite(20, 5, arcade.color.DARK_YELLOW)self.bullet_list.append(bullet)   #  把子弹加入上面就定义过的子弹精灵组# Position the bullet at the player's current location# 定义子弹的初始位置 也就是精灵的位置start_x = self.player_sprite.center_xstart_y = self.player_sprite.center_ybullet.position = self.player_sprite.position# Get from the mouse the destination location for the bullet# IMPORTANT! If you have a scrolling screen, you will also need# to add in self.view_bottom and self.view_left.# 记录点击位置的坐标dest_x = xdest_y = y# Do math to calculate how to get the bullet to the destination.# Calculation the angle in radians between the start points# and end points. This is the angle the bullet will travel.# 使用高一会学习的三角函数知识 计算 玩家相对于平面和鼠标点击位置之间的角度x_diff = dest_x - start_xy_diff = dest_y - start_yangle = math.atan2(y_diff, x_diff)# What is the 1/2 size of this sprite, so we can figure out how far# away to spawn the bulletsize = max(self.player_sprite.width, self.player_sprite.height) / 2# Use angle to to spawn bullet away from player in proper direction# 加上size的好处是 让子弹从玩家图片外部发射出去,避免内部发射导致物理引擎误判bullet.center_x += size * math.cos(angle)bullet.center_y += size * math.sin(angle)# Set angle of bullet 设置子弹的朝向bullet.angle = math.degrees(angle)# Gravity to use for the bullet# If we don't use custom gravity, bullet drops too fast, or we have# to make it go too fast.# Force is in relation to bullet's angle.# 专门定义以下子弹垂直方向受到的重力 而不是默认的bullet_gravity = (0, -BULLET_GRAVITY)# Add the sprite. This needs to be done AFTER setting the fields above.# 使用物理引擎管理子弹 这个子弹是具有弹性的self.physics_engine.add_sprite(bullet,mass=BULLET_MASS,damping=1.0,friction=0.6,collision_type="bullet",gravity=bullet_gravity,elasticity=0.9)# Add force to bullet# 最后 让物理引擎给子弹施加一个力量,让他飞走force = (BULLET_MOVE_FORCE, 0)self.physics_engine.apply_force(bullet, force)def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

十二、创建可以上下爬动的梯子

这是目前官方文档的最后一步了,看到这里恭喜你,很快就要结束本文档的折磨了,哈哈哈,加油

1.在地图编辑器创建梯子的图层

画完依旧要记得保存哦

2.在代码中加载图块层并显示

初始化是先创建一个变量

之后在setup()方法里把梯子图层加载进来

然后在屏幕绘制时调用以下draw()方法,把梯子在界面上显示

运行程序 已显示

3.让玩家能够爬上梯子并相应移动

那首先要加载玩家 爬梯子时的动作图 待会看起来会更真实一些

然后玩家可以爬梯子是因为玩家碰到了梯子,这种情况不像玩家落在地面或者碰到墙体一样,可以由物理引擎自动控制,需要我们自己手动去检测玩家和梯子的碰撞情况,而我们自己去检测后,还可以更新更多的属性值,让玩家在梯子上时,和在地面上或者空中时拥有不同的位移速度和重力等。
因此,我们在创建玩家类时,通过参数,把梯子精灵列表也一起传给玩家类,以便于后续玩家类中pymunk_moved()刷新精灵的方法进行使用。

在主程序setup()方法中更改玩家精灵的创建方式 第二个参数是传递的,物理引擎模式,Detailed是代表我们当前用的pymunk物理引擎


在玩家类里接收参数

接收参数后 设置相应的数据 包括纵向的变化量

根据玩家和梯子的碰撞状态 更改阻尼和重力等属性 这样玩家在梯子上和不在梯子上将有不同的属性

紧接着 设置在梯子上时动作的变化情况

添加相应的上下方向的按键事件 主要针对在梯子上时


在on_update()方法 里 添加针对上下按键按下后的响应逻辑

step012.py效果示例

step012.py代码示例

"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity 重力
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1800# Close enough to not-moving to have the animation go to idle.
# 设置这个常量 当玩家角色移动的距离小于0.1时 不让他有动画的走路表现 而是静止 比如在挨着墙的时候按方向键 不再有走路动画
DEAD_ZONE = 0.1# Constants used to track if the player is facing left or right
# 设置面向右侧 为 0  面向左 为 1
RIGHT_FACING = 0
LEFT_FACING = 1# How many pixels to move before we change the texture in the walking animation
# 动作切换是很快的事情,那怎么控制切换频率或者速度呢,我们设置这个变量,使角色移动量每大于20像素时再进行动画 切换
DISTANCE_TO_CHANGE_TEXTURE = 20# step009.py 添加
# How much force to put on the bullet  给子弹施加的初始动力
BULLET_MOVE_FORCE = 4500# Mass of the bullet  子弹的质量
BULLET_MASS = 0.1# Make bullet less affected by gravity 给子弹一个更小的重力值
BULLET_GRAVITY = 300class PlayerSprite(arcade.Sprite):""" Player Sprite """def __init__(self, ladder_list: arcade.SpriteList,hit_box_algorithm):""" Init """# Let parent initialize 初始化父类方法super().__init__()# Set our scale 设置缩放系数self.scale = SPRITE_SCALING_PLAYER# Images from Kenney.nl's Character pack 图片来自 Kenney网站的素材 aracade 以默认内置# 以下 图片都可以用 选一个喜欢的造型集即可 名字是一样的 后缀不一样则造型不一样 所以main_path 只定义名字# main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"main_path = ":resources:images/animated_characters/female_person/femalePerson"# main_path = ":resources:images/animated_characters/male_person/malePerson"# main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"# main_path = ":resources:images/animated_characters/zombie/zombie"# main_path = ":resources:images/animated_characters/robot/robot"# Load textures for idle standing 使用load_texture_pair加载不同状态下的造型图片self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")  # 站立造型self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")  # 跳跃造型self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")  # 下落造型# Load textures for walking 由于走路的造型较多 这里用循环去加载 然后放入列表中备用self.walk_textures = []for i in range(8):texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")self.walk_textures.append(texture)# step012.py添加 爬梯子的造型加载# Load textures for climbingself.climbing_textures = []texture = arcade.load_texture(f"{main_path}_climb0.png")self.climbing_textures.append(texture)texture = arcade.load_texture(f"{main_path}_climb1.png")self.climbing_textures.append(texture)# Set the initial texture 默认的站立造型 (0,代表造型里的第一张图片,因为以后还会有左右不同方向的造型)self.texture = self.idle_texture_pair[0]# Hit box will be set based on the first image used. 角色边框由第一张造型图片定义 以便后续碰撞检测self.hit_box = self.texture.hit_box_points# Default to face-right 默认觉得是脸朝右的self.character_face_direction = RIGHT_FACING# Index of our current texture  当前造型图片在图片组中的索引值self.cur_texture = 0# How far have we traveled horizontally since changing the texture# 定义一个 记录上次变换造型以后 角色的移动距离  后续用于比较当他大于我们上面常量设置的值时 执行造型切换self.x_odometer = 0# step012.py添加self.y_odometer = 0self.ladder_list = ladder_listself.is_on_ladder = False#  这是物理引擎一个处理移动的方法,只要这个精灵是由物理引擎创建的,那么物理引擎自己就会去调用这个固定方法的#  dx 代表每一帧之间 物体横向变化的距离  dy 代表每一帧之间 物体纵向变化的距离#  d_angle则代表每一帧之间 物体角度朝向的变化量def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle being moved by the pymunk engine 这是物理引擎一个处理移动的方法"""# Figure out if we need to face left or right  判断我们是否要改变角色的朝向if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:self.character_face_direction = LEFT_FACINGelif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:self.character_face_direction = RIGHT_FACING# Are we on the ground? 判断当前对象是否在地面上 self代表当前对象 谁用这个类创建的 那么self就是谁is_on_ground = physics_engine.is_on_ground(self)# Are we on a ladder? 根据玩家精灵组和梯子精灵组的碰撞结果,设置不同的属性# 碰撞检测结果是个列表,被碰撞的精灵都会存进去,所以一旦碰撞,列表的长度就会大于0if len(arcade.check_for_collision_with_list(self, self.ladder_list)) > 0:# 碰撞以后 检查玩家是否在梯子上# 如果碰撞了 但是玩家状态显示不在梯子上 一定是刚刚上到梯子上来  则更改状态为 在梯子上,然后重力大小设置为0if not self.is_on_ladder:self.is_on_ladder = Trueself.pymunk.gravity = (0, 0)self.pymunk.damping = 0.0001  # 刚到梯子上时,玩家的速度还使用之前的速度,只不过会稍微衰减self.pymunk.max_vertical_velocity = PLAYER_MAX_HORIZONTAL_SPEED  # 在梯子上时最大的垂直速度# 如果玩家并没有碰撞到梯子else:# 但是玩家状态却还是在梯子上 那么 肯定是玩家刚从梯子上掉下来了if self.is_on_ladder:self.pymunk.damping = 1.0  # 让玩家瞬间失去原来在梯子上时的速度self.pymunk.max_vertical_velocity = PLAYER_MAX_VERTICAL_SPEEDself.is_on_ladder = Falseself.pymunk.gravity = None  # 把原来的重力(0,0)清除 开始自由落体# Add to the odometer how far we've moved  将横向的变化量累加起来self.x_odometer += dx# step012.py添加 垂直方向的变化量self.y_odometer += dy# 设置在梯子上时的动作变化if self.is_on_ladder and not is_on_ground:# Have we moved far enough to change the texture?# 当在梯子上的垂直的变化量大于我们设定的可以进行动作切换的值时 改变动作 并重置变化量if abs(self.y_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.y_odometer = 0# Advance the walking animation# 切换下一张动作图的索引self.cur_texture += 1# 有两张图就可以if self.cur_texture > 1:self.cur_texture = 0# 更改梯子上的动作self.texture = self.climbing_textures[self.cur_texture]return# Jumping animationif not is_on_ground:if dy > DEAD_ZONE:self.texture = self.jump_texture_pair[self.character_face_direction]returnelif dy < -DEAD_ZONE:self.texture = self.fall_texture_pair[self.character_face_direction]return# Idle animationif abs(dx) <= DEAD_ZONE:self.texture = self.idle_texture_pair[self.character_face_direction]return# Have we moved far enough to change the texture?if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.x_odometer = 0# Advance the walking animationself.cur_texture += 1if self.cur_texture > 7:self.cur_texture = 0self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]class BulletSprite(arcade.SpriteSolidColor):""" Bullet Sprite """def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle when the sprite is moved by the physics engine. """# If the bullet falls below the screen, remove it# 当子弹下落并小于某一个值时 令其消失 这里设置为50 容易观察 负数则在掉出屏幕底部时消失(一般也就设置为负数)if self.center_y < 50:self.remove_from_sprite_lists()class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型# self.player_sprite: Optional[arcade.Sprite] = Noneself.player_sprite: Optional[PlayerSprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# step012.py添加self.ladder_list: Optional[arcade.SpriteList] = None# step011.py添加 可移动物体层(不受其他物体力量的影响) 而不是动态物体层(会被其他物体的力量影响)self.moving_sprites_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# step012.py 添加self.up_pressed: bool = Falseself.down_pressed: bool = False# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxmy_map = arcade.tilemap.read_tmx(map_name)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# step011.py添加 移动物体层self.moving_sprites_list = arcade.tilemap.process_layer(my_map,'Moving Platforms',SPRITE_SCALING_TILES)# step012.py添加self.ladder_list = arcade.tilemap.process_layer(my_map,'Ladders',SPRITE_SCALING_TILES,use_spatial_hash=True,hit_box_algorithm="Detailed")# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数# self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.p# ng",SPRITE_SCALING_PLAYER)self.player_sprite = PlayerSprite(self.ladder_list, hit_box_algorithm="Detailed")# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add kinematic spritesself.physics_engine.add_sprite_list(self.moving_sprites_list,body_type=arcade.PymunkPhysicsEngine.KINEMATIC)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")# step010.py添加 在setup 方法里面定义两个方法并调用def wall_hit_handler(bullet_sprite, _wall_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞墙时消失"""bullet_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "wall", post_handler=wall_hit_handler)def item_hit_handler(bullet_sprite, item_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞到物体时消失 """bullet_sprite.remove_from_sprite_lists()item_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "item", post_handler=item_hit_handler)def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:self.up_pressed = True# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)elif key == arcade.key.DOWN:self.down_pressed = Truedef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falseelif key == arcade.key.UP:self.up_pressed = Falseelif key == arcade.key.DOWN:self.down_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行# step012.py 加入的 新条件时 or self.player_sprite.is_on_ladderif is_on_ground or self.player_sprite.is_on_ladder:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground or self.player_sprite.is_on_ladder:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)# step012.py添加elif self.up_pressed and not self.down_pressed:# Create a force to the up. Apply it.if self.player_sprite.is_on_ladder:force = (0, PLAYER_MOVE_FORCE_ON_GROUND)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while movingself.physics_engine.set_friction(self.player_sprite, 0)elif self.down_pressed and not self.up_pressed:# Create a force to the down. Apply it.if self.player_sprite.is_on_ladder:force = (0, -PLAYER_MOVE_FORCE_ON_GROUND)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while movingself.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# step011.py添加 当移动物体层的物体移动到给他定义的边缘时 让他再原路返回# For each moving sprite, see if we've reached a boundary and need to# reverse course.for moving_sprite in self.moving_sprites_list:# print(moving_sprite.top,moving_sprite.bottom,moving_sprite.boundary_top,moving_sprite.boundary_bottom,moving_sprite.change_y)# 下面这行写的很长 主要时为了观看时便于理解代码含义 为了规范书写 也可以换行if moving_sprite.boundary_right and moving_sprite.change_x > 0 and moving_sprite.right > moving_sprite.boundary_right:moving_sprite.change_x *= -1elif moving_sprite.boundary_left and moving_sprite.change_x < 0 and moving_sprite.left < moving_sprite.boundary_left:moving_sprite.change_x *= -1if moving_sprite.boundary_top and moving_sprite.change_y > 0 and moving_sprite.top > moving_sprite.boundary_top:moving_sprite.change_y *= -1elif moving_sprite.boundary_bottom and moving_sprite.change_y < 0 and moving_sprite.bottom < moving_sprite.boundary_bottom:moving_sprite.change_y *= -1# velocity 是一个速度列表,第一个时横向速度,第二个是纵向速度(是每秒的改变量,因此下面我们要变成每帧的变化量再用)# Figure out and set our moving platform velocity.# Pymunk uses velocity is in pixels per second. If we instead have# pixels per frame, we need to convert.velocity = (moving_sprite.change_x * 1 / delta_time, moving_sprite.change_y * 1 / delta_time)# 注意:官方文档少了下面这一行代码self.physics_engine.set_velocity(moving_sprite, velocity)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面 后执行的绘制 会覆盖先绘制的图案"""arcade.start_render()self.wall_list.draw()self.ladder_list.draw()  # step012.py添加self.moving_sprites_list.draw()  # step011.py添加self.bullet_list.draw()self.item_list.draw()self.player_list.draw()# 当鼠标点击屏幕时 会给该方法传入以下参数 x :横坐标 y:纵坐标 button:鼠标左中右按的哪个键 modifiers:修饰键 alt shift 等这些def on_mouse_press(self, x, y, button, modifiers):print(x,y)""" Called whenever the mouse button is clicked.当鼠标点击时 本方法被调用"""# 使用创建据矩形的方法 创建一个子弹# bullet = arcade.SpriteSolidColor(20, 5, arcade.color.DARK_YELLOW)bullet = BulletSprite(20, 5, arcade.color.DARK_YELLOW)self.bullet_list.append(bullet)   #  把子弹加入上面就定义过的子弹精灵组# Position the bullet at the player's current location# 定义子弹的初始位置 也就是精灵的位置start_x = self.player_sprite.center_xstart_y = self.player_sprite.center_ybullet.position = self.player_sprite.position# Get from the mouse the destination location for the bullet# IMPORTANT! If you have a scrolling screen, you will also need# to add in self.view_bottom and self.view_left.# 记录点击位置的坐标dest_x = xdest_y = y# Do math to calculate how to get the bullet to the destination.# Calculation the angle in radians between the start points# and end points. This is the angle the bullet will travel.# 使用高一会学习的三角函数知识 计算 玩家相对于平面和鼠标点击位置之间的角度x_diff = dest_x - start_xy_diff = dest_y - start_yangle = math.atan2(y_diff, x_diff)# What is the 1/2 size of this sprite, so we can figure out how far# away to spawn the bulletsize = max(self.player_sprite.width, self.player_sprite.height) / 2# Use angle to to spawn bullet away from player in proper direction# 加上size的好处是 让子弹从玩家图片外部发射出去,避免内部发射导致物理引擎误判bullet.center_x += size * math.cos(angle)bullet.center_y += size * math.sin(angle)# Set angle of bullet 设置子弹的朝向bullet.angle = math.degrees(angle)# Gravity to use for the bullet# If we don't use custom gravity, bullet drops too fast, or we have# to make it go too fast.# Force is in relation to bullet's angle.# 专门定义以下子弹垂直方向受到的重力 而不是默认的bullet_gravity = (0, -BULLET_GRAVITY)# Add the sprite. This needs to be done AFTER setting the fields above.# 使用物理引擎管理子弹 这个子弹是具有弹性的self.physics_engine.add_sprite(bullet,mass=BULLET_MASS,damping=1.0,friction=0.6,collision_type="bullet",gravity=bullet_gravity,elasticity=0.9)# Add force to bullet# 最后 让物理引擎给子弹施加一个力量,让他飞走force = (BULLET_MOVE_FORCE, 0)self.physics_engine.apply_force(bullet, force)def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

十三、 打包成exe软件包

1.首先在入口文件 的代码里加上三行代码

这些代码是为了后续 让程序自动去找素材文件的。不然会出现,代码文件找不到素材文件的错误
不需要素材文件的可以不加

import os, sysif getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):os.chdir(sys._MEIPASS)

这里我是又用了一个新的文件存储代码 名字自己起就行。这里叫arcade_game.py

2.安装打包工具库pyinstaller

直接在pycharm里 或者项目所在python环境的终端
安装 pyinstaller

用国内镜像源安装 多少会快一些 这是豆瓣的

pip install pyinstaller -i https://pypi.doubanio.com/simple

3.修改arcade库源文件的一行代码

为什么修改 参看这里报错情况

4.进行打包

在项目所在终端 输入
这样打包的软件 运行时会默认打开一个终端窗口 输出程序里可能输出的信息

pyinstaller arcade_game.py

如果想让软件在 运行时 只显示游戏或者主要程序相关的窗体,而不默认打开终端窗口,则使用下面的代码

pyinstaller -w arcade_game.py

打包时可能会触发安全软件的检测 选择允许程序的操作即可 总之就是各种开绿灯

打包完后 会看到在项目目录下 有两个新的文件夹 disk 和 buld 我们在disk目录里找到


下面的exe既可以双击运行。
也可以拖进cmd终端窗口运行(这样有报错的话,可以直接看到报错信息),程序运行异常,也这样去查看异常原因

(如果我们的代码文件本身不需要别的 文件【如图片 音乐】,那么到这里软件就打包完成了,如果像我们这个游戏项目一样,还需要地图,图块集等别的文件,那么我们就要再执行别的代码,把文件也打包进去,具体看下面)

5.把素材文件加入软件包

同样是在原来的项目终端执行以下代码

如果素材是和代码文件并列单独存放的 执行下面的语句

pyinstaller arcade_game.py --add-data "tmw_desert_spacing.tsx;."  --add-data "pymunk_test_map.tmx;."

如过素材是放在某个文件夹里的 执行下面代码 文件多时建议存到一个统一的文件夹
例如有一个名为images的文件夹

pyinstaller myscript.py --add-data "images;images"

6.再次双击exe程序 搞定

十四、拓展(1):屏幕移动(随玩家)

原理解释:
我们给屏幕创建一个内边框,当玩家移动到内边框时,就让屏幕进行移动跟踪。这样可以体验更大的地图

1.在原来地图基础上加入更多元素

地图一旦开始创建成功,便没有了重新设置大小的属性,不过我们可以把地图属性改为无无限大,待创建完成后,再取消无限大,即可自动调整成我们当前设计的大小。
操作步骤
打开原来的地图编辑器,点击工具栏上方地图选项,选择地图属性,给无限那个方框打上对勾。一般需要双击才能勾选好。然后添加元素,添加完毕后记得取消对勾 和保存文件。
之后重新运行程序,页面可以正常展示和使用

2.定义内边框的范围 和 当前边框数值


在窗口大小固定的情况下,我们只要知道左侧和底部边框的值,就可以知道右侧和顶部,所以之定义左 下 两个即可

3.改保存地图的变量 为 类属性(也就是加个self)

参照上图,先把__init__里的加上
然后在setup()方法里 把我们原来写的my_map 都用self.my_map替代

4.在update()方法里计算屏幕的滑动量,并让屏幕滑动


简单的代码 往往需要花更多的时间理解 加油

step013.py效果示例

step013.py代码示例

# coding=<UTF-8>"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity 重力
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 450
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 8000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1800# Close enough to not-moving to have the animation go to idle.
# 设置这个常量 当玩家角色移动的距离小于0.1时 不让他有动画的走路表现 而是静止 比如在挨着墙的时候按方向键 不再有走路动画
DEAD_ZONE = 0.1# Constants used to track if the player is facing left or right
# 设置面向右侧 为 0  面向左 为 1
RIGHT_FACING = 0
LEFT_FACING = 1# How many pixels to move before we change the texture in the walking animation
# 动作切换是很快的事情,那怎么控制切换频率或者速度呢,我们设置这个变量,使角色移动量每大于20像素时再进行动画 切换
DISTANCE_TO_CHANGE_TEXTURE = 20# step009.py 添加
# How much force to put on the bullet  给子弹施加的初始动力
BULLET_MOVE_FORCE = 4500# Mass of the bullet  子弹的质量
BULLET_MASS = 0.1# Make bullet less affected by gravity 给子弹一个更小的重力值
BULLET_GRAVITY = 300# How many pixels to keep as a minimum margin between the character
# and the edge of the screen.
# 设置物体距离屏幕边缘的最小距离,当小于之这个距离时执行界面的滑动。
# 换言之就是根据 可视的界面,构建一个内部的内边框,当玩家移出边框时,进行屏幕滑动
# 这里我们使用上面定义过的屏幕大小的常量来定义内边框 到 外边框(可视界面的边缘)的距离 这样更通用些
LEFT_VIEWPORT_MARGIN = SCREEN_WIDTH/4
RIGHT_VIEWPORT_MARGIN = SCREEN_WIDTH/4
BOTTOM_VIEWPORT_MARGIN = SCREEN_HEIGHT/2
TOP_VIEWPORT_MARGIN = SCREEN_HEIGHT/4class PlayerSprite(arcade.Sprite):""" Player Sprite """def __init__(self, ladder_list: arcade.SpriteList,hit_box_algorithm):""" Init """# Let parent initialize 初始化父类方法super().__init__()# Set our scale 设置缩放系数self.scale = SPRITE_SCALING_PLAYER# Images from Kenney.nl's Character pack 图片来自 Kenney网站的素材 aracade 以默认内置# 以下 图片都可以用 选一个喜欢的造型集即可 名字是一样的 后缀不一样则造型不一样 所以main_path 只定义名字# main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"main_path = ":resources:images/animated_characters/female_person/femalePerson"# main_path = ":resources:images/animated_characters/male_person/malePerson"# main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"# main_path = ":resources:images/animated_characters/zombie/zombie"# main_path = ":resources:images/animated_characters/robot/robot"# Load textures for idle standing 使用load_texture_pair加载不同状态下的造型图片self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")  # 站立造型self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")  # 跳跃造型self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")  # 下落造型# Load textures for walking 由于走路的造型较多 这里用循环去加载 然后放入列表中备用self.walk_textures = []for i in range(8):texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")self.walk_textures.append(texture)# step012.py添加 爬梯子的造型加载# Load textures for climbingself.climbing_textures = []texture = arcade.load_texture(f"{main_path}_climb0.png")self.climbing_textures.append(texture)texture = arcade.load_texture(f"{main_path}_climb1.png")self.climbing_textures.append(texture)# Set the initial texture 默认的站立造型 (0,代表造型里的第一张图片,因为以后还会有左右不同方向的造型)self.texture = self.idle_texture_pair[0]# Hit box will be set based on the first image used. 角色边框由第一张造型图片定义 以便后续碰撞检测self.hit_box = self.texture.hit_box_points# Default to face-right 默认觉得是脸朝右的self.character_face_direction = RIGHT_FACING# Index of our current texture  当前造型图片在图片组中的索引值self.cur_texture = 0# How far have we traveled horizontally since changing the texture# 定义一个 记录上次变换造型以后 角色的移动距离  后续用于比较当他大于我们上面常量设置的值时 执行造型切换self.x_odometer = 0# step012.py添加self.y_odometer = 0self.ladder_list = ladder_listself.is_on_ladder = False#  这是物理引擎一个处理移动的方法,只要这个精灵是由物理引擎创建的,那么物理引擎自己就会去调用这个固定方法的#  dx 代表每一帧之间 物体横向变化的距离  dy 代表每一帧之间 物体纵向变化的距离#  d_angle则代表每一帧之间 物体角度朝向的变化量def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle being moved by the pymunk engine 这是物理引擎一个处理移动的方法"""# Figure out if we need to face left or right  判断我们是否要改变角色的朝向if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:self.character_face_direction = LEFT_FACINGelif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:self.character_face_direction = RIGHT_FACING# Are we on the ground? 判断当前对象是否在地面上 self代表当前对象 谁用这个类创建的 那么self就是谁is_on_ground = physics_engine.is_on_ground(self)# Are we on a ladder? 根据玩家精灵组和梯子精灵组的碰撞结果,设置不同的属性# 碰撞检测结果是个列表,被碰撞的精灵都会存进去,所以一旦碰撞,列表的长度就会大于0if len(arcade.check_for_collision_with_list(self, self.ladder_list)) > 0:# 碰撞以后 检查玩家是否在梯子上# 如果碰撞了 但是玩家状态显示不在梯子上 一定是刚刚上到梯子上来  则更改状态为 在梯子上,然后重力大小设置为0if not self.is_on_ladder:self.is_on_ladder = Trueself.pymunk.gravity = (0, 0)self.pymunk.damping = 0.0001  # 刚到梯子上时,玩家的速度还使用之前的速度,只不过会稍微衰减self.pymunk.max_vertical_velocity = PLAYER_MAX_HORIZONTAL_SPEED  # 在梯子上时最大的垂直速度# 如果玩家并没有碰撞到梯子else:# 但是玩家状态却还是在梯子上 那么 肯定是玩家刚从梯子上掉下来了if self.is_on_ladder:self.pymunk.damping = 1.0  # 让玩家瞬间失去原来在梯子上时的速度self.pymunk.max_vertical_velocity = PLAYER_MAX_VERTICAL_SPEEDself.is_on_ladder = Falseself.pymunk.gravity = None  # 把原来的重力(0,0)清除 开始自由落体# Add to the odometer how far we've moved  将横向的变化量累加起来self.x_odometer += dx# step012.py添加 垂直方向的变化量self.y_odometer += dy# 设置在梯子上时的动作变化if self.is_on_ladder and not is_on_ground:# Have we moved far enough to change the texture?# 当在梯子上的垂直的变化量大于我们设定的可以进行动作切换的值时 改变动作 并重置变化量if abs(self.y_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.y_odometer = 0# Advance the walking animation# 切换下一张动作图的索引self.cur_texture += 1# 有两张图就可以if self.cur_texture > 1:self.cur_texture = 0# 更改梯子上的动作self.texture = self.climbing_textures[self.cur_texture]return# Jumping animationif not is_on_ground:if dy > DEAD_ZONE:self.texture = self.jump_texture_pair[self.character_face_direction]returnelif dy < -DEAD_ZONE:self.texture = self.fall_texture_pair[self.character_face_direction]return# Idle animationif abs(dx) <= DEAD_ZONE:self.texture = self.idle_texture_pair[self.character_face_direction]return# Have we moved far enough to change the texture?if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.x_odometer = 0# Advance the walking animationself.cur_texture += 1if self.cur_texture > 7:self.cur_texture = 0self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]class BulletSprite(arcade.SpriteSolidColor):""" Bullet Sprite """def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle when the sprite is moved by the physics engine. """# If the bullet falls below the screen, remove it# 当子弹下落并小于某一个值时 令其消失 这里设置为50 容易观察 负数则在掉出屏幕底部时消失(一般也就设置为负数)if self.center_y < 50:self.remove_from_sprite_lists()class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型# self.player_sprite: Optional[arcade.Sprite] = Noneself.player_sprite: Optional[PlayerSprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# step012.py添加self.ladder_list: Optional[arcade.SpriteList] = None# step011.py添加 可移动物体层(不受其他物体力量的影响) 而不是动态物体层(会被其他物体的力量影响)self.moving_sprites_list: Optional[arcade.SpriteList] = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# step012.py 添加self.up_pressed: bool = Falseself.down_pressed: bool = False# 屏幕滚动相关添加# Used to keep track of our scrollingself.view_bottom = 0self.view_left = 0# 创建存储地图的变量self.my_map = None# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# 屏幕滚动相关添加# Used to keep track of our scrollingself.view_bottom = 0self.view_left = 0# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxself.my_map = arcade.tilemap.read_tmx(map_name)# 使用类属性作为地图变量,方便后续在设置屏幕滚动时程序获取地图的宽高,地图快大小等信息# self.map_size = self.my_map .map_size# print('地图的宽高为:',self.map_size)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(self.my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(self.my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# step011.py添加 移动物体层self.moving_sprites_list = arcade.tilemap.process_layer(self.my_map,'Moving Platforms',SPRITE_SCALING_TILES)# step012.py添加self.ladder_list = arcade.tilemap.process_layer(self.my_map,'Ladders',SPRITE_SCALING_TILES,use_spatial_hash=True,hit_box_algorithm="Detailed")# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数# self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.p# ng",SPRITE_SCALING_PLAYER)self.player_sprite = PlayerSprite(self.ladder_list, hit_box_algorithm="Detailed")# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add kinematic spritesself.physics_engine.add_sprite_list(self.moving_sprites_list,body_type=arcade.PymunkPhysicsEngine.KINEMATIC)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")# step010.py添加 在setup 方法里面定义两个方法并调用def wall_hit_handler(bullet_sprite, _wall_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞墙时消失"""bullet_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "wall", post_handler=wall_hit_handler)def item_hit_handler(bullet_sprite, item_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞到物体时消失 """bullet_sprite.remove_from_sprite_lists()item_sprite.remove_from_sprite_lists()self.physics_engine.add_collision_handler("bullet", "item", post_handler=item_hit_handler)def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:self.up_pressed = True# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)elif key == arcade.key.DOWN:self.down_pressed = Truedef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falseelif key == arcade.key.UP:self.up_pressed = Falseelif key == arcade.key.DOWN:self.down_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行# step012.py 加入的 新条件时 or self.player_sprite.is_on_ladderif is_on_ground or self.player_sprite.is_on_ladder:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground or self.player_sprite.is_on_ladder:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)# step012.py添加elif self.up_pressed and not self.down_pressed:# Create a force to the up. Apply it.if self.player_sprite.is_on_ladder:force = (0, PLAYER_MOVE_FORCE_ON_GROUND)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while movingself.physics_engine.set_friction(self.player_sprite, 0)elif self.down_pressed and not self.up_pressed:# Create a force to the down. Apply it.if self.player_sprite.is_on_ladder:force = (0, -PLAYER_MOVE_FORCE_ON_GROUND)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while movingself.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# step011.py添加 当移动物体层的物体移动到给他定义的边缘时 让他再原路返回# For each moving sprite, see if we've reached a boundary and need to# reverse course.for moving_sprite in self.moving_sprites_list:# print(moving_sprite.top,moving_sprite.bottom,moving_sprite.boundary_top,moving_sprite.boundary_bottom,moving_sprite.change_y)# 下面这行写的很长 主要时为了观看时便于理解代码含义 为了规范书写 也可以换行if moving_sprite.boundary_right and moving_sprite.change_x > 0 and moving_sprite.right > moving_sprite.boundary_right:moving_sprite.change_x *= -1elif moving_sprite.boundary_left and moving_sprite.change_x < 0 and moving_sprite.left < moving_sprite.boundary_left:moving_sprite.change_x *= -1if moving_sprite.boundary_top and moving_sprite.change_y > 0 and moving_sprite.top > moving_sprite.boundary_top:moving_sprite.change_y *= -1elif moving_sprite.boundary_bottom and moving_sprite.change_y < 0 and moving_sprite.bottom < moving_sprite.boundary_bottom:moving_sprite.change_y *= -1# velocity 是一个速度列表,第一个时横向速度,第二个是纵向速度(是每秒的改变量,因此下面我们要变成每帧的变化量再用)# Figure out and set our moving platform velocity.# Pymunk uses velocity is in pixels per second. If we instead have# pixels per frame, we need to convert.velocity = (moving_sprite.change_x * 1 / delta_time, moving_sprite.change_y * 1 / delta_time)# 注意:官方文档少了下面这一行代码self.physics_engine.set_velocity(moving_sprite, velocity)# 屏幕滚动相关添加# --- Manage Scrolling ---# Track if we need to change the viewportchanged = False# Scroll leftleft_boundary = self.view_left + LEFT_VIEWPORT_MARGINif self.player_sprite.left < left_boundary:self.view_left -= left_boundary - self.player_sprite.leftchanged = True# Scroll rightright_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGINif self.player_sprite.right > right_boundary:self.view_left += self.player_sprite.right - right_boundarychanged = True# Scroll uptop_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGINif self.player_sprite.top > top_boundary:self.view_bottom += self.player_sprite.top - top_boundarychanged = True# Scroll downbottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGINif self.player_sprite.bottom < bottom_boundary:self.view_bottom -= bottom_boundary - self.player_sprite.bottomchanged = True# print('地图块大小',self.my_map.tile_size)#  self.my_map.tile_size 代表组成地图的 每一个地图块的大小 和地图编辑器中创建时一样,是32*32 但是由于地图编辑器输出文件的时候#  设置输出成了16*16的,所以我们要再整除2 变成游戏中实际的大小# self.my_map.map_size.width 地图单行横向的格子数# self.my_map.map_size.height 地图单行纵向的格子数# 设置地图界面从左到右的宽度 结果是用 图块个数表示TILED_MAP_WIDTH = self.my_map.map_size.width * self.my_map.tile_size.width // 2# 设置地图界面从上到下的高度 结果是用 图块个数表示TILED_MAP_HEIGHT = self.my_map.map_size.height * self.my_map.tile_size.height // 2if changed:# Only scroll to integers. Otherwise we end up with pixels that# don't line up on the screenself.view_bottom = max(min(int(self.view_bottom), TILED_MAP_HEIGHT-SCREEN_HEIGHT), 0)self.view_left = max(min(int(self.view_left), TILED_MAP_WIDTH - SCREEN_WIDTH), 0)view_right = min(SCREEN_WIDTH + self.view_left, TILED_MAP_WIDTH)view_top = min(TILED_MAP_HEIGHT, self.view_bottom+SCREEN_HEIGHT)# view_top = min(TILED_MAP_HEIGHT+20, self.view_bottom+SCREEN_HEIGHT+20)# if SCREEN_WIDTH + self.view_left > TILED_MAP_WIDTH:#     self.view_right = TILED_MAP_WIDTH#     self.view_left =TILED_MAP_WIDTH -SCREEN_WIDTH# else:#     self.view_right = SCREEN_WIDTH + self.view_left# if self.view_bottom <= 0:#     self.view_bottom = 0# if self.view_left <= 0:#     self.view_left = 0# Do the scrolling  设置视口大小arcade.set_viewport(self.view_left,view_right,self.view_bottom,view_top)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面 后执行的绘制 会覆盖先绘制的图案"""arcade.start_render()self.wall_list.draw()self.ladder_list.draw()  # step012.py添加self.moving_sprites_list.draw()  # step011.py添加self.bullet_list.draw()self.item_list.draw()self.player_list.draw()# 当鼠标点击屏幕时 会给该方法传入以下参数 x :横坐标 y:纵坐标 button:鼠标左中右按的哪个键 modifiers:修饰键 alt shift 等这些def on_mouse_press(self, x, y, button, modifiers):print(x,y,'左侧',self.player_sprite.left,'底部',self.player_sprite.bottom)""" Called whenever the mouse button is clicked.当鼠标点击时 本方法被调用"""# 使用创建据矩形的方法 创建一个子弹# bullet = arcade.SpriteSolidColor(20, 5, arcade.color.DARK_YELLOW)bullet = BulletSprite(20, 5, arcade.color.DARK_YELLOW)self.bullet_list.append(bullet)   #  把子弹加入上面就定义过的子弹精灵组# Position the bullet at the player's current location# 定义子弹的初始位置 也就是精灵的位置start_x = self.player_sprite.center_xstart_y = self.player_sprite.center_ybullet.position = self.player_sprite.position# Get from the mouse the destination location for the bullet# IMPORTANT! If you have a scrolling screen, you will also need# to add in self.view_bottom and self.view_left.# 记录点击位置的坐标dest_x = xdest_y = y# Do math to calculate how to get the bullet to the destination.# Calculation the angle in radians between the start points# and end points. This is the angle the bullet will travel.# 使用高一会学习的三角函数知识 计算 玩家相对于平面和鼠标点击位置之间的角度x_diff = dest_x - start_xy_diff = dest_y - start_yangle = math.atan2(y_diff, x_diff)# What is the 1/2 size of this sprite, so we can figure out how far# away to spawn the bulletsize = max(self.player_sprite.width, self.player_sprite.height) / 2# Use angle to to spawn bullet away from player in proper direction# 加上size的好处是 让子弹从玩家图片外部发射出去,避免内部发射导致物理引擎误判bullet.center_x += size * math.cos(angle)bullet.center_y += size * math.sin(angle)# Set angle of bullet 设置子弹的朝向bullet.angle = math.degrees(angle)# Gravity to use for the bullet# If we don't use custom gravity, bullet drops too fast, or we have# to make it go too fast.# Force is in relation to bullet's angle.# 专门定义以下子弹垂直方向受到的重力 而不是默认的bullet_gravity = (0, -BULLET_GRAVITY)# Add the sprite. This needs to be done AFTER setting the fields above.# 使用物理引擎管理子弹 这个子弹是具有弹性的self.physics_engine.add_sprite(bullet,mass=BULLET_MASS,damping=1.0,friction=0.6,collision_type="bullet",gravity=bullet_gravity,elasticity=0.9)# Add force to bullet# 最后 让物理引擎给子弹施加一个力量,让他飞走force = (BULLET_MOVE_FORCE, 0)self.physics_engine.apply_force(bullet, force)def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

十五、拓展(2):优化玩家速度

1. 让玩家不再逆天弹跳

十六、拓展(3):添加背景层和前景层

前景:即可以挡住玩家的元素
背景:即可以被玩家挡住的元素
前景 背景 取决于图块层的加载先后,即on_draw()顺序

1.地图编辑器里创建新层并绘制相应图块

前景层 foreground_layer

背景层 background_layer

2、在代码中添加相关图层并显示

1、准备初始变量


2、给初始变量赋值确定类型

3、加载前景和背景图块层

4、绘制前景和背景层

十七、拓展(4):添加音效和背景音乐

1.添加音效

第一步加载音效
第二步 在合适的时机 或者代码位置 播放音乐
首先 还是初始化变量(属性)

其次 加载音乐文件

最后

2.添加背景音乐

背景音乐方法播放略有不同,因为我们要监听播放状态,并适时候切换音乐


播放

切换

控制台效果如下

代码示例

# coding=<UTF-8>"""
Example of Pymunk Physics Engine Platformer
使用Pymunk物理引擎的示例游戏 该物理引擎可以为物体也就是说的精灵
添加 质量 弹性 摩擦力 弹力 动力等等 并且可以进行碰撞检测
"""import math
from typing import Optional
import arcadeSCREEN_TITLE = "arcade结合PyMunk引擎示例"# How big are our image tiles 我们的图片基础大小尺寸是多少,后续尺寸都在此基础上计算
SPRITE_IMAGE_SIZE = 64# Scale sprites up or down  图片的缩放系数 0.5 是原来的一半大小
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_TILES = 0.5# Scaled sprite size for tiles  构成地图的单个基准网格的大小 是图片基础尺寸的倍数(0.5倍)
SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)# Size of grid to show on screen, in number of tiles  以基准格子数来计算地图大小
SCREEN_GRID_WIDTH = 25
SCREEN_GRID_HEIGHT = 15# Size of screen to show, in pixels  重新定义地图尺寸 这里以格子数量为计量单位 之前step001是直接设置的像素大小
SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT# step005添加
# --- Physics forces. Higher number, faster accelerating.
# 数字越大 加速率越大
# Gravity 重力
GRAVITY = 1500# Damping - Amount of speed lost per second
# 默认的阻尼 和 玩家的阻尼(每帧损失速度的比例)
DEFAULT_DAMPING = 1.0
PLAYER_DAMPING = 0.4# Friction between objects 不同物体间的摩擦力
PLAYER_FRICTION = 1.0
WALL_FRICTION = 0.7
DYNAMIC_ITEM_FRICTION = 0.6# Mass (defaults to 1) 玩家质量设置为2 可以推动默认1.0质量的其他物体
PLAYER_MASS = 2.0# Keep player from going too fast  # 玩家角色最大的 水平 和 垂直 方向的速度 可以自由调整
PLAYER_MAX_HORIZONTAL_SPEED = 300
PLAYER_MAX_VERTICAL_SPEED = 1600# Force applied while on the ground 玩家在地面移动时将受到的推力
PLAYER_MOVE_FORCE_ON_GROUND = 4000# Force applied when moving left/right in the air 玩家在空中时受到的左右方向的推力 大小可以自己调节
PLAYER_MOVE_FORCE_IN_AIR = 900# Strength of a jump  玩家起跳时给玩家施加的弹射力 力越大跳的越高 可以自己优化
PLAYER_JUMP_IMPULSE = 1400# Close enough to not-moving to have the animation go to idle.
# 设置这个常量 当玩家角色移动的距离小于0.1时 不让他有动画的走路表现 而是静止 比如在挨着墙的时候按方向键 不再有走路动画
DEAD_ZONE = 0.1# Constants used to track if the player is facing left or right
# 设置面向右侧 为 0  面向左 为 1
RIGHT_FACING = 0
LEFT_FACING = 1# How many pixels to move before we change the texture in the walking animation
# 动作切换是很快的事情,那怎么控制切换频率或者速度呢,我们设置这个变量,使角色移动量每大于20像素时再进行动画 切换
DISTANCE_TO_CHANGE_TEXTURE = 20# step009.py 添加
# How much force to put on the bullet  给子弹施加的初始动力
BULLET_MOVE_FORCE = 4500# Mass of the bullet  子弹的质量
BULLET_MASS = 0.1# Make bullet less affected by gravity 给子弹一个更小的重力值
BULLET_GRAVITY = 300# How many pixels to keep as a minimum margin between the character
# and the edge of the screen.
# 设置物体距离屏幕边缘的最小距离,当小于之这个距离时执行界面的滑动。
# 换言之就是根据 可视的界面,构建一个内部的内边框,当玩家移出边框时,进行屏幕滑动
# 这里我们使用上面定义过的屏幕大小的常量来定义内边框 到 外边框(可视界面的边缘)的距离 这样更通用些
LEFT_VIEWPORT_MARGIN = SCREEN_WIDTH/4
RIGHT_VIEWPORT_MARGIN = SCREEN_WIDTH/4
BOTTOM_VIEWPORT_MARGIN = SCREEN_HEIGHT/2
TOP_VIEWPORT_MARGIN = SCREEN_HEIGHT/4class PlayerSprite(arcade.Sprite):""" Player Sprite """def __init__(self, ladder_list: arcade.SpriteList,hit_box_algorithm):""" Init """# Let parent initialize 初始化父类方法super().__init__()# Set our scale 设置缩放系数self.scale = SPRITE_SCALING_PLAYER# Images from Kenney.nl's Character pack 图片来自 Kenney网站的素材 aracade 以默认内置# 以下 图片都可以用 选一个喜欢的造型集即可 名字是一样的 后缀不一样则造型不一样 所以main_path 只定义名字# main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"main_path = ":resources:images/animated_characters/female_person/femalePerson"# main_path = ":resources:images/animated_characters/male_person/malePerson"# main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"# main_path = ":resources:images/animated_characters/zombie/zombie"# main_path = ":resources:images/animated_characters/robot/robot"# Load textures for idle standing 使用load_texture_pair加载不同状态下的造型图片self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")  # 站立造型self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")  # 跳跃造型self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")  # 下落造型# Load textures for walking 由于走路的造型较多 这里用循环去加载 然后放入列表中备用self.walk_textures = []for i in range(8):texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")self.walk_textures.append(texture)# step012.py添加 爬梯子的造型加载# Load textures for climbingself.climbing_textures = []texture = arcade.load_texture(f"{main_path}_climb0.png")self.climbing_textures.append(texture)texture = arcade.load_texture(f"{main_path}_climb1.png")self.climbing_textures.append(texture)# Set the initial texture 默认的站立造型 (0,代表造型里的第一张图片,因为以后还会有左右不同方向的造型)self.texture = self.idle_texture_pair[0]# Hit box will be set based on the first image used. 角色边框由第一张造型图片定义 以便后续碰撞检测self.hit_box = self.texture.hit_box_points# Default to face-right 默认觉得是脸朝右的self.character_face_direction = RIGHT_FACING# Index of our current texture  当前造型图片在图片组中的索引值self.cur_texture = 0# How far have we traveled horizontally since changing the texture# 定义一个 记录上次变换造型以后 角色的移动距离  后续用于比较当他大于我们上面常量设置的值时 执行造型切换self.x_odometer = 0# step012.py添加self.y_odometer = 0self.ladder_list = ladder_listself.is_on_ladder = False#  这是物理引擎一个处理移动的方法,只要这个精灵是由物理引擎创建的,那么物理引擎自己就会去调用这个固定方法的#  dx 代表每一帧之间 物体横向变化的距离  dy 代表每一帧之间 物体纵向变化的距离#  d_angle则代表每一帧之间 物体角度朝向的变化量def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle being moved by the pymunk engine 这是物理引擎一个处理移动的方法"""# Figure out if we need to face left or right  判断我们是否要改变角色的朝向if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:self.character_face_direction = LEFT_FACINGelif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:self.character_face_direction = RIGHT_FACING# Are we on the ground? 判断当前对象是否在地面上 self代表当前对象 谁用这个类创建的 那么self就是谁is_on_ground = physics_engine.is_on_ground(self)# Are we on a ladder? 根据玩家精灵组和梯子精灵组的碰撞结果,设置不同的属性# 碰撞检测结果是个列表,被碰撞的精灵都会存进去,所以一旦碰撞,列表的长度就会大于0if len(arcade.check_for_collision_with_list(self, self.ladder_list)) > 0:# 碰撞以后 检查玩家是否在梯子上# 如果碰撞了 但是玩家状态显示不在梯子上 一定是刚刚上到梯子上来  则更改状态为 在梯子上,然后重力大小设置为0if not self.is_on_ladder:self.is_on_ladder = Trueself.pymunk.gravity = (0, 0)self.pymunk.damping = 0.0001  # 刚到梯子上时,玩家的速度还使用之前的速度,只不过会稍微衰减self.pymunk.max_vertical_velocity = PLAYER_MAX_HORIZONTAL_SPEED  # 在梯子上时最大的垂直速度# 如果玩家并没有碰撞到梯子else:# 但是玩家状态却还是在梯子上 那么 肯定是玩家刚从梯子上掉下来了if self.is_on_ladder:self.pymunk.damping = 1.0  # 让玩家瞬间失去原来在梯子上时的速度self.pymunk.max_vertical_velocity = PLAYER_MAX_VERTICAL_SPEEDself.is_on_ladder = Falseself.pymunk.gravity = None  # 把原来的重力(0,0)清除 开始自由落体# Add to the odometer how far we've moved  将横向的变化量累加起来self.x_odometer += dx# step012.py添加 垂直方向的变化量self.y_odometer += dy# 设置在梯子上时的动作变化if self.is_on_ladder and not is_on_ground:# Have we moved far enough to change the texture?# 当在梯子上的垂直的变化量大于我们设定的可以进行动作切换的值时 改变动作 并重置变化量if abs(self.y_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.y_odometer = 0# Advance the walking animation# 切换下一张动作图的索引self.cur_texture += 1# 有两张图就可以if self.cur_texture > 1:self.cur_texture = 0# 更改梯子上的动作self.texture = self.climbing_textures[self.cur_texture]return# Jumping animationif not is_on_ground:if dy > DEAD_ZONE:self.texture = self.jump_texture_pair[self.character_face_direction]returnelif dy < -DEAD_ZONE:self.texture = self.fall_texture_pair[self.character_face_direction]return# Idle animationif abs(dx) <= DEAD_ZONE:self.texture = self.idle_texture_pair[self.character_face_direction]return# Have we moved far enough to change the texture?if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:# Reset the odometerself.x_odometer = 0# Advance the walking animationself.cur_texture += 1if self.cur_texture > 7:self.cur_texture = 0self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]class BulletSprite(arcade.SpriteSolidColor):""" Bullet Sprite """def pymunk_moved(self, physics_engine, dx, dy, d_angle):""" Handle when the sprite is moved by the physics engine. """# If the bullet falls below the screen, remove it# 当子弹下落并小于某一个值时 令其消失 这里设置为50 容易观察 负数则在掉出屏幕底部时消失(一般也就设置为负数)if self.center_y < 50:self.remove_from_sprite_lists()class GameWindow(arcade.Window):""" Main Window 主窗体"""def __init__(self, width, height, title):""" Create the variables 创建变量"""# 继承父类的属性super().__init__(width, height, title)# Physics engine 初始化物理引擎self.physics_engine = Optional[arcade.PymunkPhysicsEngine]# Player sprite 定义玩家精灵变量 冒号后面是说明变量的类型# self.player_sprite: Optional[arcade.Sprite] = Noneself.player_sprite: Optional[PlayerSprite] = None# Sprite lists we need  精灵列表变量的创建self.player_list: Optional[arcade.SpriteList] = Noneself.wall_list: Optional[arcade.SpriteList] = Noneself.bullet_list: Optional[arcade.SpriteList] = Noneself.item_list: Optional[arcade.SpriteList] = None# step012.py添加self.ladder_list: Optional[arcade.SpriteList] = None# step011.py添加 可移动物体层(不受其他物体力量的影响) 而不是动态物体层(会被其他物体的力量影响)self.moving_sprites_list: Optional[arcade.SpriteList] = None# 前景修饰层添加self.foreground_list = None# 背景修饰层添加self.background_list = None# 声音相关的变量# Variables used to manage our music. See setup() for giving them# 发射子弹的声音self.bullet_fire_sound = None# 子弹和动态物体碰撞爆炸的声音self.boom_explode_sound = None# 子弹撞墙的声音self.bullet_hit_wall_sound = None# 玩家跳跃的声音self.jump_sound = None# 背景音乐列表self.music_list = []# 当前播放的背景音乐索引self.current_song_index = 0# 当前正在播放的音乐self.current_player = None# 当前音乐self.music = None# Track the current state of what key is pressed# 监听方向按键状态的变量 按下为 True 未按下 为False 这里暂时只设置了左右键的状态self.left_pressed: bool = Falseself.right_pressed: bool = False# step012.py 添加self.up_pressed: bool = Falseself.down_pressed: bool = False# 屏幕滚动相关添加# Used to keep track of our scrollingself.view_bottom = 0self.view_left = 0# 创建存储地图的变量self.my_map = None# Set background color  设置一个指定的窗口背景颜色,颜色单词都大写arcade.set_background_color(arcade.color.AMAZON)# 添加切换音乐的方法 默认下一首def advance_song(self):""" Advance our pointer to the next song. This does NOT start the song. """self.current_song_index += 1if self.current_song_index >= len(self.music_list):self.current_song_index = 0print(f"Advancing song to {self.current_song_index}.")# 控制音乐的暂停def play_song(self):""" Play the song. """# Stop what is currently playing.# if self.music:#     self.music.stop()# Play the next songprint(f"Playing {self.music_list[self.current_song_index]}")self.music = arcade.Sound(self.music_list[self.current_song_index], streaming=True)self.current_player = self.music.play(0.5)  # 括号里数字设置音乐声音大小# This is a quick delay. If we don't do this, our elapsed time is 0.0# and on_update will think the music is over and advance us to the next# song before starting this one.import timetime.sleep(0.03)def setup(self):""" Set up everything with the game 设置变量具体代表的物体"""# 音乐播放相关# List of musicself.music_list = [":resources:music/funkyrobot.mp3", ":resources:music/1918.mp3"]# Array index of what to playself.current_song_index = 0# Play the songself.play_song()# 屏幕滚动相关添加# Used to keep track of our scrollingself.view_bottom = 0self.view_left = 0# Create the sprite lists 创建精灵列表(精灵组)# 玩家精灵组self.player_list = arcade.SpriteList()# 子弹精灵组 后续我们添加self.bullet_list = arcade.SpriteList()# 前景修饰层添加self.foreground_list = arcade.SpriteList()# 背景修饰层添加self.background_list = arcade.SpriteList()# 添加音效# Load sounds# 发射子弹的声音self.bullet_fire_sound = arcade.load_sound(":resources:sounds/hit2.wav")# 子弹和动态物体碰撞爆炸的声音self.boom_explode_sound = arcade.load_sound(":resources:sounds/explosion2.wav")# 子弹撞墙的声音self.bullet_hit_wall_sound = arcade.load_sound(":resources:sounds/hurt5.wav")# 玩家跳跃的声音self.jump_sound = arcade.load_sound(":resources:sounds/jump4.wav")# Read in the tiled map 设置tiled编辑的地图的名字 并读取map_name = "pymunk_test_map.tmx"  # 这个就是我们编辑地图时定义的名字 这里带了后缀名.tmxself.my_map = arcade.tilemap.read_tmx(map_name)# 使用类属性作为地图变量,方便后续在设置屏幕滚动时程序获取地图的宽高,地图快大小等信息# self.map_size = self.my_map .map_size# print('地图的宽高为:',self.map_size)# Read in the map layers  # 读取地图中图层的内容self.wall_list = arcade.tilemap.process_layer(self.my_map, 'Platforms', SPRITE_SCALING_TILES)self.item_list = arcade.tilemap.process_layer(self.my_map, 'Dynamic Items', SPRITE_SCALING_TILES)# step011.py添加 移动物体层self.moving_sprites_list = arcade.tilemap.process_layer(self.my_map,'Moving Platforms',SPRITE_SCALING_TILES)# step012.py添加self.ladder_list = arcade.tilemap.process_layer(self.my_map,'Ladders',SPRITE_SCALING_TILES,use_spatial_hash=True,hit_box_algorithm="Detailed")# 前景修饰层添加self.foreground_list = arcade.tilemap.process_layer(self.my_map,'foreground_layer',SPRITE_SCALING_TILES)# 前景修饰层添加self.background_list = arcade.tilemap.process_layer(self.my_map,'background_layer',SPRITE_SCALING_TILES)# Create player sprite 创建玩家精灵对象 图片地址是arcade库自带的,第二个参数是缩放系数# self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.p# ng",SPRITE_SCALING_PLAYER)self.player_sprite = PlayerSprite(self.ladder_list, hit_box_algorithm="Detailed")# Set player location 定位玩家位置 加的那个SPRITE_SIZE / 2 是由于素材是由中心点位置定位的grid_x = 1grid_y = 1.5self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2# Add to player sprite list 把玩家精灵 加入玩家精灵组self.player_list.append(self.player_sprite)# step005.py添加# --- Pymunk Physics Engine Setup ---# The default damping for every object controls the percent of velocity# the object will keep each second. A value of 1.0 is no speed loss,# 0.9 is 10% per second, 0.1 is 90% per second.# For top-down games, this is basically the friction for moving objects.# For platformers with gravity, this should probably be set to 1.0.# Default value is 1.0 if not specified.damping = DEFAULT_DAMPING# Set the gravity. (0, 0) is good for outer space and top-down.gravity = (0, -GRAVITY)# Create the physics engineself.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,gravity=gravity)# Add kinematic spritesself.physics_engine.add_sprite_list(self.moving_sprites_list,body_type=arcade.PymunkPhysicsEngine.KINEMATIC)# Add the player.# For the player, we set the damping to a lower value, which increases# the damping rate. This prevents the character from traveling too far# after the player lets off the movement keys.# Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from# rotating.# Friction normally goes between 0 (no friction) and 1.0 (high friction)# Friction is between two objects in contact. It is important to remember# in top-down games that friction moving along the 'floor' is controlled# by damping.self.physics_engine.add_sprite(self.player_sprite,friction=PLAYER_FRICTION,mass=PLAYER_MASS,moment=arcade.PymunkPhysicsEngine.MOMENT_INF,collision_type="player",max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)# Create the walls.# By setting the body type to PymunkPhysicsEngine.STATIC the walls can't# move.# Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC# PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be# repositioned by code and don't respond to physics forces.# Dynamic is default.self.physics_engine.add_sprite_list(self.wall_list,friction=WALL_FRICTION,collision_type="wall",body_type=arcade.PymunkPhysicsEngine.STATIC)# Create the itemsself.physics_engine.add_sprite_list(self.item_list,friction=DYNAMIC_ITEM_FRICTION,collision_type="item")# step010.py添加 在setup 方法里面定义两个方法并调用def wall_hit_handler(bullet_sprite, _wall_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞墙时消失"""bullet_sprite.remove_from_sprite_lists()# 播放子弹撞墙的声音self.bullet_hit_wall_sound.play()self.physics_engine.add_collision_handler("bullet", "wall", post_handler=wall_hit_handler)def item_hit_handler(bullet_sprite, item_sprite, _arbiter, _space, _data):""" Called for bullet/wall collision 子弹撞到物体时消失 """bullet_sprite.remove_from_sprite_lists()item_sprite.remove_from_sprite_lists()# 播放子弹射中物体后爆炸的声音self.boom_explode_sound.play()self.physics_engine.add_collision_handler("bullet", "item", post_handler=item_hit_handler)def on_key_press(self, key, modifiers):"""Called whenever a key is pressed. 监听鼠标点击事件"""if key == arcade.key.LEFT:self.left_pressed = Trueelif key == arcade.key.RIGHT:self.right_pressed = Trueelif key == arcade.key.UP:self.up_pressed = True# find out if player is standing on ground# 判断玩家按下上方向键时的位置是否在地面上 在则可以跳 不在则不能跳  这样避免了二连跳等操作if self.physics_engine.is_on_ground(self.player_sprite):# 播放玩家跳跃的音效self.jump_sound.play()# She is! Go ahead and jump  在地面上 那个则执行跳跃功能impulse = (0, PLAYER_JUMP_IMPULSE)  # 第一个参数代表左右的力 第二个代表上下的力# 由于物理有质量 受重力影响 自己还会落下来 所以 下方向键后续可以再补充(用于下蹲或者上下楼梯等)self.physics_engine.apply_impulse(self.player_sprite, impulse)elif key == arcade.key.DOWN:self.down_pressed = Truedef on_key_release(self, key, modifiers):"""Called when the user releases a key. 监听鼠标释放事件"""if key == arcade.key.LEFT:self.left_pressed = Falseelif key == arcade.key.RIGHT:self.right_pressed = Falseelif key == arcade.key.UP:self.up_pressed = Falseelif key == arcade.key.DOWN:self.down_pressed = Falsedef on_update(self, delta_time):""" Movement and game logic 控制屏幕画面内容的刷新和变化"""# 控制音乐的切换,因为一个音乐播放完后,会自动重置播放进度为0。这样无法切换下一首# 于是我们就去检测 当音乐播放进度为0时,切换音乐 或者重新播放position = self.music.get_stream_position(self.current_player)# The position pointer is reset to 0 right after we finish the song.# This makes it very difficult to figure out if we just started playing# or if we are doing playing.if position == 0.0:self.advance_song()self.play_song()# Update player forces based on keys pressed  在按键按下时更新玩家受到的力# 在刷新时再次检测物体当前是否在地面上 或者 空中 以便于设置不同的向左 或向右的力is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)if self.left_pressed and not self.right_pressed:# Create a force to the left. Apply it. 创建一个向左的力 并开始执行# step012.py 加入的 新条件时 or self.player_sprite.is_on_ladderif is_on_ground or self.player_sprite.is_on_ladder:force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为0的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)elif self.right_pressed and not self.left_pressed:# Create a force to the right. Apply it. 创建一个向右的力 并开始执行if is_on_ground or self.player_sprite.is_on_ladder:force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)else:force = (PLAYER_MOVE_FORCE_IN_AIR, 0)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while moving 当移动的时候,物理引擎给玩家精灵一个值为 0 的摩擦力self.physics_engine.set_friction(self.player_sprite, 0)# step012.py添加elif self.up_pressed and not self.down_pressed:# Create a force to the up. Apply it.if self.player_sprite.is_on_ladder:force = (0, PLAYER_MOVE_FORCE_ON_GROUND)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while movingself.physics_engine.set_friction(self.player_sprite, 0)elif self.down_pressed and not self.up_pressed:# Create a force to the down. Apply it.if self.player_sprite.is_on_ladder:force = (0, -PLAYER_MOVE_FORCE_ON_GROUND)self.physics_engine.apply_force(self.player_sprite, force)# Set friction to zero for the player while movingself.physics_engine.set_friction(self.player_sprite, 0)else:# Player's feet are not moving. Therefore up the friction so we stop.# 当没有单个按键按下时,给玩家精灵一个最大的摩擦力 1 以快速停止移动self.physics_engine.set_friction(self.player_sprite, 1.0)# step011.py添加 当移动物体层的物体移动到给他定义的边缘时 让他再原路返回# For each moving sprite, see if we've reached a boundary and need to# reverse course.for moving_sprite in self.moving_sprites_list:# print(moving_sprite.top,moving_sprite.bottom,moving_sprite.boundary_top,moving_sprite.boundary_bottom,moving_sprite.change_y)# 下面这行写的很长 主要时为了观看时便于理解代码含义 为了规范书写 也可以换行if moving_sprite.boundary_right and moving_sprite.change_x > 0 and moving_sprite.right > moving_sprite.boundary_right:moving_sprite.change_x *= -1elif moving_sprite.boundary_left and moving_sprite.change_x < 0 and moving_sprite.left < moving_sprite.boundary_left:moving_sprite.change_x *= -1if moving_sprite.boundary_top and moving_sprite.change_y > 0 and moving_sprite.top > moving_sprite.boundary_top:moving_sprite.change_y *= -1elif moving_sprite.boundary_bottom and moving_sprite.change_y < 0 and moving_sprite.bottom < moving_sprite.boundary_bottom:moving_sprite.change_y *= -1# velocity 是一个速度列表,第一个时横向速度,第二个是纵向速度(是每秒的改变量,因此下面我们要变成每帧的变化量再用)# Figure out and set our moving platform velocity.# Pymunk uses velocity is in pixels per second. If we instead have# pixels per frame, we need to convert.velocity = (moving_sprite.change_x * 1 / delta_time, moving_sprite.change_y * 1 / delta_time)# 注意:官方文档少了下面这一行代码self.physics_engine.set_velocity(moving_sprite, velocity)# 屏幕滚动相关添加# --- Manage Scrolling ---# Track if we need to change the viewportchanged = False# Scroll leftleft_boundary = self.view_left + LEFT_VIEWPORT_MARGINif self.player_sprite.left < left_boundary:self.view_left -= left_boundary - self.player_sprite.leftchanged = True# Scroll rightright_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGINif self.player_sprite.right > right_boundary:self.view_left += self.player_sprite.right - right_boundarychanged = True# Scroll uptop_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGINif self.player_sprite.top > top_boundary:self.view_bottom += self.player_sprite.top - top_boundarychanged = True# Scroll downbottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGINif self.player_sprite.bottom < bottom_boundary:self.view_bottom -= bottom_boundary - self.player_sprite.bottomchanged = True# print('地图块大小',self.my_map.tile_size)#  self.my_map.tile_size 代表组成地图的 每一个地图块的大小 和地图编辑器中创建时一样,是32*32 但是由于地图编辑器输出文件的时候#  设置输出成了16*16的,所以我们要再整除2 变成游戏中实际的大小# self.my_map.map_size.width 地图单行横向的格子数# self.my_map.map_size.height 地图单行纵向的格子数# 设置地图界面从左到右的宽度 结果是用 图块个数表示TILED_MAP_WIDTH = self.my_map.map_size.width * self.my_map.tile_size.width // 2# 设置地图界面从上到下的高度 结果是用 图块个数表示TILED_MAP_HEIGHT = self.my_map.map_size.height * self.my_map.tile_size.height // 2if changed:# Only scroll to integers. Otherwise we end up with pixels that# don't line up on the screenself.view_bottom = max(min(int(self.view_bottom), TILED_MAP_HEIGHT-SCREEN_HEIGHT), 0)self.view_left = max(min(int(self.view_left), TILED_MAP_WIDTH - SCREEN_WIDTH), 0)view_right = min(SCREEN_WIDTH + self.view_left, TILED_MAP_WIDTH)view_top = min(TILED_MAP_HEIGHT, self.view_bottom+SCREEN_HEIGHT)# view_top = min(TILED_MAP_HEIGHT+20, self.view_bottom+SCREEN_HEIGHT+20)# if SCREEN_WIDTH + self.view_left > TILED_MAP_WIDTH:#     self.view_right = TILED_MAP_WIDTH#     self.view_left =TILED_MAP_WIDTH -SCREEN_WIDTH# else:#     self.view_right = SCREEN_WIDTH + self.view_left# if self.view_bottom <= 0:#     self.view_bottom = 0# if self.view_left <= 0:#     self.view_left = 0# Do the scrolling  设置视口大小arcade.set_viewport(self.view_left,view_right,self.view_bottom,view_top)# 物理引擎在帧率刷新时被调用 on_update方法里的代码默认每秒执行60遍self.physics_engine.step()def on_draw(self):""" Draw everything 绘制界面 后执行的绘制 会覆盖先绘制的图案"""arcade.start_render()self.wall_list.draw()self.ladder_list.draw()  # step012.py添加self.moving_sprites_list.draw()  # step011.py添加self.bullet_list.draw()  # 子弹self.item_list.draw()  # 动态物体(例如箱子)self.background_list.draw()  # 背景修饰层 先添加的会被后添加的挡住 放在玩家前添加 即为背景self.player_list.draw()  # 玩家self.foreground_list.draw()  # 前景修饰层 后添加的会挡住先添加的 放在玩家后添加 即为前景# 当鼠标点击屏幕时 会给该方法传入以下参数 x :横坐标 y:纵坐标 button:鼠标左中右按的哪个键 modifiers:修饰键 alt shift 等这些def on_mouse_press(self, x, y, button, modifiers):print(x,y,'左侧',self.player_sprite.left,'底部',self.player_sprite.bottom)""" Called whenever the mouse button is clicked.当鼠标点击时 本方法被调用"""# 使用创建据矩形的方法 创建一个子弹# bullet = arcade.SpriteSolidColor(20, 5, arcade.color.DARK_YELLOW)bullet = BulletSprite(20, 5, arcade.color.DARK_YELLOW)self.bullet_list.append(bullet)   #  把子弹加入上面就定义过的子弹精灵组# Position the bullet at the player's current location# 定义子弹的初始位置 也就是精灵的位置start_x = self.player_sprite.center_xstart_y = self.player_sprite.center_ybullet.position = self.player_sprite.position# Get from the mouse the destination location for the bullet# IMPORTANT! If you have a scrolling screen, you will also need# to add in self.view_bottom and self.view_left.# 记录点击位置的坐标dest_x = xdest_y = y# Do math to calculate how to get the bullet to the destination.# Calculation the angle in radians between the start points# and end points. This is the angle the bullet will travel.# 使用高一会学习的三角函数知识 计算 玩家相对于平面和鼠标点击位置之间的角度x_diff = dest_x - start_xy_diff = dest_y - start_yangle = math.atan2(y_diff, x_diff)# What is the 1/2 size of this sprite, so we can figure out how far# away to spawn the bulletsize = max(self.player_sprite.width, self.player_sprite.height) / 2# Use angle to to spawn bullet away from player in proper direction# 加上size的好处是 让子弹从玩家图片外部发射出去,避免内部发射导致物理引擎误判bullet.center_x += size * math.cos(angle)bullet.center_y += size * math.sin(angle)# Set angle of bullet 设置子弹的朝向bullet.angle = math.degrees(angle)# Gravity to use for the bullet# If we don't use custom gravity, bullet drops too fast, or we have# to make it go too fast.# Force is in relation to bullet's angle.# 专门定义以下子弹垂直方向受到的重力 而不是默认的bullet_gravity = (0, -BULLET_GRAVITY)# Add the sprite. This needs to be done AFTER setting the fields above.# 使用物理引擎管理子弹 这个子弹是具有弹性的self.physics_engine.add_sprite(bullet,mass=BULLET_MASS,damping=1.0,friction=0.6,collision_type="bullet",gravity=bullet_gravity,elasticity=0.9)# Add force to bullet# 最后 让物理引擎给子弹施加一个力量,让他飞走force = (BULLET_MOVE_FORCE, 0)self.physics_engine.apply_force(bullet, force)self.bullet_fire_sound.play()def main():""" Main method 主程序 由最后一行调动 然后再调动游戏整体"""''' 初始化传入屏幕的宽 高 标题'''window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)window.setup()arcade.run()if __name__ == "__main__":main()

十八、拓展(5):添加分数文字等

老规矩,添加什么就要准备什么的变量
这里 我们先准备分数的变量即可 因为他真的是随全局程序会变化的
init

setup()


on_draw()
把分数写到屏幕上

十九、拓展(6):定时器功能

本拓展将使用定时器功能制作一个 倒计时,然后变成文本在界面显示

当你想在游戏中,想让某个功能代码像手表的指针一样有规律的循环执行时,你可以时用arcade自带的schedule()方法

代码中用法如下:

放到on_draw()方法里显示

终结

总体代码比看官网的更准确一些,至于思路和过程,也建议有能力的同学,结合官网的英文讲解进行理解(网页自动翻译成的汉语作为辅助就行,翻译不准时实在是影响理解)
祝大家都能做出自己心仪的小游戏,我也会不断努力的

【日常点滴016】python游戏库arcade结合Tiled map editor地图编辑器和Pymunk物理引擎制作游戏过程记录,并最终打包为exe文件相关推荐

  1. Python制作小软件——4. 利用PyInstaller打包成exe文件

    在前面:Python制作小软件--3. 利用PyQt5实现界面中的功能,介绍完了Python中如何呼唤出我们的界面后 最后最重要的就是将我们做好的东西,全部打包成一个.exe可执行文件.这里我们使用的 ...

  2. python实现音乐定时开关,模拟上下课铃声(含程序打包微小exe文件方法介绍)

    本文分两部分,先介绍python实现模拟上下课定时铃声播放与关闭,再讲python程序打包成小型exe文件. 一.python实现模拟上下课定时铃声播放与关闭 不说废话,完整代码奉上. import ...

  3. 使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件

    目录 一.安装Pyinstaller 1.直接安装 2.Anaconda环境下安装 二.使用Pyinstaller打包生成exe文件 三.运行生成的exe文件 四.exe文件运行中的问题 一.安装Py ...

  4. 解决 Python打包成exe 文件过大问题的一些方法

    前言 之前有做过Python的pyqt桌面应用,当时每次更新打包的时候整个文件下来都需要300~400M,但是一直没有找到合适的方法解决,而是尽量Python少安抓库包,但效果一般,最近找到了解决方法 ...

  5. Tips--解决Python打包成exe文件大启动慢的问题

    解决Python打包成exe文件大启动慢的问题 1. 问题描述 2. 产生原因 2. 解决方法 2.1 建立虚拟环境 2.2 安装所需库 2.3 在环境中打包 2.4 附pyinstaller参数表 ...

  6. python打包为exe文件_Pyinstaller(python打包为exe文件)

    需求分析: python脚本如果在没有安装python的机器上不能运行,所以将脚本打包成exe文件,降低脚本对环境的依赖性,同时运行更加迅速. 当然打包的脚本似乎不是在所有的win平台下都能使用,wi ...

  7. 把python语言翻译出来_Python语言实现翻译小工具(Python打包成exe文件)

    本文主要向大家介绍了Python语言实现翻译小工具(Python打包成exe文件),通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. 1.环境 windows10 python3.5 ...

  8. python文件图标变成小电脑_手把手教你给Python程序写图形界面,并且打包成exe文件-exe文件...

    环境配置 官网下载Python3,LZ的配置环境是Python3.6,PyCharm 2017.2.1pip3 install PyQt5 #下载PyQt5 pip install PyQt5-too ...

  9. python打包成.exe文件时出现“系统找不到指定路径”

    python打包成.exe文件时出现"系统找不到指定路径" 我在一开始写工程时就想到最后打包的时候可能会出现文件位置会发生移动,所以并没有使用绝对路径,而都是以相对路径写的程序. ...

最新文章

  1. ​纳米孔测序揭示冻土冻融对土壤微生物群落变化的影响
  2. 口腔微生物——“你的大能量,超乎我想象”
  3. KMP模版 KMP求子串在主串出现的次数模版
  4. 查看安卓keystore别名
  5. Uploadify在asp.net下使用Demo
  6. 【算法】红黑树-二叉树-算法
  7. Git本地仓库文件的创建、修改和删除
  8. mybatis:在springboot中的配置
  9. Laravel学习笔记之一
  10. 信息安全工程师考试大纲
  11. nodejs与php通信,使用DNode实现php和nodejs之间通信的简单实例
  12. JSP页面中taglib的uri设置
  13. 众多时间时钟Flash动画素材一键即可获取
  14. 跨专业考计算机研究生有专业限制吗,我想跨专业考计算机专业研究生
  15. java 快递打印_基于java的快递打印系统
  16. 地图可视化 - 气泡点图
  17. unity3d好学吗?
  18. “区块链”技术在传统行业中的应用
  19. 【OpenCV3经典编程100例】(26)2D特征:Shi-Tomasi角点检测goodFeaturesToTrack()函数
  20. Echarts图表根据浏览器窗口缩放进行动态缩放,多个echarts同时缩放

热门文章

  1. 百度推广创意中的通配符有哪些注意事项
  2. excel2021 打印圆不圆
  3. 笔记本电脑突然无法链接wifi、无法连接宽带
  4. 无力回天...机关算尽,还是死在上线之中.............
  5. 抖音的服务器到底啥配置?
  6. java程序员的转正述职ppt
  7. SVAC编解码技术标准:诞生、质疑与发展
  8. Python项目:外星人入侵(汇总)
  9. 阿里云设置密钥对登录服务器
  10. 高数 | 洛必达法则的隐藏细节、广义洛必达法则(分母无穷直接洛必达)使用条件