Ilingis 发表于 2023-2-2 07:06

游戏开发工具箱(1) 打造游戏感的利器——Unity Feel 插件浅析

前言

你是不是曾在GameJam最后的1个小时里,还在为游戏的手感不佳、打击感不强而抓耳挠腮?
你是不是也曾在开发游戏Demo时,苦于要解决玩家反馈的“操作好飘”、“反应太迟钝”等问题却没有头绪?
那么,这款Unity商店中能够【帮你快速实现各种交互和反馈效果】的Feel插件,将是你的不二之选。


简要介绍

Feel是什么?

Feel是一款开箱即用的为游戏提供游戏感(反馈)的Unity平台下的工具插件。
模块化的设计,易于扩展;编辑器下的预览,十足便捷。
在《游戏感——游戏操控感和体验设计指南》一书中,Steve Swink将游戏感拆解为了——实时操控(real-time control)、模拟空间(simulated space)和润色(polish)——三个基本要素,在本文中提及Feel插件相关的游戏感,主要在润色层面。
游戏感是一个比较有深度的话题,感兴趣的读者可以在文末的扩展阅读中,了解更多如何在设计层面上为游戏注入游戏感的内容。
Feel解决了什么痛点?

游戏感是为玩家打开游戏魔法门的钥匙。
当玩家在游戏中有所行动时,合理的反馈,比如一个受击时刻的镜头晃动、一个物体起跳前的弹性缩放都令人更加沉浸其中。


虽然在讨论游戏感时,感受是主观的,但Feel将其拆解成了100+个基本的反馈操作(MMF_Feedback),主要覆盖了 音频、镜头、动画、GameObject、特效、后处理、UI、文字等方面。所有的反馈操作可以在文末的扩展阅读中查看。
像拼积木一样组合好这些反馈操作,游戏感便跃然纸上了。
核心概念

反馈器(MMF_Feedback)

MMF_Feedback是所有反馈行为的基类,处理了反馈效果的基于时间的状态管理(播放、暂停、恢复、停止等)。
不同反馈器的参数大相径庭,以一个旋转(Rotation)操作为例,我们看下它的参数配置:


这是一个在0.2秒内,把BgWhite对象在Z轴上按照Animate Rotation Z曲线进行旋转的操作。
点开Timing页签,有更细致的 时间模式、延迟、冷却、重复、正反向播放等微操配置。


播放器(MMF_Player)

MMF_Player是MMF_Feedback的容器及管理器,开发者在MMF Player组件下配置好所需的Feedback操作,在运行时调用该实例的PlayFeedbacks()即可触发效果的播放。


在Player播放时,该组件可以显示当前所有Feedback的播放状态。可以看作是另一种形式的timeline。


振动器(Shakers)

MMF_Player既可以控制游戏实体的表现,也需要能控制游戏内的,像是镜头(Camera)、音频监听器(Audio Listener)等组件。出于解耦的考虑,Feel在镜头、音乐监听器上添加Shaker来监听Feedback抛出的广播。对,Shaker就是特定Feedback的Listener。
信道(Channel)

每个Shaker和触发Shaker的Feedback都有一个信道(Channel)参数,信道用来控制Feedback触发哪个相匹配的Shaker。



Shaker监听128信道



Feedback广播消息到信道128

暂停器(Pauses)

默认情况下,Player管理的所有Feedback同时播放。Feel插件中有两种类型的暂停器,分别是Pause和Holding Pause。
Pause会和其他在它之上的Feedback一起被激活,而之后的Feedback会在Pause执行之后再执行。在Feedback列表中添加Pause,相当于为Pause之后的Feedback统一增加一个延迟时间。



Pause的执行

Holding Pause在等待其之上(前面)的所有操作完成之后才会执行。在Feedback列表中添加Holding Pause相当于等待之前的所有Feedback完成,再等待Holding Pause的Pause Duration之后才继续执行。


循环(Loops)

虽然在所有的Feelback的Feelback Setting->Timing下,可以通过设置Repeat相关的选项来控制Feelback的循环播放;但如果想循环播放一系列的Feelback操作,就需要使用Looper操作了。


可以通过Looper操作中Loop的配置,配置(1)从上一次最近的Pause到Looper之间的操作进行重复播放,或者(2)从定义好的Looper Start操作到Looper之间的操作进行重复播放。


比如上图,就是重复播放Looper Start和Looper之间两个的操作无限循环下去。
使用说明

导入插件

Feel插件支持Unity2019.4.3及以上的版本。以下以Unity2020.3.39f1为例,新建一个默认的3D项目。



创建一个空3d项目

然后打开Package Manager,下载并导入已经购买的Feel插件。



从Package Manager中导入Feel插件

Feel插件依赖了几个Unity的包资源,如果想预览所有Demo,或者实际应用中会用到这些功能,可以导入。(不导入也不会影响到其他功能的使用)



导入Feel依赖的所有包资源

依赖的四个package分别是:Cinemachine、Postprocessing、TextMeshPro和2d Animation。
在Assets/Feel/FeelDemos/目录下是部分Demo,我们随机打开一个Barbarians/FeelBarbarians.unity的场景,可以正常运行,即Feel插件导入成功。


使用插件示意

使用Feel的流程分为两步,一步是通过MMF_Player建立Feedback列表,另一步是触发MMF_Player播放效果。现在我们来一步一步实现一个方块受击的反馈。
我们在这个示意中将为方块添加4个反馈效果:击退、闪白、飘字和镜头摇晃。
(1)准备工作:
新建一个场景,在场景中创建一个Cube名为Enemy。



创建一个Cube,命名为Enemy

为Enemy添加MMF Player组件。



为Enemy添加MMF Player组件

将MMF Player的Initialization Mode设置为Awake。



设置Initialization Mode为Awake

(2)添加击退位移效果
为MMF Player添加一个击退的位移Feedback。



添加Position反馈

为Position->Position Target->Animate PositionTarget绑定Enemy实体。



将Target绑定为Enemy

通过修改Position->Transition下的配置,调整物体根据曲线进行位移的效果。
Transition->Mode 选择Along Curve,表示物体在Animate Position Duration的时间内,根据Animate X/Y/Z下的曲线进行位移。位移的幅度(曲线的的范围)为。



设置位移遵循曲线设置

点击Unity的运行,在Hierarchy窗口选中Enemy,在Inspector窗口点击MMF Player下的Play按钮,即可看到方块受击的位移反馈。



编辑器下预览所有反馈效果



击退效果预览

(3)添加闪白效果
既然要闪白,我们就得先把Enemy变个颜色。新建一个材质球名为SlashMat,设置Shader为MoreMountains/MMToon。



新建材质并设置Shader为MMToon

修改材质球的DiffuseColor为红色,勾选Emission选项,将EmissionColor设为白色,EmissionForce设置为0。



修改SlashMat材质的表现

将新的SlashMat材质球赋给Enemy。



将SlashMat赋给Enemy的Mesh Renderer

然后我们为Enemy添加一个MMBlink组件。修改Method为Shader Float Value,将Enemy赋给Target Renderer,将Shader Property Name改为_EmissionForce,并新建一个Phases,赋值如下图。



为Enemy添加MMBlink组件,并修改配置

接下来我们在Enemy的MMF Player中添加新的Renderer/MMBlink的反馈。



给MMF Player添加MMBlink反馈

将Enemy赋给Blink->Target Blink,然后点击Grab Duration From Blink Component,它会将Enemy身上的MMBlink的Phase Duration的配置自动同步。



绑定MMBlink的Target,手动同步Duration

点击Unity的运行,在Hierarchy窗口选中Enemy,在Inspector窗口点击MMF Player下的Play按钮,即可看到方块受击的位移+闪白反馈。



击退+闪白效果预览

(4)添加飘字效果
在场景中新建一个空GameObject命名为FloatTextSpawner,为其添加MMFloatingTextSpawner组件。这个组件是创建飘字的工厂(池)。



新建飘字工厂,并添加MMFloatingTextSpawner组件

为Pooler->Pooled SimpleMMFloatingText 附上一个飘字的Prefab。我们直接用工程中已有的Prefab:Assets/Feel/FeelDemos/Tactical/Prefabs/FeelTacticalSimpleFloatingText.prefab。



为MMFloatingTextSpawner组件绑定飘字Prefab

可以修改Spawn Settings 的Lifetime来改变飘字效果持续的时长。修改Spawn Offset来改变飘字的初始偏移。



调整飘字持续时长和偏移

可以修改Animate Position来调整飘字的移动距离。



调整飘字位移曲线

可以修改Animate Scale来调整飘字的缩放。



调整飘字缩放

配置完成飘字生成器后,我们开始为Enemy的MMF Player添加UI->FloatingText的反馈效果。



为MMF Player添加Floating Text反馈

点击Unity的运行,在Hierarchy窗口选中Enemy,在Inspector窗口点击MMF Player下的Play按钮,即可看到方块受击的位移+闪白反馈+飘字效果。



击退+闪白+飘字效果预览

(5)添加镜头摇晃
在场景中新建空GameObject命名为CameraRig,在它之下建立空GameObject命名为CameraShaker,将Main Camera作为CameraShaker的子节点。



更改Main Camera的嵌套

为CameraShaker添加MMCameraShaker组件。



为CameraShaker添加MMCameraShaker组件

MMCameraShaker会为CameraShaker附加添加上MMWiggle组件。


在MMWiggle组件中,我们勾选上Position,取消勾选Wiggle Permitted,设置Wiggle Type为Noise。增加Amplitude的振幅。



调整默认的镜头位移配置

设置完成振动器后,我们为Enemy的MMF Player添加新的Camera/Camera Shake反馈效果。



为MMF Player添加Camera Shake反馈触发器

配置镜头晃动的时间、振幅、频率和不同轴向上的振幅比例。



调整镜头摇晃的参数

点击Unity的运行,在Hierarchy窗口选中Enemy,在Inspector窗口点击MMF Player下的Play按钮,即可看到方块受击的位移+闪白反馈+飘字+镜头摇晃的效果。



击退+闪白+飘字+镜头摇晃效果预览

(6)见证奇迹的时刻
做到上面的几步,虽然好像加上了反馈,但是效果却好像什么也没加,难道是加了个寂寞吗?


让我们为场景添加一个地面(此处我从Feel的其他Demo中直接借来了一个地面)
再次运行:



见证魔法

是不是有点Feeling了?
再加上开枪的音效和击中的特效,当当当当~



加点调料

是内(那)味了。
在结合了击退、闪白、飘字、镜头摇晃再加上音效和特效之后,最终受击的反馈便有Feeling了。
(7)通过UI Button触发反馈(示意)
在Hierarchy窗口中新加一个Button。



添加Button

微调Button的位置和文字,然后将选中Button,在OnClick列表中添加一个新条目。



添加一个OnClick条目

将Enemy拖拽到OnClick新条目上,选择触发的Function为MMF Player->PlayFeedbacks()



绑定触发反馈效果

点击Unity的运行,点击Button即可触发反馈效果播放。


(8)通过代码触发反馈(示意)
在工程中新建一个MonoBehavior脚本,命名为TestClickTrigger,编写如下内容:
using MoreMountains.Feedbacks;
using UnityEngine;

public class TestClickTrigger : MonoBehaviour
{
    private MMF_Player _enemyPlayer;

    private void Awake()
    {
      _enemyPlayer = GetComponent<MMF_Player>();
    }

    private void OnMouseUp()
    {
      if (_enemyPlayer)
      {
            _enemyPlayer.PlayFeedbacks();
      }
    }
}
为Enemy添加上TestClickTrigger组件。



给Enemy添加TestClickTrigger组件

运行Unity,鼠标点击到Enemy即可触发反馈。



鼠标点击Enemy触发反馈效果

编辑器预览

虽然上文中【使用插件示意】的图文内容一气呵成,但实际我们在使用Feel插件的过程中,最多的时间是用来在运行时进行效果调整。Feel在编辑器上提供的预览功能极大地提高了编辑效率,值得好评!
前文中我们在运行时,通过点击MMF Player的All Feedbacks Debug的Play按钮来预览效果。



通过All Feedback Debug来预览

实际上,每一个Feedback页签内部都有一个Play按钮,可以单独调试相应的反馈效果。



Feedback单独播放效果

当调整完成,得到合适的参数,点击Keep Playmode Changes按钮即可保存运行时的修改。(这一步真是贴心)



保存运行时修改

在Shaker、MMBlink和MMFloatingTextSpawner等组件上,也都有预览选项,此处就不再吝述。
代码控制

通过代码修改MMF_Player的MMF_Feedback上的参数,就像是通过GameObject修改挂载其上的Component。我们为前面编写的TestClickTrigger添加一个功能,在播放反馈前修改飘字的文字和颜色。
using MoreMountains.Feedbacks;
using UnityEngine;
using Random = UnityEngine.Random;

public class TestClickTrigger : MonoBehaviour
{
    private MMF_Player _enemyPlayer;
    private MMF_FloatingText _floatingText;
   
    private Gradient _normalHitGradient;
    private Gradient _largeHitGradient;

    private void Awake()
    {
      _enemyPlayer = GetComponent<MMF_Player>();
      _floatingText = _enemyPlayer.GetFeedbackOfType<MMF_FloatingText>();
      
      InitFloatingTextColor();
    }

    private void InitFloatingTextColor()
    {
      _normalHitGradient = new Gradient();
      var normalGradientKey = new GradientColorKey;
      normalGradientKey.color = Color.white;
      normalGradientKey.time = 0.0f;
      normalGradientKey.color = Color.white;
      normalGradientKey.time = 1.0f;
      var normalAlphaKey = new GradientAlphaKey;
      normalAlphaKey.alpha = 1.0f;
      normalAlphaKey.time = 0.0f;
      normalAlphaKey.alpha = 0.0f;
      normalAlphaKey.time = 1.0f;
      _normalHitGradient.SetKeys(normalGradientKey, normalAlphaKey);
      
      _largeHitGradient = new Gradient();
      var largeGradientKey = new GradientColorKey;
      largeGradientKey.color = Color.yellow;
      largeGradientKey.time = 0.0f;
      largeGradientKey.color = Color.red;
      largeGradientKey.time = 1.0f;
      var largeAlphaKey = new GradientAlphaKey;
      largeAlphaKey.alpha = 1.0f;
      largeAlphaKey.time = 0.0f;
      largeAlphaKey.alpha = 0.0f;
      largeAlphaKey.time = 1.0f;
      _largeHitGradient.SetKeys(largeGradientKey, largeAlphaKey);
    }

    private void OnMouseUp()
    {
      if (_enemyPlayer)
      {
            SetRandomFloatText();
            _enemyPlayer.PlayFeedbacks();
      }
    }

    private void SetRandomFloatText()
    {
      if (_floatingText == null)
      {
            return;
      }

      var randomVal = Random.Range(1, 100);
      _floatingText.Value = randomVal.ToString();
      _floatingText.ForceColor = true;
      _floatingText.AnimateColorGradient = randomVal < 50 ? _normalHitGradient : _largeHitGradient;
    }
}



修改飘字后的效果

其中关键的代码是通过MMF_Player获取其上的MMF_FloatingText:
_floatingText = _enemyPlayer.GetFeedbackOfType<MMF_FloatingText>();
通过_floatingText修改飘字的大小和颜色:
var randomVal = Random.Range(1, 100);
_floatingText.Value = randomVal.ToString();
_floatingText.ForceColor = true;
_floatingText.AnimateColorGradient = randomVal < 50 ? _normalHitGradient : _largeHitGradient;
通过代码修改Feedback上参数的步骤就是以上内容。具体到每一个Feedback相关的参数和接口,可以通过下方文档进行查询:
注意事项

Feel插件在制作游戏Demo以及参加GameJam时请放心使用,但是在制作上线稍大规模的游戏项目时,需要谨慎使用。
限制Feedback的使用范围

简单拖拽就能处理各种绑定关系,看似方便了开发者,可是随着时间和项目规模的变迁,不经限制的绑定关系将变成一场灾难。这不是Feel插件的问题,而是本身Unity在设计方面的灵活性导致的。
只要我们保证MMF_Player的所有Feedback(除去Shaker触发监听)处理的绑定关系都是其子节点,即可防止混乱的绑定关系。
Feel只用于表现,不用于逻辑

虽然Feel插件的功能可以天花乱坠地处理各种表现和异步操作,但是请只用它来处理表现上的反馈。务必不要让游戏逻辑依赖于Feel的反馈。理想情况下,我们一键移除所有的MMF_Player的Feedback,虽然表现上的效果没了,但是逻辑应该还能够正常运行。
如果你使用了Feel上的各种回调来耦合游戏的逻辑,等到有一天项目要增加联网模式时,请不要挠头发。


注意资源引用和性能

在制作Demo时我们可能拖拖拽拽不用考虑太多,但是一旦开始正式制作游戏项目,资源管理和性能优化就是我们不能忽略的重点。使用Feel插件时,要像没有使用它时一样小心去处理资源的依赖和引用。如果本身Feel内置的池没有满足需求,我们可以通过自己重写Feedback和使用自己的Pool Manager来提高性能。
其他

虽然通篇没有提到时间线(Timeline)和曲线(Curve),但这两个元素才是Feel反馈能够提升游戏感的关键因素。它们在Feel中无处不在以至于容易被忽略。就像空气一样,虽然没有存在感,但却是最最不可或缺的。
如果你还是一个游戏开发的萌新,不妨一边使用Feel,一边学习如何打造更好的游戏感。
如果你已经开发游戏有些年头了,或许Feel的设计思路和对工作流的改进,能为你提供一些新鲜的思路。
游戏感是一个令游戏开发者着迷的话题,我们以后再接着聊它的方方面面。
以上。
扩展阅读

《游戏感——游戏操控感和体验设计指南》

这是一本讨论游戏感的书籍,主要讲述了如何来打造出色的游戏体验。


下面的链接是关于这本书的简要介绍。
Best Practices for fast game design in Unity - Unite LA

这是Feel插件的作者在2018年Unity开发者大会上做的关于快速游戏原型开发的分享。


他在2019年的Unity开发者大会上还做了一场更细致的“How to design with feedback and game feel in mind”的分享。



Juice it or lose it - a talk by Martin Jonasson & Petri Purho

这是Martin Jonasson和Petri Purho在开发者大会上的分享,通过使用不同的Tween曲线,从0到1为一个简单的打砖块游戏赋予了游戏感的灵魂。


The art of screenshake at INDIGO Classes 2013

这是Jan Willem Nijman在INDIGO Classes 2013上做的为一款平平无奇的2d横屏射击游戏Demo注入游戏感的分享。


Feel官方文档

下图便是当前所有Feel支持的反馈操作。

页: [1]
查看完整版本: 游戏开发工具箱(1) 打造游戏感的利器——Unity Feel 插件浅析