【Godot4.2】Godot中的继承与组合

server/2024/9/25 8:30:35/

概述

继承组合是编程中常用的两种策略,旨在尽可能多地重用代码。继承应用得非常广泛,但我认为组合在很多场景下会更加合适一些。
基于组合,游戏开发前辈们专门设计出了实体组件模式(EC模式)和进阶的ECS模式。本篇所提及的Godot中的“组合”可能更倾向于UML类图中描述类与类关系的“组合”,而并不完全是EC模式或ECS模式所提到的严格的形式,尤其是在Godot中,更是以一种父子节点的关系来实现。
但是这对理解什么是“组合”和“继承”,以及两者的区别和优劣,并为进一步学习ECS模式提供基础。

参考视频

在笔者搜寻已有教程资料的过程中,发现B站这个搬运视频是最简单和容易理解的。
【中文语音】如何在 Godot 4 中轻松简化代码_哔哩哔哩_bilibili
所以我就在扒字幕的基础上,进行了扩充和修改,让其变成了一篇更容易阅读和理解组合模式的文章。建议在看原视频的基础上,将本文当做是一个笔记和总结来看。希望对Godoter们的学习发挥一点小小的作用。

继承

假设我们有这样一个设定:

  • 我们有一个玩家类Player,它能够攻击,有生命值,并且有一个HitBox(攻击范围盒),同时还能处理用户输入。
  • 相应地,我们也有一个敌人类Enemy,它同样能够攻击,拥有生命值和HitBox,以及一些敌人特有的东西,比如AI。

在这里插入图片描述
如果我们使用继承的思想和原则,就可以将Player类和Enemy类中重复和相同的部分helth属性、attack()方法和hitbox属性提取出来,放到一个单独的类Entity中,并让Player类和Enemy继承Entity

在这里插入图片描述

这样做的好处是:

  • 敌人和玩家的代码得到简化
  • 而且将重复性的内容放到单独的类,可以提高代码的可重用性
  • 除了敌人和玩家,我们可以基于Entity创建其他具有类似特征(属性)和行为(方法)的类
  • 修改Entity类,会自动将修改传递到子类

继承的缺陷

如果故事到这里就结束,继承将是完美的解决方案。因为这一切看起来十分自然,也很容易理解。
但是如果让我们引入另一个类Tree来代表游戏中的树。
和玩家及敌人一样,树也可以受到伤害,也应该有生命值。

在这里插入图片描述

我们可以让Tree类也继承Entity,但是Tree继承Entityhealth属性的同时也继承attack()方法和hitbox属性,这意为着树拥有了发起攻击的能力。
除非你做的是魔幻类的游戏和类似指环王里的树人角色,否则作为一般意义的“树”是不应该具有攻击能力的。


注意
这里的Tree类名在Godot中肯定是不能使用的,因为Tree控件已经占用了这个类名。这里只做理解,实际中可以起其他名称。


可以发现单纯的抽离父类和通过继承实现子类的方法,会存在父类某些属性或方法子类用不上的情况。此时,就表明你的父类需要进行更细致的拆分,并增加继承的层级。而这就会增加继承的复杂度,并让修改变得牵一发而动全身,甚至引发不可控的错误。
在Godot中,还有一个更深层次的问题:

  • 玩家(Player类)和敌人(Enemy类)都继承CharacterBody2D
  • Tree则应该继承StaticBody2D
  • 在Godot中,你无法同时继承多个类,也就是“即是…又是…”。

在这里插入图片描述

通过上面的类图其实已经可以发现:

  • 玩家(Player类)和敌人(Enemy类)已经出现多重继承的问题,也就是既是Entity类的子类,又是CharacterBody2D的子类,而这在目前Godot4.2为止的GDScript中是无法实现的
  • 树(Tree类)也是相同的多重继承的问题,既是Entity类的子类,又是StaticBody2D的子类

如果我们将Entity设为CharacterBody2D的子类:

  • 则可以瞬间解决玩家(Player类)和敌人(Enemy类)的多重继承问题
  • 但是树(Tree类)的多重继承问题则继续存在,Tree不仅是Entity,同时还是StaticBody2D,而且还是CharacterBody2D,这在Godot中是无法实现的

在这里插入图片描述

此时,你或许会想到将Entity类拆分为StaticEntityCharacterEntity,分别用来代表不能自主移动的“静态实体”和可以自由移动的“角色实体”。
并让Tree继承StaticEntityPlayerEnemy继承CharacterEntity
并分别设计StaticEntityCharacterEntity的成员。

在这里插入图片描述

这样做的好处,确实是解决了多重继承的问题,但是肉眼可见的增加了类设计的难度,并且无法消除重复代码的问题。
这还只是引入了一个Tree类,如果你需要创建一个具有很多游戏实体类型的复杂游戏,则每添加一两个类,就要想方设法增加一些基类用于继承
这便是使用继承存在的问题。

组合

好在面向对象设计中,除了继承我们还可以使用其他的方式,而其中类的组合关系,可以解决一部分继承关系带来的问题。
我们换个角度思考一下我们面临的问题:我们只是需要将不同的特征(属性)和行为(方法)附加到不同的类上而已。而这就是“组合”思想。
我们将所需要添加的特征(属性)和行为(方法)称为是“组合”方法中的“组件”。
在UML类图中,用一条带实心菱形箭头的连线代表类之间的组合关系。
我们将之前继承关系改为组合关系:

  • 首先我们将需要添加到各个类的特征(属性)和行为(方法)单独封装为一个类,并以“xxxComponent”形式命名,比如:AttackComponentHealthComponentHitboxComponent
  • 接着我们使用组合关系,将这些组件按需求添加到需要它们的类之上

在这里插入图片描述

可以看到类的关系一下子清晰起来,数量也有所减少,而且每个组件都是可以重用于之后的类的。
而且因为组件不是某个游戏实体的子类,所以不受继承影响。实体本身是什么类型,组件也不关心。并且重复代码的问题也消失了。
而这就是“组合”的魅力。

在Godot中实现组合

在Godot中,2D游戏中的“组件”是用Node2D节点扩展而来的。它们通常作为某个“游戏实体”,比如PlayerCharacterBody2D类型)节点、EnemyCharacterBody2D类型)节点或TreeStaticBody2D类型)节点的子节点而添加和发挥作用。
image.png
作为组件,我们可以按需添加到任何游戏实体上,比如我们新增了一个代表子弹的类Bullet,则我们可以将AttackComponentHitboxComponent添加到Bullet上,这将让它自动拥有发动攻击攻击碰撞检测的能力。而为子弹设定血量是没有必要的,因为一般来说子弹与某些可被射中的游戏实体接触后,便会自动销毁。因此不需要给Bullet添加HealthComponent组件。

实际案例

extends Node2D
class_name HealthComponent
@export var MAX_HEALTH := 10.0var health:floatfunc _ready():health = MAX_HEALTHfunc damage(attack:Attack):health -= attack.attack_damageif health <=0:get_parent().queve_free()
extends Area2D
class_name HitboxComponent@export var health_component:HealthComponentfunc damage(attack:Attack):if health_component:health_component.damage(attack)

上面定义了两个简单的组件:HealthComponentHitboxComponent,我们便可以在任何需要相应功能的实体上添加组件,并调用其中的方法。
【原视频有使用组件的演示】

总结

  • 游戏开发中难免使用OOP面向对象的思想,也难免落入父类、子类、继承和扩展的惯性思维
  • 但是一味的使用继承,可能会导致设计的类越来越多,类与类之间的关系越来越复杂,最后导致可扩展性下降,并出现不可消除的冗余和重复代码
  • 我们可以使用组合思维,将游戏中的类分为“实体”和“组件”,实体可以根据游戏的复杂度不断增加,组件也可以按需开发和增加,而实体的功能由添加的组件定义,而且可以随时增删。
  • 组合”形式,大大降低了类之间的关系的复杂度,并避免了复杂继承关系,父类修改导致子类崩坏的可能性。

http://www.ppmy.cn/server/43491.html

相关文章

PyCharm基本配置内容

如何更换 Python 解释器 输入一段代码点击运行后&#xff0c;画面下方有一个路径如图中框中所示&#xff1a; 上面的路径为虚拟路径&#xff0c;可以改为我们自己设置的路径 点击设置&#xff0c;选择settings 选择Project&#xff1a;y002———》Python Interpreter&#…

3D应用开发工具HOOPS如何优化复杂3D大模型的实时渲染和交互?

在现代工程设计、仿真和可视化领域&#xff0c;处理和交互大型三维模型&#xff08;3D Models&#xff09;的需求日益增加。随着模型复杂度和数据量的增长&#xff0c;如何有效地管理和实时操作这些模型成为了一个关键挑战。HOOPS技术以其卓越的速度和效率&#xff0c;成为了应…

ChAMP加载肺癌数据和分析甲基化数据流程

ChAMP加载肺癌数据和分析甲基化数据流程 1. 加载数据分析 #!/bin/evn R rm(list = ls()) library(rstudioapi) current_script_path <- getActiveDocumentContext()$path# 将路径转换为当前目录 current_directory <- dirname(current_script_path)# 设置当前工作目录 …

java spring cloud 企业工程管理系统源码+二次开发+定制化服务 em

在建筑行业中&#xff0c;工程项目管理软件&#xff08;工程项目管理系统&#xff09;扮演着至关重要的角色&#xff0c;它为建设工程项目管理提供了全方位、全过程的综合管理支持。从项目组织建设、策划决策、规划设计&#xff0c;到施工建设、竣工交付、总结评估&#xff0c;…

EventSource

什么是EventSource EventSource 是一个用于服务器推送事件&#xff08;Server-Sent Events, SSE&#xff09;的接口&#xff0c;它允许服务器推送实时更新到浏览器。与 WebSocket 不同&#xff0c;SSE 是单向的&#xff08;服务器到客户端&#xff09;&#xff0c;适用于更新频…

【Pandas】深入解析`pd.read_pickle()`函数

【Pandas】深入解析pd.read_pickle()函数 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&…

慢性乙型肝炎肝脏剪切波弹性成像的深度学习放射学显著改善了肝纤维化的诊断性能 | 文献速递-深度学习结合影像组学

慢性乙型肝炎肝脏剪切波弹性成像的深度学习放射学显著改善了肝纤维化的诊断性能 | 文献速递-深度学习结合影像组学 麦田医学 美好事物中转站 2024-05-21 11:03 Title 题目 Deep learning Radiomics of shear wave elastography significantly improved diagnostic performa…

时空AI软件:地理信息与遥感领域的智慧引擎

在地理信息与遥感技术的广阔疆域&#xff0c;时空AI软件如同一颗璀璨新星&#xff0c;将时空信息与智能深度融合&#xff0c;驱动着地理信息分析、决策支持、环境监测、城市规划等领域的深刻变革。本文将深入剖析其技术核心、应用实例、未来趋势&#xff0c;探索时空AI软件如何…