【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡,复制粘贴即可

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • 为什么使用CharacterController
  • SimpleMove和Move如何选择?
    • 1. SimpleMove
    • 2. Move
  • 配置CharacterController参数
  • 控制相机
  • 移动
  • 跳跃
  • 下蹲
  • 处理下坡抖动问题
  • 实现奔跑和不同移速控制
  • 完整代码
  • 完结

前言

其实一开始我是不打算写的,我感觉这种简单的功能,网上随便一搜一大堆,但是我发现网上很多都是复制粘贴,要么没有实操过,要么就是功能不全,或者毫无解释的把代码丢出来,我自以为简单的3D角色控制,我整整花了3-4天才研究明白(虽然每天只花几个小时),下面是记录我的一些思路过程,希望对你有帮助。

其实之前实战有做过FPS移动控制,只是没有说的很全面,刚兴趣可以查看之前的文章:
【用unity实现100个游戏之18】从零开始制作一个类CSGO/CS2、CF第一人称FPS射击游戏——基础篇1

为什么使用CharacterController

Unity中常用的三种角色移动方式如下:

  1. 使用刚体(Rigidbody)组件:这种方式将角色对象添加刚体组件,并通过力(Force)或者速度(Velocity)来控制移动。你可以使用输入控制器(例如键盘、手柄)获取移动输入,然后将对应的力或速度施加给角色刚体,从而实现移动。

  2. 使用Transform组件的Translate方法:这种方式直接使用Transform组件的Translate方法来移动角色。你可以通过获取输入控制器的输入,计算出移动方向和距离,然后调用Translate方法将角色移动到指定位置。

  3. 使用Character Controller组件:这种方式需要将角色对象添加Character Controller组件,并使用Character Controller提供的Move方法来移动角色。你可以通过输入控制器获取移动输入,然后将输入转换为移动向量,并传递给Character Controller的Move方法来实现移动。

刚体自带重力和物理效果,但是对于爬坡,走楼梯要单独处理,比较麻烦。(ps:当然,后面有机会我在研究Rigidbody如何控制人物,可以关注期待一下

Transform呢,不带重力又不带碰撞,移动不受物理引擎控制,可能导致穿透墙壁或其他对象。不支持碰撞和重力等物理效果。所以我说直接pass的

CharacterController主要是不适用于需要处理复杂物理交互的情况,例如推动物体等。但是对于爬坡,楼梯自带了处理方式,完美解决了刚体的痛点,而如果你想推动物体也可以直接给物体一个推力即可解决

SimpleMove和Move如何选择?

而对于CharacterController有用两种移动实现方式,SimpleMove和Move。

1. SimpleMove

  • 不受Y轴速度影响,自带重力效果,无法实现跳跃功能
  • 返回值为Bool,当角色接触地面返回True,反之为False。
  • SimpleMove方法是CharacterController组件提供的一个用于处理角色平面移动的简化方法,它自动处理了角色与地面的碰撞* 检测和摩擦,但它不支持跳跃等垂直方向的动作

2. Move

  • 无重力效果,自行实现重力,可做跳跃功能
  • 返回值(CollisionFlags对象),返回角色与物体碰撞的信息

可以看到SimpleMove看着虽好,但是最大的痛点是无法实现跳跃,所以我们只能忍痛Pass掉

配置CharacterController参数

新增一个胶囊体,代表角色,在角色身上新增CharacterController组件,参数配置如下
在这里插入图片描述

控制相机

将相机拖入为玩家的子物体,放置在角色的头部位置,修改
新增MouseLook脚本,挂载在相机上,控制相机视角

public class MouseLook : MonoBehaviour
{// 鼠标灵敏度public float mouseSensitivity = 1000f;// 玩家的身体Transform组件,用于旋转public Transform playerBody;// x轴的旋转角度float xRotation = 0f;void Start(){// 锁定光标到屏幕中心,并隐藏光标Cursor.lockState = CursorLockMode.Locked;}// Update在每一帧调用void Update(){// 执行自由视角查看功能FreeLook();}// 自由视角查看功能的实现void FreeLook(){// 获取鼠标X轴和Y轴的移动量,乘以灵敏度和时间,得到平滑的移动速率float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;//限制旋转角度在-90到90度之间,防止过度翻转xRotation = Mathf.Clamp(xRotation, -90f, 90f);// 累计x轴上的旋转量xRotation -= mouseY;// 应用摄像头的x轴旋转transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);// 应用玩家身体的y轴旋转playerBody.Rotate(Vector3.up * mouseX);}
}

效果
在这里插入图片描述

移动

经过上面的分享,我们使用Move实现一下人物的移动
新增MovementScript脚本,挂载在角色身上

public class MovementScript : MonoBehaviour
{[Tooltip("角色控制器")] public CharacterController characterController;private float horizontal;private float vertical;[Header("移动")][Tooltip("角色行走的速度")] public float walkSpeed = 6f;[Tooltip("当前速度")] private float speed;void Start(){speed = walkSpeed;}void Update(){horizontal = Input.GetAxis("Horizontal");vertical = Input.GetAxis("Vertical");moveDirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向,效果和上面的一样// moveDirection = transform.TransformDirection(new Vector3(h, 0, v));moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快characterController.Move(moveDirection * Time.deltaTime * speed);}
}

效果
在这里插入图片描述

跳跃

地面检测我们就用圆形球体把,因为人物是胶囊体,这种方法最合适

[Header("地面检测")]
[Tooltip("地面检测位置")] public Transform groundCheck;
[Tooltip("地面检测半径")] public float sphereRadius = 0.4f;
[Tooltip("是否在地面")] private bool isGround;[Header("跳跃")]
[Tooltip("角色跳跃的高度")] public float jumpHeight = 2.5f;
[Tooltip("判断是否在跳跃")] private bool isJumping;void Update()
{//地面检测isGround = IsGrounded();SetJump();
}//控制跳跃
void SetJump()
{if (Input.GetButtonDown("Jump") && isGround){isJump = true;moveDirection.y = jumpHeight;}moveDirection.y -= Gravity * Time.deltaTime;characterController.Move(moveDirection * Time.deltaTime * speed);isJump = false;
}//是否在地面
bool IsGrounded()
{Collider[] colliders = Physics.OverlapSphere(groundCheck.position, sphereRadius);foreach (Collider collider in colliders){if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform)) // 忽略角色自身和所有子集碰撞体{return true;}}return false;
}//判断child是否是parent的子集
bool IsChildOf(Transform child, Transform parent)
{while (child != null){if (child == parent){return true;}child = child.parent;}return false;
}//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{Gizmos.color = Color.red;//地面检测可视化Gizmos.DrawWireSphere(groundCheck.position, sphereRadius);
}

配置地面检测点位置
在这里插入图片描述
效果
在这里插入图片描述

下蹲

下蹲的逻辑就是让CharacterController 的高度减半,还有中心点的位置也跟着减半,当然还有摄像机的高度,还需要注意的是人物如果头顶有东西的时候我们是不允许他起立的,不然会穿模,所以还需要一个头顶检测,头顶我们使用盒子检测最好,可以覆盖整个头部

[Header("相机")]
[Tooltip("摄像机相机")] public Transform mainCamera;
[Tooltip("摄像机高度变化的平滑值")] public float interpolationSpeed = 10f;
[Tooltip("当前摄像机的位置")] private Vector3 cameraLocalPosition;
[Tooltip("当前摄像机的高度")] private float height;[Header("头顶检测")]
[Tooltip("头顶检测位置")] public Transform headCheck;
[Tooltip("盒子半长、半宽、半高")] public Vector3 halfExtents = new Vector3(0.4f, 0.5f, 0.4f);
[Tooltip("判断玩家是否可以站立")] private bool isCanStand;[Header("下蹲")]
[Tooltip("下蹲时候的玩家高度")] private float crouchHeight;
[Tooltip("判断玩家是否在下蹲")] private bool isCrouching;
[Tooltip("正常站立时玩家高度")] private float standHeight;void Start()
{standHeight = characterController.height;crouchHeight = standHeight / 2;cameraLocalPosition = mainCamera.localPosition;speed = walkSpeed;
}void Update()
{//头顶检测isCanStand = CanStand();SetCrouch();
}//控制下蹲
void SetCrouch()
{if (Input.GetKey(KeyCode.LeftControl)){Crouch(true);}else{Crouch(false);}
}//newCrouching控制下蹲起立
public void Crouch(bool newCrouching)
{if (!newCrouching && !isCanStand) return; //准备起立时且头顶有东西,不能进行站立isCrouching = newCrouching;float targetHeight = isCrouching ? crouchHeight : standHeight;characterController.height = targetHeight; //根据下蹲状态设置下蹲时候的高度和站立的高度characterController.center = new Vector3(0, targetHeight / 2, 0); //将角色控制器的中心位置Y,从头顶往下减少1半的高度// 设置下蹲站立时候的摄像机高度float heightTarget = isCrouching ? cameraLocalPosition.y / 2 + characterController.center.y : cameraLocalPosition.y;height = Mathf.Lerp(height, heightTarget, interpolationSpeed * Time.deltaTime);//平滑过渡mainCamera.localPosition = new Vector3(cameraLocalPosition.x, height, cameraLocalPosition.z);
}//是否可以起立,及头顶是否有物品
bool CanStand()
{Collider[] colliders = Physics.OverlapBox(headCheck.position, halfExtents);foreach (Collider collider in colliders){//忽略角色自身和所有子集碰撞体if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform)){return false;}}return true;
}//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{Gizmos.color = Color.red;//头顶检测可视化Gizmos.DrawWireCube(headCheck.position, halfExtents * 2f);
}

配置头顶检测
在这里插入图片描述
效果
在这里插入图片描述

处理下坡抖动问题

你的人物在下斜坡时可能会出现一个问题,人物下坡出现抖动,抖动其实本身可以理解,但是这个下坡抖动会影响地面检测准度,导致我们移动下坡时可能跳不起来,当然这不是我们想要的,具体的处理思路就是当我们判断在斜面时,给人物一个向下的压力,让人物没那么容易离地,而判断在斜面的方法就从人物向下打一条射线,判断射线和地面的法线如果非90度就是在斜面了

[Header("斜坡检测")]
[Tooltip("斜坡射线长度")] public float slopeForceRayLength = 0.2f;
[Tooltip("是否在斜坡")] private bool isSlope;[Header("斜坡")]
[Tooltip("走斜坡时向下施加的力度")] public float slopeForce = 6.0f;void Update()
{//斜坡检测isSlope = OnSlope();SetJump();
}//控制跳跃
void SetJump()
{//。。。//为了不影响跳跃,一定要在isJumping = false之前加力SetSlope();isJumping = false;
}//控制斜坡
public void SetSlope()
{//如果处于斜坡if (isSlope && !isJumping){//向下增加力moveDirection.y = characterController.height / 2 * slopeForceRayLength;characterController.Move(Vector3.down * characterController.height / 2 * slopeForce * Time.deltaTime);}
}//是否在斜面
public bool OnSlope()
{RaycastHit hit;// 向下打出射线(检测是否在斜坡上)if (Physics.Raycast(transform.position + characterController.height / 2 * Vector3.down, Vector3.down, out hit, characterController.height / 2 * slopeForceRayLength)){// 如果接触到的点的法线,不在(0,1,0)的方向上,那么人物就在斜坡上if (hit.normal != Vector3.up)return true;}return false;
}//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{Gizmos.color = Color.red;//斜坡检测可视化Debug.DrawRay(transform.position + characterController.height / 2 * Vector3.down, Vector3.down * characterController.height / 2 * slopeForceRayLength, Color.blue);
}

斜坡检测线
在这里插入图片描述
效果
在这里插入图片描述

实现奔跑和不同移速控制

[Header("移动")]
[Tooltip("角色行走的速度")] public float walkSpeed = 6f;
[Tooltip("角色奔跑的速度")] public float runSpeed = 9f;
[Tooltip("角色下蹲的速度")] public float crouchSpeed = 3f;
[Tooltip("角色移动的方向")] private Vector3 moveDirection;
[Tooltip("当前速度")] private float speed;
[Tooltip("是否奔跑")] private bool isRun;//速度设置
void SetSpeed()
{if (isRun){speed = runSpeed;}else if (isCrouching){speed = crouchSpeed;}else{speed = walkSpeed;}
}//控制奔跑
void SetRun()
{if (Input.GetKey(KeyCode.LeftShift) && !isCrouching){isRun = true;}else{isRun = false;}
}

效果
在这里插入图片描述

完整代码

注意:代码的参数都是经过我测试过的,复制即可使用,并不推荐大家修改,除非你知道自己在干什么

[RequireComponent(typeof(CharacterController))]
public class MovementScript : MonoBehaviour
{[Tooltip("角色控制器")] public CharacterController characterController;[Tooltip("重力加速度")] private float Gravity = 9.8f;private float horizontal;private float vertical;[Header("相机")][Tooltip("摄像机相机")] public Transform mainCamera;[Tooltip("摄像机高度变化的平滑值")] public float interpolationSpeed = 10f;[Tooltip("当前摄像机的位置")] private Vector3 cameraLocalPosition;[Tooltip("当前摄像机的高度")] private float height;[Header("移动")][Tooltip("角色行走的速度")] public float walkSpeed = 6f;[Tooltip("角色奔跑的速度")] public float runSpeed = 9f;[Tooltip("角色下蹲的速度")] public float crouchSpeed = 3f;[Tooltip("角色移动的方向")] private Vector3 moveDirection;[Tooltip("当前速度")] private float speed;[Tooltip("是否奔跑")] private bool isRun;[Header("地面检测")][Tooltip("地面检测位置")] public Transform groundCheck;[Tooltip("地面检测半径")] public float sphereRadius = 0.4f;[Tooltip("是否在地面")] private bool isGround;[Header("头顶检测")][Tooltip("头顶检测位置")] public Transform headCheck;[Tooltip("盒子半长、半宽、半高")] public Vector3 halfExtents = new Vector3(0.4f, 0.5f, 0.4f);[Tooltip("判断玩家是否可以站立")] private bool isCanStand;[Header("斜坡检测")][Tooltip("斜坡射线长度")] public float slopeForceRayLength = 0.2f;[Tooltip("是否在斜坡")] private bool isSlope;[Header("跳跃")][Tooltip("角色跳跃的高度")] public float jumpHeight = 2.5f;[Tooltip("判断是否在跳跃")] private bool isJumping;[Header("下蹲")][Tooltip("下蹲时候的玩家高度")] private float crouchHeight;[Tooltip("判断玩家是否在下蹲")] private bool isCrouching;[Tooltip("正常站立时玩家高度")] private float standHeight;[Header("斜坡")][Tooltip("走斜坡时施加的力度")] public float slopeForce = 6.0f;void Start(){standHeight = characterController.height;crouchHeight = standHeight / 2;cameraLocalPosition = mainCamera.localPosition;speed = walkSpeed;}void Update(){horizontal = Input.GetAxis("Horizontal");vertical = Input.GetAxis("Vertical");//地面检测isGround = IsGrounded();//头顶检测isCanStand = CanStand();//斜坡检测isSlope = OnSlope();SetSpeed();SetRun();SetCrouch();SetMove();SetJump();}//速度设置void SetSpeed(){if (isRun){speed = runSpeed;}else if (isCrouching){speed = crouchSpeed;}else{speed = walkSpeed;}}//控制奔跑void SetRun(){if (Input.GetKey(KeyCode.LeftShift) && !isCrouching){isRun = true;}else{isRun = false;}}//控制下蹲void SetCrouch(){if (Input.GetKey(KeyCode.LeftControl)){Crouch(true);}else{Crouch(false);}}//控制移动void SetMove(){if (isGround){moveDirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向// moveDirection = transform.TransformDirection(new Vector3(h, 0, v));moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快  }}//控制跳跃void SetJump(){if (Input.GetButtonDown("Jump") && isGround){isJumping = true;moveDirection.y = jumpHeight;}moveDirection.y -= Gravity * Time.deltaTime;characterController.Move(moveDirection * Time.deltaTime * speed);//为了不影响跳跃,一定要在isJumping = false之前加力SetSlope();isJumping = false;}//控制斜坡public void SetSlope(){//如果处于斜坡if (isSlope && !isJumping){//向下增加力moveDirection.y = characterController.height / 2 * slopeForceRayLength;characterController.Move(Vector3.down * characterController.height / 2 * slopeForce * Time.deltaTime);}}//newCrouching控制下蹲起立public void Crouch(bool newCrouching){if (!newCrouching && !isCanStand) return; //准备起立时且头顶有东西,不能进行站立isCrouching = newCrouching;float targetHeight = isCrouching ? crouchHeight : standHeight;float heightChange = targetHeight - characterController.height; //计算高度变化characterController.height = targetHeight; //根据下蹲状态设置下蹲时候的高度和站立的高度characterController.center += new Vector3(0, heightChange / 2, 0); //根据高度变化调整中心位置// 设置下蹲站立时候的摄像机高度float heightTarget = isCrouching ? cameraLocalPosition.y / 2 + characterController.center.y : cameraLocalPosition.y;height = Mathf.Lerp(height, heightTarget, interpolationSpeed * Time.deltaTime);mainCamera.localPosition = new Vector3(cameraLocalPosition.x, height, cameraLocalPosition.z);}//是否可以起立,及头顶是否有物品bool CanStand(){Collider[] colliders = Physics.OverlapBox(headCheck.position, halfExtents);foreach (Collider collider in colliders){//忽略角色自身和所有子集碰撞体if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform)){return false;}}return true;}//是否在地面bool IsGrounded(){Collider[] colliders = Physics.OverlapSphere(groundCheck.position, sphereRadius);foreach (Collider collider in colliders){if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform)) // 忽略角色自身和所有子集碰撞体{return true;}}return false;}//是否在斜面public bool OnSlope(){RaycastHit hit;// 向下打出射线(检测是否在斜坡上)if (Physics.Raycast(transform.position + characterController.height / 2 * Vector3.down, Vector3.down, out hit, characterController.height / 2 * slopeForceRayLength)){// 如果接触到的点的法线,不在(0,1,0)的方向上,那么人物就在斜坡上if (hit.normal != Vector3.up)return true;}return false;}//判断child是否是parent的子集bool IsChildOf(Transform child, Transform parent){while (child != null){if (child == parent){return true;}child = child.parent;}return false;}//在场景视图显示检测,方便调试private void OnDrawGizmos(){Gizmos.color = Color.red;//头顶检测可视化Gizmos.DrawWireCube(headCheck.position, halfExtents * 2f);//地面检测可视化Gizmos.DrawWireSphere(groundCheck.position, sphereRadius);//斜坡检测可视化Debug.DrawRay(transform.position + characterController.height / 2 * Vector3.down, Vector3.down * characterController.height / 2 * slopeForceRayLength, Color.blue);}
}

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/278963.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis执行Sql的流程分析

本文介绍MyBatis执行Sql的流程,关于在执行过程中缓存、动态SQl生成等细节不在本章中体现 还是以之前的查询作为列子: public class MyApp {public static void main(String[] args) {String resource "mybatis-config.xml";Reader reader;t…

【Spring Boot】快速入门

一、引言 1、什么是spring boot? Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff…

如何在Centos 7环境下安装MySQL并登录

目录 先获取MySQL官方yum源 然后正常使用yum命令下载mysql即可完成MySQL的下载 使用mysql客户端登录mysqld服务端 能够登录mysql客户端后,我们最后还需要做一点配置 先获取MySQL官方yum源(包括对yum源的介绍) 介绍一下yum源 yum源就是一…

超燃超欢乐!修仙喜剧动画《师兄啊师兄》第二季稳健开播

12月14日,备受瞩目的《师兄啊师兄》第二季终于稳健开播!首播两集连放,同时第一季全13集限免,不仅便于新观众丝滑入坑,老观众也可以二刷重温,可以说是非常良心了! 《师兄啊师兄》改编自人气网络小…

指针(笔记2)

一、const修饰指针 变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量也可以修改这个变量。 我们希望给一个变量加上一些限制,不能被修改,那么这就由const起作用。 1.const修饰变量 (1)const如果放在&#xff…

基于Qt的登录页面设计

题目: 完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到其他界面 如果账号和…

【数据结构和算法】判断子序列

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一:双指针 三、代码 3.1 方法一:双指针 3.1.1 Java易懂版:…

K8s可视化kuboard 部署

创建资产 [rootkube-master ~]# kubectl apply -f https://addons.kuboard.cn/kuboard/kuboard-v3.yaml 查看对应资源 [rootkube-master ~]# kubectl get pod -n kuboard NAME READY STATUS RESTARTS AGE kuboard-agent-2-5c4f886…

基于JSP+Servlet+Mysql的建设工程监管信息

基于JSPServletMysql的建设工程监管信息 一、系统介绍二、功能展示1.企业信息列表2.录入项目信息3.项目信息列表 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目名称:基于JSPServlet的建设工程监管信息 项目架构:B/S架构 开发语言:…

论文阅读——GroupViT

GroupViT: Semantic Segmentation Emerges from Text Supervision 一、思想 把Transformer层分为多个组阶段grouping stages,每个stage通过自注意力机制学习一组tokens,然后使用学习到的组tokens通过分组模块Grouping Block融合相似的图片tokens。通过这…

软考科目如何选择?

软考科目繁多,让许多学弟学妹感到困惑,不知道该选择哪个科目。以下是一些建议,可以根据个人实际需求选择备考的科目。 1、初级是可选的 软考初级非常简单,适合刚刚入门学习的朋友报考。对于一些有基础的朋友,建议直接…

UE5 动画 Sequencer-学习笔记

P2. 课程介绍 资料:https://www.bilibili.com/video/BV1Ag411873f?p2&vd_source707ec8983cc32e6e065d5496a7f79ee6 Sequencer不仅可以做互动动画,还可以导出视频与序列帧 P3-4. 界面介绍 https://www.bilibili.com/video/BV1Ag411873f?p3&spm_…