Unity有没有办法让GetComponent()调用脚本不依赖其具体的名字?
作者:卡卷网发布时间:2025-01-08 18:49浏览数量:71次评论数量:0次
你这个问题很初级,但是很常见,并且问题里透露了一个行业里常见的错误,尤其是培训班出来的孩子特别容易犯这个错误,所以我决定回答一下。
首先记住一个口诀
在oop里面的一个设计类的核心口诀是:
<>不同类要做同一件事情用接口,同一个类做同一件事情不同效果用托。>
这句话什么意思呢?就是在不同的类要在同一个流程里进行处理的时候,最好的选择是用接口,如你游戏中角色能挨打,建筑物也能同样的攻击方式打损坏,还有一些小物件如场景里一个棒球一样可以被攻击,被钝器攻击飞走,被斩击攻击变两半,这里角色(Character)、场景建筑(Construction)和小物件(Doodad)其实是不同的类,他们都需要能挨打,所以要一个ICaneAttacked接口,需要他们实现一个类似pulicxxxeAttacked(...)的函数。而如果都是角色(Character),A角色受到攻击的反馈与角色不同,那么Character这个类里就需要一个delegatexxxeAttacked(...),也就是Func<...,xxx>也许是ActioneAttacked,这取决于你游戏的具体设计,不同的角色的eAttacked的值不同,但是核心流程都是xxx?.eAttacked?.Invoke(...)??ooo这样的写法来做到。
当你记住这个口诀的时候,就可以正式进入你的这个问题了,你的这个问题其实具体下来有很多很多个问题:
抛开正确设计谈你这个需求(coder层出发看问题)
GetComponent可以拿到Intece
首先你要知道,Unity是可以GetComponent<omeIntece>()的,换而言之,到这里就可以解答你的问题,如果不负责的话。
你的设计(不考虑对错),完全可以是
只要确保他们都是派生自Monoehio的,这些Component就能在GetComponent<IeAttack>()中取到,你可以用xxxisEnemy1等方法来判断他们是什么玩意儿。
那假如你说我即使是Enemy1,他的2个实例要在受到伤害时不一样怎么办?如一般的Enemy1受到伤害就掉血,鲁(Enemy1)受到伤害不仅是2倍于damage参数的,还会当damage>10的时候尿裤子怎么办?可以改这个接口函数:
这个是用intece的做法,但实际上unity,或者确切地说是unity期望你使用的是组件模式,即一种数据驱动(data-driven)的设计模式,而非是oop的继承模式,所以这个intece就不太对劲了,itjustworks。
直接用Component是unity的期望
所以在使用unity的这个EC架构(也就是组件式)的时候,你最好先定义好什么叫“受到攻击”,然后将这个做一个Monoehio,换而言之,所有能受到攻击的Oject,都应该有这个Component:
也就是无论何物,他的受击是被这个Component所作的,有这个Component的GameOject会“”,没有的就不会——这才是标准的数据驱动(data-driven),而不是我们常听到的“填了表他就能工作”(这是excel驱动,或者好听点叫“数值驱动”,不是程序级别的data-driven)。
当然我们依然可能会发生这个eAttacked只适合于角色,是吧,虽然data-driven中已经没有角色概念了,但是我们脑袋里还有:“他是个角色,所以他有eAttacked”这个人脑的正确(但是对于计算机来说是错误的)归纳。所以你可能还会想,如果我是一个建筑物,我挨打不一样呢?这时候确实你得有另一个ConstructioneAttack:Monoehio,而unity对此提供的解决方案,就是这些XXeAttacked:Monoehio,omeIntece这个方案,也就是上面一段说过的那个。
正确的实现应该是怎样的?(programmer层出发看问题)
到了正的程序员级别了,就是要对业务进行正确的分析和归纳,而不再是根据需求打字儿了。那么你这里犯了什么错误,要如何纠正呢?
首先一个是对事物抽象的问题
在你的抽象里,分出了player,enemy1,enemy2,enemy3,你能告诉我他们【本质的】区别吗?注意,我把【本质的】标出来了,很多人嘴上都爱说“本质上”,实际上那都不是本质,正的本质,是对事物的抽象。
你可能会说:<>首先player和enemy肯定要分开吧,毕竟player受玩家控制>。<>这就是一个典型的抽象错误>,正确的抽象是怎样的?或者说这件事情的本质,他是这样的——
无论是你理解的player还是enemy,他们在游戏中都会收到一些指令,根据自己的能力(aility)来执行这些指令,如“从当前位置移动到哪儿”。无论你是玩家输入的指令,还是ai脚本运算结果,到了角色这一层,都是“给我去那里!”,而至于这个指令是不是有玩家作摇杆发出的,对于角色这个类来说,根本不关心。所以【本质上】对于角色来说——命令源头,不应该造成区分,也就是说,并不存在你理解的player和enemy的区别,这个区别是我们地球人的理解,也就是玩家看到的现象,<>其本质是业务逻辑分别“翻译了”来自手柄和ai脚本的指令,传递给了他的目标角色,所以绝对不能用“是否接受玩家作”来分类,这根本是一个对业务理解和抽象的错误>。
接着你还会对enemy做出解释:<>如enemy1是飞着走的,enemy2一边走一边拉屎,enemy3是个炮台,他不会走路,所以他们有一个父类是enemy,然后各自有不同的能力,再放到不同的子类里。其实这也是错误的>。
思考一个最简单的问题,这些不同的逻辑,由谁来执行?你可能说我每个mono自己执行自己对吧?这里又是大错特错的,因为如移动之类的能力,都是无法自我执行的,必须有一个Manager来执行,因为只有GameManager同时依赖了角色和地形(Map,或者更确切的硕士地图),你角色不能依赖于这个Map,很简单的道理,因为当你地图上还有别的角色会跟你碰撞的时候,按照你的理解是不是我map上得记录有什么角色在哪儿?因此Map依赖于Character,反之Character移动要判断阻挡,阻挡不仅是别的character还有map上的阻挡物对吧,那你Character又要依赖Map,是不是典型的耦合(互相依赖)?而实际上在这个业务中,或者说在一个游戏中,他可以有map没有Character,也可以有Character没有Map,或者两者皆有或皆无——因此Map和Character之间本身就不互相依赖,所以这里是个典型的依赖关系错误。
所以最终的,不管你是enemy几,都得被GameManager所,那么请问,我要怎么写enemy1的移动是飞着走,enemy2边走边拉呢?我应该这样吗?
所以正如我前面说的,你的每个角色都打算移动,所以他们都有一个delegateV3Move(...),这才是对的——<>这是一个函数式编程的思维方式,也是我的MF>(全球第一个正确抽象技能和游戏开发的框架,这里有一篇实际范例:
猴与花果山:用Unity一个极具扩展性的顶视角射击游戏战斗)<>的中心思想——就是当你在游戏开发中,发现一件事情需要枚举的时候,其实他需要的是一个函数作为值,MF诞生于2010年,正可以用于unity,是在C#3.0.net3.5引入lamda之后。>接下来举一个典型的例子:
如游戏中的,dota类游戏,如莫甘娜的q这种都算是,的弹道是怎样的?有很多菜鸟会分析,把弹道做成一个枚举,如:直线、曲线,最后弹道经过coder就变成了一个union。但是你有没有想过,如果有些弹道在你的枚举里没有你要怎么办?如现在我要一个先飞sin函数2秒,然后直线向回飞3秒(速度会根据命中过的人改变,如每打中一个人减速当前速度的10%)的弹道你怎么办?再加一个枚举?
所以当我们把思维逆转过来的时候,会发现,事实上移动这件事情的【本质】就是——在这一帧为找到他的坐标。是不是,那么这个本质是什么?返回值为坐标(V3或者tranorm)的一个函数,也就是它的值是Func<ullet,...,Tranorm>,即(ullet,....)=>Tranorm这个函数作为值(lamda支持)。
用这个思维你再去看角色,其实Character本当就是一个类,因为他在GameManager逻辑中,无论你是什么样的character,都是“一视同仁”的,都是一样的【处理流程】,只是<>【不同Character实例,在同一个流程中,可能会执行不同的东西】>对吧,还记得本篇开头我们那句口诀说了什么吗?所以无论你的player还是enemy几号,他们都应该是classCharacter,只是不同的实例值不同——再次强调函数式编程中,函数是第一公民,所以可以视作值和参数,lamda就是让传oop中允许函数作为值,所以允许匿名函数,就像inta=3,SomeMethod(a)==SomeMethod(3)一样:
你 发表评论:
欢迎