Newer
Older
DungeonShooting / DungeonShooting_Document / 项目帮助文档.md

前言: 该文档仅针对`DungeonShooting_Godot`目录下的Godot工程

---
## 1.启动项目
**Godot版本:** Godot4x   
**.net版本:** .net6.0  
使用Godot打开`project.godot`, 如果是第一次打开项目会弹出一个找不到资源的提示, 这是因为项目没有编译过, 点击Godot右上角`build`, 然后打`开项目设置`, 在`插件`这一个页签下启用`DungeonShooting_plugin`这个插件, 然后项目就可以正常运行了

---
## 2.项目资源

### 2.1.目录结构
所有资源严格划分类别, 并放入指定的文件夹  
**项目目录结构如下:**
* ./addons: 项目插件目录  
* ./prefab: 预制体资源目录  
* ./resource 美术,音乐,配置文件等资源放置路径  
* ./scene 场景资源  
* ./src 代码资源

### 2.2.脚本获取资源
为了方便代码获取资源以及排除代码中引用丢失资源的情况, 项目中使用`ResourcePath`类来放置所有资源路径, 该类常量值即代表资源路径, 使用`ResourceManager.Load()`来加载资源  
举个例子, 某资源在编辑器中的路径为: 
```text
res://resource/theme/mainTheme.tres
```
那么在`ResourcePath`中的代码就为:
```csharp
public const string resource_theme_mainTheme_tres = "res://resource/theme/mainTheme.tres";
```
加载该资源的代码为:
```csharp
var resource = ResourceManager.Load<Theme>(ResourcePath.resource_theme_mainTheme_tres);
```

### 2.3.重新生成`ResourcePath`
如果项目中有资源变动, 则可以使用`Tools`页签下的`重新生成ResourcePath.cs文件`  
![](文档资源/image_6.png)

---
## 3.游戏框架
### 3.1.简述
游戏框架分为三部分:
1. 游戏核心系统
2. UI模块系统
3. 代码生成系统

**游戏核心系统**: 以游戏玩法为中心的逻辑代码, 包括玩家, 敌人, 武器, 被动, 道具, 地牢生成, 房间规则, 存档逻辑等  
**UI模块系统**: 用户操作界面的逻辑代码  
**代码生成系统**: 自动生成便于开发的资源的逻辑代码, 包括生成UI模板, 生成地牢模板, 生成代码等  

### 3.2.游戏核心系统

#### 3.2.1.什么是`ActivityObject`
定义: 游戏内所有可活动物体的基类叫做`ActivityObject`  

`ActivityObject`的意由来: 为了方便统一管理物体, 并且减少子类代码沉积, 因此将所有活动物体都需要用到的逻辑抽到一个统一的类中, 并命名为`ActivityObject`, 所有的活动物体都需要继承该类   

`ActivityObject`提供的基础功能:
* `Component`组件管理
* 协程功能
* 外力控制运动
* 纵轴运动模拟 (自由落体, 投抛物体等)
* 数据标记
* 对象归属区域
* 互动逻辑接口

通过下面这张图可以了解游戏中的物体与`ActivityObject`的关系 (注意: 该图为早期开发版本的继承关系图, 后面开发可能会有修改)  
![](文档资源/2023-03-26_030144.png)

#### 3.2.2.什么是`Activity模板场景`
定义: `Activity模板场景`是指可以可以被实例化出`ActivityObject`对象的场景, 但是场景根节点必须是`ActivityObjectTemplate`节点  

上面定义看起来有矛盾: `ActivityObjectTemplate`没有继承`ActivityObject`, 为啥以它为根节点的场景却能实例化出`ActivityObject`?  
这就得提到一个概念: **场景与脚本分离**, 顾名思义, 场景中的节点与`ActivityObject`的脚本是完全分离的, 场景中的节点并没有挂载`ActivityObject`脚本, 在编辑器中它们是两互不干涉的.  
游戏运行中, 如果需要实例化`ActivityObject`, 那么就先需要在`ActivityObject`脚本代码中指定该物体的模板场景, 实例化过程中游戏会先实例化出模板场景, 再用`ActivityObject`的实例顶替掉模板场景的根节点, 因此就能打到最终的效果.
为什么要这么做? 原因很简单, 因为我们的游戏是一个Roguelite游戏, 因此游戏中肯定会有大量的武器道具和敌人来填充内容, 但是总会有类似功能或者类似场景结构的物体, 这样就没有必要每一个物体都新建一个单独的场景, 而是让功能让这些类似功能或者结构的物体使用同一个场景, 但为了因对有不同行为逻辑的物体, 我们就设计了一套**场景与脚本分离**的设计模式来因对上述情况  
总结: `Activity模板场景`是不挂载逻辑脚本的, 但是`ActivityObject`必须包含使用的模板场景, 并由统一的Api来实例化`ActivityObject`对象, 至于`ActivityObject`如何绑定模板场景, 请看: 3.2.3.如何创建一个`ActivityObject`

通过下面这张图可以更好的立即`Activity模板场景`和`ActivityObject`的关系  
(缺张图...)

#### 3.2.3.如何创建`ActivityObject`
这里的创建分为两步:  
##### 第一步, 创建模板场景: 
创建一个空场景, 并且添加`ActivityObjectTemplate`节点  
![](文档资源/image_4.png)  
创建完成后编辑器会自动创建必要的子节点  
![](文档资源/image_5.png)  
此时就可以随意添加子节点和重命名更节点了, 最后记得保存到`./prefab`文件夹下  
**注意**: `ShadowSprite`,`AnimatedSprite`,`Collision`这三个节点不能改名, 但是可以修改属性和添加子节点

##### 第二步, 创建脚本:   
创建脚本放到在`./src/game`下, 脚本必须继直接或间接承`ActivityObject`, 并且需要在类上加`[RegisterActivity(id, path)]`标记用于注册对象, `物体id`必须唯一  
参考代码如下:  
```csharp
using Godot;

[RegisterActivity("物体唯一Id", "模板场景路径")]
public partial class YourActivity : ActivityObject
{
	
}
```
为了方便区分物体类型, 可以使用`ActivityIdPrefix`类中的常量来添加`id`前缀, 目前支持的类型如下:  
* Role: 角色
* Enemy: 敌人
* Weapon: 武器
* Bullet: 子弹
* Shell: 弹壳
* Other: 其他类型, 例如门, 箱子等
* Test: 测试物体

例如我们创建一个敌人, 那么`[RegisterActivity()]`就可以这么写:
```csharp
[RegisterActivity(ActivityIdPrefix.Enemy + "0001", ResourcePath.prefab_role_Enemy_tscn)]
```

##### 实例化`ActivityObject`
可通过`ActivityObject.Create(id)`创建物体, 这个`id`可以结合`ActivityIdPrefix`, 那么创建敌人最终可以这样写
```csharp
var enemy = ActivityObject.Create<Enemy>(ActivityIdPrefix.Enemy + "0001");
```

### 3.2.4.自定义`RegisterActivity`
某些情况下需要更改`RegisterActivity`的参数, 或者需要对实例化出来的`ActivityInstance`进行统一的操作, 那么就需要我们自己写一个子类来继承`RegisterActivity`.  
操作`ActivityInstance`需要重写:
```csharp
/// <summary>
/// 该函数在物体实例化后调用, 可用于一些自定义操作, 参数为实例对象
/// </summary>
public virtual void CustomHandler(ActivityObject instance)
{
}
```

例子: 注册武器, [RegisterWeapon.cs](../DungeonShooting_Godot/src/game/item/weapon/RegisterWeapon.cs)  
由于创建武器必须指定武器属性数据, 那么原来的`[RegisterActivity()]`就不适用了, `RegisterWeapon`重写了构造函数, 改变了初始化参数, 并且重写`CustomHandler()`, 对`ActivityInstance`进行初始化属性操作

### 3.2.5.`ActivityObject`常用功能