组件如何进行多继承实现?

假设我希望实现如下几个组件用于功能拼接。

  1. 开关
  2. 开关受体
  3. 出口触发器
  4. 障碍物

通常来说
一个出口控件,需要支持添加出口触发器,以及一个开关受体。

正常的开发思路我觉得应该是把两个控件的逻辑写在一起处理。

但是,实际上开关受体可以被放在其他逻辑上,比如一个炮台是否被激活。
而如果拆开,这会导致没法在一个组件里去同时控制两者的逻辑。
而,如果能够使用多继承,让我的这个出口同时可以继承开关受体以及出口触发器,这其实就完全能做。

所以说,creator有没有办法可以进行多继承的实现?
或者说,大佬们通常是怎么去实现这种逻辑的呢?

为什么是继承而不是组合

建议用组合,多个组件,然后多个组件如果牵涉到交互,就单独再做个独立的controller来。
就例如你这个例子:出口控件 = 出口触发器组件 + 开关受体组件 + 出口控件Controller组件
其中前2个控件如果需要交互逻辑,就提到这个controller里面。

另外还有一种方式,叫mixin,你可以查一下,类似多继承,你把每个组件做成mixin的形式
具有多个组件功能的类 class AwithBwithC_Component extends A<B<C< Component >>>> (大概的意思,ts不知道是不是这样写)。
https://www.typescriptlang.org/docs/handbook/mixins.html

但我还是建议用组合比较方便。这种继承的组件自身的代码,其实也是起那个controller的粘合作用,还不如就单独controller方便点。

关于设计上,其实cocos和unity这种白板GameObject,确实缺少了一个好地方放组件粘合代码,UE的Actor其实是一个比较好的设计,Actor本身有类,也可以放ActorComponent组件,粘合的代码在Actor继承结构里面放就可以了。

想了解一下,“组合”这个概念是什么意思?才接触creator,对这个没有概念。

是指我提的那个,把开关受体,出口触发器,做成两个独立的组件放到一个节点里的意思吗?

class A extend component{}
class B extend A {}
这样能解决你的问题吗

就是类似现在cocos组件化的方式,各个组件是通过addComponent的方式,就能提供不同的功能

这个不能,因为两者不是互相继承的关系,是并列的关系。

你可以试试定义多个接口
interface IA{}
interface IB{}
然后分别写一个实现类
class A implements IA{}
class B implements IB{}
然后写一个AB类,持有两个接口实现对象_a和_b,
class AB implements IA, IB {_a: IA,_b: IB}
AB自己对A 接口的实现交给_a,对 B 接口的实现交给_b,是不是相当于实现了多继承,和多继承比,麻烦地方在于要实现接口,实现内容其实就是转交任务,但是也有优点,不想继承的东西可以完全不带了

合成复用原则(Composite Reuse Principle,CRP)是面向对象设计的基本原则之一,它强调优先使用对象组合(has-a),而不是通过继承(is-a)来扩展系统的功能。这个原则有助于降低系统间的耦合度,使得系统更灵活、更易于维护和扩展。

合成复用原则的主要思想包括:

  1. 优先使用组合 :当需要复用某个类的功能时,首先考虑使用组合的方式,即在一个新的类中包含需要复用的类的对象。这种方式可以将已有类的功能集成到新的类中,同时保持新类与已有类的松耦合。
  2. 谨慎使用继承 :继承虽然可以实现代码复用,但过度使用继承可能导致系统结构变得复杂、难以维护。因此,在使用继承时应该谨慎,确保子类与父类之间确实存在“is-a”的关系。
  3. 减少依赖 :通过组合和继承来复用代码时,应尽量减少类之间的依赖关系。这有助于降低系统的耦合度,提高系统的可维护性和可扩展性。

遵循合成复用原则的好处包括:

  • 降低耦合度 :通过组合方式复用代码可以减少类之间的直接依赖关系,降低系统的耦合度。
  • 提高灵活性 :组合方式允许在运行时动态地改变对象的行为,而继承方式则相对固定。因此,使用组合方式可以提高系统的灵活性。
  • 易于维护 :由于降低了耦合度,当某个类的实现发生变化时,对其他类的影响较小,从而降低了维护成本。

总之,合成复用原则鼓励我们在设计面向对象系统时优先考虑使用对象组合来实现代码复用和扩展功能,而不是过度依赖继承。这有助于我们创建出更加灵活、可维护和可扩展的系统。

2赞

多年前一个大神给我讲的例子送给你:
假如你在开发一个游戏,基类有道具、怪物、宝箱、树等等,现在策划要你新增一个“树妖”,你是让它继承“树”还是继承“妖怪”?
游戏行业是一个充满想象力的行业,它的业务更灵活多变,所以请你多多考虑使用组合。
关于组合的基本概念,你可以这样理解:
经典的class Fish extends Animal和class Dog extends Animal各自实现Move方法
变成了Fish.swimComponent.swim和Dog.runComponent.run
因此,我们关注的重点从“从下向上抽象”变成了“从内向外抽象”

这树妖看笑了,,

我以前看饥荒脚本代码时,看到过里面的战斗、血量分成了两个组件。
这给我一个启发:
如果你设计了一个游戏,定义了一个类:生物
然后继承这个生物类,写出了各种生物类。
后来突然要你为一些非生物加入血量机制,这些非生物存在血量,能被攻击,掉血到0会毁坏,具有自己的减伤参数等等。
这时,你会发现,血量相关的这部分内容,和你的生物类里的一大部分内容是完全一致的。
但是这个非生物它只能挨打,不能动,也不会发出攻击。
这就是只用继承的缺陷,有时会出现a继承了b的一部分,又继承了c的一部分之类的混乱情况。
但是如果用组件,打不了将组件继续拆分一下。逻辑上就清晰多了。
现在引擎流行的这种实体-组件架构,就是为了遵循组合大于继承的原则,可以让代码结构更加清晰明了

如果学过 rust,就不会想要多继承了,rust 甚至连继承的概念都没有。

已经在用MVC+ECS做了。有人会说ECS在cocos中的性能没啥作用。但是就一个组合的理念,就值得去做。

是的当时把我问蒙了,然后他给我讲了unity的Component

组合。addComponent。比如一个游戏人物。有跑 跳 射击 。那么组件就是 runComponent,jumpCompoent,shotCompoent。这样就可以灵活组合多种功能。不需要的直接remove或者active==false;

2赞

多继承在游戏里面就是屎山代码

请教下,像这个swimCom.swim和runCom.run组件里都是实现什么东西,坐标的移动吗。
那如果有一种fish2,它的swim是另一种方式的那怎么区分实现,新加一个swim2Com吗

这四个都继承Component就可以了吧,需要的node上add下

大家都说组合,我说一个纯组合存在的问题,内聚组件的功能粘合代码。上面帖子也说过了,不过看大家都没有聊到就再提出来一下讨论。
就拿Player来说吧,有Health,Transform,Battle 等组件组合,一般来说,每个组件都会完成独立的功能,并不相互依赖,但实际业务中,往往需要多个组件配合的粘合代码,所以一般都有额外一个粘合组件,可以直接叫做Player或者PlayerController,承担的功能是,粘合其他组件成为业务逻辑,并且提供该对象对外的复合接口。这个组件其实并不属于通用组件,而是属于特殊对象的组件,往往不能用于其他类型。在这个情况下,这些代码写成组件意义并不大。
这种空Entity或者空GameObject,纯组合Component的设计,在逻辑框架层面上并不全面。所以我推荐UE的Actor,有ActorComponents进行组合,但Actor本身又是继承的类,本身可以加入粘合代码。