unity小厂面试应届生一般会问什么问题?
unity小厂面试应届生一般会问什么问题? 如何设计一个可扩展的技能系统,以支持未来的新技能?答:可以使用策略模式,将技能抽象成一个接口,每个技能都实现这个接口,然后在游戏中动态加载技能的实现类。这样,当需要添加新技能时,只需要添加一个新的实现类即可。
对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大佬,欢迎你来交流学习。
一个可扩展的技能系统应该具备以下特点:
[*]技能数据和逻辑分离:技能数据应该存储在可配置的数据文件中,而技能的逻辑应该在代码中实现。这样,添加新技能时,只需要添加新的数据文件即可。
[*]技能的动态加载:技能的实现应该是动态加载的,这样可以避免游戏启动时间过长。可以使用反射机制来实现技能的动态加载。
[*]技能的扩展性:技能应该是可扩展的,这样可以支持未来的新技能。可以使用策略模式来实现技能的扩展性,每个技能都实现一个技能接口,这样添加新技能时,只需要添加新的实现类即可。
[*]技能的升级和进阶:技能应该支持升级和进阶,这样可以增加游戏的可玩性。可以在技能数据文件中定义技能的升级和进阶规则。
[*]技能的效果和特效:技能应该有对应的效果和特效,这样可以增加游戏的可视化效果。可以使用动画和粒子系统来实现技能的效果和特效。
如何实现技能的连击效果?
答:可以使用状态机来处理技能的连击效果。每个技能可以定义多个状态,例如:准备、攻击、结束等。在攻击状态下,如果玩家再次触发攻击,就可以切换到下一个技能的攻击状态,从而实现连击效果。
实现技能的连击效果可以按照以下步骤进行:
[*]定义技能状态:每个技能都应该定义多个状态,例如:准备、攻击、结束等。可以使用状态机来管理技能的状态。
[*]定义技能连击规则:定义技能的连击规则,例如:第一次攻击后,如果在一定时间内再次攻击,就可以触发连击效果。
[*]实现连击效果:在攻击状态下,如果玩家再次触发攻击,就可以切换到下一个技能的攻击状态,从而实现连击效果。可以使用状态机来实现技能的连击效果。
[*]处理技能的动画效果:每个技能的攻击状态应该对应一个动画状态,播放相应的攻击动画,从而实现技能的动画效果。
[*]处理技能的伤害效果:在攻击状态下,检测技能区域内是否有敌人,如果有就造成伤害。可以使用碰撞检测来实现技能的伤害效果。
[*]处理技能的特效效果:在攻击状态下,播放相应的粒子系统,从而实现技能的特效效果。
需要注意的是,实现技能的连击效果需要考虑玩家的操作体验,连击效果应该不仅仅是简单的技能复制,还应该增加一些新的元素,例如:连击技能的攻击范围更大、攻击速度更快等,从而使得连击技能更加有吸引力。
如何实现技能的伤害效果?
答:可以使用碰撞检测来实现技能的伤害效果。在技能的攻击状态下,检测技能区域内是否有敌人,如果有就造成伤害。可以使用Unity自带的物理引擎或者自己实现碰撞检测。
实现技能的伤害效果可以按照以下步骤进行:
[*]确定技能的攻击范围:每个技能都应该有一个攻击范围,可以使用碰撞检测来检测技能区域内是否有敌人。
[*]确定技能的攻击力:每个技能都应该有一个攻击力,可以在技能数据文件中定义技能的攻击力。
[*]确定技能的攻击方式:每个技能都应该有一个攻击方式,可以在技能数据文件中定义技能的攻击方式,例如:近战攻击、远程攻击等。
[*]计算伤害:在技能的攻击状态下,检测技能区域内是否有敌人,如果有就计算伤害。伤害的计算方式可以使用技能攻击力和敌人的防御力来计算。
[*]处理伤害效果:在计算出伤害后,需要处理伤害效果,例如:扣除敌人的血量、触发敌人的受伤动画等。
需要注意的是,技能的伤害效果应该与游戏的平衡性相符,不能过于强大或过于弱小。另外,技能的伤害效果也应该与游戏的主题相符,例如:在魔法题材的游戏中,技能的伤害效果应该以魔法为主。
如何处理技能的动画效果?
答:可以使用动画状态机来处理技能的动画效果。每个技能可以定义多个动画状态,例如:准备、攻击、结束等。在攻击状态下,播放攻击动画,从而实现技能的动画效果。
处理技能的升级和进阶可以按照以下步骤进行:
[*]确定技能的升级规则:技能的升级规则应该在技能数据文件中定义。可以定义技能升级所需的经验值、升级后的属性加成等。
[*]实现技能的升级:在玩家获得足够的经验值后,可以触发技能的升级。技能的升级可以通过增加技能的属性加成、增加技能的攻击力等方式实现。
[*]确定技能的进阶规则:技能的进阶规则应该在技能数据文件中定义。可以定义技能进阶所需的条件、进阶后的属性加成等。
[*]实现技能的进阶:在玩家满足技能进阶条件后,可以触发技能的进阶。技能的进阶可以通过增加技能的属性加成、增加技能的攻击力等方式实现。
需要注意的是,技能的升级和进阶应该与游戏的平衡性相符,不能过于强大或过于弱小。另外,技能的升级和进阶也应该与游戏的主题相符,例如:在魔法题材的游戏中,技能的升级和进阶应该以魔法为主。
如何实现技能的特效效果?
答:可以使用粒子系统来实现技能的特效效果。在技能的攻击状态下,播放相应的粒子系统,从而实现技能的特效效果。可以使用Unity自带的粒子系统或者自己实现。
处理技能的特效效果可以按照以下步骤进行:
[*]确定技能的特效效果:每个技能都应该有对应的特效效果,可以在技能数据文件中定义技能的特效效果。可以使用粒子系统来实现技能的特效效果。
[*]播放特效效果:在技能的攻击状态下,播放相应的粒子系统,从而实现技能的特效效果。可以使用Unity自带的粒子系统或者自己实现。
[*]特效效果的升级和进阶:特效效果也应该支持升级和进阶,这样可以增加游戏的可玩性。可以在技能数据文件中定义特效效果的升级和进阶规则。
需要注意的是,技能的特效效果应该与游戏的主题相符,不能过于突兀或过于夸张。另外,特效效果也应该与游戏的平衡性相符,不能过于强大或过于弱小。 如果想通过unity面试,那么你需要了解:
[*]多播委托的原理,它的底层的技术实现,因为我们在二面的时候往往是由一些项目的组长负责人帮我们面试的,所以在二面的时候,面试官会对技术进行深挖,所以掌握多播委托的原理是非常重要的
[*]我们知道多波委托是一个委托可以指向多个方法,那么我们如何取得所有方法的返回值呢?也许你没有深入的使用过多播委托,甚至都不理解讲的这个问题是什么意思,那在面试的时候对于这个问题就挂掉了,所以这个问题也是我们需要进阶了解的问题
[*]委托除了能够用在一些同步调用的环境下,也可以用在一些异步调用的环境下,那么在异步调用的环境下,我们如何使用委托呢?这是我们的第三个问题
[*]大家知道事件是比委托更加安全的,它是对委托的包装,那么在C#底层在C#内部,它是如何做到只让事件在内部被引发的呢?
接下来可以先了解一下想达到委托与事件合格分需要了解的内容
如何实现攻击武器切换?
首先通过一个实际问题了解委托能帮我们解决什么问题
假设要开发一款游戏,这款游戏中游戏的主角可以使用主武器或者副武器对敌人实施攻击,当使用主武器时,使用主武器的伤害计算公式计算对敌人造成的伤害,当使用副武器时,使用副武器的伤害计算公式计算对敌人造成的伤害
那么问题来了,如何设计代码优雅地只写一行,发出攻击指令的代码就能够针对当前使用的武器,不管当前使用的是主武器还是副武器,计算武器伤害呢?
要实现这个功能,只要使用委托就够了
如何定义变量/如何定义委托
要理解如何定义委托,首先需要了解如何定义变量
[*]大家可以看到在左边定义了一个attack变量,同时还定义了两个常量,分别代表了主武器和副武器的攻击力,在如图所示的Start程序代码段中,当我把attack的值赋值为主武器攻击时,attack就能代表主武器的攻击力,当我复制为副武器攻击时,它就代表副武器的攻击力
类似的,可以定义一个委托
[*]前面讲过委托是一个函数的容器,它可以装得下任何类型的函数,所以现在定义的委托可以装下返回值为void,没有任何参数,大家注意,括号里面是参数表,参数表为空,就是没有任何参数的一种委托,我给它起个名字叫做MyDelegate
[*]于是就定义一个MyDelegate类型的变量,叫做attack,在Start方法里面,当我把attack复制为主武器攻击时,就指向了主武器攻击的函数,那么它就会运用主武器攻击函数里面定义的伤害计算公式,计算对游戏当中敌人造成的伤害
[*]同样的道理,当我把attack复制为副武器攻击时,它就会指向副武器攻击函数,从而用副武器攻击的伤害计算公式,计算对敌人造成的伤害
所以,说委托是函数的容器是有道理的,你可以这样理解,在左边的代码里面,用attack这样的房间容纳了主武器的攻击力数值3,或者副武器的攻击力数值5
在右边的代码里面,用attack这样的代理,它也相当于是一个房间,这个房间里面容纳的不是一个数字,而是一个函数,它要么是主武器攻击的函数,要么是副武器攻击的函数
当然有同学可能会问,代理是一个变量,变量怎么能容纳函数的呢?这个就要从对计算机的理解说起
[*]其实在计算机当中,每个定义的函数,不管是主武器攻击还是副武器攻击,函数内部都有一段程序代码,这个程序代码是存储在内存当中的,就像变量在内存当中有存储地址一样,函数代表的一段指令也是有存储地址的
[*]假设主武器攻击的存储地址是16进制的0xAB,是一个16进制地址,而副武器对应的存储地址是0xCD,这是函数的开始地址,把attack赋值为主武器攻击,实际上就是让attack指向0xAB的地址,而如果我把attack复制为副武器攻击,那么这时实际上是让它去指向0xCD的地址
[*]到这里大家应该能明白,其实attack作为一个函数容器,它存储的就是函数的入口地址
既然定义了attack委托,可以指向主武器或者副武器攻击,那我们如何发出一条攻击指令,针对当前使用的武器进行伤害计算呢?
[*]方法很简单,我们只需要在定义完委托以后,调用这个委托就可以了,大家可以看到在右半边的代码里面,我们在unity的一个update函数里面,实时的检测当前用户按下的键,如果它按下的是空格键,这时就调用attack委托
[*]如果你的attack委托是指向主武器攻击,它就会调用主武器攻击的伤害计算函数,如果它指向的是副武器攻击,那么它就会调用副武器攻击的伤害计算函数
委托可能为null
在使用委托的时候,新手同学往往会忽视一个问题,委托可能是空值,那么什么情况下它会是空值呢?
[*]比如在前面的程序当中,如果既没有把attack复制为主武器攻击,也没有复制为副武器攻击,假如玩家刚从新手村出生,这时它是赤手空拳的状态,那么它既不会有主武器,也不会有副武器,这时attack的默认值等于null
[*]如果你像前面一样直接调用attack函数,当你一调用这个函数,C#包括unity的控制台就会报程序异常,告诉你空委托是不能进行调用的,所以我们写程序的时候一定要注意,对于一个委托,一定要先判断它是否为空,不为空的情况下才能调用attack
但是如果每一次用委托的时候,都写一个if判断,那这个程序看上去会非常的冗长不优雅,怎么办呢?
[*]我们完全可以用另外一种C#新的语法形式,当我去调用一个委托的时候,如图所示,可以写attack(),这是第一种写法
[*]也可以写attack.Invoke(),如果没有参数,就直接写一对括号,如果在调用函数的时候,有一些参数,要把参数写在这对括号里面,这是正常的调用attack委托的两种方式
[*]前面我们讲过了,要判断attack委托是不是为空,可以在attack和点中间加一个问号,这个问号表达的意思就是if判断,也就是说如果attack是空值,这个问号就判断它是不是为空,如果不为空就会执行Invoke,如果attack为空就跳过执行
所以C#的这个语法糖还是挺有用的,它可以帮助程序更加简洁,请大家也了解一下这个用法,特别是面试一些新手同学的时候,面试官为了考察新手同学是不是经常写代码,有项目的经验,或者是经常阅读代码,就会用这些新的语法糖,语法形式考察同学掌握的知识面和知识的深度
相信大家学到现在应该已经知道,如何使用代理去切换我们的主武器和副武器攻击了,下面是一个完整的项目示例
我们设置了通过输入数字1键,切换到主武器攻击,通过数字2键,切换到副武器攻击,通过按下空格键,释放攻击技能,大致就是这样的实现方式
多播委托
现在我们把问题延伸一下,假设要同时实现主武器和副武器攻击,用委托还能实现吗?
[*]答案是:用传统的委托不可以——但C#在很早很早的标准更新,大概是2.0的时候,对委托进行了改进,就是一个委托能够指向多个函数,我们称这种用法叫做多播委托
[*]通过多播委托,可以在程序代码当中写+=的运算符,这样就可以给attack委托增加它所指向的函数,这时只要把主武器攻击,副武器攻击都添加到attack以后,当我同样发出一个attack调用的时候,就会同时执行主武器和副武器攻击的函数,我们怎么理解这件事情呢?
[*]attack相当于一间房子,这间房子只能住一位客人,现在你可以把attack理解成一栋大楼,这个大楼有很多层楼,每一层楼都可以住一位客人,也就是说attack函数,可以同时存放,同时指向多个函数的入口地址
其实当大家能够回答委托的底层原理,也就是指向函数的入口地址的时候,就已经回答到面试问题的60分了,如何能达到90分的状态呢?
[*]这时大家就需要进一步的深入理解多播委托是如何实现的,这一部分内容会在小白的游戏梦、主程进阶之路当中给大家做系统的讲解,以及在面试模拟环节,帮大家做技术的深挖,使你在面试过程中达到令面试官满意,甚至超过面试官心理预期的状态
委托与设计模式
下面一起看一看委托与设计模式的关系,运用好设计模式不仅可以使代码更优雅,也可以使代码性能更高,并且关于委托与设计模式的问题,也是在游戏开发面试中的重点
当玩家在游戏中死亡时
下面我们从一个具体的问题看起,假设现在正在开发的一款游戏,当游戏玩家死亡时,需要执行图上面所列的这些处理
[*]通知输入系统关闭游戏的输入
[*]通知ui系统更新到死亡的UI界面
[*]通知敌人停止攻击
[*]通知场景重启到死亡状态,或者玩家重生的状态
如果要实现这个需求,程序怎样设计它的效率是比较高的,它的架构是比较优雅的呢?
[*]我们需要先对需求做一个分析,玩家死亡是一个事件,当事件发生的时候,有许多对象要处理这个事件,包括输入对象,UI对象,敌人对象,场景对象,处理的方式对于不同的对象是不一样的
[*]比如对于输入对象,应该关闭输入,对于UI对象,应该更新UI界面,对于敌人对象,应当停止攻击,对于场景对象应该把场景进行重启,每个对象如何能得知这个玩家死亡了呢?这是在处理玩家死亡的时候的要点问题,在这里处理方式有三种
解决方法1:每个模块在Update中检测
第一种方式是在每一个需要接收玩家死亡事件的模块里面,每一帧的更新函数,也就是我们所熟知的unity中update函数当中,检测当前玩家是不是发生死亡
比如输入模块可以在update里面检测玩家的血值是否小于零,ui模块,敌人模块,场景模块也可以这么做,但这样做会带来一个问题,就是性能比较低,为什么这样做性能会比较低呢?
[*]因为现在输入模块是四个,这四个模块需要检测血值,如果将来有更多的模块需要检测玩家的血值是否小于零,是否死亡了,那是不是要继续增加玩家死亡的检测语句,要知道每条语句的执行都是有开销的
[*]而且本身update在游戏当中,如果游戏比较流畅的话,以每秒60次的频率进行执行,它本身每一个模块的开销就比较大,整个检测玩家死亡的性能开销是模块数乘以每个模块里面每秒钟的检测次数
[*]假设一款游戏比较流畅,每秒钟有60次的检测,那这个性能开随着模块增加,性能开销会越来越大,所以第一种方法并不是性能比较好的方式,同时第一种方式会有很多的冗余代码,因为每个模块都需要写检测代码,这些代码是重复复制粘贴的
解决方法2:玩家模块中调用每个模块
那么有没有更好点的办法呢?于是就想到了第二种方法,不是每个模块主动检测玩家的血值,而是玩家模块在玩家死亡以后,调用每个模块的死亡处理函数,这种方式看上去是不是优雅一些
在玩家模块当中Update里面,每一帧都去检测玩家自身的血值是不是小于零,如果小于零,就向输入系统发出玩家死亡的消息,向ui系统,敌人系统,场景系统也发出消息,这样的做法看上去代码会更加优雅一些,并且效率也会更高一些,为什么呢?
[*]原来是每个模块都要有玩家的血值检测,它的模块数量越多,检测次数越多,现在只要集中在一个模块里面进行检测,它的性能也提高了,另外代码看上去也更加简洁优雅
[*]但问题是这样的模块,在设计模式里面是不符合叫做对修改封闭,对增加开放的最基本的原则,也就是所谓的半开半闭原则的
[*]比如现在要增加一个xxx模块,当增加该模块的时候,就需要修改玩家.Update的代码,这样,玩家模块的代码就比较难以维护,需要因为每当增加一个模块,就要增加死亡处理的代码(形如:xxx.OnPlayerDeath())
解决方法3:用观察者模式解决问题
更加优雅的方式是第三种解决方案,在设计模式里面叫做观察者模式,首先介绍一下观察者模式
[*]观察者模式是一种软件的设计模式,它不像解决方案一一样,实时的检测某一个事件有没有发生,而是在事件发生的时候通知你,但它又不像方法二一样,当事件发生的时候,因为模块的增加去修改代码,这就是观察者模式要解决的两个问题,也是解决方案三希望达到的目标
[*]要实现观察者模式,过去我们需要编写很长的程序代码实现这样的模式,但是由于C#引入了委托的机制,相当于帮我们实现了观察者模式很大一部分工作,因此在C#里面实现观察者模式会更加容易,在这里只需要定义一个所谓的事件
[*]这里的事件只是一个概念,并不是C#语法层面的事件,是用delegate委托的形式定义一个OnGameOver的事件,就是玩家死亡的事件,然后定义一个事件委托变量onGameOver,当玩家的血值小于零的时候,通过事件委托引发这个事件
[*]因为前面讲过这个委托有一种形式叫多播委托,所以并不需要像前面的解决方法二一样,为每一个要处理玩家死亡事件的模块调用方法,而是利用多播委托
[*]相当于多播委托是一个容器,这个容器里面已经放了输入模块,ui模块,敌人模块,场景模块的事件处理函数,当invoke的时候,一次性就把这些要调用的这处理函数调用完
[*]当然如果有更多的模块,也不需要在这里增加程序代码,只需要在新增加的模块里面订阅ongameover委托就可以了,ongameover仍然能够继续调用到新增加模块的事件处理的函数
这就是委托如何帮我们实现观察者模式,优雅的解决前面讲的事件处理的问题
在前面的代码里,我们需要注意一件事情,由于OnGameOver事件是被其他模块所访问的,因此如果像刚才写的代码那样,定义一个delegate,把它定义成OnGameOver类型的对象,这时外部的程序模块是无法访问OnGameOver的
因为委托是一个变量,既然是PlayerHealth类的一个字段,就需要遵守类的规则,变量定义或者字段定义的默认情况下,类的字段是private私有的,外部访问不到,委托也是一样,因此需要把委托声明成public,像右边的代码写的这样,这样外部就能订阅到OnGameOver的事件委托了
外部订阅委托
假设有一个游戏的控制器,它是外部模块,可以在游戏控制器开启的时候,就是所谓OnEnable事件发生的时候,可以去订阅PlayerHealth模块的OnGameOver委托,用+=进行订阅,当游戏结束或者游戏退出,禁用了游戏控制器模块时,可以通过-=符号解除对OnGameOver委托的订阅
这样就实现了完整的观察者模式,在观察者模式当中有几个要素
[*]第一个,需要定义一个事件型的委托,我称之为事件委托
[*]第二个,在程序里面需要引发事件型的委托
[*]第三个,我们还需要有订阅者,订阅者就是GameController,它通过+=订阅OnGameOver事件
所以对于观察者模式事件的定义,事件的发布和事件的订阅,三者缺一不可,如果在面试的时候,有面试官问什么是观察者模式,就能达到观察者模式基本的要点
但是有一个严重的问题!
虽然可以订阅委托,但是有一个严重的问题,在进行团队开发的时候,这个问题尤其严重,委托对象可以用+=进行订阅,想象一下,现在有三个功能模块,ABC三个模块分别是由三个程序员开发
A程序员开发的A模块用+=订阅了onGameOver事件,B程序员也用+=订阅了,但是C程序员,他觉得功能模块里面ongameover的时候处理游戏退出,退出的时候认为游戏已经结束了,于是就写了onGameOver=null,把委托置为空
对于C程序员来说,他觉得这样是没有问题的,因为游戏已经结束了,这个委托不再需要了,但是对于A和B程序员,也许会造成灾难,为什么呢?
[*]也许A和B程序员在游戏结束的时候,他俩开发的两个模块需要在游戏结束的时候做额外的处理
[*]假设C程序员写的C模块优先执行,由于C程序员已经把onGameOver置为空了,再执行A和B写的模块,这时A和B程序员就会发现,他们虽然订阅了onGameOver,但是他们俩写的onGameOver的事件处理程序根本就执行不到,在团队开发的时候会造成一些程序的意外情况发生
解决方法:使用事件!
那怎么去避免这个意外情况发生呢?我们就需要对委托的使用进行约束,怎么约束呢?
[*]因为C#的开发者也发现了这个问题,所以他引入了一个所谓的事件机制,我们就可以用事件机制来解决这个问题,如果没有事件机制,那架构师就需要去手动解决这个问题
所以不管是C#语言层面有没有事件的概念,作为开发者,要知道事件产生的原因是什么,先了解事件为什么能解决这个问题
[*]事件是一种特殊类型的特殊的多播委托,一般来说,它的工作方式与常规的委托是相同的,但是委托可以被其他的脚本调用,而事件(这边说的事件委托就是事件)只能从自己所在的类当中触发
[*]大家看图中的程序代码,比如这个程序代码是前面GameController处理游戏场景的模块,因为只有玩家模块能够能够引发玩家死亡事件,所以GameController应该是不能够引发玩家死亡的,它只能处理玩家死亡
[*]所以在游戏控制GameControl模块里,当我调用玩家的onGameOver事件时,必须要把它从委托改成事件,这时你会得到一个报错的提醒,告诉你the event事件如果把onGameOver定义为事件,只能通过+=或者-=进行订阅,但你不能在PlayerHealth玩家模块之外对它进行调用,这样就保证了事件调用的安全性,这也是使用事件的第一点好处
那我们如何使用事件呢?
事件的定义
比如前面定义的代理类型,写一个onGameOver前面加Delegate关键字,这是代理也是委托的返回值,因为有些术语翻译成委托,有些术语翻译成代理,在这儿就不加区分了
那么如何把代理改成事件呢?
[*]方法很简单,只需要把原来写代理的第二行代码加上event关键字就可以了
事件的本质是什么呢?
[*]事件的本质其实是对代理的包装,它限制了引发代理的范围只能在定义委托的程序模块里引发委托,并且只能在外部模块,比如GameControl模块里面进行订阅,而不能对它进行赋值,如果在这儿写onGameOverr=null是会报错的,所以这是使用事件的第二个好处
但是如果要使用一个事件,就必须要提前定义这个事件的委托类型,什么叫委托呢?
[*]说白了,定义委托实际上就是定义委托的订阅者的规范,比如onGameOver可以有很多订阅者,每个订阅者写的订阅函数都必须是返回值为空,没有参数的函数,如果不是这样,就不符合委托规则,这样在程序的编译阶段,C#编译器会在订阅委托的时候报错
[*]事件也是一样的,如果要针对每个返回值是void,没有参数的委托,起个名字定义委托类型太麻烦了,所以C#当中就给了两种预定义事件的泛型类型
C#中两种预定义的事件泛型
[*]如果是没有返回值的委托,就直接写Action,如果有参数就把参数写在泛型的Action的尖括号(尖括号代表泛型)里,写T1表示第一个参数的类型,T2表示第二个参数类型
[*]同理如果定义的事件泛型,定义的个事件委托类型是有返回值的,那就写Func(表示函数的意思),第一个参数是这个函数的返回值,第二个参数是这个函数的第一个参数,第三个类型是这个函数的第二个参数,以此类推
[*]比如现在同样写OnGameOver函数,如果这个函数在角色死亡的时候要说一句话,可以把要说的这句话在引发事件的时候通过函数的参数传入,同时需要把事件的类型改成能够传参数的类型,那就直接写Action(Sting)就可以了
Unity中预定义的事件类型
同样unity当中也预定义了一些事件类型,比如处理玩家的死亡事件,如果对unity的事件类型比较熟悉,就知道可以定义UnityEvent,在unity的属性面板上面,在PlayerHealth脚本上面可以看到定义的属性叫On Player Death的事件,如果你写的是C#的事件,在属性面板上是看不到的,这就是unity预定义的事件跟C#的区别
并且它还有一个最本质的区别,在面试的时候一定要注意,如果你只说unity定义的事件在属性面板能看到,说明你还没有理解它的本质,为什么在属性面板能看到
[*]因为Unity所有在面板上能看到的属性是可以序列化的,所以On Player Death在UnityEvent最本质的区别是能够序列化,什么叫能够序列化?
[*]就是当你在这儿设置了On Player Death的时间处理函数,比如定义为DisableInput以后,当我把unity关掉,重新启动unity以后,仍然可以看到设置的On Player Death的事件,处理函数还在,就是设置的DisableInput在
[*]而如果在这儿像上面一样定义C#的action,那这时在属性面板上面看不到onGameOver
[*]当unity重启以后就算能看到,赋值的结果也会丢失,那么这个事件是没有指向任何的处理函数的
定义自己的UnityEvent
当然也可以定义自己的UnityEvent,比如定义带有浮点数参数的事件,可以从UnityEvent继承,UnityEvent有好几个版本,其中有带一个参数的泛型版本,可以从UnityEvent,float去继承
这个float是要填充的泛型参数,表示它带有一个参数,这个参数是浮点数类型的,于是就可以根据FloatEvent的类型定义一个变量,这个变量叫onPlayerHurt,它可以在属性面板上看到,可以指定一个参数
onPlayerHurt在图上面是指向UpdateHealthBar函数的,大家注意,因为事件带一个参数,所以当你定义了一个事件处理程序叫做UpdateHealthBar的时候,这个UpdateHealthBar一定要带有一个浮点类型的参数
如果这是一个没有参数的UpdateHealthBar,它是不能作为onPlayerHurt事件的处理函数的
小结
现在我们总结一下,委托和事件的哪些区别
[*]首先委托是使用Delegate关键字来进行声明,而事件是使用Event关键字来证明
[*]第二,如果学过C++,那么你就知道前面讲过委托相当于是一栋建筑,这栋建筑里面可以存很多的函数,所以为什么它能存很多函数呢?
[*]因为委托的本质实际上就是函数的指针数组,它在运行时保存了一个或者多个方法的引用
[*]当然具体它是怎么样保存的,让面试官对你刮目相看,这就需要进一步学习事件和委托的进阶内容
[*]而事件实际上是一种基于委托的通知机制,通俗来说,事件是对委托更加安全的包装,所以委托是独立的,它不依赖于事件,而事件依赖于委托,没有委托就无法创建事件
[*]事件最主要的用途是为了防止用户重置委托,赋值为空,并且防止在委托所在的程序模块的外部进行调用,这就是事件比委托更安全的地方,也是为什么事件是对委托的包装的原因
[*]第三个区别,如果去了解一下委托的底层原理,那你会知道委托是通过Combine()和Remove()方法实现多播的,而事件是通过AddEventHandler()和RemoveEventHandler()添加事件的处理程序
[*]第四,委托可以作为方法参数传递,这是非常重要的一点区别,比如写一个麻将游戏,要比较麻将游戏里面的两张牌的大小,这种比较规则是比较特殊的,于是可以自己把一个方法作为比较规则传入,那这个方法怎么传入呢?
[*]可以定义一个委托,把这个委托指向具体的比较的方法作为排序函数的参数进行传入
[*]而事件是不可以的,事件只能作为类的字段成员,但不能作为方法的参数,所以委托的用途其实比事件更加广泛,事件比委托更加安全
这就是对前面内容的总结
事件与委托进阶
在面试的过程中,当然不希望在面试的时候只能拿零分,所以我们通过前面课程的学习,可以拿到面试中及格的表现
那么我们如何拿到一个进阶的表现,让面试官能够这个刮目相看,甚至能达到一些面试官都不太了解的内容呢?在学习的过程中,如果能注意我讲的这些点,因为一面的面试官可能相对工作经验没有那么丰富,你甚至可以回答一些一面的面试官都不怎么了解的内容
这样对于同学的面试是非常有利的,这其实还牵涉到一些具体的面试技巧,我会在面试模拟环节,帮大家做具体的指导,如果你想拿到关于事件与委托面试90分的回答,那么你需要了解
[*]多播委托的原理,它的底层的技术实现,因为我们在二面的时候往往是由一些项目的组长负责人帮我们面试的,所以在二面的时候,面试官会对技术进行深挖,所以掌握多播委托的原理是非常重要的
[*]我们知道多波委托是一个委托可以指向多个方法,那么我们如何取得所有方法的返回值呢?也许你没有深入的使用过多播委托,甚至都不理解讲的这个问题是什么意思,那在面试的时候对于这个问题就挂掉了,所以这个问题也是我们需要进阶了解的问题
[*]委托除了能够用在一些同步调用的环境下,也可以用在一些异步调用的环境下,那么在异步调用的环境下,我们如何使用委托呢?这是我们的第三个问题
[*]大家知道事件是比委托更加安全的,它是对委托的包装,那么在C#底层在C#内部,它是如何做到只让事件在内部被引发的呢?
更多详细内容可以参考《委托与事件》公开课 你好题主,那我就说说Unity面试的过程中面试官会考察的东西。这里我参考了一下几个大厂朋友的面试经验贴,总结了一些面试需要注意的点,希望可以帮到你顺利通过面试。
1.首先就是自我介绍,自我介绍基本是面试官了解你经历、能力的首要手段,因此一个表达流利,逻辑紧密,内容丰富的自我介绍可以加不少的第一印象分。我们的自我介绍需要向传递表达以下几个方面的信息:
学历背景(向面试官传达你优秀的学历)、工作履历(向面试官传达你的项目经验、技能掌握的程度)、个人优势(对自己能力的一个总结)。
以上三个方面的要素,请记住每一句话都不能是废话,都要通过自己的经历向面试官传达自己的优势和能力。这里我可以给大家简单举个例子:
“我曾今带过有带过团队开发某款游戏的经历,这款游戏对某方面技术的要求非常高,我们在开发的前期充分评估了可能出现的问题,最终结合整个项目分析出问题所在,通过整个团队的努力最终成功的完成了整个项目。”
看似一段简单的关于工作经历的描述,其实非常严谨的向面试官展现了:
(1)你的团队领导以及协作能力
(2)你的技术能力(业务能力)
(3)解决问题的能力
(4)你是否具备成功的项目经验
2.接下来就是面试官会对你技术能力的考察,这部分主要分为两个维度:
(1)既定的面试问题,这块儿我已经贴心帮大家整理大厂的面试真题(附大厂总程的解答与拓展)还有5天实战训练营课程点击下方小卡片领取即可!
点击卡片领取免费/面试攻略/实战训练营
(2)根据你的项目经验、DEMO提出的一些综合性的问题,因此这里就需要我们对自己demo以及项目经验一定要用心总结,而且当我们的DEMO、项目经验体现出来能力一定需要迎合目标公司,这都需要提前做好功课。
最后也是最重要的,就是一定重视基础很多朋友准备面试时,可能因为过于紧张或太过于倾向DEMO和项目经验,结果被问到一个基础问题突然大脑一片空白了。所以无论任何时候底层基础能力一定是需要重视的!
三连一下,了解更多精彩内容
对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀
你是如何做好Unity项目性能优化的
在面试中,我们经常会被问各种”莫名其妙”的问题, 比如这道:”你是如何做好Unity项目性能优化的?”。“这个问题也太泛了吧,没有具体的优化点,这怎么回答?” 瞬间跃入脑海。做面试复盘的时候,你可能会想这个面试官是不是什么都不懂,是个”青铜”啊。没错,能问这道问题的面试官要么是个”青铜”, 要么就是”王者”。说他是青铜,可能真的什么都不懂,只是他听人说Unity 项目做性能优化的时候比较麻烦,所以就来问你,看你如何处理。还有一类是王者,就是来考你各方面的综合能力,接下来我们从
(1)从项目技术管理的角度来杜绝性能问题;
(2)性能问题定位与分析;
(3)性能问题的常用解决方案;
这3个方向来回答这道很泛的面试题,让面试官对你”刮目相看”。
1: 从项目技术管理的角度来杜绝性能问题
“性能优化”最重要的方式(没有之一),不是任何的编程技术与技巧,而是从”项目管理”上防止”游戏做完马上要上线”时出现的性能问题。在日常项目管理中,把性能监控起来,问题尽早发现,尽早动态清零,这样才能做到项目从性能风险上是可控的。我的一些经验如下:
a:用心做好技术调研
我们很多小伙伴不重视技术调研,或者技术调研做的非常的马虎,导致对游戏的性能预估不足。比如我们做渲染管线定制,编写了Shader,效果出来自己比较满意,渲染这块的技术调研就结束了。其实不然,这个事情才做了1/3不到,还要注意必须要最快的速度尽早多平台测试(有些同学的项目真的好笑,到了上线前一晚才发布到不同的手机真机上跑)。你在项目开始就上多平台,才能发现各个平台不同的差异,尽快解决这些差异与问题。同时还要构建游戏核心玩法中的极限情况模拟,来应对项目后期大量战斗单元出现导致的性能问题。
b: 从项目正式开发的第一天起就引入多平台测试与完整的测试机制;
这个强调多少次都不为过,什么时候,哪个改动,引发了哪个问题,什么时候出现了一个什么样的问题,如何解决的,作为项目管理者,要一路对这个过程非常的熟悉,只有这样你才能真正了解你这个项目的稳定性。
c: 留出专门时间与专人review代码;
开发项目的时,我们需要有专门的技术负责人review整个项目的代码,了解团队成员的实现思路与代码设计,把控项目质量,确保没有太大的问题,技术负责人都知道具体是如何实现的。这样有非常复杂bug的时候,团队至少有人能从整个全局来了解整个项目和代码。出现性能问题的时候,能从大局着手,进行分析组织大家攻关。
d: 了解运行中技术参数的变化,做好技术参数统计;
开发项目时,每次有功能完成提交测试后,我这边会让测试出一些性能参数的统计,比如main thread多少,render thread多少, batches多少等。每次数据的变化,结合review代码,看下是否合理,能够做到什么时候引入了哪个功能,让drawcall增加了几十,是什么引起的项目中作为管理者都要清楚和关注。
总之完整的工程管理机制,与测试共存, 动态清零包含性能在内的问题,是做好性能优化的最重要的手段。做项目是一个工程问题,质量靠管理,不靠程序员的”天分”。(注:防疫动态清零也是个工程问题)
2: 性能问题定位与分析
当项目开发过程中发现性能问题后,如何定位性能问题,定位到具体是哪里引发的,这个就变得非常重要了目前主要的手段如下(按照我认为的重要程度):
a:代码算法与系统底层功底: 为什么大厂老要求算法,OS底层,数据结构,引擎底层,只有对这些基本的原理原则了解了,思考问题的时候才知道如何分析与解决。比如算法的时间复杂度,空间复杂度,比如Drawcall合批所带来的性能提升与性能开销的权衡等。
b:工程管理手段: 通过工程手段测试出来了性能问题,我们可以比对上一次测试与本次测试的开发记录,看看是引入与编写了哪些代码导致的性能问题,屏蔽掉可疑的代码,反复确定,这样能帮助我们瞬间就定位到出现的性能问题。(注:95%以上的问题我都是通过这种对比发现的,通过开发记录对比可疑代码,屏蔽可以代码确认,从而分析问题最后解决)
c: 打印: 没有打印解决不了的问题,如果有继续打印,linux 内核的开发与调试基本就靠打印,因为复杂的多线程调度环境,任何工具都不好定位,所以没有打印解决不了的问题。
d: 性能工具剖析手段: 通过Unity提供的性能剖析工具,来进行问题定位与分析。Unity引擎运行中会提供很多API与性能参数给用户获取,而Unity引擎自带的工具也是调用这些API获取游戏的性能,比如stats统计与Profiler统计。同时还有一些第三方的插件,也是做性能监视的,本质也是读取Unity引擎提供的性能参数API,把自带工具没有显示出来的重要性能参数显示出来给用户。注:不要迷恋工具,要从管理+数据+执行流程上对程序进行分析,性能工具是做这些分析的方法之一,甚至不是最重要的
3:性能问题常用的解决方案
说到这里了,才是我们很多面试的同学一开始就回答的,可以用xxxx手段来解决xxx问题。可能我们说的都对,但是在”王者”面试官看来,他出这个面试题,目的就是看你是否从系统上去思考如何把控游戏项目的性能。定位到了是某方面的问题,再去应对一些常用的手段,这里我就把一些常用的列举一下,可能有遗漏。
Drawcall优化: 确定是Drawcall问题后,根据游戏项目选择一种合适的合批技术,为这个技术创建可合批的条件,做好合批,具体可以参考我写的《Drawcall优化系列》教程详细的分析了Drawcall合批的技术手段与原理,性能与开销(what?合批还有开销?没有错)。
渲染优化: 看下pass的次数与set pass 次数, pass 次数,比如阴影这些都会导致多次pass,多光源这些会导致多次pass, 我们可以通过定制渲染管线,优化shader代码, 优化光照计算等,从Shader+渲染管线级别来做好渲染优化,现在比较火的UPR渲染,也可以参考我写的《URP 实战系列》的教程。还有LOD优化,远处用的面数少,近处用的面数多。抗锯齿算法优化等。
物理引擎优化: 这块化没有太多的空间了,用其它的技术去替代不用物理引擎,减少物理引擎的迭代参数,减少计算量,减少物理刚体的数目。
网络优化: 异步IO代替同步IO,多线程处理网络消息, protobuf序列化与反序列化优化网络包体体积。KCP 替换传统的TCP。
包体优化: 优化图片,声音体积,通过改变压缩参数来降低这些资源的体积大小。可以使用服务器上部署资源包来实现打空包机制进一步减少包体体积。
热更新优化:版本管理,增量下载,断点续传等。
内存优化: 减少资源的内存占用,不用的资源卸载掉,吃入显存的纹理可以采用平台支持的压缩格式。缓存池,减少内存碎片,减少对象的反复构建,避免GC峰值冲击等。具体可以看下《性能优化-内存篇》。
模型优化:通过细节增强,法线贴图,高度贴图,凹凸纹理等减少模型面试的同时获得很好的效果。
寻路导航优化: 优化算法,流场寻路等,多线程优化寻路算法。
代码写法优化: for循环内部不要过多跳转打乱CPU Cache等。……
其它的一些具体的优化手段,针对不同的问题来做一一的处理就可以了。具体问题具体分析。
如果你是面试官,看到一个年轻的小伙,能系统的思考”性能优化”这个工程问题,你会录取么?今天的面试题分享就到这里了,谢谢大家,其它想了解的面试问题可以私信我或评论区留言,也许下一篇就是你推荐的内容。 其实有两点会很吸引面试官:
1。你做过自己的游戏或者作品,并且的确是你自己做的,不是培训学校的那些上课内容
2。面试会问你的作品的一些相关实现细节,以确认是你自己做的。
有这两点,基本上就很容易面试通过。
页:
[1]