找回密码
 立即注册
查看: 387|回复: 1

从零开始独立游戏开发学习笔记(二十五)--Unity学习笔记 ...

[复制链接]
发表于 2022-4-27 18:34 | 显示全部楼层 |阅读模式
很抱歉,最近花了一点时间稍微玩了下某款热门游戏。
更新慢很抱歉,但实在是太好玩了,虽然我不喜欢宏大叙事,但是无奈它探索感做的太好,美术实在太棒。
游玩时间,敬请见证。




不过不管怎么说,至少小狐狸教程还是做完了(
1. Class 调用

首先按照创建青蛙的逻辑创建老鹰,很简单就不说了。
1.1 爆炸效果

我们想让敌人在消灭前播放这个动画,爆炸效果的制作就不说了。着重说一下 animator 里之后的做法。 在 Animator 里设置一个 trigger 变量,而不是之前一直使用的 bool 变量。
1.1.1 trigger

trigger 和 bool 不一样的是,当 trigger 被触发后,会立刻回到 false 状态。
1.1.2 在 player 里改变 enemy 的状态

我们的消灭敌人的代码是在 player 的 script 里的。但是我们要改变的并不是 player 的动画而是 frog 的动画。因此需要获取到对方的 animator。
当然,因为在 collider 里已经给出了碰撞体的信息,可以通过 collision.gameObject.GetComponent<Animator>() 来获取,不过此时我们换一种思路,让死亡的逻辑写在 enemy 上而不是 player 上。方法就是我们在 frog 里写一个 public 函数,然后在 player 里调用这个函数。但是如果这么写的话会遇上这么个情况:


ide 会报错,因为对方并没有 Die 这个方法。所以应该在上面获取 frog 这个 class。


以及,在 frog 里,当然不能在动画后立刻衔接 destroy,因为要等动画播放完。因此 Destroy 要使用 event 在动画播放完后调用,这个之前讲过就不再提了。
1.2 继承

不可能每一类敌人都要写一次销毁函数。因此我们新建一个 enemy 类来让这些功能复用。 这里就需要使用代码了:

  • 给 Enemy 添加 Animator 变量,并在 Start 中赋值。全部给 protected 是因为这两个属性方法会在子类中被调用。其中 Start 会重名因此加上 virtual 以便 override。



2. 再回看 EnemyFrog 类,把继承的类从 monobehaviour 改成了 Enemy,并引掉了之前的 animator 变量和赋值的代码,此外,还调用了 base.Start() ,因为不调用的话 Enemy 里的 Start 并不会调用。


3. 对 EnemyEagle 做同样的事。
2. audio 音效

首先点开 main camera,会发现里面已经有了一个 audio listener 的组件。这个是用来收集声音的。
一般来说 audio 分为 listener 和 source。listener 自动集成在 main camera 上。然后在游戏里摆放各种 audio source 来让 listener 听。
2.1 人物自带 BGM

给 player 添加一个 audio source 组件,然后将 BGM 拖进 audio clip 属性中即可给 player 绑定一个随时播放的 bgm。




2.2 消灭音效

要给敌人添加消灭音效。先添加一个音效,然后去掉 play on awake 和 loop 因为只想在爆炸的时候播放。 随后在代码里获取 AudioSource 组件后调用 Play 方法即可。




2.2.1 快捷键重命名

ctrl + r (两次),可以给当前变量批量改名。比 ctrl + f 的好处是会自动判断是否同一个变量(毕竟是强类型语言)。
2.3 受伤音效,拾取物品音效

如果添加多个音效的话,GetComponent 如何分别呢。教程里给的方式是换用 public 变量,这样在外面拖组件就行了。




3. 对话框 dialog

通过 panel 来写,大部分之前基础课程学过了,直接看效果:



大概流程为:

  • 新建 panel,加上文字,修改字体。
  • 在脚本里使用 setActive 来实现 panel 的出现和消失。
  • UI 动画通过录制制作。
4. 趴下动画

4.1 按键

Project Setting->Input 这里可以这样添加新的按键:



动画效果的制作就不再说了,直接略过。
4.2 碰撞体 DEBUG

有时候我们想在游玩的时候看到实时的碰撞体,这个时候就要启用右上角 Gizmos 按键,并在 hierarchy 中选中想要观察的物体。



4.3 碰撞体切换

我们可以看到蹲下的时候,碰撞体并不会改变。因此蹲下的时候,我们把头部的碰撞体 disable 掉。



4.4 上方有障碍物的时候保持蹲下

当我们通过比较矮的地方时,如果松开下蹲键,人物会恢复站立,如图:




因此我们需要添加判断,这里我们采用 Physics2D.OverlapCircle 函数,该函数会判断一个点周围是否含有某个 LayerMask。我们给 Player 添加一个点,然后判断这个点周围是否有障碍物即可:




5. 场景切换

这个在之前的基础教程里讲过,不过附带一些小知识。
5.1 如何引入 GameManager

Unity 会把命名为 GameManager 的脚本图标换成齿轮。GameManager 一般用于管理场景切换(重新开始,胜利,失败,等等)。不过如何在其他脚本里引入这个脚本呢?
5.1.1 public

首先尝试新 public 一个 GameManegr 类:


然后尝试在外面把脚本拖进去,但是其实拖不进去,select 选项告诉我们整个项目都没有能拖进去的东西:


原因就出在脚本只是脚本,是一个非静态类,并不是实例。想要起作用还是要实例化。因此我们创建一个空物体,上面引入 GameObject 脚本:


现在我们终于就可以拖进去了。
(实际上也可以弄成静态类或者静态方法,这样就不用在 unity 里拖来拖去了,但是我们要用 Invoke,Invoke 是非静态方法,所以还是得实例化)



5.1.2 多个脚本的 object

一个 object 上可以挂载多个脚本,这种 object 同属于多个类。比如说刚刚的空物体,我们可以添加一个 enemy 脚本,这样 GameManager 也可以作为 Enemy 类被引入(当然没有必要,这只是为了演示效果)。





5.1.3 tilemap 小缝隙

有些时候 tilemap 制作的地图之间会有缝隙,此时将 grid 的 cell size 的 x,y 值调成 0.99 即可解决。
5. 2d 光效

项目创建的时候并没有选择 URP,这里就先做一个简单的光效: 先创建一个 material,shader 换成 sprite/diffuse


将其赋与场景所有物体,会发现物体都变黑了,此时终于可以添加光效了,在 heirarchy 里直接添加一些简单的即可,效果如下:


有时候光源不亮是因为其原理还是 3d 光照,到 3d 界面看一下是不是离界面太远了,改一下 z 轴即可。
被光照的背景有了缝隙,反而需要我们把 grid 的 cell size 从 0.99 改回 1,如果还有可以去 project setting->graphic 里将抗锯齿关掉。
6. 优化代码

6.1 敌人销毁

销毁动画最后我们加了个触发器来 Destroy 敌人。但是当时我们触发器事件放在最后一帧后面的一些位置,我们应该和最后一帧放一起,以让动画更加顺滑。
7. 视觉差 parallax

接下来要做人尽皆知但又酷炫的视觉差效果。
7.1 cinemachine 更换跟随

之前 cinemachine 的边界是挂在 backgroud 上的,但是现在我们要让 background 移动了,因此我们得换个地方。新建一个空物体,把 background 的 collider 组件和 position 复制过去即可。
7.2 代码

新建一个 Parallex 脚本,以供复用。 public 一个 transform 用于记录 camera 的位置。以及一个速率以供调整。最终代码如下:



当 FollowRate 为 1 的时候,背景和人物同时移动,背景相对人物静止。所以我们只要给背景和中景不同速率即可。
8. 主菜单


  • 新建一个 scene,添加 UI panel。
  • 如果不想用图片,给 panel 的 background 设置为 None,再设置 color 纯色背景。
  • 如果要用图片,将 background 换成我们想要的图片,再调整 color 为我们想要的感觉。
  • 想要给 button 绑定事件,就必须有一个 object,引用这个 object 上的函数。因此我们给 canvas 添加一个脚本,在脚本里写函数,然后从这个 object 里读取函数。(无法直接读取脚本,必须得有一个实例)
9. 暂停菜单

和主菜单区别不大,就是些 UI 操作。 不过涉及到暂停和音量调节。
9.1 暂停

通过改变 timescale 来暂停:



9.2 音量改变

通过 AudioMixer 来实现:

  • 创建一个 AudioMixer,发现 inspecter 窗口属性很少,因为主要在 window->audio->audio mixer 面板里。
  • 可以看到,-80 是最小值,0 是正常音量。因此我们给之前 UI 里的 slider 调整下最小值和最大值。
  • 在 player 当时创建的 audio source 里,给 output 里加上刚刚创建的 audiomixer,audiomixer 会自动创建一个 master 的 group,选那个就行。
  • 然后我们想在脚本里修改,但是发现 audioMixer 根本无法添加组件,也就无法添加脚本。那么对于这样的情况,我们有一个办法,就是将想要调整的属性 expose 出去,这里我们想要修改 volume,因此右键 volume 属性即可看到。



5. 然后在这里可以修改变量名:


6.随后我们在代码里写一个函数,这个函数即将作为 slider 的事件函数:


SetFloat 用于设置某属性的值,第一个参数为刚刚我们命名的变量,第二个为想要赋予的值。这里 value 是slider 事件提供的参数,也就是滑动条的值。 7.添加的时候要小心,这里有两个 SetMasterVolume,我们用的是上面的。如果使用上面的话,value 会由滑动条决定。如果选下面的话会让你填一个固定值,value 为你预先设定好的这个固定值。



有些版本没有上面的 dynamic float,此时可以在函数里直接获取 slider 的值即可。毕竟 slider 也只是个组件。
10. 手感调整

10.1 FixedUpdate

之前提到过 FixedUpdate 是一定每一段时间执行一次,但其实并不是。试想一下你电脑就是 literally 很卡,FixedUpdate 怎么可能保证的了。它只能尽量去保障,并且如果你电脑不是经常卡,而是某一段时间突然卡一下,那么接下来 FixedUpdate 会一下次执行好几次,以便保持 1s 内执行的次数是固定的。比如说本来应该 0.2s 执行一次,结果某次突然卡了 1s,那之后就会立刻执行 5 次 FixedUpdate 来弥补。
10.2 跳跃手感调整,二段跳

用和下蹲的时候同样的方法来检测人物和地面的接触,以此来判断跳跃动作。

  • 给人物添加一个地面监测点。
  • 代码里添加 jumpPressed 和 jumpTimes,前者是为了在 update 里读取在 fixedUpdate 里跳跃。后者是为了二段跳。



3. 检测检测点周围是否有 ground 图层。


4. fixedUpdate 代码调整,添加了二段跳逻辑,以及 isGround 判断来代替 isTouchingLayer。


5. 二段跳动画处理



10.3 单向平台

所谓单向平台,就是可以跳上某一个平台,并在跳跃的时候无视这个平台障碍物。也就是说可以从平台下方直接跳上来。

  • 首先随便创建一个平台。创建 collider。
  • 勾选 collider 中的 used by effector
  • 添加一个 platform effector 2D,没错,单向平台是一个内置组件,不需要我们手动实现。
  • 勾上 use one way 即可。
10.4 bug 修复

(这里内容来自 Brackeys)
10.4.1 从高处跳下会被卡住:

这是因为速度太大,碰撞的那一帧没有判断到,在 player 的 rigidBody 里将 collision detection 改成 continuous。



10.4.2 卡在平台边缘

这是因为头部的 box colllider 比身体的 circle collider 大,这样头部就会被卡住。 修改方法就是把身体的 collider 调整为略微大于头部即可。
11. 音效管理 SoundManager

作者觉得 player 身上挂了太多 audioSource 了。想解离出来:

  • 首先和 GameManager 一样,先创建一个 object,上面创建 SoundManager 脚本。
  • 添加一个 AudioSource 组件,我们想让之后通过更换这个组件的 clip,再播放,达到只使用一个 audio source 的效果。






  • 把之前的 audio 相关组件代码都删掉。
  • 添加播放 bgm 的方法。



5.那么之后想要播放 bgm 的时候,只需要找到 gamemanager 播放即可。
11.1 单例模式

之前使用 gamemanager 的时候,每次我们都要引入 gamemanager 那个 object,拖拽再调用。
我们当然也可以吧 gamemanager 或者 soundManager 设置为静态类,这样就不用每次拖拽了。(前者因为要用 invoke 还不能用静态类)
不过除了静态类,还有一种方式不用引入也能调用方法,那就是单例模式。 将代码改成如下:
(注意:单例模式依然要实例化挂载一个空物体上,只是不用拖拽了)



注意标红的位置:

  • 首先创建了一个静态变量,类型为自身。
  • 在 awake 生命周期函数里让 instance 等于 this。也就是 instance 为实例本身。
  • 也就是说,这个类有一个静态变量可以被外面调用,而这个静态变量里储存的值刚好是自身的实例化。因此可以通过这个 instance 来调用方法。因为 instance 就是一个 soundManager 的实例。
接下来我们就可以非常简单地播放 bgm 了,如下图,无需 public 后拖拽:



同样的方式我们可以用来创建剩余的 audio,甚至还可以把之前的 GameManager 也优化了。
11.2 字段优化

刚刚创建的那么多 clip,其实不太想设置成 public,不然代补补全东西太多了,但是我们又想在外面拖拽,那么就需要用 SerializeField 字段了:



这样即使是 private ,也可以在外面拖拽了,但是仍然能够保持在别的代码里无法访问的限制。
11.3 BGM 同时播放

刚刚把bgm放进去其实并不好,因为只能同时存在一个声音,如果跳起来的话,bgm 就没有了。因此 bgm 应该单独拿出来。



11.4 锁定 inspector

如果实际操作会发现,每次换音频,insepector 都会变一下,每次都要点回成 soundManager 的 insepector,很麻烦。可以选择 inspector 右上角的锁来锁定住。
12. build

之前基础教程里很详细了,不再赘述。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-4-27 18:40 | 显示全部楼层
沙发[欢呼]
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-16 18:05 , Processed in 0.094598 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表