unity 脚本中 调用另一个脚本_从0到1,用unity做出我想要的作品

这篇文章是记录我自学unity完全从小白阶段到做出一个作品踩过的坑。遇到哪些坑和值得记录的地方就写下来吧。

前期准备:

1.我有一个idea,我要做成一款游戏!那么如何才能做成一款游戏呢?

unity和UE4我都简单接触过,虽然UE4还挺上手的而且蓝图系统和公司的蓝图系统很类似,但UE4竟然要扣10%的销售分成!而且网上看了很多攻略都觉得Unity更适合做手游和小项目,UE4比较适合做大项目,再掐指一算,反正也要学代码,自己写脚本可比蓝图效率高多了,而且unity对其他工具的扩展支持也很广,资料查找也方便些。UE4不知道坑要多多少,其实也不怕坑多,起码别人都走过了给你输了个牌子“此处有坑”还会告诉你怎么走出坑。看不见的路才是最坑的。所以果断选择unity了!

接下来就是了解什么是unity,unity能干什么,unity能做到什么地步,unity应用最广的游戏类型是哪些,unity适合哪些平台,哪些平台目前还不适合。unity要搭配哪些东西使用,哪些是核心且必须的,哪些是高性价比的,哪些是可有可无的,哪些是能极大提升效率的。

坑1:

目前unity还不是很适合开发微信小游戏,微信小游戏用得最多的还是cocosceator。

2.选择好了引擎后,就要开始规划制作整个项目的资源了。虽然是个小项目,但我毕竟是一个完全不会开发的人员,所以学习如何实现,把整个项目从0到1跑起来是最重要也是最基础的一步。

(1)程序方面:C#语言搭建框架编写脚本是必须的。

(2)UI系统公司项目中使用的是FGUI,既然公司已经踩过其他坑了,我也不纠结了,FGUI学习使用也比较简单。

(3)美术方面:美术资源暂时用cube替代,不影响玩法验证。后续可行的解决方案是unity的商店购买相对合适的美术资源,比起外包或者自己画性价比要高一些。

不过特效系统我希望后期有时间了可以自己做,一是特效定制化要求比较高,比较难找到完全合适的,即便买了感觉还行的资源也需要自己手动调一下;二是自己做特效感觉挺有意思;三是特效描述起来相对比较花时间,整理特效需求文档的时间都可以自己做大概的效果了。

光照系统也需要研究一下,光照对于画面表现力的提升非常大,能起到遮瑕凸瑜的效果。

(5)策划方面:虽然自己就是策划,项目体积也不大,但也需要在动工之前把整个项目想清楚,最好有个项目文档来帮助理清思路方便日后查阅。不过整个小工程我已经了然于胸了,不做也罢!此处暂不赘述。

(6)音乐方面:暂时没什么想法,由于这个项目比较简单,只需要一些简单的音效即可。要么网上白嫖要么在资源商店购买。

(7)资源管理:

工作计划表:计划每周末抽4个小时出来做项目工程,平时每天学习1个小时的视频或书籍了解基础知识(事实证明在自律性极差的人面前这种计划极不靠谱)。

学习内容表:unity,c#,FGUI,特效。

计划完成时间:6个月(事实证明在自律性极差的人面前这种计划极不靠谱)。

那就,开始学习吧!

1.了解unity,学习使用unity,如果项目中能使用到unity熟悉起来极快。

课程:siki学院,跟着做2-3个小项目课程(一个打砖块,一个塔防,还有个啥忘了)

学习时长:1个月。

学习结果:大概了解了unity是如何运作的,如果能熟练使用,制作起来还是挺快的。对于独立完成一款游戏有了一些信心。

2.学习C#。

学习教材:C#入门经典(第七版),C#语言入门详解(bilibili-刘铁锰),大话设计模式,C#本质论,Unity3D脚本编程

学习时长:C#入门经典--2周

C#语言入门详解--1个月

其余几本还没开始看

学习心得(此处过于唠叨,可跳过不看):我先是看完siki的教程后就开始动手做项目了,然后在编写脚本的过程中发现对C#太陌生了,只会一些简单的if else之类的逻辑语法,对于什么是类,类有几个成员,参数传递这些东西概念和用法十分模糊。虽然大家都说C#很简单,但对我这样的程序小白来说,还是太南了!

然后我请教了一下大佬之后停下项目开始看C#入门经典,把第一部分C#语言看完了,说实话看到正则表达式的时候就感觉很吃力了(后来发现其实前面委托也没看太懂- -)。感觉差不多够用了我又继续开始做项目,做到某个部分,又感觉到自己的基础非常不牢固,已经到了无从下手的地步。于是我又停下项目,浏览了下bilibili上的C#课程,然后发现刘铁锰老师的C#语言入门详解非常适合我!先从原理、定义、使用环境讲起,而不是一上手就讲这个是怎么用的。

中间由于公司项目进度非常紧张,基本9106的作息,所以停止了半年...

拖到过年由于疫情原因,我终于把timiLiu的教程看完了!!但是由于中间拉的时间太长而且前面讲的我都懂,后面讲的我比较赶进度,都没跟着练习!实际上只是有了一个印象而已,没达到理解的地步!后来很多时候我都是用到了某个东西又看着视频一遍遍练...

看完timi的视频后信心大增,翻出大半年前的项目开始撸 。然后发现关于unity脚本编程的知识又忘得差不多了- -,不过还好,脚本都看得懂,慢慢也就想起是怎么回事了。这时候看着自己之前写的代码乱得像一坨屎一样,好像草翻重做。不过朋友阻止了我,他说你这时候优化没什么意义,过不了多久就会发现自己现在重构的代码也是一坨屎。先实现版本最重要。我想想也是,我现在C#的功底这么差,肯定还有很大的进步空间,没必要现在就做优化。

在接下来撸代码的过程中发现对于一个小白来说太多坑了!我决定都记录下来,即可作为经验,也是日后复习梳理的宝贵资料。

学习结果:对C#半懂不懂,但至少可以自己撸代码了TAT

3.FGUI、特效和其他内容,还没开始(以后填)。

这个游戏想要做成啥样?

以下是核心逻辑:

1.期望PC和手机端能同时进行

2.两边人数相等,蓝方在上,红方在下

3.玩家只可左右移动,不可上下移动

4.在移动过程中玩家会蓄力,当停止移动时会根据蓄力多少进行攻击

5.受到攻击会后退,退出边界即离场。如果一方的全部玩家离场,即失败

那就,开始动手制作吧!

1.基础布局。

先实现战斗场景中的所有元素:地板,光照,三个小圆球代表友军,三个小圆球达标敌军。这一步很简单,利用unity现有的资源拖拽即可。

2.视角。

确定游戏视角:俯视。我是打算做成2D游戏的,但仍在3D场景中建模,考虑到美术表现,光照,后期可能增加额外的视角等原因,仍创建的3D工程。其实3转2很简单啦,Y轴永远是0就可以了

3.移动操作。

实现操作代表自己的小圆球的的移动。这一步开始就要用到脚本了,创建了第一个Player的脚本!一边看siki的教程一边自己动手。然后遇到了第一个问题,输入指令怎么玩儿?我在Update里用Input方法判断玩家是否进行了输入,but又遇到了问题,我是打算在PC和手机端都能游戏,这里两端的输入指令不一样,怎么弄呢?不过暂时不纠结这个,毕竟我还是在PC端调试。所以先实现了PC端的输入指令逻辑。

然后又遇到问题:判断输入指令的方法有很多种,用哪一种呢?unity本身自带Input接口

v2-75efed014035337cc3c3250e6eda6c03_b.jpg

可以用下面这种方式实现输入操作小圆球移动的逻辑

v2-b5bcd678bd88a40527b51fcfeaf19923_b.png

但这种方式和我的需求不符,我要的是按下方向键,小圆球立刻马上给我朝对应的方向移动,我在Input的参数设置里调了许久也没调出这种立刻马上就能变向的体验。所以我换了个方法。自己写逻辑吧!先判断是否按下了对应的按键

v2-dbffa5a3bb2d4bf40401a1f7846b855e_b.jpg

然后在Update()里调用IsInput(),如果有输入的话,进行移动。

v2-76f58d9c66b120dd076b8cb78b22f383_b.jpg

v2-7b997f7148025ccf0e4c4fee3b727ee4_b.png

这里得Move()就直接调用这个方法了!当然,这里免不了先在Update()里写得一团乱麻,然后把方法一个个抽离出来。

实现移动还是花了大半天时间,真正在运行模式下能移动的时候心里贼开心!

4.子弹系统

可以移动了,接下来就该干点男人该干的事了!发射子弹!

逻辑需求:如果玩家在移动过程中停止移动了,就发射一颗子弹。

首先开始创建了一个红色的小圆球代表子弹,做成了prefab。此处开始意识到文件分类的问题了,创建了几个子目录来分别存放prefab,材质等。(Resources是后面用到的,之前并没有这个目录分类)

v2-983283f8c2e189f291a51e1296014611_b.jpg

子弹自然应该有个属于自己的脚本,创建了一个Bullet的脚本先挂在子弹的prefab上,暂时不处理。

那么问题来了,玩家停止移动的时候怎么创建这个子弹对象呢?这里我还扣了好久的脑袋

v2-6605d1c69d97201dc6ba39d6a389a7bc_b.jpg

v2-817f4142f357dc036a8095c64068392d_b.jpg

当然了,最开始的代码不是这样的,这个Attack()的方法一开始也是放在Update()的判断里的..这里踩了个坑,不知道怎么创建出子弹的prefab,搞了好久,最后发现原来在unity的Inspector窗口直接拖上去就可以了...

不过现在我换了个方式,我觉得把对象一个一个拖上去挺麻烦的,后面如果我有100种子弹岂不是要拖100遍?而且Inspector窗口也被污染了!我就想能不能直接找到这个资源读取啊?(我进步了!)面向百度查了一下,发现可以用Resources.Load()方法读取!说起这个Resource文件夹,在公司的时候我还纳闷为什么程序给策划调用的资源路径都配置在Resources路径下,当时我还以为只是资源结构设计如此。现在才发现原来这个文件夹还有特殊的用途!(奇怪的知识又提升了!)

v2-07d6332f99d38993abe8f9181c9ee614_b.jpg

v2-1a507ef805897f909a1e0f31ad493950_b.jpg

当然,这还不是最舒服的配置方法,后续扩展维护也不方便,不过对于目前实现功能绰绰有余了。

能创建子弹后,子弹总得干点什么吧,不然宁还配叫子弹么?

把蓄力值传递给子弹!让子弹飞一会儿!子弹怎么飞?先直线飞吧!子弹飞多久?子弹接触到人还飞吗?子弹时间到了后怎么表现?子弹打到人怎么扣血?子弹消失时候的爆炸效果怎么表现?子弹属于谁的?子弹的伤害值怎么算?……求豆麻袋!我捋一捋= =

一个一个来,既然是直线飞行,那就很简单了,移动的逻辑一句话就写完了。

v2-48c5fbdafcbb4c53e654093a0fe8a09a_b.jpg

如果子弹的计时器超过了自身的endTime呢,就执行Die()的逻辑。

but这个endTime是怎么传过来的,是我遇到的第一个感觉到自己基础太差的地方...也就是如何传参的问题。实际上方法如下图就可以了

v2-dd10d9a7fb3dfdcdd9e4e8e97739f4a6_b.jpg

但是这短短一句话非常的知识点其实非常多!!首先是GetComponent<>()方法,功能其实很简单,获取对象身上的脚本。但学完timi的教程后又心生疑惑,不对啊?这个脚本是个什么东西?看他的结构明明就是一个类嘛!我为什么能通过一个对象获取一个类?Unity里的实例对象又是什么?到底是一个个实例还是一个个类?陷入沉思...

于是我又梳理了一下gamObject和脚本的关系,梳理完了之后发现这和GetComponent怎么取到另一个脚本的半毛钱关系都没有啊喂!(虽然结构更加清晰了)

暂时不管了,此处花了大概三天的时间才搞清楚怎么传递参数的(其实我觉得还是八太行,不过能用暂时就这样吧)

既然子弹能飞行了,那么怎么判定子弹是否撞到了敌人呢?

又经过漫长的思考后,发现原来unity的组件里已经准备好了碰撞器供检测使用了

v2-4ea64f06a08b7300c26c27f270df31eb_b.jpg

v2-18b4911b2c2f1cc0d39b70650d047d18_b.jpg

当然了,这里查询碰撞器,trigger碰撞器,刚体之间的关系又花了许久……(而且这玩意儿没有理解深入的话隔几天就忘了)

有了碰撞器后呢,就可以用下面这段方法啦

v2-046d5e28961c09f0fa2efcc91e418461_b.jpg

other是谁?自然就是你碰到的那个对象,然后用tag来检测一下是否是“Enemy”。这里顺便就给红蓝双方打上了标签,Player和Enemy。

拿到碰撞的对象之后,这里的逻辑是对象受到多少伤害就击退多远。这里怎么控制碰撞对象的移动呢?由于没用物理引擎,实际上要控制对象的移动还是通过脚本Enemy来实现的。

v2-00cd4ff01cf15f77e8cf9a674ea35963_b.jpg

计算呢,先瞎瘠薄写一个放着吧

v2-5f0c1b36fdf88bfefec42678ba95344a_b.jpg

对方已经受到一万点暴击了,子弹的使命也就完成了,接下来就该把他销毁掉

v2-96b507e74679c73a256ec0464f4f3f7f_b.jpg

先播个自己做的爆炸特效,然后执行销毁特效和物体的方法。Destroy很贴心的给了延迟参数,我总不能自己手动等待0.5s再销毁吧

这样下来,子弹的功能暂时也就完成了!跑了一下,没有问题

5.战斗初始化。

目前我已经可以操作自己的小圆球移动并攻击敌人了,接下来先考虑下初始化创建的问题。我们可以自己选择人数,是1v1,2v2,还是3v3。先创建个GameManager的脚本挂在一个空物体上,这样只要加载这个场景后初始化场景中的物体及组件后,就会自动运行GameManager这个脚本了。

v2-bf91e063437e997883b18b7605a0c765_b.jpg

目前还是一个很粗糙的版本,技能点不够,还不知道这儿咋处理。先实现手动拖6个物体上去代表6个小人儿吧!

v2-f07cb55f6a765ec72a7f0c14131669a4_b.jpg

然后用一种非常丑陋的方法实现不同人数下创建出来的prefab实例

v2-4778e4a6921037d6e19423e3a1fff6de_b.jpg

v2-0a61d451c121ed01df721d27ac1cc173_b.jpg

这里也踩了一个挺大的坑,switch的语法就不说了,熟能生巧。在一口气儿创建多个对象时候的一一应对的逻辑关系上想了好久,做一步错一步,慢慢debug才调试好的。新手debug绝对是一件非常崩溃的事情,信心值唰唰的往下掉。我是不是不行了,这好难啊,错在哪里了,一脸懵逼...TAT

熟练掌握debug和断点技术是非常重要的事情!

刚才那一步只是创建出来了这些对象,还没给他们身上挂的脚本进行初始化赋值呢。写到这里的时候,前后已经跨越大半年了。我稍加思索,既然要初始化赋值,那就在构造函数里赋值!这样创建对象的时候不是就可以传参了吗!我真skr天才!

在脚本里写完构造函数后,在这个类里实例化了Player后,总感觉哪里不对...调试的时候我才发现,实例化了Player又不代表创建了gameObject啊!实例个鬼啊!根本就不是一个东西啊,MonoBehaviour明确写了派生的类不允许自己写构造函数啊!摔!

没办法, 老老实实的挨个给所有对象赋值

v2-582a04a8cf159e1ff469d3c815f7b1a0_b.jpg

一坑未平一坑又起,GerComponent<>()的泛型参数里我一开始写的是Player,死活报错。又是一顿查,才发现有些友军对象上没有Player脚本,所以报错了。由于之前已经把玩家、友军、敌人这三类都抽象出来写了个基类Contestant,询问了下大神,这里也是可以直接拿基类进行获取的。

这样子,初始化的工作算是暂告一段落。

6.武器系统

武器!是武器!我在打砖块中加了武器系统!

武器系统应该算是这个小项目的趣味点之一,通过改变武器来进攻的手段。

最开始我想的是所有武器都在一个数组中,需要用哪个就取哪个。于是有了这样一段代码

v2-b4992662df7cc6386abeb32916150ab1_b.jpg

后来让我扣脑壳的是我在创建子弹的时候,由于我把创建子弹的方法也写在了武器类里,导致每次都要去取当前是什么武器,然后还要创建一个子弹的数组,再去取对应的子弹是什么。更坑爹的是,如果2个武器用的是同一个子弹prefab,我仍需要在子弹的数组里填2个一模一样的prefab...

再往深了想,每个武器的攻击方式都不一样啊,如何才能在武器类里用不同的攻击方法对应不同的武器呢...

19年由于业务繁忙到此就暂停开发了,中间看了下课程,重新理解了虚类,基类和继承。回看这段代码的时候我才意识到,由于每个武器都有自己独一无二的攻击方法,所以每个武器都应该是一个类,继承自武器这个基类。子弹是武器的一个属性,每个武器单独重写这个属性就可以把武器和子弹绑定起来了。

下面就是继承自武器类的Pistol武器,暂时只重写了创建子弹这一个方法。

v2-831d106651935fb20685d88e98c06843_b.jpg

好了,有了武器的子类,那么我怎么知道要调用的是它呢?显然我们需要在玩家的小圆球上挂上这样一个脚本。在Start()方法里默认挂上Pistol这个组件。后续如果更换了武器,再删除这个组件,添加新的组件即可。

v2-3e8b2cb2226b1306464cd7d7031579e8_b.jpg

在判定攻击的时候,去查找Weapon基类。然后调用Weapon里的Attack()方法,由于CreatBullet()已经被子类重写,这样就会调用到子类的CreatBullet()。

v2-e49357d1d27af9514904d8a28531663b_b.jpg

v2-b5e41fe1cd3700bfcd3d7966bebc8a3a_b.jpg

这样可行吗?打个断点调试看看走的是哪。

v2-210bbec6038f99aff60eb5a62f4a406c_b.jpg

走到了Weapon的Attack()方法。

v2-1a461afdda461a4a623264527d7255bb_b.jpg

下一行跳到了子类的CreatBullet()方法。说明是没问题的。

v2-4585992414005acee57de3a7527042b2_b.jpg

到此武器系统暂告一段落(当然远远不止= =)。

7.怪物AI

来了!它来了!无数策划和程序的噩梦!

一说起AI整得还挺高大上的,但这里的需求暂时只是让对方的小圆球也能左右移动和攻击而已...由于没有寻路需求,也不需要用到其他插件(有也不会用=w=)。所以自己写一个吧!

功能需求:判断当前离自己最近的玩家,并向其移动。如果X轴偏差小于0.1就停止移动并攻击。

看起来好像不是很难的亚子。

Emmm,第一个问题是,怪物AI的脚本应该是单独的一个类呢,还是继承自Enemy呢,还是一个接口呢....单独一个类吧,处处又要用到Enemy的字段,继承吧,又感觉怪怪的,接口吧,好像也不对劲...(我还没想很清楚,欢迎大佬指导)

不过实现功能要紧,先暂定他是Enemy的一个子类吧,于是先写了这些字段

v2-969785718c8a1e7a2b246a4c53040fd2_b.jpg

然后初始化了必要的一些信息。话说回来,多个怪物的AI都初始化同样的信息,感觉效率有点浪费,应该可以抽出来才对,后续可以优化。

v2-b7b9ceeacde2b11e148f2813e0d294da_b.jpg

然后呢,每帧检测对手的位置并调用移动或攻击的方法。

v2-0d75623f701128d59fb703b0120f048a_b.jpg

我这里之所以用继承Enemy,就是因为玩家,队友AI,敌人AI的行为模式是一样的,所以我可以把方法抽象出来放在基类Contestant里。值得注意的是,这里Mathf.Sign()返回值只有1和-1,如果参数为0时,返回值也是1.和C#System.Math.Sign()不太一样。

v2-7d4ca3ffe984c1d297a5722f04a6cc1f_b.jpg

然后去调用获取和目标的偏移量,由于需要知道自己要不要动,所以是个bool类型的返回值,而偏移量用out参数来返回了。在这里遇到一个大坑,之前错误的把xDvalue的返回值定义为了目标需要移动向x的点,也就是xPoint,而不是x的偏差值。所以out参数返回的值是xD...导致目标一移动就一直超一个方向停不下来。。打断点调了老半天才发现代码逻辑没问题,是参数的意义定义错了...

v2-fb5a2ba5ed6b8c04d8466816d5835d40_b.jpg

判断目标是否可以移动。

v2-a992f9e7a848225ebe7a42f40f71e11f_b.jpg

好了,之前的复盘差不多就到这儿了,之后会新开文章继续更新开发中遇到的坑坑洼洼。

最后演示一下现在的效果。

v2-d7c5671124fe756272f3c0425de0a05c.jpg
未完工的Demohttps://www.zhihu.com/video/1213884467363930112

好了,相信你也看到里面的各种bug了- -后续我会继续修(sheng)复(chan)bug的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值