Unity简单2d动画和状态机

3949 字
20 分钟
Unity简单2d动画和状态机

2D渲染组件#

sprite renderer组件#

为了使角色贴图能够显示,我们需要加上 sprite renderer组件 ,不过大部分我们这节课不需要用到,故将表附在下面,供大家自行了解

Sprite定义该组件应渲染的精灵纹理,即贴图
Color定义精灵的顶点颜色,用于对精灵的图像进行着色或重新着色
Filp沿选定的轴翻转精灵纹理。这不会翻转游戏对象的变换位置。
Draw Mode定义精灵尺寸发生变化时的缩放方式
Mask Interaction设置精灵渲染器在与精灵遮罩交互时的行为方式
Sprite Sort Point在计算精灵和摄像机之间的距离时,在精灵中心 (Center) 或其轴心点 (Pivot Point) 之间进行选择
Material定义用于渲染精灵纹理的材质
Sorting Layer设置精灵的排序图层 (Sorting Layer),此图层用于控制渲染期间的精灵优先级
Order In Layer设置精灵在其排序图层中的渲染优先级。首先渲染编号较低的精灵,编号较高的精灵叠加在前者之上

创建并设置 Sprite Renderer#

  1. 创建角色(这一步在class_01已讲)
  2. **添加 Sprite Renderer ** 组件 :
    1. 选择 Character(或其他命名的对象)。
    2. Inspector 面板中点击 Add Component 按钮,搜索并添加 Sprite Renderer 组件。
  3. **设置 Sprite ** 图像 :
    1. Inspector 面板中的 Sprite Renderer 组件下,点击 Sprite 字段。
    2. 选择一个 2D 图像(Sprite)文件。确保该图像已经被导入到 Unity 项目中。
  4. 调整颜色(注意) :
  5. Color 字段中调整图像的颜色。使用颜色选择器可以让图像的颜色变得更亮或更暗。记住,这种改变只会使颜色变深,不能变亮。

动画系统#

动画系统分为两个方面,一个 animation 一个 animator

  1. animation :编辑或新建动画,是实际改动动画的内容
  2. animator :用来管理动画的播放逻辑,例如我按什么键播放什么动画,我移动时候播放什么动画

每一个动画文件都是由一个叫animation clip的东西保存,animation clip存放在Assets文件夹里面,而实际播放动画是物体上的animator组件来完成

动画系统的组件非常复杂且繁多,故我们仅会介绍本节课需要用到的内容,剩余的大家可以自行查阅


创建、编辑动画#

1.创建 Animation Clip#

  1. 创建动画 :
  2. Project 面板中右键点击,选择 Create > Animation 来创建一个新的动画文件。
  3. 给动画文件命名(例如:CharacterMove)。
  4. 编辑动画 :
  5. Window 菜单中,选择 Animation,打开 Animation 窗口。
  6. 将你切好的精灵图(如果是的话)拖入其中,并进行调整。
  7. Animation 窗口中点击 Record 按钮,开始录制动画。
  8. 录制动画 :
  9. Unity 会自动记录每个关键帧。
  10. 停止录制 :
  11. 完成动画编辑后,点击 Stop 按钮,停止录制。

2.创建 Animator Controller#

  1. 创建 Animator Controller :
  2. Project 面板中右键点击,选择 Create > Animator Controller 来创建一个新的 Animator Controller。
  3. 给 Animator Controller 命名(例如:CharacterAnimatorController)。
  4. 将 Animator Controller 关联到 GameObject :
  5. 选择 Character(或其他命名的 GameObject)。
  6. Inspector 面板中,点击 Add Component,添加 Animator 组件。
  7. CharacterAnimatorController 拖到 Animator 组件的 Controller 字段中。

3.配置 Animator Controller#

  1. 打开 Animator 窗口 :
  2. Window 菜单中,选择 Animation,然后点击 Animator,打开 Animator 窗口。
  3. 添加动画状态 :
  4. Animator 窗口中,将之前创建的 CharacterMove(或其他 Animation Clip)拖到 Animator 窗口内。这将自动创建一个新的动画状态(例如:CharacterMove)。
  5. 创建动画过渡 :
  6. Animator 窗口中,右键点击动画状态,选择 Make Transition,并将其连接到另一个动画状态(例如:CharacterIdle)。
  7. 设置过渡条件,例如:如果按下键盘的某个键时播放 CharacterMove 动画,否则播放 CharacterIdle 动画。
  8. 设置动画参数 :
  9. Animator 窗口的左侧,点击 Parameters 标签。
  10. 添加新的参数,比如:Bool 类型的 IsWalking,用于决定角色是否行走。
  11. 设置条件:当 IsWalkingtrue 时,从 CharacterIdle 过渡到 CharacterMove

4.控制动画播放#

  1. 在脚本中控制动画 :
  2. 创建一个新的 C# 脚本(这里我们就用之前的 Test_CharacterMove 即控制角色移动的脚本)。
  3. 在脚本中获取 Animator 组件,并根据输入控制动画的播放:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test_CharactorMove : MonoBehaviour
{
Rigidbody2D rigidbody2D;
[SerializeField] float speed = 5.0f;
[SerializeField] float upForce = 2.0f;
[SerializeField] private float groundCheckDistance = 0.1f;
[SerializeField] private float groundCheckStartOffset = 0.1f;
public bool isGrounded = false;
private Animator animator; //修改的位置
void Start()
{
if (rigidbody2D == null)
{
rigidbody2D = GetComponent<Rigidbody2D>();
}
animator = GetComponent<Animator>(); //获取Animater组件
}
void Update()
{
CheckGrounded();
Move();
Jump();
}
private void Move()
{
if (Input.GetKey(KeyCode.A))
{
rigidbody2D.velocity = new Vector2(-speed, rigidbody2D.velocity.y);
transform.localScale = new Vector3(1, 1, 0);
animator.SetBool("isMoving", true); //动画播放
}
else if (Input.GetKey(KeyCode.D))
{
rigidbody2D.velocity = new Vector2(speed, rigidbody2D.velocity.y);
transform.localScale = new Vector3(-1, 1, 0);
animator.SetBool("isMoving", true); //动画播放
}
else
{
rigidbody2D.velocity = new Vector2(0, rigidbody2D.velocity.y);
animator.SetBool("isMoving", false); //动画停止
}
}
private void Jump()
{
if (Input.GetKeyDown(KeyCode.Space) &&isGrounded)
{
rigidbody2D.AddForce(Vector2.up * upForce, ForceMode2D.Impulse);
animator.SetBool("IsJumping", true); //动画播放
}
}
private void CheckGrounded()
{
isGrounded = false;
Vector3 startPostion = transform.position - new Vector3(0,groundCheckStartOffset,0);
RaycastHit2D hit = Physics2D.Raycast(
startPostion,
Vector2.down,
groundCheckDistance,
LayerMask.GetMask("Ground")
);
if (hit.collider != null)
{
isGrounded = true;
}
// isGrounded = hit.collider.CompareTag("Ground") && rigidbody2D.velocity.y <= 0.1f;
}
private void OnDrawGizmosSelected()
{
Vector3 startPostion = transform.position - new Vector3(0,groundCheckStartOffset,0);
Gizmos.color = Color.green ;
Gizmos.DrawWireSphere(transform.position ,0.05f);
Gizmos.DrawLine(startPostion, startPostion + Vector3.down * groundCheckDistance);
}
}
  1. 连接脚本与 GameObject :
  • 将移动脚本添加到角色对象上,并确保 Animator 已经正确关联。

5.其他的动画效果(仅示例)#

通过将 Sprite RendererAnimator 结合使用,你可以创建丰富的 2D 动画效果。

  1. 动态切换精灵 :
  • 你可以使用代码动态切换精灵。例如,行走时显示不同的图像,站立时显示其他图像:
public class SpriteChanger : MonoBehaviour
{
public SpriteRenderer spriteRenderer;
public Sprite walkingSprite;
public Sprite idleSprite;
void Update()
{
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))
{
spriteRenderer.sprite = walkingSprite;
}
else
{
spriteRenderer.sprite = idleSprite;
}
}
}
  1. 动态更改颜色 :
  • 你还可以通过脚本动态更改精灵的颜色。例如,角色在移动时改变颜色:
public class CharacterColorChanger : MonoBehaviour
{
private SpriteRenderer spriteRenderer;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
void Update()
{
if (Input.GetKey(KeyCode.W))
{
spriteRenderer.color = Color.green; // 移动时变为绿色
}
else
{
spriteRenderer.color = Color.white; // 静止时恢复白色
}
}
}

6.动画优先级(补充)#

动画优先级是非常非常非常高的,甚至是比代码的优先级还要高,比如说有物体站播放控制它位置的动画,那么无论如何,我们在代码里是不可能改它的位置的,有不少所谓的bug是因为这个导致的。

比如说我的移动代码为什么没有作用呢,可能就是在某一处用动画进行了控制。

我们可以进行一些尝试

我们新建一个物体,将移动脚本挂载上(记得将动画相关代码注释掉),再给它创建一个时刻移动的动画,并使其在开始运行时就播放。

这个时候我们就能发现,我们无论怎么按移动键,我们都无法使该动画停止。


动画状态机#

其实在完成上面的操作时我们已经完成了一个简单的状态机

那么,状态机是什么呢

是一种非常常用的工具,用于控制角色或物体的动画播放。它可以帮助开发人员更好地管理动画,从而更好地实现游戏的交互效果。

动画状态机的基本元素#

动画状态机由多个状态(State)组成,每个状态对应一个动画片段(Animation Clip)。状态之间可以通过转换(Transition)进行切换,从而实现动画的连贯播放。下面我们来逐一介绍动画状态机的基本元素。

  1. 状态(State)

状态是动画状态机的基本元素,它对应一个动画片段(Animation Clip)。状态可以包含多个动画片段,但只有一个动画片段处于活动状态。

在动画状态机中,状态通常用于控制角色或物体的不同动作。例如,一个角色可以有站立、走路、奔跑、跳跃等不同的状态,每个状态对应一个动画片段。

  1. 转换(Transition)

转换用于将一个状态切换到另一个状态。转换可以设置触发条件,只有满足触发条件才能进行状态切换。

在动画状态机中,转换通常用于控制角色或物体的动作切换。例如,当一个角色从站立状态转换到走路状态时,可以设置速度触发条件,只有当角色移动速度大于一定值时才能进行状态切换。

  1. 参数(Parameter)

参数是用于控制状态机中的转换和状态的变量。参数可以是布尔值、浮点数、整数和触发器等类型。

在动画状态机中,参数通常用于控制动画的播放速度、方向、状态等。例如,可以设置一个浮点数参数来控制角色的移动速度,当移动速度大于一定值时,自动切换到奔跑状态。

动画状态之间互相连接,形成了错综复杂的拓扑结构。这些动画状态还仅仅是攻击动作相关的状态。

要是用代码控制这些动画的播放与切换,把这些拓扑结构转换成面条代码,不用想就知道这种方法既编写麻烦又难以维护。

过渡#

为什么需要状态过渡?

如果你的动画模型有骨骼的话,效果收益会非常显著:它会为前后两个动画生成平滑的衔接动作,避免了从前个动画状态切换到下个动画状态时动画不自然的突变。让我们从上到下依次的看一看状态过渡的Inspector面板里的参数有什么作用吧:

Transitions :数组,记录着上个动画指向其他动画的所有状态过渡,为一个状态过渡勾选SOLO则禁用其他所有状态过渡,只能切换到该状态过渡指向的动画。MUTE同理。

Has Exit Time :如果不勾选则当切换条件(condition)被满足时就立刻切换为下一个状态;如果勾选,则即使切换条件满足时,也会在播放到Exit Time后才会切换到下一个状态。

需要注意的是,在勾选的情况下,如果切换条件满足时播放进度已经过了Exit Time,将不会切换到下一个状态。通俗的讲,当出招动画已经播放到后摇之后再输入指令,无法派生下一招。反面例子是黑魂,轻击一段后,即使收招快复位成站姿的时候再输入轻击指令,也会派生轻击二段,操作有种粘滞感。这种处理方式似乎叫输入延长?勾选Has Exit Time的同时设置Interruption Source似乎也可以做出“输入延长”。

Exit Time :退出当前动画状态的时间。数值是百分比。

Fixed Duration :不勾选时下列数值的单位是百分制,勾选后下列数值的单位是秒。

Transition Duration :切换到下一个动画状态时,将两个动画混合的平滑衔接动画的长度。

Transition Offset :切换到下一个动画状态时,从下一个动画的哪个时间开始。


以下内容为介绍

状态机行为脚本#

动画状态上也是可以挂载脚本的。

继承自StateMachineBehaviour的类提供了三个默认方法

OnStateEnter() //Animator 切换到这个 State 的那一帧
OnStateUpdate() //在当前状态每一帧都会执行
OnStateExit() //Animator 离开此 State 的瞬间(transition 开始时)

另外因为该脚本属于资源对象,不能在该类的全局变量里获取场景对象,但是可以从这几个方法传入的Animator获取场景对象。

任意动画状态Any State#

一般玩家不做动作的时候,总是会复原到闲置站姿对吧?不管玩家在干什么,挨打的时候都会立刻切换到受击动画对吧?手动把所有动画状态都连回闲置站姿会让状态机变成乱七八糟的蜘蛛网。用Any State吧!

用Any State连接一个状态,设置一个切换条件,不管在任何状态,只要条件满足的时候都能切换到该状态。

如果希望某个状态不能从Any State切换到另一个状态,多设置一个切换条件就好了。

动画层Layer#

当你需要角色同时做两个动作的时候,可以设置其他的动画层。一些FPS游戏里会把上半身和下半身分开在两个动画层级里;上半身持枪动画,下半身行走跳跃站立,同时进行。2D游戏则可以把本身动作和受击闪烁分开。

子状态机#

很多时候,角色的一个行动由多个动作组成。比起使用一个动画完成这个行为,可能定义几个不同的阶段,并且给每个阶段使用一个单独的State会更合理。比如一个“稳定射击”的动作,包含蹲下、射击、站起的动作。

这样分拆的动画更利于控制,但是带来的负面影响是状态机会变得庞大、复杂、难以管理。这时候,可以使用状态机提供的子状态机的功能,将这些状态整合到一个子状态中。

创建子状态机#

在Animator窗口的空白处右键,在弹出菜单中选择Create Sub-State Machine。

子状态机会显示为一个细长的六边形用来和正常的state区分。

编辑子状态机#

双击这个子状态机可以打开它,界面会显示这个子状态机内的内容(初始会是一个空的状态机)。窗口的上方会有一个路径显示当前编辑的是哪个子状态机(子状态机内也可以再创建子状态机),点击对应的路径可以直接打开对应的位置。

子状态机的Transition#

子状态机只是从视觉上将一些状态折叠到一个子状态机中,所以如果想转换到sub-state machine时,需要选择具体转换到哪一个状态或整个状态机。

选择状态时,Transition会直接转换到对应的状态。选择状态机时,相当于将Transition转换到对应状态机的Entry状态。

子状态机中有一个额外的状态名字为 (Up)XXX

这个状态代表上一层的状态机。你可以在子状态机中创建Transition转换到上一层状态机中的某个状态。

选择状态时,Transition会直接转换到对应的状态。

选择状态机时,相当于将Transition转换到对应状态机的Entry状态。

其他的一些常用的#

Animator.GetCurrentAnimatorStateInfo(int LayerIndex)。会返回当前的动画状态。返回的这个AnimatorStateInfo类无法以字符串形式得到状态的名称,但是可以用IsName(String name)或IsTag(String tagName)来判断是否是这个名称或有该标签。

LayerIndex是动画层编号。Base Layer是0,其他动画层的编号从上到下依次增大。

Animator.GetNextAnimatorStateInfo(int LayerIndex)。当动画处在状态过渡Transition中,会返回该状态过渡指向的下一个动画状态。

Animator Override Controller…

还有很多,故本文无法在这里将剩余的全部列出来

Unity简单2d动画和状态机
https://blog.rouming-fei.top/posts/unity-simple-2d-animation-and-state-machine/
作者
废江流
发布于
2025-11-08
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
废江流
提灯寻影,灯到影灭。
分类
标签
站点统计
文章
15
分类
4
标签
18
总字数
37,248
运行时长
0
最后活动
0 天前

文章目录