Unity Animancer插件(二)精确控制

news2024/12/26 10:53:00

一、通过名称播放动画

前面我们讲的都是直接通过动画片段的引用播放动画,Animancer也提供了直接通过动画名称来播放动画的方法。但这并不是推荐的使用方式,因为通过字符串播放比通过引用播放效率略低,且更难维护。

首先我们需要在角色身上挂载NamedAnimancerComponent组件。NamedAnimancerComponent继承于AnimancerComponent,它的内部多了一个字典,可以用来映射动画名称与动画引用的对应关系。我们可以在面板上指定一个默认动画状态,这里指定为Idle,但取消自动播放选项。

接下来创建一个脚本NamedAnimations,并编写如下代码。

public class NamedAnimations : MonoBehaviour
{
	public NamedAnimancerComponent animancer;
	public AnimationClip walk;
	public AnimationClip run;
	
	public void PlayIdle()
	{
		animancer.TryPlay("Relax-Idle");
	}

	public void PlayWalk()
	{
		var state = animancer.TryPlay("Relax-Walk-Forward");

		if (state == null)
			Debug.LogWarning("'Relax-Walk-Forward' 没有被注册");
	}

	public void InitializeWalkState()
	{
		animancer.States.Create(walk);
		Debug.Log("创建状态:" + walk, this);
	}

	public void PlayRun()
	{
		animancer.Play(run);
	}
	
}

将脚本挂载到角色身上,并对变量进行赋值。然后在场景中添加四个按钮,分别绑定脚本中的四个方法

接下来运行游戏。先来看PlayIdle的效果

正常播放。这是因为在NamedAnimancerComponent的字典中,我们事先添加了Idle动画,从而可以映射成功。

接下来再试试PlayWalk

显然,Walk动画并不在字典中,所以播放失败了。要在运行时动态注册动画,只需要像脚本中的InitializeWalkState()方法一样,创建一个包含对应动画剪辑的状态即可。

最后PlayRun就是直接通过动画剪辑的引用播放动画,并不需要字典中持有该动画的映射。

二、控制动画的速度和时间

接下来我们来实现一个蜘蛛机器人的动画。它只有MoveWakeUp两个动画,我们要利用这两个动画实现两种状态间的无缝切换。这就需要对动画的速度和时间进行控制。

首先创建一个Spider Bot脚本。对于机器人当前的状态,我们可以通过对外界暴露一个IsMoving属性来进行控制。

public class SpiderBot : MonoBehaviour
{
	private bool _isMoving;

	public bool IsMoving
	{
		get => _isMoving;
		set => _isMoving = value;
	}
}

然后在Toggle的值改变事件中绑定这个属性,就可以通过UI来控制机器人的状态了。

接下来,我们要让机器人一开始就处于Sleep状态。这可以通过反向播放WakeUp动画实现。因为WakeUp动画并不是循环的,所以如果动画的Time达到0时,将会一直保持第一帧的姿势。一种简单的实现方式是直接把动画的Speed设置为-1。

public AnimancerComponent animancer;
public ClipTransition wakeUp;
public ClipTransition move;

private void Awake()
{
	var state = animancer.Play(wakeUp);
	state.Speed = -1;
}

但这种方式存在一些问题:首先,动画的Time会变成负数;其次,虽然实际上没有播放动画,但动画仍然会在每一帧进行计算,这会造成性能上的浪费。除此之外,将Speed设置为0或将IsPlaying设置为false,都无法避免类似的性能消耗。

一种更好的方式是直接暂停整个Playable Graph,这样就可以避免无意义的计算了。

private void Awake()
{
	animancer.Play(wakeUp);
	// 暂停整个图
	animancer.Playable.PauseGraph();
	// 计算第一帧
	animancer.Evaluate();
}

接下来就是在IsMoving的值改变时,进行动画的切换了。定义两个方法WakeUp()GoToSleep(),在IsMoving发生变化时调用

public bool IsMoving
{
	get => _isMoving;
	set
	{
		if (value)
			WakeUp();
		else
			GoToSleep();
	}
}

WakeUp()方法中,我们需要播放WakeUp动画,将其速度设置为1,并解除暂停Playable Graph

private void WakeUp()
{
	if(_isMoving) return;
	_isMoving = true;

	var state = animancer.Play(wakeUp);
	state.Speed = 1;
	
	animancer.Playable.UnpauseGraph();
}

WakeUp播放结束后,我们需要播放Move动画,可以通过添加结束事件实现(注意需要区分是唤醒还是睡眠)

private void Awake()
{
	// ...
	wakeUp.Events.OnEnd = OnWakeUpEnd;
}

private void OnWakeUpEnd()
{
	// 速度大于0是唤醒
	if (wakeUp.State.Speed > 0)
	{
		animancer.Play(move);
	}
	// 否则是睡眠
	else
	{
		animancer.Playable.PauseGraph();
	}
}

GoToSleep()方法中,我们需要反向播放WakeUp动画。这里需要注意,WakeUp动画在播放结束时NormalizedTime等于1,但向Move过渡时,NormalizedTime仍会继续增长并超过1。这意味着如果我们反转动画,NormalizedTime也需要花时间回到1,并在达到1时触发OnEnd事件,导致Playable Graph被暂停。此时角色会卡在一个奇怪的姿势。所以我们需要将NormalizedTime手动赋值为1。

private void GoToSleep()
{
	if(!_isMoving) return;
	_isMoving = false;

	var state = animancer.Play(wakeUp);
	state.Speed = -1;
	
	if (state.Weight == 0 || state.NormalizedTime > 1)
	{
		state.NormalizedTime = 1;
	}
}

最后来看下效果

三、更新频率

为了节省性能,在某些情况下,当角色离相机较远时,我们希望降低动画的更新频率。Animancer也可以很轻松地实现这点,只需要在Evaluate()方法的参数中传入一个时间差x,就可以计算出距离上次更新x秒后动画的动作,并更新到角色身上。

首先创建一个脚本LowUpdateRate,并编写如下代码

public class LowUpdateRate : MonoBehaviour
{
	public AnimancerComponent animancer;
	// 更新速率
	public float updatesPerSecond = 10;
	// 上次更新时间
	private float _lastUpdateTime;

	private void OnEnable()
	{
		animancer.Playable.PauseGraph();
		_lastUpdateTime = Time.time;
	}

	private void OnDisable()
	{
		if (animancer != null && animancer.IsPlayableInitialized)
			animancer.Playable.UnpauseGraph();
	}

	private void Update()
	{
		var time = Time.time;
		// 计算距离上次更新的时间差
		var timeSinceLastUpdate = time - _lastUpdateTime;
		// 如果时间差超过了更新速率则更新动画
		if (timeSinceLastUpdate > 1 / updatesPerSecond)
		{
			animancer.Evaluate(timeSinceLastUpdate);
			_lastUpdateTime = time;
		}
	}
}

可以给角色挂载NamedAnimancerComponent,并指定一个默认播放动画。运行一下看看效果

左侧是正常播放的机器人,右侧是低速率动画的机器人。可以看出左侧机器人的动画要更丝滑一些(受录制帧率的限制可能不明显)。

下面来实现根据摄像机距离动态启用/禁用LowUpdateRate的脚本。创建一个脚本DynamicUpdateRate,并编写如下代码

public class DynamicUpdateRate : MonoBehaviour
{
	public LowUpdateRate lowUpdateRate;
	public float slowUpdateDistance = 5;

	private Transform _camera;

	private void Awake()
	{
		_camera = Camera.main.transform;
	}

	private void Update()
	{
		// 计算相机与角色的距离
		var offset = _camera.position - transform.position;
		var squaredDistance = offset.sqrMagnitude;
		// 根据距离启用/禁用LowUpdateRate脚本
		lowUpdateRate.enabled = squaredDistance > slowUpdateDistance * slowUpdateDistance;
	}
}

将挂载LowUpdateRate的机器人复制一份,并挂载上面这个脚本。运行游戏看下效果

最左侧是正播放的机器人,中间是始终低速率播放动画的机器人,最右侧是应用了动态控制动画速率的机器人。

四、独播动画

有些物体只有一个动画,且不需要任何控制播放的逻辑,那就不需要前面那样复杂的操作,我们只需要挂载一个SoloAnimation组件即可实现。

如图所示,我们只需要指定一个动画片段,它就能在游戏运行时自动进行播放。

五、参考资料

[1]. https://kybernetik.com.au/animancer/docs/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022年ACM杰出会员名单公布:23位华人学者入选

12月7日,2022年度ACM杰出会员(Distinguished Member)名单公布! 本次评选设有三个奖项,分别表彰在计算机领域做出的教育贡献、工程贡献和科学贡献。 ACM创立于1947年,目前在全球130多个国家和地区拥有超过…

OceanBase 4.0 解读:降低分布式数据库使用门槛,谈谈我们对小型化的思考

关于作者 赵裕众 OceanBase 资深技术专家,2010 年加入支付宝后从事分布式事务框架的研发,2013 年加入 OceanBase 团队,目前负责存储引擎相关的研发工作。 近年来,随着应用场景多样化和数据量的增长,我们看到分布式数据…

判别分析-书后习题回顾总结

5-2 题目 理论基础 多总体的距离判别 马氏距离:dG2(x)(x−μ)2σ2d^{2}_{G}(x)\frac{(x-\mu)^{2}}{\sigma^{2}}dG2​(x)σ2(x−μ)2​ 取马氏距离最小的那一个,就属于这类。 贝叶斯判别准则 计算qtft(x)q_{t}\times f_{t}(x)qt​ft​(x) ft(x)12πσ…

001:Object-C介绍、创建第一个iOS工程、MVC架构

常见APP基础业务模块: 常见App类型:通信与存储、流媒体、直播技术、图片处理、内容展示、Web、组件化、IM类型、音视频、直播类型、摄影摄像类型、资讯类型、工具、购物类型。 App 展示界面动画:底部TabBar、Navigation、列表、图片ImageVi…

一文囊括Ceph所有利器(工具)

原文链接: 知乎专栏: 一文囊括Ceph所有利器(工具) - 知乎 前言 ceph的工具很多,包括集群管理与运维,还有性能分析等等。 所以本文期望应收尽收所有的工具,也当做自己的一个梳理与总结,当自己需要的时候知道有哪些利器可以使用…

一文搞懂SSL/TLS

SSL/TLS1. 概述2. 协议组成2.1 握手协议(Handshake protocol)2.2 记录协议(Record Protocol)2.3 警报协议(Alert Protocol)3. 密码套件与密钥生成1. 概述 安全套接字层(SSL,Secure …

使用 X2MindSpore 迁移 Pytorch 训练脚本mobileNet支持分布式训练

简介 MindSpore是华为昇腾开发的深度学习框架,旨在提供端边云全场景的AI框架。 Pytorch是由Facebook推出的AI框架。 本教程使用MindStudio中的X2MindSpore功能自动将Pytorch脚本转换为MindSpore脚本的案例。使用的模型是分类任务模型mobileNet,数据集是c…

【自动化测试】Selenium IDE脚本编辑与操作(了解)

之前,我们录制脚本时是录制鼠标和键盘的所有在浏览器的操作,那么脚本会出现多余的步骤,有时候我们需要手动填写脚本或修改脚本,所以我们有必要对selenium IDE脚本编辑与操作有所了解;(采用录制的方式很容易…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java校园招聘管理系统968b0

毕业设计也不需要做多高端的程序,毕业设计对于大多数同学来说,为什么感觉到难,最重要的一个原因,那就是理论课到实践课的转变,很多人一下不适应,本能开始拒绝,如果是一个考试,大家都…

【大数据入门核心技术-Hbase】(一)HBase简介

目录 一、HBase基本概述 1、Hbase是什么 2、什么时候用Hbase? 二、HBase基本架构 1、Client 2、Zookeeper 3、HMaster 4、 RegionServer 三、HBase逻辑结构 一、HBase基本概述 1、Hbase是什么 HBase是一个分布式的、面向列的开源数据库,该技术…

pstore

pstore简介 pstore最初是用于系统发生oops或panic时,自动保存内核log buffer中的日志。不过在当前内核版本中,其已经支持了更多的功能,如保存console日志、ftrace消息和用户空间日志。同时,它还支持将这些消息保存在不同的存储设…

新手教程 | 手把手教你 谷歌浏览器如何使用HTTP代理?

本文将针对谷歌浏览器如何使用代理IP进行详细说明,具体步骤如下: 1、代理IP信息查看 查看自己所购买的相对应的代理IP面板,点击代理IP -查看详情-进行查看“AuthKey”以及“AuthPwd”信息。 2、代理IP资源提取 在控制台-代理IP-中的“提取…

Spring cloud Gateway 服务网关 实战

Spring cloud Gateway 服务网关一、简介优点:特性:总结:二、核心概念三、路由规则1、Path2、Query3、Method4、Datetime5、RomoteAddr6、Header四、动态路由1、依赖2、配置动态获取URI服务名称转发五、过滤器1、网关过滤器 GatewayFilter局部…

Allegro如何给差分过孔添加禁布操作指导

Allegro如何给差分过孔添加禁布操作指导 Allegro支持给差分过孔添加禁布,让它避让周围的铜皮,具体操作如下 以下图两个过孔为例,需要做一个和via避让铜皮尺寸一样大的禁布 选择show element命令 Find选择Other segs 鼠标移动到铜皮避让圆形的地方 出现一个report,记住…

【并发编程】SemaphoreCountDownLatchCyclicBarrier

一、Semaphore Semaphore 通常又被称为信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。 1.简单的使用 1-1.控制线程的并发连接数 public static void main(String[] args) {// 只允许两个线程执…

中英翻译《动物看见了什么》

What animals see 动物看见了什么 一、Pre-reading activity 阅前思考 1.What animals do you like? 你喜欢什么动物? 2.Do you have any animals in your home? 你家里有动物吗? 3.Do you think most animals can see as well as we can? 你认为大多…

Windows下docker安装

安装 1.打开Hyper-V ,在"启用或关闭Windows功能" 如果这里的Hyper-V平台灰色不能勾选 显示无法安装Hyper-v该固件中的虚拟化支持被禁用,则需要开启 开始方式: 重启电脑进入BIOSS界面 点击高级–>CPU设置 —> Intel virtu…

44. python的for循环嵌套

44. python的for循环嵌套 文章目录44. python的for循环嵌套1. 什么是嵌套2. for循环中嵌套有if条件判断语句2.1 先创建一个奇数序列2.2 判断一个数是否能被7整除2.3 将2部分代码合二为一3. for循环中嵌套有for循环1. 什么是嵌套 嵌套是指一个对象中包含另一个与它相似的对象。…

Python 中在两个字典中查找公共键

Python 中要在两个词典中查找公共键: 使用 dict.keys() 方法获取每个字典的键的视图对象。使用 & 符号获取常用键。使用 list() 类将结果转换为列表对象。 dict1 {name: jiyik, topic: Python, salary: 100} dict2 {name: alice, salary: 100, experience: …

R语言探索BRFSS数据可视化

设定 加载包 最近我们被客户要求撰写关于BRFSS数据的研究报告,包括一些图形和统计输出。在本实验中,我们将使用dplyr软件包探索数据,并使用ggplot2软件包对其进行可视化以进行数据可视化 library(ggplot2) library(dplyr) 载入资料 load…