前言: 该文档仅针对DungeonShooting_Godot
目录下的Godot工程
Godot版本: Godot4x
.net版本: .net6.0
使用Godot打开project.godot
, 如果是第一次打开项目会弹出一个找不到资源的提示, 这是因为项目没有编译过, 点击Godot右上角build
, 然后打开项目设置
, 在插件
这一个页签下启用DungeonShooting_plugin
这个插件, 然后项目就可以正常运行了
所有资源严格划分类别, 并放入指定的文件夹
项目目录结构如下:
为了方便代码获取资源以及排除代码中引用丢失资源的情况, 项目中使用ResourcePath
类来放置所有资源路径, 该类常量值即代表资源路径, 使用ResourceManager.Load()
来加载资源
举个例子, 某资源在编辑器中的路径为:
res://resource/theme/mainTheme.tres
那么在ResourcePath
中的代码就为:
public const string resource_theme_mainTheme_tres = "res://resource/theme/mainTheme.tres";
加载该资源的代码为:
var resource = ResourceManager.Load<Theme>(ResourcePath.resource_theme_mainTheme_tres);
ResourcePath
如果项目中有资源变动, 则可以使用Tools
页签下的重新生成ResourcePath.cs文件
游戏框架分为三部分:
游戏核心系统: 以游戏玩法为中心的逻辑代码, 包括玩家, 敌人, 武器, 被动, 道具, 地牢生成, 房间规则, 存档逻辑等
UI模块系统: 用户操作界面的逻辑代码
代码生成系统: 自动生成便于开发的资源的逻辑代码, 包括生成UI模板, 生成地牢模板, 生成代码等
ActivityObject
定义: 游戏内所有可活动物体的基类叫做ActivityObject
ActivityObject
的意由来: 为了方便统一管理物体, 并且减少子类代码沉积, 因此将所有活动物体都需要用到的逻辑抽到一个统一的类中, 并命名为ActivityObject
, 所有的活动物体都需要继承该类
ActivityObject
提供的基础功能:
Component
组件管理通过下面这张图可以了解游戏中的物体与ActivityObject
的关系 (注意: 该图为早期开发版本的继承关系图, 后面开发可能会有修改)
Activity模板场景
定义: Activity模板场景
是指可以可以被实例化出ActivityObject
对象的场景, 但是场景根节点必须是ActivityObjectTemplate
节点
上面定义看起来有矛盾: ActivityObjectTemplate
没有继承ActivityObject
, 为啥以它为根节点的场景却能实例化出ActivityObject
?
这就得提到一个概念: 场景与脚本分离, 顾名思义, 场景中的节点与ActivityObject
的脚本是完全分离的, 场景中的节点并没有挂载ActivityObject
脚本, 在编辑器中它们是两互不干涉的.
游戏运行中, 如果需要实例化ActivityObject
, 那么就先需要在ActivityObject
脚本代码中指定该物体的模板场景, 实例化过程中游戏会先实例化出模板场景, 再用ActivityObject
的实例顶替掉模板场景的根节点, 因此就能打到最终的效果. 为什么要这么做? 原因很简单, 因为我们的游戏是一个Roguelite游戏, 因此游戏中肯定会有大量的武器道具和敌人来填充内容, 但是总会有类似功能或者类似场景结构的物体, 这样就没有必要每一个物体都新建一个单独的场景, 而是让功能让这些类似功能或者结构的物体使用同一个场景, 但为了因对有不同行为逻辑的物体, 我们就设计了一套场景与脚本分离的设计模式来因对上述情况
总结: Activity模板场景
是不挂载逻辑脚本的, 但是ActivityObject
必须包含使用的模板场景, 并由统一的Api来实例化ActivityObject
对象, 至于ActivityObject
如何绑定模板场景, 请看: 3.2.3.如何创建一个ActivityObject
通过下面这张图可以更好的立即Activity模板场景
和ActivityObject
的关系
(缺张图...)
ActivityObject
这里的创建分为两步:
创建一个空场景, 并且添加ActivityObjectTemplate
节点
创建完成后编辑器会自动创建必要的子节点
此时就可以随意添加子节点和重命名更节点了, 最后记得保存到./prefab
文件夹下
注意: ShadowSprite
,AnimatedSprite
,Collision
这三个节点不能改名, 但是可以修改属性和添加子节点
创建脚本放到在./src/game
下, 脚本必须继直接或间接承ActivityObject
, 并且需要在类上加[RegisterActivity(id, path)]
标记用于注册对象, 物体id
必须唯一
参考代码如下:
using Godot; [RegisterActivity("物体唯一Id", "模板场景路径")] public partial class YourActivity : ActivityObject { }
为了方便区分物体类型, 可以使用ActivityIdPrefix
类中的常量来添加id
前缀, 目前支持的类型如下:
例如我们创建一个敌人, 那么[RegisterActivity()]
就可以这么写:
[RegisterActivity(ActivityIdPrefix.Enemy + "0001", ResourcePath.prefab_role_Enemy_tscn)]
ActivityObject
可通过ActivityObject.Create(id)
创建物体, 这个id
可以结合ActivityIdPrefix
, 那么创建敌人最终可以这样写
var enemy = ActivityObject.Create<Enemy>(ActivityIdPrefix.Enemy + "0001");
RegisterActivity
某些情况下需要更改RegisterActivity
的参数, 或者需要对实例化出来的ActivityInstance
进行统一的操作, 那么就需要我们自己写一个子类来继承RegisterActivity
.
操作ActivityInstance
需要重写:
/// <summary> /// 该函数在物体实例化后调用, 可用于一些自定义操作, 参数为实例对象 /// </summary> public virtual void CustomHandler(ActivityObject instance) { }
例子: 注册武器, RegisterWeapon.cs
由于创建武器必须指定武器属性数据, 那么原来的[RegisterActivity()]
就不适用了, RegisterWeapon
重写了构造函数, 改变了初始化参数, 并且重写CustomHandler()
, 对ActivityInstance
进行初始化属性操作