找回密码
 立即注册
查看: 760|回复: 0

[简易教程] 初学者用Unity重制魂斗罗 - 第2节 比尔的动作、跑动、射击、卧倒、跳跃

[复制链接]
发表于 2024-8-2 09:44 | 显示全部楼层 |阅读模式
本小节我们开始进入更有意思的环节,进一步控制比尔,让它做出跑步、跳跃、卧倒等等动作,满足你的控制欲。  
# 章节目录


  • 第1节 介绍开发环境、瓦片地图制作、精灵、角色的运动,碰撞体
  • 第2节 比尔的动作、跑动、射击、卧倒、跳跃
  • 第3节 状态机介绍、实现简单有限状态机,补完角色动作
  • 第4节 发射子弹、仇敌
# Unity动画中的几个主要概念

凡是人物的动作通过动画完成,Unity的动画功能辅佐开发者更便利的实现动作效果,尤其是复杂的3D人物动作。但在这个教程中,我们的动画都是简单的动作,甚至不需要Unity Animation的强大功能也可以实现。尽管如此,了解Unity的动画机制仍是十分必要的。它可以辅佐你在其他游戏中节省大量的时间和精力。下面让我们开始了解”动画”。

  • 旧动画组件(Animation)
    Animation是传统的动画组件,在旧版的Unity中采用这个组件引入动画机制。此刻保留Animation仅仅是为了对旧版的兼容。对于当前版本的Unity最好使用”动画Animator”组件。  
    这是Unity官方资料的内容。所以不才面的内容中不会涉及Animation。
  • 动画剪辑(Animation Clips)
    动画剪辑(Animation Clips)是动画的基础对象,可以通过在**时间线**上来改变”游戏对象”的**属性**,从而使”游戏对象”在一段时间内呈现出变化的效果。上面这句话有两个重要的概念”**属性**”和”**时间线**”,这两个概念组合在一起就构成了千变万化的形式。  
    动画剪辑(Animation Clips)不是一个**组件**,是一个对象,它需要装配到”动画状态(Animator State)”才能使用。
  • 动画组件(Animator)
    动画组件(Animator)作为桥梁,将”游戏对象”、”代码组件”与”动画控制器(Animator Controller)对象”链接起来。使得他们之间可以彼此通讯和控制。
  • 动画状态(Animator State)
    动画状态(Animator State)构成”动画控制器(Animator Controller)”的基础元素,通过多个”状态”和状态之间的”转换”,Animator可以实现切换分歧的状态从而播放此中装配的”动画剪辑”。
  • 动画控制器(Animator Controller)
    动画控制器(Animator Controller)是动画的核心对象,它不是一个组件,而是一个对象,需要装配到”动画组件(Animator)”才可以被”游戏对象”使用。  
    动画控制器(Animator Controller)通过配置的状态和条件,选择某一个”动画剪辑(Animation Clips)”播放动画。同时通过”动画层(Animation Layer)”的机制可以同时播放多个”动画剪辑(Animation Clips)”。实现复杂的动画效果。
动画层(Animation Layer),不是”游戏对象(GameObject)”的”层(Layer)”,它们是两个完全分歧的概念。  

# 添加”动画组件(Animator)”  

选择游戏对象”角色-站”,此刻我们在”查抄器”中将最上的名字改为”比尔”,以后我们就叫它”比尔”。  
给比尔添加组件”Animator”。其属性如下:  

  • Controller:组件对应的”动画控制器(Animator Controller)”
  • Avatar:用于映射人体模型骨骼的虚拟布局,这个一般用于3D人物的运动。我们的主角名叫”比尔.史莱姆”所以他没有骨骼。
  • Update Mode:动画组件何时计算动画效果对”游戏对象”属性的改变。

    • Normal:在`Update`函数执行后。而且当`Time.timeScale`变化时,动画速度跟着做调整。
    • Animate Physics:在`Fixed Update`后执行。
    • UnScaled Time:与”Normal”类似,但`Time.timeScale`变化时,动画速度不变。

  • Culling Mode : 当游戏对象不进行衬着时,例如在摄像机窗口之外。是否仍进步履画。

    • Always Animate:始终进步履画。
    • Cull Update Transform:不显示游戏对象时,动画不合错误Transform组件的属性进行改变。
    • Cull Completely:不显示游戏对象时,动画完全遏制。

## 新建一个”动画控制器(Animator Controller)”  

在”Project”窗口成立目录”Asset/Animations”,接下来创建的”动画剪辑”和”控制器”对象都放到这个目录。  
在新建的目录中点击鼠标右键,`”Create->Animator Controller”`。然后定名为`”Player Animator Controller”`。  
选择游戏对象”比尔”,点击”Animator”组件的”Controller”属性右侧的圈圈按钮,选择新建的”Controller”。  


此刻点击菜单`”Window->Animation->Animator”`打开动画组件编纂窗口。找个巴适的位置放好。  



此刻动画组件已经筹备好了,下一步添加一个”动画剪辑(Animation Clip)”,在Clip中要让比尔跑起来。
# 比尔跑动


## 让比尔跑起来


选中游戏对象”比尔”,点击菜单`”Window->Animation->Animation”`(或者直接按`ctrl-6`,最好记住以后常用),打开动画剪辑窗口。  


由于此刻没有任何剪辑,所以白茫茫真干净。点击中间的”Create”按钮创建第一个动画剪辑。在弹出的文件选择中找到”Animations”目录,剪辑文件放到这里,文件名叫做`”Player-跑步.anim”`。  
点击”Add Property”按钮。


可以看到”比尔”的所有组件及属性都可以选择,此刻我们展开”Sprite Render”组件,选择”Sprite”属性(点击右侧的”+”号)。  
接着我们把比尔的跑步动作对应的精灵拖拽的右侧的”时间线”上。  





  • 首先介绍**帧率**: 帧率(FPS,Frame Per Second)就是每秒播放多少帧。在模拟器中每秒跑60帧,为了尽量的符合原生游戏的效果,这里的动画帧率也设置为`60`。
  • 右侧时间线: 上面的刻度是时间,下面每条竖线是1帧。我们把精灵按照挨次拖到对应的帧上。在模拟器中逐帧计算,每一个动作会持续`8`帧,那么前5个精灵分袂放置到`[0,8,16,24,32]`帧的位置,第6个精灵与第3个一致放到40帧位置。动画剪辑播放到最后一帧后如果循环会当即转到0帧位置继续播放,那么最后要与0帧一致,否则会发生动作跳跃,所以我们把0帧位置的精灵在放到48帧的位置。
  • 凡是1秒位置缺省会有一个精灵,鼠标右键点击上方的菱形删除就可以了。
在模拟器中测量比尔的跑步速度,在60帧中移动了60个像素,所以我们要改削一下代码中比尔的移动速度。
  1. //移动速度(60帧移动了60个像素)
  2. //speed=(60像素)/(16像素/单元)*(60帧/秒)/(60帧)=3.75单元/秒   
  3. float speed = 60f / 16f * 60f / 60f;
复制代码
此刻点击左上部门东西按钮中的”播放按钮”,可以看到”场景(Scene)”窗口中的比尔开始跑步了。跑步动作频率和移动速度的还原度都不错。  
此刻的比尔只能向右跑不能向左跑,我们让比尔转身。
## 让比尔转身,并使用`FixedUpdate`函数

Update还是FixedUpdate
首先让我们引入`FixedUpdate`函数,目前代码全部写在了`Update`函数中。之前介绍过`Update`函数是每一帧执行一次,但如果卡顿会导致丢帧无法保证`Update`函数的调用频率。以使用`FixedUpdate`函数来确保比尔的移动状态不会因为卡顿而呈现错误。  

这里需要做下面几项改削:

  • 在`Update`函数中保留接收按键数据的代码,因为Unity在`Update`函数调用前会读取输入设备。如果在`FixedUpdate`中接受按键数据,则可能呈现虽然按下按键,但`Input`返回值是`0`的情况。
  • 将速度计算和改削刚体属性的代码放到`FixedUpdate`函数。
  • 将`speed`和按键值`axisX,axisY`定义到放到类级变量,这样在`Update`函数和`FixedUpdate`函数中都可以访谒这些属性。
  • 将刚体的对象变量也定义到类级,同时在`Awake`中进行初始化,这样就不用每次调用`FixedUpdate`函数都取得一次刚体对象。
下面插手转身的代码:

  • 定义一个`Vector2`变量,名叫`lookAt`,在取得按键数值后:
  • 如果按下的是左,那么设置`lookAt.x=-1`
  • 如果是右则设置 `lookAt.x=1`
  • 如果没有按摆布键,则保持上一个`lookAt.x`,注意是保持不变,而不是设置为`0`
  • 在`FixedUpdate`函数中按照`lookAt.x`值设置`transform.localScale`的值。
`transform.localScale`在控制器中是”Transform”组件的”Scale”属性,调整属性值会将对象拉伸变形,设置为`-1`则会在对应的轴上反转对象。  
改削后的代码如下:
  1. public class Player : MonoBehaviour
  2. {
  3.     //移动速度(60帧移动了60个像素)
  4.     //speed=(60像素)/(16像素/单元)*(60帧/秒)/(60帧)=3.75单元/秒   
  5.     float speed = 60f / 16f * 60f / 60f;
  6.     //WSAD按键值
  7.     float axisX, axisY;
  8.     //记录比尔面朝的标的目的,默认面朝右侧
  9.     Vector2 lookAt = Vector2.right;
  10.     //定义刚体组件
  11.     Rigidbody2D rig;
  12.     private void Awake()
  13.     {
  14.         // 取得刚体组件对象,只执行一次
  15.         rig = GetComponent<Rigidbody2D>();
  16.     }
  17.     void Update()
  18.     {
  19.         //取得按键
  20.         axisX = Input.GetAxis(”Horizontal”);
  21.         axisY = Input.GetAxis(”Vertical”);
  22.         //按左键面朝左,按右键面朝右,不按键保持当前朝向
  23.         if (axisX < 0)
  24.         {
  25.             lookAt.x = -1;
  26.         }
  27.         else if (axisX > 0)
  28.         {
  29.             lookAt.x = 1;
  30.         }
  31.     }
  32.     void FixedUpdate()
  33.     {
  34.         //按照朝向,改变Transform的Scale属性,只考虑X轴
  35.         Vector2 scale = transform.localScale;
  36.         scale.x = lookAt.x;
  37.         transform.localScale = scale;
  38.         //计算刚体速度
  39.         Vector2 velocity = new Vector2(speed * axisX, rig.velocity.y);
  40.         //设置刚体速度
  41.         rig.velocity = velocity;
  42.     }
  43. }
复制代码
此刻可以将游戏跑起来看看。操作摆布比尔就可以转身了。
一个细节问题
当比尔转身的时候,不会立刻向反标的目的移动,而有一个逐渐减速的刹车效果。这样与原版纷歧致。  
措置方式是,菜单`”Edit->Project Settings->Input Manager->Axes(展开)->Horizontal(展开)”` ,将`Snap`属性勾选。这样转身时就没有刹车效果了。  
再仔细与原版斗劲,发现起步和转身后,跑步速度时逐渐提高的,原版没有这个现象。如果我们要与原版保持细节一致,那么可以将`”Input.GetAxis”`改为`”Input.GetAxisRaw”`,再尝尝看。
## 让比尔停下来休息

此刻的比尔像是一个永动机,动起来就停不下。应该让他休息休息了。选择”比尔”,打开”Animator”窗口。


比尔的”站立”和”跑步”时两个分歧的动画状态,所以我们要添加一个状态。在”Animatro”窗口右侧主界面点击鼠标右键`”Create State->Empty”`,由于比尔的默认精灵就是站立,所以这个状态可以不设置动画剪辑。将这个动画状态改名为”Player-站立”。  
此刻将默认的状态设置为”Player-站立”,右键点击`”Empty->Set StateMachine Default State”`,然后将箭头拉向”Player-站立”,然后点击左键确认。  



下面要让”Player-站立”通过条件转换到”Player-跑步”,我们先来设置条件。  
成立一个”变量(Parameter)”名叫`”Run”`。如果`”Run”`为”真(true)”就开始跑步,如果`”Run”`为”假(false)”就停下来。


点击”Animator”窗口左上部的”Parameters”下拉菜单,选择`”Bool”`类型,给新建变量定名为`”Run”`。  
右键点击`”Player-站立->Make Transition”`,拉向`”Player-跑步”`,点击确认。创建一个从”Player-站立”->”Player-跑步”的转换条件。点击这个转换条件。



  • 点击下面的”+”号,添加条件。设置为`”Run=true”`。
  • 打消`”Has Exit Time”`,并将`”Transition Duraiton(s)”`设置为`”0”`,这样转换满足条件后当即转换状态。否则即使满足条件,动画状态还会有一个退出时间和过度时间,导致不能当即转换状态。
同样添加一个从”Player-跑步”->”Player-站立”的转换,条件设置为`”Run=false”`。  
下面添加代码:  

  • 创建一个`”Vector2”`变量`”keyDirection”`,用来记录按键的标的目的,这和`”lookAt”`纷歧样。当`”keyDirection.x==0”`时`”lookAt.x!=0”`。
  • 按照`axisX,axisY`确定`”keyDirection”`的值,按照`”keyDirection”`确定`”lookAt”`值。
  • 定义`animator`变量。
  • 创建一个专门用于给`”animator”`对象发送动静的函数`”UpdateAnimator”`。并在`Update`函数中调用它。
  • 在`”UpdateAnimator”`中发送动画变量`”Run”`。
改削部门的代码如下:
  1.    ...
  2.    //记录按键的标的目的
  3.    Vector2 keyDirection = Vector2.zero;
  4.    //定义动画组件
  5.    Animator animator;
  6.    private void Awake()
  7.    {
  8.       ...
  9.       //取得动画组件对象
  10.       animator = GetComponent<Animator>();
  11.    }
  12.    void Update()
  13.    {
  14.       //取得按键
  15.       axisX = Input.GetAxisRaw(”Horizontal”);
  16.       axisY = Input.GetAxisRaw(”Vertical”);
  17.       //设置按键标的目的
  18.       keyDirection.x = axisX > 0 ? 1 : (axisX < 0 ? -1 : 0);
  19.       keyDirection.y = axisY > 0 ? 1 : (axisY < 0 ? -1 : 0);
  20.       //按左键面朝左,按右键面朝右,不按键保持当前朝向
  21.       if (keyDirection.x < 0)
  22.       {
  23.          lookAt.x = -1;
  24.       }
  25.       else if (keyDirection.x > 0)
  26.       {
  27.          lookAt.x = 1;
  28.       }
  29.       UpdateAnimator();
  30.    }
  31.    void FixedUpdate()
  32.    {
  33.    ...
  34.    }
  35.    void UpdateAnimator()
  36.    {
  37.       animator.SetBool(”Run”, keyDirection.x != 0);
  38.    }
复制代码
运行看看效果。
# 比尔开枪动作-上半身的发抖

我们先考虑开枪动作,而不是真的开枪发射子弹。比尔的一般姿态下,有`”站立”、”站立射击”、”跑步”、”跑动射击”`四个情况。此中`”站立”、”站立射击”`可以视为同样的。`”跑步”、”跑动射击”`下半身不异,上半身不不异。同时我们在模拟器中不雅察看细节。  


比尔在开枪的时候,受到反冲力影响上半身在发抖。为了还原这个效果,我们需要将上半身和下半成分隔作为两个”游戏对象”,开枪时单独对上半身进行操作实此刻跑步的同时发抖。  
## 分割精灵

选择贴图文件`”魂斗罗素材-通用和第1关精灵.png”`,将比尔站立和跑步精灵的上下半成分成两个精灵。下半身”轴心(Pivot)”不变,上半身”轴心(Pivot)”,`X`对齐下半身轴心,`Y`放到底部。  


然后我们要给比尔添加两个子对象,分袂显示上下半身的精灵,这样可以分袂操作搁在的位置属性。  

  • 选中”游戏对象比尔”,鼠标右键`”2D Object->Sprites->Square”`,创建一个对象定名为`”上半身”`,对象的`SpriteRender`组件中设置`Sprite`属性为”站立的上半身”精灵。同样创建一个下半身精灵。
  • 然后把比尔这个对象上的`SpriteRender`组件删掉,点击组件右上方的”三个点”按钮,选择”Remove Component”。
  • 调整上下半身的`Transform的Position`属性:

    • 上半身 `X:0,Y:1.1875,Z:0`
    • 下半身 `X:0,Y:0,Z:0`

`1.1875=19/16` ,为什么这样算,给可以本身思考一下。  

## 调整动画

为上下半身对象分袂设置精灵,使上下半身在动画中动作同步。  

  • 选择”比尔”,按`”ctrl-6”`打开动画剪辑。
  • 点击属性右侧的点,选择”Remove Property”。
  • 然后再添加上下半身的`”Sprite属性”`。
  • 分袂按照之前的法式添加精灵。


## 跑步和跑动射击

跑动射击动画与跑步类似,下半身精灵一致,上半成分为3个姿势,`”平射”、”斜上射击”、”斜下射击”`。每种姿势分袂对应一个动画剪辑,创建好后我们会有4个动画剪辑。`”Player-跑步”、”Player-平射”、”Player-斜上射击”、”Player-斜下射击”`。  



动画错位
依次点击动画剪辑的`Play`按钮,查看”Scene”窗口中比尔的动画效果,会发现比尔跑步平射时,上半身和下半身会错位。  
原因是之前我们采用比尔脸的`X`位置作为上下半身的轴对齐位置,但这个位置在分歧的动作中并不能与站立姿势的上半身保持一致。我们可以改削精灵的轴位置。我采用腰部左侧的位置作为轴,各位可以本身测验考试一下。
动画混合树(Animation Blend Tree)
此刻我们成立了4个姿态的动画剪辑,下面要成立两个”动画状态”,`”跑步”、”跑动射击”`。  
为了解决”动画状态”过多导致转换过于复杂,Unity有一个”动画混合树(Animation Blend Tree)”,它可以将几个具有类似状态下的分歧姿态的动画剪辑归类为一个”动画状态”,在内部通过对”动画变量”的判断来选择则对应的剪辑。下面我们成立”动画混合树(Animation Blend Tree)”。

  • 创建”混合树状态”

    • 选中比尔对象,进入”Animator”窗口。删除杂七杂八的状态只保留`”Player-站立”`。
    • 点击右键`”Create State->From New Blend Tree”`,定名为`”Player-跑步”`。双击”动画状态”进入编纂。

  • 给”混合树”添加”动画剪辑”

    • 在左侧`”Parameters”`中可以看到一个名叫`”Blend”`的`float`变量,这个是自动创建的,我们把它改名叫`”Y”`。
    • 点击中间的`”Blend Tree”`,确保右侧”查抄器”中的`”Parameter”`属性选中`”Y”`,然后点击下面的`+`号,创建两个`Motion`,此刻应该有3个`Motion`,分袂装配上3个”动画剪辑”,`”Player-斜下射击”、”Player-跑步”、”Player-斜上射击”`。
    • 将`”Automate Thresholds”`打消勾选。
    • 分袂给3个剪辑设置`”Threshold”`

      • `”Player-斜下射击”=-1`
      • `”Player-跑步”=0`
      • `”Player-斜上射击”=1`


  • 然后给这个混合树状态添加状态转换

    • 添加一个`Bool`类型的动画变量`”Fire”`
    • 添加一个`”Player-站”->”Pkayer-跑步”`的”状态转换”,设置条件为`”Run”=true`而且`”Fire”=false`
    • 添加一个`”Player-跑步”->”Pkayer-站”`的”状态转换”,设置条件为`”Run”=false`
    • 设置这两个”状态转换”的属性`”Has Exit Time”不勾选`,`”Transition Duration(s)”=0`

同样的法式添加一个名叫`”Player-运动射击”`的”混合树状态”,装配上3个”动画剪辑”,`”Player-斜下射击”、”Player-平射”、”Player-斜上射击”`。”状态转换”的条件中`”Fire”=true`。  

这样从”站立”姿态到”跑步”和”运动射击”的状态转换就有了,但还需要添加”跑步”和”运动射击”之间的状态转换。各位可以本身想一下该如何添加。  

下面要给新加的”动画变量`Fire`,`Y`”添加代码:  

  • 在类级定义一个`bool`变量`firePressed`,用来记录是否按下了开枪键
  • 在`Update`函数中接收按键
  • 在`UpdateAnimator`函数中给动画发送变量。
  1.   ...
  2.   //是否按下了按键 开枪
  3.   bool firePressed;
  4.   ...
  5.   void Update()
  6.   {
  7.     //取得按键
  8.     axisX = Input.GetAxisRaw(”Horizontal”);
  9.     axisY = Input.GetAxisRaw(”Vertical”);
  10.     firePressed = Input.GetButtonDown(”Fire1”);
  11.     ...
  12.   }
  13.   void UpdateAnimator()
  14.   {
  15.     animator.SetFloat(”Y”, keyDirection.y);
  16.     animator.SetBool(”Fire”, firePressed);
  17.   }
复制代码
此刻跑起来看看效果。
解决收枪发抖问题
这是应该发现一个问题,跑动时”平射”收枪动作太快会发生抽搐的效果。这个需要措置一下。措置方式有两种:

  •   在`”Player-运动射击”->”Player-跑步”`的”状态转换”中,勾选`”Has Exit Time”`。在满足收枪条件后仍会持续播放一小段”开枪”的动画剪辑。实现延迟收枪的效果。但是...如果我们持续不竭的按住开枪,但愿的效果是不做收枪动作。这个方式虽然延迟了收枪动作,但仍然会在延迟后发生收枪的动作。在持续开枪的时候会有闪动收枪的动作。不保举这种方式。
  • 在代码中实现延迟收枪的逻辑。在开枪时记录开枪的时间,发送动画变量时判断上一次开枪时间是否小于一个固定的开枪持续时间。每次开枪刷新开枪的时间,持续开枪时”动画变量”`”Fire”`就始终为`true`,从而保持开枪姿势不变。
下面看看代码:
  1.   ...
  2.   //解决开枪发抖
  3.   //记录上一次的开枪时间
  4.   float lastFireTime = -1;
  5.   //设定开枪的持续时间(通过模拟器测量,开枪姿势保持16帧)
  6.   float firePostureKeepingTime = 16f / 60f;
  7.   ...
  8.   void Update()
  9.   {
  10.     firePressed = Input.GetButtonDown(”Fire1”);
  11.     //每次开枪刷新开枪时间
  12.     if (firePressed)
  13.     {
  14.         lastFireTime = Time.time;
  15.     }
  16.     ...
  17.   }
  18.   void UpdateAnimator()
  19.   {
  20.     //在设定的时间范围内,保持开枪姿势
  21.     animator.SetBool(”Fire”, Time.time - lastFireTime <= firePostureKeepingTime);
  22.   }
复制代码
角色被布景遮挡了怎么办
有些小伙伴做到这里会呈现这种情况。在”游戏对象”的`”Sprite Render”`组件中找到`”Sorting Layer”`属性,成立一个”玩家角色”的”排序层(Sorting Layer)”就可以了。  
”层(Layer)”负责碰撞校验,”排序层(Sorting Layer)”负责显示时的衬着挨次。
## 上半身发抖



动画控制器(Animator Controller)可以允许通过”动画层(Animator Layers)”实现同时播放多个动画剪辑。我们需要的是在开枪的同时,播放上半身发抖的剪辑就可以了。  
先创建动画剪辑`”Player-射击发抖”`。  





在这个剪辑中我们不合错误Sprite做改变,只是改变上半身的`”Transform.Position”`属性。  
在`”0,4,8”`帧成立关键帧,`”0”`帧是原始状态,`”4”`帧`Y值`变为`”1.125”`(即:向下移动1个像素,1.125=1/16),`”4”`帧`Y值`还原为初始值。  
改削值时,必然要拖动(或点击)白线位置到需要改削的关键帧位置。(点击选中关键帧只能通过右键菜单删除关键帧,而不能改变关键帧的值,这个要注意)。  
下面成立”动画层(Animator Layer)”。在”Animator”窗口左侧选择”Layers”。将”Base Layer”改名为”跑步+射击”,点击`”+”`号新建一个层定名为”射击发抖”,创建如下图的状态。  


`”Idle”->”射击发抖”`的设置

  • 条件:`”Fire”=true`
  • `”Has Exit Time”不勾选`
  • `”Exit Time”=0`
  • `”Transition Duration(s)”=0`
`”射击发抖”->”Idle”`的设置:  

  • 条件:没有
  • `”Has Exit Time”勾选`
  • `”Exit Time”=1`
  • `”Transition Duration(s)”=0`
在不设定转换条件时,如果`”Has Exit Time”勾选`剪辑会在播放完撤退退却出。如果`”Has Exit Time”不勾选`则会持续播放剪辑。  
解决反复发抖问题
仔细不雅察看发现点击开枪一后上半身会发抖两次。原因是我们在代码中使用了`”firePostureKeepingTime”`这个变量来防止转身时的发抖现象。  
解决方式,将动画变量`”Fire”`改名为`”KeepFire”`。添加一个`Trigger`类型的动画变量`”Fire”`。  
将`”Idle”->”射击发抖”`状态转换中的`”KeepFire”`改为`”Fire”`。
在代码中的`UpateAnimator`函数中插手代码
  1.   //在设定的时间范围内,保持开枪姿势
  2.   animator.SetBool(”KeepFire”, Time.time - lastFireTime <= firePostureKeepingTime);
  3.   if (firePressed)
  4.   {
  5.       animator.SetTrigger(”Fire”);
  6.   }
复制代码
## 站立向上射击

”向上射击”与”站立”这两种姿态可以作为同一个”动画状态”来措置。  

  • 成立两个”动画剪辑”分袂对应”向上射击”和”站立射击”(只需要上半身动画即可)。
  • 成立一个”混合树状态”代替本来的”Player-站”,保持”状态转换”与本来一致。
  • 在混合树内部,使用变量`Y`来区别”向上射击”和”站立射击”两个剪辑。
看效果时细心的会发此刻按住”上”并快速切换跑步标的目的时,会发生发抖,”向上射击”的姿态乱入。这时因为在切换跑步标的目的时,某一个时间片段中跑步标的目的是`”0”`所以动画会切换到”站立射击”的姿态。从而发生发抖。可以参照收枪发抖的解决方式。下面看代码:  
  1.   ...
  2.   //解决快速切换跑动标的目的时发生的发抖
  3.   //记录上一次的持续跑动的时间
  4.   float lastRunTime = -1;
  5.   //在此设定的时间间隔内仍视为持续跑动(这里6帧时间是估计值)
  6.   public float runPostureKeepingTime = 6f / 60f;
  7.   ...
  8.   void UpdateAnimator()
  9.   {
  10.     //措置切换跑动标的目的时发生的的发抖
  11.     if (keyDirection.x != 0)
  12.     {
  13.         lastRunTime = Time.time;
  14.     }
  15.     animator.SetBool(”Run”, Time.time - lastRunTime <= runPostureKeepingTime);
  16.   }
复制代码
# 比尔卧倒

卧倒姿势我们采用一个单独的子对象来表示。
## 分割卧倒精灵

在贴图文件中分割出”卧倒”的精灵。来测量一下卧倒的轴心。


依据这张图,”卧倒精灵”轴心应设置为`(14,0)`。  
## 创建比尔的卧倒对象

创建一个子对象,定名为`”卧倒”`,改削对应的精灵为方才的卧倒精灵。
## 添加卧倒姿态下的开枪发抖

`”ctrl-6”`编纂动画剪辑`”Player-射击发抖”`。添加属性`”卧倒”->Transform->Position`。时间线设置与上半身的设置不异。`”4”`帧的`Y值`设置为`”-0.0625”`,即向下移动1个像素。  
改削代码
此刻比尔有两个状态了,”跑步+射击”和”卧倒”这两个状态需要显示的子组件是分歧的。我们通过枚举把他们区分隔。

  • 定义一个枚举类型`”PlayerState”`:

    • `”PlayerState.Normal”=跑步+射击`
    • `”PlayerState.LieDown”=卧倒`

  • 给每个身体子对象生命一个`”GameObject”`变量,定义一个数组,用来涵盖所有的身体子对象的身体部件。
  • 在`”Awake”`函数中初始化身体部件及数组。
  • 添加`”UpdatePosture”`函数,按照`”playerState”确定显示的身体部件。
  • 在`”FixedUpdate”`函数中判断如果按住了`”下”`键则将状态变为”LieDown”,否则状态变为”Normal”。
代码如下:
  1.   //角色的状态
  2.   public enum PlayerState
  3.   {
  4.     None = 0,
  5.     //站立+跑步
  6.     Normal,
  7.     //卧倒
  8.     LieDown,
  9.   }
  10.   ...
  11.   //当前状态
  12.   PlayerState playerState = PlayerState.Normal;
  13.   //上一个状态
  14.   PlayerState lastPlayerState = PlayerState.None;
  15.   ...
  16.   //上半身
  17.   GameObject upperBody;
  18.   //下半身
  19.   GameObject lowerBody;
  20.   //卧倒
  21.   GameObject lieDownBody;
  22.   GameObject[] bodys;
  23.   void Awake()
  24.   {
  25.     //初始化身体部件
  26.     bodys = new GameObject[]
  27.     {
  28.         upperBody = transform.Find(”上半身”).gameObject,
  29.         lowerBody = transform.Find(”下半身”).gameObject,
  30.         lieDownBody = transform.Find(”卧倒”).gameObject,
  31.     };
  32.     UpdatePosture();
  33.   }
  34.   void Update()
  35.   {
  36.     ...
  37.     UpdatePosture();
  38.     ...
  39.   }
  40.   void FixedUpdate()
  41.   {
  42.     ...
  43.     //改变状态
  44.     if (playerState == PlayerState.Normal)
  45.     {
  46.       //正常状态时,判断是否应该卧倒
  47.       if (keyDirection.x == 0 && keyDirection.y < 0)
  48.       {
  49.           playerState = PlayerState.LieDown;
  50.       }
  51.     }
  52.     else if (playerState == PlayerState.LieDown)
  53.     {
  54.         //卧倒状态时,判断是否应该站立
  55.         if (keyDirection.y >= 0)
  56.         {
  57.             playerState = PlayerState.Normal;
  58.         }
  59.     }
  60.   }
  61.   void UpdatePosture()
  62.   {
  63.     //状态不改变不会反复执行
  64.     if (playerState != lastPlayerState)
  65.     {
  66.       //当前状态下该显示哪些身体部件
  67.       HashSet<GameObject> activeBodys = new();
  68.       switch (playerState)
  69.       {
  70.         case PlayerState.Normal:
  71.           activeBodys.Add(upperBody);
  72.           activeBodys.Add(lowerBody);
  73.           break;
  74.         case PlayerState.LieDown:
  75.           activeBodys.Add(lieDownBody);
  76.           break;
  77.         default:
  78.           activeBodys.Add(upperBody);
  79.           activeBodys.Add(lowerBody);
  80.           break;
  81.       }
  82.       //设置身体部件是否可显示
  83.       foreach (GameObject body in bodys)
  84.       {
  85.         if (activeBodys.Contains(body))
  86.         {
  87.           body.SetActive(true);
  88.         }
  89.         else
  90.         {
  91.           body.SetActive(false);
  92.         }
  93.       }
  94.       //保留当前状态
  95.       lastPlayerState = playerState;
  96.     }
  97.   }   
复制代码
## 关于斜下射击时快速切换摆布标的目的导致的发抖  

解决方式与斜上射击时切换标的目的的发抖类似,但我仔细不雅察看原版魂斗罗也有同样的现象,包罗斜上射击。呃~~~这个就参照原版的效果吧,看来之前是我想多了。  

# 跳跃翻腾

同卧倒类似,翻腾也定义为一个单独的子对象,这里不多说了,主要介绍翻腾的高度和初速度。



  • 跳跃开始时,翻腾动作离地面16像素,这表白翻腾的精灵对象比父对象高16个像素。
  • 跳跃高度61个像素用时30帧,按照加速度公式,初始速度应该为`”15.25”`,重力加速度应为`”30.5”`。
  • 比尔在空顶用时20帧旋转一周,旋转时精灵每5帧顺时针旋转90°。
我们先定义跳跃动画

  • 新建动画变量`bool`类型`”Jump”`。添加动画层”跳跃翻腾”。通过`”Jump”`完成”跳跃翻腾”剪辑的播放和遏制。
  • 创建动画剪辑`”Player-跳跃翻腾”`
  • 在动画剪辑中,将20帧分为5个关键帧,每5帧一个关键帧。设置属性`”Transform.Rotation.Z”`。注意`”Z轴”`标的目的默认是”Forward”,旋转是”正顺负逆”。所以每个关键帧`Z值`为`”0”、”-90”、”-180”、”-270”、”0”`。
  • 动画的播放默认是平滑的,为了模拟原版的效果,要打消平滑效果。通过改削”动画曲线(Curves)”来实现。参考下图:


改削代码:

  • 在`”PlayerState”`枚举添加一个状态`”Roll”`。
  • 添加类级变量`”JumpSpeed”`作为跳跃的初速度。
  • 添加`rollBody`作为翻腾子对象变量。并在`”Awake”`函数中初始化。
  • 在`”Awake”`函数中设置`”rig.gravityScale”`为比尔受到的重力加速度调整值`30.5/9.81`。
  • 在`”FixedUpdate”`函数中插手判断跳跃按键并跳起的逻辑。

    • 设置状态为`”Roll”`
    • 设置`”velocity.y”=jumpSpeed`

  • 在`”UpdateAnimator”`函数中发送`”Jump”`变量。
  • 在`”UpdatePosture”`中添加显示翻腾子对象的逻辑。
# 检测是否站到地面

此刻跳跃落地后无法还原状态,一直翻腾。所以要判断是否落到地面。  
需要使用Unity提供的物理引擎。从脚底发射一条射线,判断射线是否与跳台层碰撞,如果发生了碰撞则暗示当前已经站到了跳台上。具体的函数如下:
  1.   void FixedUpdate()
  2.   {
  3.     //从角色当前位置的脚下(transform.position),向下(Vector3.down)发射一条长度为(0.1)的射线,判断是否与跳台层(1 << LayerMask.NameToLayer(”跳台”))发生了碰撞
  4.     onGround=Physics2D.Raycast(transform.position, Vector3.down, 0.1f, 1 << LayerMask.NameToLayer(”跳台”));
  5.     ...
  6.       //措置正常、卧倒、跳跃三个状态的转换
  7.       if (playerState == PlayerState.Normal)
  8.       {
  9.           //正常状态时,判断是否应该卧倒
  10.           if (keyDirection.x == 0 && keyDirection.y < 0)
  11.           {
  12.               playerState = PlayerState.LieDown;
  13.           }
  14.           //在正常状态下按跳跃键,开始跳跃
  15.           if (jumpPressed)
  16.           {
  17.               velocity.y = jumpSpeed;
  18.               playerState = PlayerState.Roll;
  19.           }
  20.       }
  21.       else if (playerState == PlayerState.LieDown)
  22.       {
  23.           //卧倒状态时,判断是否应该站立
  24.           if (keyDirection.y >= 0)
  25.           {
  26.               playerState = PlayerState.Normal;
  27.           }
  28.       }
  29.       else if (playerState == PlayerState.Roll)
  30.       {
  31.           //跳跃状态下措置落地和移动
  32.           if (onGround)
  33.           {
  34.               // 跳跃落地
  35.               playerState = PlayerState.Normal;
  36.           }
  37.           else
  38.           {
  39.               float currentSpeed = Mathf.Abs(rig.velocity.x);
  40.               if (currentSpeed == 0)
  41.               {
  42.                   //站立状态下起跳,在空中通过标的目的键可移动
  43.                   currentSpeed = speed * keyDirection.x;
  44.               }
  45.               else
  46.               {
  47.                   //跳跃状态下松开标的目的键仍会移动
  48.                   currentSpeed *= lookAt.x;
  49.               }
  50.               velocity.x = currentSpeed;
  51.           }
  52.       }
  53.   }
  54.   ...
复制代码
# 越来越复杂的状态判断

有没有发现我们用来判断当前状态并转换状态的代码越来越复杂了?虽然目前我们只有三个状态,但也已经让我们头晕了。需要用更好的方式来措置这些状态和转换,需要引入一个非常重要的概念`”有限状态机”`、  
下一节我们会花费一整节来介绍这个概念,让我们的代码更简洁有层次。  

好了到这里吧,休息!休息一下再说。

本帖子中包含更多资源

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

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-21 21:08 , Processed in 0.242575 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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