[muzzik 分享]:优化单脚本开发流程

看到了这个帖子里,很多人都不喜欢组件式开发的流程,最主要的原因有

1.审查代码不便

由于各种引用预制体,或者直接通过路径加载预制体,导致审查代码需要

  1. 搜索预制体
  2. 打开预制体
  3. 跳转脚本

而使用纯代码方式可以直接跳转到另一个模块脚本内,只需要一步

两全其美的办法

// 模块代码

@ccclass('test')
export default class test extends Component {
    static open(config?: 可选初始化配置) {
        // ... 在这里加载模块的预制体
    }
}

// 其他模块使用,这样可以直接跳转

@ccclass('main')
export default class main extends Component {
    click() {
        test.open();
   }
}

2. UUID 变更导致各种 miss

在 2.x 的时候我也经常遇到这个问题,但是 3.x 没有遇到一次,不知道各位的问题场景是什么?

不是不喜欢组件式开发,而是不喜欢编辑器封装好的各种编辑操作导致的黑箱。
比如拖拽绑定节点,拖拽绑定点击事件,这种本质上都是让原本需要写代码的地方简化成非代码操作。
但是带来了一个严重的问题,那就是这种编辑操作,程序员无法在代码中搜索到。
一个函数,无法知道它被哪些地方引用了,这就等于回到了没有代码编辑器智能提示的年代,甚至更糟糕,没有智能提示还能靠全局搜索找到,编辑器操作的索引绑定都在预制体中,这个查找难度暴增。

1赞

感觉项目复杂了依旧不好用,而且要做的事情变多了

和组件式开发无关的,在编辑器之外写代码,也可以手搓一个组件式的Entity-Component结构,用一个ViewObjectComponent去引用一个prefab都可以。
前面那个帖子,A方案的最大的问题就是,编辑器内的关联是文本上不可见的。用过依赖注入的就知道,实际上就是一种依赖注入,但是是用编辑器可视化注入的。一旦关联不是文本而是依赖于资源或者可视化编辑工具,就很不好管理了。
只要不可视化关联,那随便你用什么架构都可以。从OOP到纯ECS,中间各种结构随便用。但可视化关联就绑定你一定要用这个结构了。我觉得是Unity起了一个坏头,明明以前的引擎编辑器基本上都是纯粹资源编辑的。
在调试中也一样,你在调试某个挂场景上的组件或者场景绑定事件的时候,是不是不知道它从哪里生成从哪里调用过来的?因为上层的堆栈就是引擎自己调度的堆栈,而不是我们代码逻辑的堆栈了。会很难调试。

这个方法是排除工具脚本之外的组件对编辑器的引用,简单说就是在代码里增加引用,保留开发的速度,又不用每个模块都跳转去编辑器看

我知道你说的意思,因为这种其实比较常用。
例如:

public class Enemy : MonoBehavior 
{
      public static Enemy createEnemyByID(int id) 
      {
               // 创建 ID 下的prefab,拿到最上层那个Enemy组件,然后返回出去。
      }
}

Enemy e = Enemy.createEnemyByID(123)

就是让一个组件+prefab的结构,通过工厂创建返回,在代码上看上去是一个主要的对象类。
这种在某些情况下比较有用,特别是例如godot的gdscript不好写组件之外的代码的情况下。比较好。
我本身不太喜欢这个方法,因为Enemy这个类会带上很多Monobehavior的接口,污染了逻辑封装。但可以用。

所以我说的是单脚本,可以挂载在预制体,又可以通过代码引用,保证了开发速度和代码审查速度,唯一大的缺点是视图和逻辑耦合

其他缺点是工具组件如果不在代码内,那么只能通过编辑器查找引用,但是很少去改工具组件的 property 属性,所以我就忽略了这个问题

  1. 那个帖子里讨论的不是要不要组件式,而是要不要拖曳的问题;
  2. 当然,拖曳的方式更适配组件式,如果反过来将组件式更适合拖曳则就是那个帖子里的讨论的问题了;
  3. 你的这个方案,有个问题就是,代码都需要提前引用,无法做到界面与代码同时加载同时销毁的方案,比如在main中还未使用test弹框就引用了test脚本。

这个有啥问题,我一直这样用没遇到什么问题,就导入一个 class 而已,而且本来就是为了增加引用才这样做

不然组件式开发你直接在外部模块 用路径或者 property 来加载新的模块,那就是套娃,就是很多人说的问题

代码过多,需要分包的时候~
或者是游戏集合

其实这个方法在我这边最大的缺点就是,逻辑对象接口被组件接口污染,因为已经认为Enemy是一个逻辑对象了,那么这个逻辑对象的方法应该完全匹配逻辑。例如enemy.StartCoroutine(xxx) 这个接口就是污染的。
所以我一般做Actor或者这类继承结构的时候,不会继承组件。内部自己可能分自己写的组件而不会使用引擎的这一套组件结构。
对应于界面的也是,例如TestView 是一个界面,那TestView.addComponent 是在界面上加组件吗?也是污染的接口。照理说界面有open/close正常,其他无关接口就会污染了。所以一般界面我也不会继承自Component,会内部加载prefab去使用。

接口污染咋看上去没什么,不调用就好了。但是多人的时候不能保证每个人都不调用。如果不控制好,经常会发现违反的情况,例如假设我们把Coroutine全控制在一个CoroutineManager里面,方便管理和自定义,但因为enemy有StartCouroutine函数,保不准哪个人就调用了,找也找不到。

不过其实关系不大,没要求那么严格就好。也算是一种方案。

没遇到过这个情况,经历浅薄,不过不能通过 Bundle 划分吗?我一直做的大厅子游戏,基本一个 Bundle 一个子游戏,各自管理