# pixel_game **Repository Path**: orangeflash/pixel_game ## Basic Information - **Project Name**: pixel_game - **Description**: godot 学习笔记 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-03-24 - **Last Updated**: 2025-03-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [b站视频](https://www.bilibili.com/video/BV15D4y1U7j5?p=1) [资源地址](https://github.com/uheartbeast/youtube-tutorials/blob/master/Action%20RPG/Action%20RPG%20Resources.zip) [其他教程](https://www.bilibili.com/video/BV1ok4y1r7az/?spm_id_from=333.788.recommend_more_video.3) ## 1. 第一课 1. 导入资源, filter 2. 项目设置 -> dislay -> Window width: 320 height: 180 3. 项目设置 -> dislay -> Window Test width: 320, Test height: 180 (测试时候可以看更大屏幕区) 4. 项目设置 -> dislay -> Window 拉到底 Mode: 2d (游戏视图就会被拉伸到测试宽度和高度) World 结点下添加 KinematicBody2D -rename-> Player - StaticBody 通常用于游戏中不会移动的物体 - RigidBody 用于使用屋里系统运动的物体 - KinematicBody 用代码操控的物理碰撞体 适用于玩家角色 Player |_ Sprite 精灵 拖动 static/Player/Player.png -> Sprite 检查器下Sprite Texture 下 检查器: 设置 Animation Hframes: 62 frames:8 Player 场景增加 脚本: Player/Player.gd ```gd func _physics_process(delta): # 获取按键 并且更新物体位置 if Input.is_action_pressed("ui_right"): position.x += 10 # 设置实体速度 遇到碰撞会停下 move_and_collide(velocity) ``` 改进 去掉4个if else 来判断方向 ```gd func _physics_process(delta): var input_vector = Vector2.ZERO input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up") if input_vector != Vector2.ZERO: velocity = input_vector else: velocity = Vector2.ZERO move_and_collide(velocity) ``` 有个问题是对角的速度会快根号2倍 ## 2. 优化主角移动 加入加速度, 最大速度, 摩擦力 ```gd extends KinematicBody2D const ACCELERATION = 20 const MAX_SPEED = 100 const FRICTION = 10 var velocity = Vector2.ZERO # 物理引擎调用 func _physics_process(delta): var input_vector = Vector2.ZERO input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up") input_vector = input_vector.normalized() # 归一化速度,无论哪个方向速度值都是1 if input_vector != Vector2.ZERO: velocity += input_vector * ACCELERATION * delta velocity = velocity.clamped(MAX_SPEED * delta) else: velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta) move_and_collide(velocity) ``` ## 3. 碰撞 1. 调整速度 ```gd const ACCELERATION = 500 const MAX_SPEED = 100 const FRICTION = 500 var velocity = Vector2.ZERO # 物理引擎调用 func _physics_process(delta): var input_vector = Vector2.ZERO input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up") input_vector = input_vector.normalized() if input_vector != Vector2.ZERO: velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta) else: velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta) move_and_collide(velocity * delta) ``` ### 添加碰撞体 collisionShape2 形状使用 Capsule 胶囊形 , 旋转90度 ![img](img/Player_collisionShape2D.png) 在World场景 下增加 StaicBody2D |_ collisionPolygon2D 加点 ![img](img/world_static_collision2d_select.png) 菜单: 调试-》勾选 显示碰撞区域 ![img](img/world_static_collision2d_show.png) move_and_collide 碰到区域会停下 move_and_slide 碰到区域会改方向滑过去, 不需要考虑 delta 项目设置 -》 window -> Aspect: Keep 通常设置Keep 就行了, 它会自动保持高度或者保持宽度 如果屏幕分辨率和我们的比例不同的话, 缩放后会自动给加上黑色填充 ```gd extends KinematicBody2D const ACCELERATION = 500 const MAX_SPEED = 100 const FRICTION = 500 var velocity = Vector2.ZERO # 物理引擎调用 func _physics_process(delta): var input_vector = Vector2.ZERO input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up") input_vector = input_vector.normalized() if input_vector != Vector2.ZERO: velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta) else: velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta) velocity = move_and_slide(velocity) ``` ## 4. 树场景 Player 右键 保存为场景 增加新场景 加结点 StaticBody2D -rename-> Bush |_ Sprite 拖动static/World/Bush.png 到 Texture |_ CollisionShape2D 调整如下 ![img](img/bush_collision2D.png) 保存在 World 目录下 Node2D 和 node 的区别 - Node2D: 移动时候, 所有子结点都会跟着移动 - Node: 独立的 ysort: 对于它的所有的子结点,它回根据它们的Y位置进行排序 World 场景右键 更改结点类型选择 YSort 如图类似前后关系的效果 ![img](img/world_ysort.png) ![img](img/world_ysort_2.png) 修改主角用脚去判定 点击 Player 场景, shift 选择 Sprite 和 CollisionShape2D移动到脚的位置 ![img](img/Player_center_on_foot.png) ## 5. 主角动作 点击Player 场景 Player 增加 AnimationPlayer 子结点 可以把任意属性添加关键帧 屏幕中创建动画 runRight ![img](img/Player_animation_runright.png) 数字 从1到6 表示秒 ![img](img/Player_animation_runright_frame.png) 点击frame 旁边的上下, 插入关键帧, 依次插入5帧,最后一帧用第一帧就行 ![img](img/Player_animation_runright_play.png) 依次类推添加 RunUp 向上跑 RunRight 向上跑 RunLeft 向左跑 RunDown 向下跑 Idle 用前面的一帧就够了 IdleUp 向上停 IdleRight 向上停 IdleLeft 向左停 IdleDown 向下停 设置run时候勾选 循环播放,这样跑的时候一直跑 ![img](img/Player_animation_runright_recycle.png) # 一开始就初始化动画 onready var animationPlayer = $AnimationPlayer ## 6. 使用AnimationTree 减少方向判断代码 TreeRoot: - AnimationNodeStateMechine - Anim Player 选择 AnimationPlayer 在空白框加动画 IdleRight RunRight ![img](img/Player_animationtree.png) 可以看到来回切换动画 上面都删掉 添加BlendSpace2D 分别重命名为Idle Run 点击Idle 旁边的铅笔 ![img](img/Player_animation_idle.png) 点击点 ![img](img/Player_animationtree_idle.png) 混合选择虚线 实线可以做2d 骨骼动画? ![img](img/Player_animationtree_run1_1.png) 设置顶点时候,可以设置坐标范围为 ± 1.1 这样 斜线走时候偏向左或者右 ## 7. 设置游戏背景,贴图 将World 场景改为 Node2D 添加新的点为 YSort Player 和 bush 都拖动到 YSort 下,成为YSort 的子结点 ### 地图背景 方法1 World 场景下添加子节点 Sprite 并排在 YSort的前面 将static/World/GrassBackGround.png 拖到Sprite 检查器 的Texture中 Sprite 检查器 中 Offset -> Centered 取消勾选 Sprite 检查器 中 Region -> 启用 ![img](img/world_ysort_background.png) 吸附模式选择像素吸附 可以选择全部图片,也可以选择网格吸附,设置像素大小来选择区域 ![img](img/world_ysort_background_impot.png) 设置导入 Repeat 选Enabled ,取消勾选 Filter , 重新导入 Sprite 检查器 Region 设置 W:320: H:180 ### 地图背景 方法2: World 场景添加 子节点 TextureRect 重命名为 TextureRectBackground ; 放在Sprite 上面, Spirite 改名为 Background 将图片导入 Texture Stretch Mode 设置为Tile 可以平铺背景图 作者喜欢方法1 ### 设置TileMap tileset World添加子节点 TileMap 检查器 Cell Size x:16 y:16 TileSet 新建TileSet 点击新建的TileSet ![img](img/world_tile_map.png) 点击坐下角加号 选择 static/World/DirtTileset.png 右上角 新建自动图块 出现 区域, 点击网格,出现网格 ![img](img/world_tile_map_autotile.png) 在检查器展开 Snap Options step x:16 y:16 可以将网格缩小 重新选择图片区域 检查器 Select Tile 展开 设置 Subtile Size x:16 y:16 AutoTile BitmaskMod : 3x3(minimal) 再点击掩码 点击图片时候就是红色 ![img](img/world_tile_map_bitmask.png) bitmask 的意思是, 设置掩码, 考虑连续的砖头 ![img](img/world_tile_map_mask.png) 没看明白,网友推荐 [Tiled](https://www.mapeditor.org/) ## 8 加悬崖 上一个Tile 命名为 DirtPathTileMap 添加一个新的TileMap 命名为 DirtCliffTileMap 检查器 Cell 大小 x:32 y:32 新建Tile 并点击tile 进入tile, 点击右下角 + 号导入图片, 选择悬崖图 点击新建自动图块,选择区域,设置step 32 32; 设置Subtile Size x:32 y:32 ![img](img/world_tile_cliff.png) tileMode 选择 3x3(minimal) 画掩码 如下图 ![img](img/world_tile_cliff1.png) 设置悬崖的碰撞, 选择碰撞 然后鼠标点个格子,点击方框,双击下个格子,总共要点击N次(mmp) ![img](img/clifftile_collesion.gif) 设置完成之后,运行跑动就可以撞墙了 ![img](img/clifftile_collesion_run.gif) ## 9 攻击动作 打开Player 场景 点击AnimationPlayer AnimationTree 先设置 Active 为不起用 编辑代码,增加 ```python func _ready(): animationTree.active = true ``` 添加动画 添加 AttackRight AttackUp AttackLeft AttackDown 操作步骤通 RunRight 代码中加状态, 标识主角动作 ```gd enum { MOVE, ROLL, ATTACK } var state ``` 将不同状态用不同的函数封装 打开项目 项目设置, 和常规并列的有键位映射 可以增加键位映射 ![img](img/key_add_set.gif) ![img](img/attack_key.gif) attact 增加 J键映射 在代码中增加状态切换的判断 ```gd if Input.is_action_just_pressed("attack"): state = ATTACK ``` 点击AnimationTree 增加 BlendSpace2D 命名 Attack 类似Idle 添加4个方向的动画点 ![img](img/animationTree_attack.png) 改代码 , 在运动函数当中增加攻击方向,以便于攻击不改动主角面向 ```gd animationTree.set("parameters/Attack/blend_position", input_vector) ``` 现在攻击不会停下来 在攻击动画后增加一个轨道,用来修改主角状态 点击 AnimationPlayer 调整帧到最后一帧 左上角添加轨道 方法调用轨道 添加关键帧之前先保存 ```gd func attack_animation_finished(): state = MOVE ``` ![img](img/attack_finished.gif) 然后把所有的 attack 动画都加上 attack_animation_finished() 没有用!!!(小点没在帧上 如下,否则动画结束就卡) ![img](img/animationTree_attack_call_back.png) ## 10 攻击加伤害 YSort 把 Bush 编组, 可以不影响Bush和Player层次 增加草 新增场景 Node2D 增加子节点 Sprite 重命名 Sprite 点击 Texture 从 World/Grass.png 调整位置 offset x:8 y:8 加草到World 场景下,并编组到YSort 下,组名Grass Grass 场景增加脚本 增加草特效 新建场景 node2D 重命名 GrassEffect 添加子节点 AnimatedSprite 检查器点击 Frames 添加新动画弹出动画窗口 动画窗口添加点击添加帧 弹出选择框, static/Effect ,调整水平和垂直 ![img](img/effect_grass.gif) 写代码 给草加上特效 ![img](img/attack_grass.gif) 代码改动请查看 ```bash git diff --stat f7832f1 5795913 ``` ## 11 检测和添加伤害 上一章攻击就会造成所有草消灭 新建两个场景 Hitbox Hurtbox, 并且分别添加子节点 CollisionShape2D 转到Grass 场景,点击 实例化子场景 (锁链形状) 添加 Hurtbox 选择Hurtbox 右键 子场景可编辑,选择形状长方形,覆盖草 ![img](img/grass_hurtbox.png) 也就是主角的伤害打的是 这个area 点击area, 找到信号, area_entered , 连接到Grass Player 增加 Hitbox, 编辑子结点, 设置胶囊形覆盖主角 ![img](img/player_hitbox.png) Player 增加Position2d 重命名为HitboxPivot y:-5 将Hitbox 置于 HitboxPivot 下,设置形状覆盖剑挥舞范围 ![img](img/player_sword_hit.png) 给HitboxPivot添加关键帧点击Node2D点击rotation degre   ![img](img/attack_rotation.gif) 设置启用碰撞体启用和失效位置 ![img](img/attack_collesion_create_distroy.gif) hitbox 和 hurtbox 是为了分层 在项目 项目设置 常规 layer Names 设置不同层的名字 - 第一层 World - 第二层 Player - 第3层 PlayerHurtbox - 第4层 EnemyHurtbox Hitbox 和 Hurtbox 的collision 层取消勾选 Player 场景 Collision - Layer: 第二个框 - Mask: 第一个框 - Layer: 是主角所在层次 - Mask: 是主角所在面对层次 world 场景 点击 DirtCliffTileMap Collision layer点击第一个,Mask 取消勾选 Grass 场景设置collesion层次 第四层 Player 场景 swordHitbox 设置 Mask: 4, layer: 空 ## 12 增加翻滚 Player 场景 点击 AnimationPlayer 增加动画 时间0.5秒 点击 Sprite 点击 Frame 找到翻滚的帧 加关键帧 分别对应到相应的滚动动画 添加 RollUp RollRight RollLeft RollDown 动画时间0.5秒 动画循环 给角色增加 roll_animation_finished 方法 添加轨道 -> 方法调用 在最后一帧增加方法调用,添加关键帧 选择roll_animation_finished 方法 点击 AnimationTree 添加 BlentSpace2D 连接 Idle ![roll_tree](./img/roll_tree.png) 点击铅笔图标可以进入编辑, 点击AnimationTree 勾选 Inspector 中的 Active 启用 添加动画, 参考之前添加跑的步骤 ![roll_tree](./img/roll_tree.gif) 给滚添加快捷键 k键 ![roll_key](./img/roll_key.gif) 设置全局变量 roll_vector 保存角色的面向方向, 初始方向需要手动设置和角色面向一直 ## 13 野怪之 小蝙蝠 1 1 新建场景 添加根结点 KinematicBody2D 重命名 Bat 保存到文件夹 Enemies 添加子节点 sprite 将 static/Ememies/Bat.png 拖动到 texture 有5张图,分为5个小动画, 点击Hframes 设置为5 让蝙蝠变高一点,设置offset y:-12 Bat 子节点添加 Sprite 重命名 ShadowSprite, Texture 使用 static/Shadows/SmallShadow.png 加阴影让蝙蝠感觉在飞 蝙蝠一上一下的,使用AnimateSprite 就够了。 修改Sprite 为 AnimateSaprit 点击 Frames 新建, 点击动画 ![bat](./img/bat_frame.gif) 该名default 为 Fly 添加帧 ,选择 static/Enemies/Bat.png 水平分割成5份,垂直分割为1份 ![bat](./img/bat_frame_15.gif) 点击 Playing 速度10FPS 不用玩家一样复杂 Bat 子场景 CollisionShape2D shape 设置圆形 覆盖阴影 项目, 项目设置中搜索layer 重命名第5层为 Enemy 将蝙蝠设置所在 Enemy 层, 面对 World层 ![img](./img/bat_layer.gif) 设置蝙蝠伤害区域,点击锁链形, 选择 hurtbox 并在其下添加子节点 CollisionShape2D ![img](./img/bat_hurtbox.gif) 给 Bat 添加脚本 将Hurtbox 放置在 第三层也就是 EnemyHurtBox 并连接信号 area_entered() 给 SwordHitbox添加脚本 代码见 gitlog ## 14 蝙蝠添加属性 添加新场景 Node 重命名 Stats 保存在根目录 添加脚本 设置生命值 点击 Bat 场景 引入 Stats 场景 stats 变动传递给 Bat 应该使用信号传递, 数据从下层往上层传递使用信号 swordHitBox 使用继承的方式,设置伤害 具体见脚本git log ## 15 蝙蝠死亡效果 优化 1 问题, 滚动方向和攻击方向与朝向不一致 点击 Player 场景 设置 检查器里面的所有动作数值相同 ![img](./img/player_init.png) 代码里 设置滚动朝下 删除草场景,改草效果代码为Effect.gd, 该文件作为通用效果 新建场景,AnimatedSprite ![img](./img/grass_destroy.gif) 具体调整见代码 新增场景 AnimatedSprite EnemyDeathEffect 新建Frame 导入图片特效,分割成9分,15FPS y方向偏移-8 把Effect.gd 拖动到 EnemyDeathEffect场景中 ![img](./img/effect_drag.gif) 优化代码具体看git log ## 16 蝙蝠基础AI 蝙蝠添加击中特效 新建场景 AnimatedSprite 重命名 HitEffect , 添加帧 点击帧 设置等具体看动图 保存到 Effects 目录 ![img](./img/hiteffect.gif) 给蝙蝠上一套类似角色的状态 如果重复使用检测区域那么新建场景 area2D 重命名 PlayerDetectionZone 保存到 Enemies 目录中 添加子结点 CollisionShape2D 增加信号 body_entered 和 body_exited 层次设置 layer 空 , Mask 面对 player 这样它就和player 碰撞 在 Bat 场景中将 PlayerDetectionZone 引入 右键 子节点可编辑 选择 CollisionShape 的形状为圆形 ## 17 玩家状态和蝙蝠攻击问题 主菜单 场景 新建继承场景 继承自 stats 重命名 PlayerStats 保存到Player 目录下 主菜单 点击项目 项目设置 选择 自动加载 选择 PlayerStats 添加 player 引入 hurtbox 子结点编辑 设置玩家伤害范围 ![img](./img/player_hurtbox.png) 连接信号 area_entered() 给蝙蝠引入一个 hitbox 同样设置攻击区域覆盖 bat 设置层 Mask 在 PlayerHurtBox Hurtbox 设置无敌时间 以解决蝙蝠一直与玩家重叠,没有后续伤害 ## 18 玩家生命值UI 添加一些树 1 World Control子结点 重命名 HealthUI,该节点下 添加 Label 标签 HealthUI 另存为场景 且保存到 UI 目录 下 HealthUI 场景添加脚本 HealthUI 添加子节点 TextureRect 重命名 HealtUIEmpty 拖动static/UI/HealthUIEmpty 到 Texture 修改Stretch 为 tile 关闭网格吸附, 拉到4个心 的范围 HealthUI 添加子节点 TextureRect 重命名 HealthUIFull 拖动static/UI/HealthUIFull 到 Texture 初始化时候 health 还未设置 max_health 值,就被比较报错,应该设置 掉血时候只剩一滴 不消失, 勾选 TextureRect 下面的 Expand 启用 2 弄个场景树 新建场景 添加 StaticBody 重命名 Tree 添加子节点 Sprite, 拖动 static/World/Tree.png 添加子节点 CollisionShape2D 形状用胶囊形状 给树添加阴影, 添加子节点Sprite, 导入 static/Shadows/LargeShadow.png 保存到World目录下 World 场景下YSort 再添加一个YSort 重命名为Trees 然后给Bush Player添加阴影 于tree 操作步骤相同 调整血条位置 ## 19 小怪软碰撞 新建场景 根节点 Area2D 重命名 SoftCollision 添加子节点 CollisionShape2D 添加脚本 保存在 HitHurt (原视屏该文件夹为 Overlap ) 作用是检测一个东西然后提供一个向量将物体推走 增加新的层次 SoftCollision 主菜单 项目 项目设置,搜索 layer , 2d Physics 将场景 SoftCollision 设置Mask 在 SoftCollision 层 将它引入 Bat 编辑子节点,设置形状 另外还可以打开监视器查看那个方法调用耗时最多 ![img](./img/monitor.png) ## 20 镜头 镜头始终对准player 在World 下 给player 添加一个Camera2d 结点 勾选Current 启用 边框加粗了 ![img](./img/camera.gif) 血条没有跟随 在World 场景下添加 CanvasLayer 把血条设置为它的子节点 ![img](./img/camera_heart.gif) 镜头有点抖动, 点击 Camera2D 结点 启用 Smoothing 可以打开 项目设置 找到 Rendering 下的 Quality 打开像素吸附 Use Pixel Snap,这样镜头完全对准每个像素点 ![img](./img/use_pixel_snap.png) 我的没找到![img](img/dog.png) 主角死亡, 镜头会重置 解决办法,移出镜头, 给主角添加一个RemoteTransform2D 一个路径,选择路径 指定为 镜头, 角色死在哪里,镜头就停在哪里 ## 21 蝙蝠游荡巡逻状态 Bat场景下添加 Node2D WanderController 保存为场景 ,保存到Enemies 下 WanderController 场景添加 子节点 Timer , 添加脚本 Timer one shot 勾选 Autostart 勾选 代码 ## 22 为玩家和小怪增加闪烁和声音效果 点击Player 场景 添加子结点 AudioStreamPlayer , AudioStreamPlayer2D 与距离有关的声音 点击 AnimationPlayer 对AttackDown 添加轨道 声音播放轨道 选择 AudioStreamPlayer 找到音轨插入关键帧,然后为它分配需要播放的声音, 点击关键帧,拖动static/Music_and_Sounds/Swipe.wav ![img](./img/audio_left.gif) 同理给其他动画也添加动画 点击AnemyDeathEffect 场景 添加子场景 AudioStreamPlayer 直接拖动 EnemyDie.wav 到 Stream 点击 Autoplay 开关 同样点击 HitEffect场景 游戏角色死亡时候音效停止 新建场景 根节点 AudioStreamPlayer 重命名 PlayerHurtSound 保存到Player 中 添加脚本 连接 finished() 信号 Player 场景下 添加 AnimationPlayer 重命名 BlinkAnimationPlayer 添加动画Start 0.2秒 Stop 0.1 秒 Sprite 点击 Material 新建 ShaderMaterial 点击 Shader 新建 Shader , 点击Shader 新建脚本,编辑脚本并保存 改代码比较多,对比代码看, 还有新的材料设置 ## 23 章 无敌bug 修复 镜头限制 两个蝙蝠攻击时候,无敌对所有蝙蝠有效 1. 镜头limit 是一个超大数值, 设置小一点 2. 使用代码限制