# Legendary Heroes **Repository Path**: OOO_admin/legendary-heroes ## Basic Information - **Project Name**: Legendary Heroes - **Description**: 一个Godot 制作的2D平台动作游戏 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-10-13 - **Last Updated**: 2023-11-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Godot4x 2D平台动作游戏 最后编写:`2023-11-25` `肚子还是好饿` 本项目用于记录与分享Godot 2D平台跳跃动作游戏的开发过程中学习到的东西 #### 参考资料: https://www.bilibili.com/video/BV1vh411N7eq/ #### 人物素材: https://brullov.itch.io/generic-char-asset #### 环境素材: https://anokolisa.itch.io/sidescroller-pixelart-sprites-asset-pack-forest-16x16 ## 项目设置及玩家角色逻辑部分 ### 项目开始的设置 在新建完项目之后我们需要设置一下游戏的分辨率以及其画面拉伸 > 项目 - 常规 - 窗口 先勾选高级设置,会有很多额外的设置选项提供 我们可以先设置 `窗口宽度覆盖` 和 `窗口高度覆盖` 这是实际运行窗口的大小 然后我们可以设置 `视口宽度` 和 `视口高度` 这是画布的大小 可以理解成Unity中的Canvas的Scale With Screen Size功能 设置 `拉伸模式` 为 `canvas_items` 画布内容会自动拉伸至窗口大小 最后可以设置 `渲染 - 画布纹理 - 默认纹理过滤` 为 `Nearest` 这样子图片的边缘不会自动补间模糊 而是实际的像素 会更适合像素游戏的开发 ### 脚本编辑设置 `编辑器设置 - 文本编辑器 - 补全 - 添加类型提示` 这样子GDS的函数就会自动补全其方法签名的类型和其返回值类型 除此之外在开发中,可以按住Ctrl将场景中的节点拖入到脚本中 代码将自动生成该节点的引用 ### 赋值 可以在赋值的时候把`=` 换成 `:=` 这样子会根据等号右边的表达式类型,自动推导左边变量的类型并执行赋值操作提高运行效率 不过具体提高多少并没有留意 ### Sprite2D 只显示图集区域 在`Region`中勾选`Enable` `编辑区域` 设置好`吸附模式`为`栅格吸附` 之后就可以让Sprite2D 只显示图集特定区域的内容了 ### 相机设置 我们可以在`Player`下创建一个`Camera` 以达到相机跟随的效果 勾选 `Drag - Horizaontal Enabled` 和 `Drag - Vertical Enabled` 可以让玩家在屏幕内一定范围移动而相机不进行跟随 > 调整 `Left Margin`,`Top Margin`,`Right Margin`,`Bottom Margin` 可以修改该范围大小 勾选 `Position Smoothing - Enabled` 可以使相机平滑位移 > 调整 `Speed` 可以修改平滑速度 设置 `Limit - Left,Top,Right,Bottom` 等参数 可以限制相机位移范围,以达到隐藏某些区域的效果 > 也可以勾选 Smoothed ,这样子相机在接近边缘时也会平滑减速 > 也可以用代码修改 ```Python camera_2d.limit_top = used.position.y * tile_size.y camera_2d.limit_left = used.position.x * tile_size.x camera_2d.limit_bottom = used.end.y * tile_size.y camera_2d.limit_right = used.end.x * tile_size.x camera_2d.reset_smoothing()' ``` 在修改完边框后可能会引起摄像机的移动,但如果设置了平滑运动的话 可以用 `camera_2d.reset_smoothing()` 来取消 ### TileMap的使用 当我们往场景中添加了`Tilemap`节点之后 在新建了`TileSet`后,我们可以点击`Physics Layers`新建碰撞层 比较有意思的是,像一些地面可以把两格当作一格来使用,但是要改锚点 ![Image text](https://gitee.com/OOO_admin/legendary-heroes/raw/master/NOTE/tilemap_1.png) ![Image text](https://gitee.com/OOO_admin/legendary-heroes/raw/master/NOTE/tilemap_2.png) ### 视差背景 在场景中新建节点 `ParallaxBackground` 随后在该节点下再创建多个 `ParallaxLayer` 节点,每个`Layer`就是一个分层 通过修改 `ParallaxLayer.Motion` 的 `Scale` 属性 就可以制作视差效果了 ### 运动控制 __coyote time 郊狼时刻__ 它的功能是指当角色离开平台一段时间仍然具有跳跃的能力。 这个词语我一开始是从某个开源插件中学来的,当时我并不能理解为什么翻译叫做郊狼时间 今天才从该UP主这知道原来是出自动画片中 ![Image text](https://gitee.com/OOO_admin/legendary-heroes/raw/master/NOTE/Coyote.png) ### 状态机 __StateMachine.gd__ `owner`是指该节点的父节点 可以通过下列函数的方式实现属性访问器 ```Python var a:int = -1: set(v): a = v get: return a ``` 状态机其实也没啥好讲的,实现 状态转移函数 `transition_state(current_state,next_state)`, 转移输入函数 `get_next_state(current_state)`, 以及状态循环 `tick_physics(current_state,delta)` 即可 ### 滑墙功能 添加完滑墙动画`WALL_SLIDING`后,由于动画的朝向与`IDLE`,`RUNNING`,....等其他动画并不一致,这里选择了给`Sprite2D`新增了一个Node2D的父节点`Graphics` 通过控制`Graphics`的`scale.x`来实现反转 由于滑墙动画与移动动画并不在同一张图集,在我们添加完动画后,需要把`Sprite2D`的`texture`也添加进轨道中 但是由于`WALL_SLIDING`中修改了`texture`轨道,而其他动画并没有该轨道,从而出现图集引用错误的问题 解决方案也很简单,只需要给其他动画加上对应的轨道即可 这里用了视频制作者的插件 [godot-animation-resetter]("https://github.com/timothyqiu/godot-animation-resetter") 具体使用方法就不介绍了 节点方面比较有意思的是 `RayCast2D` 这个节点,只需要拖拽方向和调整位置 (比Unity的射线检测方便) 项目中使用了两个,一个用于检测头部前方是否有墙壁,另一个用于检测脚前方是否有墙壁,以便于在悬崖、平台的角落或者不满足高度的悬浮物时,玩家不会触发滑墙动画 接着在脚本中调用下列代码即可 ```Python RayCast2D.is_colliding() ``` 除此之外还有 用于检测是否接触地面的函数 ```Python is_on_floor() ``` 和 用于检测是否接触墙面的函数 ```Python is_on_wall() ``` ### 蹬墙跳 新增枚举`WALL_JUMP` 判断在`WALL_SLIDING`状态下`按下跳跃键`就可以转换至`WALL_JUMP` 其实没特别好讲的,不过在教学视频中,如果我先按下了方向键再按跳跃键则会因为离开了墙面无法蹬墙跳,而且这一切发生太快辣!(手笨) 于是为了优化手感,我像`coyote time`一样增加了一个`sliding time`在玩家离开墙面一段时间内依然可以触发蹬墙跳 同时,在教学视频中UP主并没有在`WALL_SLIDING`时减缓下落速度。 这会导致如果玩家从高处落下过快,即便抓住墙壁了也无法减速或者说有足够的时间再跳起来 于是我在进入`WALL_SLIDING`时将`velocity.y`减少一半 #### 比较有意思的地方 这一节UP主展示了一个调试方式 ```python print("[%s] %s => %s" % [ Engine.get_physics_frames(), State.keys()[from] if from != -1 else '', State.keys()[to] ]) ``` 这句代码是获取当前帧的编号 `Engine.get_physics_frames()` 也可以使用这句代码来控制游戏速度,方便我们观察动作和细节 `Engine.time_scale = 0.3` ## 战斗部分 ### 第一只怪物 野猪Boar 视频UP主先是创建了一个新场景以及`CharacterBody2D`节点,并改名`Enemy` 随后在添加完`Graphics`、`CollisionShape`、`AnimationPlayer`和`StateMachine`后就完成了预制体模板了,不过Godot中所有的一切都是节点 之后为`Enemy`添加脚本`Enemy.gd`,编写基类代码 在GDScript中,使用`extends TypeName`来表示继承于某类 ```Python @export var direction := Direction.LEFT: set(v): direction = v if not is_node_ready(): await ready graphics.scale.x = -direction @onready var graphics: Node2D = $Graphics ``` #### 一个小细节 >由于`@export`是在创建节点时发生,而`@onready`是在将节点放入场景中发生,因此会发生空指针异常。为了解决该问题,在set中添加了一个异步用于等待节点完成添加 > >`@export` 类比的话相当于Unity中的Awake > >`@onready`类比的话相当于Unity中的Start 之后新建场景,并实例化子场景,选择刚刚创建的`Enemy`并改名为`Boar`,随后完成对应的帧动画,这样子我们就用这个模板弄了一个新东西出来了 视频UP主这里选择的是右键Root节点并选择扩展脚本,随后开始编写代码。 但其实个人觉得每次新建一个脚本后在里面一个个实现`get_next_state` `tick_physics` `transition_state`函数是一个很繁琐的过程,虽然可以直接去`player.gd`中复制。 于是我去官网查了一下如何创建模板代码,比Unity的要稍微简单点, 创建`res://script_templates/Node/new_enemy.gd`文件夹以及脚本 在脚本中编写 ```Python extends Enemy enum State{ IDLE, } func get_next_state(state:State) -> State: return state func tick_physics(state:State, delta:float) -> void : pass func transition_state(from:State, to:State) -> void: pass ``` 这样子右键节点添加脚本的时候 模板中就多了一个`Node:New Enemy`了 ##### 使用 RefCounted 来传递引用类型数据 `RefCounted`并不是一个节点,而是用于引用类型传递的父级 在`Script`界面下点击`文件 - 新建脚本 - 继承`中可以找到,由于它不是节点,所以无法在场景中创建子节点来添加 用于`伤害数据`,`交易数据`一类的无实体对象数据就非常好用 ##### 使用 方法调用轨道 来执行怪物死亡函数 在动画机中`添加轨道`,选择挂在了脚本的节点后并右键点击`插入关键帧`,即可在该位置关联对应的函数. 这点和`Unity的帧事件`没特别大区别 ##### 优化 状态机 上个提交中 `StateMachine.gd`中,`_process`函数是 ```python if current_state == next: break ``` 这会导致无法重新进入同一个状态 于是添加一个新的标识变量,`KEEP_CURRENT = -1` ```python if next == KEEP_CURRENT: break ``` 用于允许重新进入同一个状态,具体实现可以看代码 ##### 重新加载场景 可以通过以下这句代码重新加载场景 ```python get_tree().reload_current_scene() ``` ### 补间动画 通过如下代码创建一个补间效果 ```python //创建补间动画,补间某个属性 //create_tween().tween_property(要补间的节点,该节点的属性名,目标值,动画时长) //如: create_tween().tween_property(eased_health_bar,"value",percentage,0.3) ``` 这段代码的作用是,在0.3秒内将`eased_health_bar`的`value`补间到`percentage` ### 关于设置Textures时比较容易踩到的坑 制作EnergyBar时,在`Textures`中创建了`AtlasTexture`。然后我把素材直接拖在了`Over`槽上,而不是下面的`Atlas`槽中,然后我思索了半天为什么我会没有`编辑区域`这个按钮在那。