FSM人物动画状态机

2023-10-16 20:29
文章标签 动画 状态机 人物 fsm

本文主要是介绍FSM人物动画状态机,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

人物动画状态机

  • 介绍
  • FSM
  • 角色模型的设置
  • 角色动作的设置
  • 角色动画控制器的设置
  • 书写角色动画的具体状态,实现缓动起步的FSM
  • 总结

介绍

摇杆我就不介绍了,之前我在这里面讲过怎么用摇杆,摇杆连接。
这里我先说下什么是FSM人物动画状态机,说白了就是一个集中管理控制角色动画状态的一个管理器。可以切换操作对象的不同动作状态。下面我放一个实例Gif这个是人物FSM动画控制+人物动作匹配缓动起步的实例。
请添加图片描述

FSM

在这里插入图片描述
RoleState是角色的所有状态列举,后面切换状态是通过RoleFSMMgr切换玩家的RoleState来改变玩家的状态。
RoleState.cs

/// <summary>
/// 角色状态
/// </summary>
public enum RoleState
{None,Idle,Run,RunToIdle,
}

这里还有一个角色状态的抽象基类RoleStateAbstract,这里主要存储了动画状态片段以及信息,还有就是这里有三个方法分别是动作状态的进入OnEnter、动作状态的持续中OnStay、动作状态的离开OnExit,这里主要是为了具体的动作状态提供统一管理的方法,也方便了上层的RoleFSMMgr的调用管理整个状态的执行和切换状态。
RoleStateAbstract.cs

using UnityEngine;/// <summary>
/// 角色状态的抽象基类
/// </summary>
public abstract class RoleStateAbstract
{/// <summary>/// 当前角色有限状态机管理器/// </summary>public RoleFSMMgr CurrRoleFSMMgr { get; private set; }/// <summary>/// 当前动画状态信息/// </summary>public AnimatorStateInfo CurrRoleAnimatorStateInfo { get; set; }/// <summary>/// 构造函数/// </summary>/// <param name="roleFSMMgr"></param>public RoleStateAbstract(RoleFSMMgr roleFSMMgr){CurrRoleFSMMgr = roleFSMMgr;}/// <summary>/// 进入状态/// </summary>public virtual void OnEnter() { }/// <summary>/// 执行状态/// </summary>public virtual void OnStay() { }/// <summary>/// 离开状态/// </summary>public virtual void OnExit() { }
}

在RoleFSMMgr管理器中的Dictionary<RoleState, RoleStateAbstract>中存储了当前玩家的状态枚举跟具体的状态信息RoleStateAbstrac动作状态基类一一对应。并且还存储了玩家的角色控制器RoleCtrl、当前状态信息、当前状态的枚举等信息。
RoleFSMMgr除了切换玩家状态以外,还有一个作用就是要驱动玩家的当前状态,这里会提供了一个OnUpdate的方法来驱动玩家的当前状态。
因为当前管理器是角色对象需要实例化的,所以这里提供了一个构造函数,我们在构造函数中实例化我们所有的角色状态以及继承自RoleStateAbstract的所有具体角色状态。
RoleFSMMgr.cs

using System.Collections.Generic;/// <summary>
/// 角色有限状态机管理器
/// </summary>
public class RoleFSMMgr
{/// <summary>/// 当前角色控制器/// </summary>public RoleCtrl CurrRoleCtrl { get; private set; }/// <summary>/// 当前角色状态枚举/// </summary>public RoleState CurrRoleStateEnum { get; private set; }/// <summary>/// 当前角色状态/// </summary>private RoleStateAbstract m_CurrRoleState = null;/// <summary>/// 状态基类字典/// </summary>private Dictionary<RoleState, RoleStateAbstract> m_RoleStateDic;/// <summary>/// 构造函数/// </summary>/// <param name="currRoleCtrl"></param>public RoleFSMMgr(RoleCtrl currRoleCtrl){CurrRoleCtrl = currRoleCtrl;m_RoleStateDic = new Dictionary<RoleState, RoleStateAbstract>();m_RoleStateDic[RoleState.Idle] = new RoleStateIdle(this);m_RoleStateDic[RoleState.Run] = new RoleStateRun(this);m_RoleStateDic[RoleState.RunToIdle] = new RoleStateRunToIdle(this);}#region OnUpdate 每帧执行/// <summary>/// 每帧执行/// </summary>public void OnUpdate(){if (m_CurrRoleState != null){m_CurrRoleState.OnStay();}}#endregion/// <summary>/// 切换状态/// </summary>/// <param name="newState">新状态</param>public void ChangeState(RoleState newState){if (CurrRoleStateEnum == newState) return;//调用以前状态的离开方法if (m_CurrRoleState != null)m_CurrRoleState.OnExit();//更改当前状态枚举CurrRoleStateEnum = newState;//更改当前状态m_CurrRoleState = m_RoleStateDic[newState];///调用新状态的进入方法m_CurrRoleState.OnEnter();}
}

RoleCtrl人物控制器我就不过多解释了,这里其实只是一个对象的载体脚本,只是为了获取到角色对象上面的Animator动画、CharacterController人形碰撞器、JoyStickBar摇杆、RoleFSMMgr实例化的角色fsm管理器。

这里的Update的方法中有一段代码如下,这个就是检测是否在地面如果不在地面上那么就向下位移瞬间到达平面的意思,如果你是想要做跳跃的话可以修改这里,自己写向下的作用力。

        ///非落地情况 使其快速落地if (!m_char.isGrounded){m_char.Move(new Vector3 (0,-100,0));}

RoleCtrl是实体对象,所以需要在Start的时候创建我们得fsm状态管理器,然后设置自身的初始状态。
整体代码
RoleCtrl.cs
在这里插入图片描述

using UnityEngine;public class RoleCtrl : MonoBehaviour {/// <summary>/// 摇杆/// </summary>public JoyStickBar m_Joy;/// <summary>/// 当前人物触发器/// </summary>public CharacterController m_char;/// <summary>/// 动画状态机/// </summary>public RoleFSMMgr m_fsm;/// <summary>/// 动画控制器/// </summary>public Animator m_anim;private void Start(){m_Joy.m_ctrl = this;m_char = GetComponent<CharacterController>();m_anim = GetComponent<Animator>();m_fsm = new RoleFSMMgr(this);m_fsm.ChangeState(RoleState.Idle);}// Update is called once per framevoid Update () {if (!m_char) return;if (!m_Joy) return;if (m_fsm == null) return;///非落地情况 使其快速落地if (!m_char.isGrounded){m_char.Move(new Vector3 (0,-100,0));}m_fsm.OnUpdate();}
}

虽然我之前讲过摇杆,但是这里我把摇杆的脚本也贴出来,这里还是稍微有点变化的
摇杆继承了UnityEngine.EventSystems中的三个事件分别是IBeginDragHandler、IDragHandler、IEndDragHandler
JoyStickBar.cs
在这里插入图片描述

using UnityEngine;
using UnityEngine.EventSystems;public class JoyStickBar : MonoBehaviour,IBeginDragHandler,IDragHandler,IEndDragHandler {/// <summary>/// 角色控制器/// </summary>public RoleCtrl m_ctrl;/// <summary>/// 最大半径/// </summary>public float maxRadius;/// <summary>/// 计算中的半径/// </summary>public float radius;/// <summary>/// 原始位置/// </summary>private Vector2 originalPos;/// <summary>/// 遥杆中心位置/// </summary>public RectTransform joystickradius;/// <summary>/// 箭头指针方向/// </summary>public Transform joystickpointer;#region 方向控制访问器/// <summary>/// 水平方向/// </summary>private float horizontal = 0;/// <summary>/// 垂直方向/// </summary>private float vertical = 0;/// <summary>/// 水平方向属性访问器/// </summary>public float Horizontal{get { return horizontal; }}/// <summary>/// 垂直方向属性访问器/// </summary>public float Vertical{get { return vertical; }}#endregionprivate void Start(){if (!joystickradius) return;originalPos = transform.position;maxRadius = - joystickradius.anchoredPosition.x;Debug.LogError(maxRadius);ShowPointer(false);}#region 方向受力/// <summary>/// 各个方向上的受力/// </summary>private void DirPotency(){//horizontal = transform.localPosition.x;//vertical = transform.localPosition.y;horizontal = GetComponent<RectTransform>().anchoredPosition.x;vertical = GetComponent<RectTransform>().anchoredPosition.y;}#endregion#region 继承接口事件逻辑处理/// <summary>/// 开始拖拽/// </summary>/// <param name="eventData"></param>public void OnBeginDrag(PointerEventData eventData){ShowPointer(true);}/// <summary>/// 拖拽中/// </summary>/// <param name="eventData"></param>public void OnDrag(PointerEventData eventData){//偏移量Vector2 dir = eventData.position - originalPos;//Vector2 dir = new Vector2 (Input.mousePosition.x, Input.mousePosition.y) - originalPos;//获取向量长度float distance = Vector3.Magnitude(dir);//获取当前radius = Mathf.Clamp(distance,0,maxRadius);//状态切换m_ctrl.m_fsm.ChangeState(RoleState.Run);//位置赋值transform.position = dir.normalized * radius + originalPos;//方向受力度量DirPotency();//角度转换CalculateAngle(dir.normalized);}/// <summary>/// 结束拖拽/// </summary>/// <param name="eventData"></param>public void OnEndDrag(PointerEventData eventData){transform.position = originalPos;//当前半径radius = 0;//方向受力度量DirPotency();ShowPointer(false);//m_ctrl.m_fsm.ChangeState(RoleState.Idle);m_ctrl.m_fsm.ChangeState(RoleState.RunToIdle);}#endregion#region 指针逻辑/// <summary>/// 角度转换/// </summary>public void CalculateAngle(Vector2 dir){if (!joystickpointer) return;float angle = Vector2.Angle(Vector2.up,dir);joystickpointer.rotation = Quaternion.Euler(new Vector3(0, 0, -(dir.x>0?angle:-angle)));}/// <summary>/// 显示隐藏指针/// </summary>/// <param name="isshow"></param>public void ShowPointer(bool isshow){joystickpointer.gameObject.SetActive(isshow);}#endregion}

角色模型的设置

角色的模型这里使用的是通用骨骼Generic,当然这里使用的是人形或者其他形也没有问题,只要模型跟动作是同意的骨骼就没有问题。
在这里插入图片描述
根骨骼节点记得也要跟动作选择一致,Root node这里选择的是骨骼的根节点Bip001这个根节点,动画对应的也需要选择这个。
在这里插入图片描述
下面是动画的设置在这里插入图片描述

角色动作的设置

动作的设置这里详细的说一下,因为这里有些动画是包含位移的,有些动作又是不包含位移的,这里需要看你具体要做的功能是什么,比如你要做帧同步的动画状态,那么你动画肯定是不能自己带位移的,也不能用动画的位移去应用到实际运动,因为这个动作底层使用的还是float是不一致的物理,除非这里只用做显示层。至于只要不是需要同步的位移的那么你动作带有位移或者使用这个也没有关系。
待机Idle动画
Anim.Compression这个时动画的压缩我通常直接设置Off,不启用动画的压缩
在这里插入图片描述
这里是动画的名字和帧数区间,如果是做了一个完整的动画需要截取动画的的,可以自己根据动作或者策划提供的配置文件进行截取动画这里我就不多说了,名字最好是要改一下并且跟RoleState中你创建的角色状态的枚举是一致的,这样方便在动画后面持续过程中方便操作,获取动画的信息等。
在这里插入图片描述
再看一下下面这几项Loop Time是是否需要循环播放动画,这里我们要做的是Idle和Run所以肯定是都要选择Loop Time。
Root Transform Rotation中
Bake Into Pose不够选代表的是使用动作的旋转来映射到真实人物上,勾选代表产生的旋转不会映射到实际人物上。
Base Upon(at Start)代表的是旋转映射到哪根骨骼上。
Offset代表的是偏移量

Root Transfrom Position(Y)
Bake Into Pose不够选代表的是使用动作的Y轴位移来映射到真实人物Y轴上,勾选代表产生位移不会映射到实际人物Y轴上。
Base Upon代表的是旋转映射到哪根骨骼上。
Offset代表的是偏移量

Root Transform Position(XZ)
Bake Into Pose不够选代表的是使用动作的XZ轴位移来映射到真实人物上,勾选代表产生位移不会映射到实际人物XZ轴上。
Base Upon代表的是旋转映射到哪根骨骼上。
Offset代表的是偏移量
在这里插入图片描述
Curvse动画曲线这里简单说一下,可以获取动画的曲线值来方便调式动作。
Events动画事件一般人物的脚步声音,动作特效事件等需要插事件。
Mask骨骼遮罩这个就不说了,这个是做分层动画

跑步Run动画
这个动画我就不单独设置了,跟Idle的原理是一样的,设置也是完全相同的。记住设置玩动画之后要点击Apply。

角色动画控制器的设置

创建两个参数Idle是一个bool类型的
Run是一个float类型的参数为后面使用
在这里插入图片描述

单层动画控制器,这里单层动画即可,将之前设置好的Idle拖上来,并且从Any State分别连线到Idle。
在这里插入图片描述
连线设置如下
Transition Duration这个融合度根据自己的动作合适的程度设置(融合度)
Has Exit Time取消勾选(无退出动作时间)
Can Transition To Self取消勾选(不能自己切换自己)
Conditions切换条件选择Idle,true
在这里插入图片描述

创建一个动画融合树,并命名为Run
在这里插入图片描述
在这里插入图片描述
动画融合树设置如下,将Idle和Run都拖拽上来,Parameter设置为刚才创建的参数Run,此时拖动BlendTree的值动画可以看到动画的变化
在这里插入图片描述
动画变化如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
调节Run的参数0-1,上面会得到一个从Idle到Run的不同程度的融合动画,这样后面就可以做融合缓动的起步的动画了。

拖动AnyState连线到Run并且设置如下
在这里插入图片描述

书写角色动画的具体状态,实现缓动起步的FSM

RoleStateIdle状态这里是常规动画的脚本书写


/// <summary>
/// 待机状态
/// </summary>
public class RoleStateIdle : RoleStateAbstract
{/// <summary>/// 构造函数/// </summary>/// <param name="roleFSMMgr">有限状态机管理器</param>public RoleStateIdle(RoleFSMMgr roleFSMMgr) : base(roleFSMMgr){}/// <summary>/// 实现基类 进入状态/// </summary>public override void OnEnter(){base.OnEnter();CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetBool(RoleState.Idle.ToString(), true);}/// <summary>/// 实现基类 执行状态/// </summary>public override void OnStay(){base.OnStay();//CurrRoleAnimatorStateInfo = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetCurrentAnimatorStateInfo(0);}/// <summary>/// 实现基类 离开状态/// </summary>public override void OnExit(){base.OnExit();CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetBool(RoleState.Idle.ToString(), false);}
}

RoleStateRun状态这个就变成了缓动起步
下面我提供了两种缓动的方案,一种是根据摇杆拖拽的程度来控制跑动的幅度,另一种是设置了一个时间来做增速,相当于进行缓冲起步。
这里看的仔细的会发现我OnExit的方法并没有写逻辑,因为我这里如果停下的话还需要另一个状态来操作逻辑,从当前跑动的幅度在回到Idle的这么一个状态。当然这里还考虑了一点就是OnEnter的时候我提取了一个time来接收是否有初始速度,如果有初始速度那么按照初始速度来进行计算。

using UnityEngine;/// <summary>
/// 跑状态
/// </summary>
public class RoleStateRun : RoleStateAbstract
{/// <summary>/// 旋转速度/// </summary>public float m_rotateSpeed = 3f;/// <summary>/// 移动速度/// </summary>public float m_moveSpeed = 3f;/// <summary>/// 移动速度/// </summary>private float m_MoveRatio = 0f;/// <summary>/// 转身的目标方向/// </summary>private Quaternion m_TargetQuaternion;/// <summary>/// 构造函数/// </summary>/// <param name="roleFSMMgr">有限状态机管理器</param>public RoleStateRun(RoleFSMMgr roleFSMMgr) : base(roleFSMMgr){}/// <summary>/// 实现基类 进入状态/// </summary>public override void OnEnter(){base.OnEnter();time = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetFloat(RoleState.Run.ToString());}/// <summary>/// 缓冲计时/// </summary>float time = 0;/// <summary>/// 缓冲速度/// </summary>float speed = 0;/// <summary>/// 实现基类 执行状态/// </summary>public override void OnStay(){base.OnStay();#region 移动方案一  根据方向按键拖动的距离来增加移动速度获取方向键拖动比例//float radius = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.radius;//float maxradius = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.maxRadius;//m_MoveRatio = radius / maxradius;//CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), m_MoveRatio);//float hor = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Horizontal;//float ver = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Vertical;//Vector3 dir = new Vector3(hor, 0, ver);//if (dir != Vector3.zero)//{//    CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation = Quaternion.Lerp(CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation, Quaternion.LookRotation(dir), Time.deltaTime * m_rotateSpeed);//    CurrRoleFSMMgr.CurrRoleCtrl.m_char.Move(CurrRoleFSMMgr.CurrRoleCtrl.transform.forward * Time.deltaTime * m_moveSpeed * m_MoveRatio);//}#endregion#region 方案二  拖动按钮有加速度if (time <= 1){time += Time.deltaTime;speed = m_moveSpeed * time;CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), time);}float hor = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Horizontal;float ver = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Vertical;Vector3 dir = new Vector3(hor, 0, ver);if (dir != Vector3.zero){CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation = Quaternion.Lerp(CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation, Quaternion.LookRotation(dir), Time.deltaTime * m_rotateSpeed);CurrRoleFSMMgr.CurrRoleCtrl.m_char.Move(CurrRoleFSMMgr.CurrRoleCtrl.transform.forward * Time.deltaTime * speed);}#endregion//CurrRoleAnimatorStateInfo = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetCurrentAnimatorStateInfo(0);}/// <summary>/// 实现基类 离开状态/// </summary>public override void OnExit(){base.OnExit();}
}

RoleStateRunToIdle状态是从当前跑动的幅度切换回Idle的状态

using UnityEngine;public class RoleStateRunToIdle : RoleStateAbstract
{/// <summary>/// 构造函数/// </summary>/// <param name="roleFSMMgr">有限状态机管理器</param>public RoleStateRunToIdle(RoleFSMMgr roleFSMMgr) : base(roleFSMMgr){}/// <summary>/// 实现基类 进入状态/// </summary>public override void OnEnter(){base.OnEnter();time = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetFloat(RoleState.Run.ToString());CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), time);}float time;float speed = 0;/// <summary>/// 实现基类 执行状态/// </summary>public override void OnStay(){base.OnStay();if (time >= 0){time -= Time.deltaTime;speed = 3 * time;CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), time);}CurrRoleFSMMgr.CurrRoleCtrl.m_char.Move(CurrRoleFSMMgr.CurrRoleCtrl.transform.forward * Time.deltaTime * speed);}/// <summary>/// 实现基类 离开状态/// </summary>public override void OnExit(){base.OnExit();//CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), 0f);}
}

这样就实现了这一套完整的缓动的FSM动画状态机。

总结

这个FSM其实还是很常用的,包括这里我其实也没有讲的特比深入,只是一个简单的FSM入门。这个还可以考虑动作的播放到什么程度和暂定动画,加速动画等,甚至后面还有HFSM分层管理器,有兴趣的话可以多了解了解,小主后面有时间的话可以在做一个HFSM,上面文章如果有看不懂的可以找到我的资源FSMRoleCtrl.unitypackage。(设置了10积分下载,希望各位可以理解)

这篇关于FSM人物动画状态机的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/220584

相关文章

最好用的WPF加载动画功能

《最好用的WPF加载动画功能》当开发应用程序时,提供良好的用户体验(UX)是至关重要的,加载动画作为一种有效的沟通工具,它不仅能告知用户系统正在工作,还能够通过视觉上的吸引力来增强整体用户体验,本文给... 目录前言需求分析高级用法综合案例总结最后前言当开发应用程序时,提供良好的用户体验(UX)是至关重要

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

Qt QWidget实现图片旋转动画

《QtQWidget实现图片旋转动画》这篇文章主要为大家详细介绍了如何使用了Qt和QWidget实现图片旋转动画效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、效果展示二、源码分享本例程通过QGraphicsView实现svg格式图片旋转。.hpjavascript

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

动画AnimationDrawable、转动

现实开发中:很多地方都用到 点击动画的特效; 本案例本人做了三个关于“动” 画 的效果; 先上图: 总体图: A: B: 1:点击图片按钮,效果是:图片闪动; 通过在xml中定义:标签:animation-list来实现点击动画的效果;  是否循环标签:oneshot ;   时间间隔标签:duration ; 要显示的图片标签:drawable ;

13 transition数组的动画使用

划重点 动画:transitiontransition-group :数组动画数组的 添加 / 删除 豆腐粉丝汤 清淡又健康 <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><me

12 动画transition的使用2

划重点 Vue 动画:transition / transform在动画周期中执行动动画(上一篇是通过动画样式控制动画) 清蒸扇贝 <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><

【前端】animation动画以及利用vue制作简单的透明度改变动画,包含vue生命周期实现

一. 问题描述 想做一个文字透明度从1到0然后再从0到1的css动画。 二. 代码写法 2.1 animation写法 2.1.1 animation属性key 2.1.2 代码展示 <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=de