技术标签: U3D开发
原文:http://qinyuanpei.com/2015/02/11/unity2d-game-development-mecanim/
今天我想和大家分享的话题是在Unity3D中使用Mecanim动画系统来控制2D动画。
相信在大家的印象中,Mecanim动画系统主要运用在3D动画中,因为Mecanim动画系统提供了像动画重定向、人体骨骼动画等3D动画的特性,那么Unity3D的Mecanim动画系统能不能用来控制2D动画呢?如果在以前,博主和大家的理解是一样的,认为Mecanim只能运用到3D动画中,对于2D动画只能使用传统的逐帧动画和骨骼动画。可是前不久有位朋友问我,为什么不使用动画组件来控制2D动画呢?博主心想啊,这Mecanim动画系统真的能控制2D动画吗?经过博主查找大量资料和亲身实践,发现Mecanim是可以用来控制2D动画的,而且由于状态机的引入,我们对动画状态的控制会变得更为简单,从写代码的角度来看,这样可以减少我们的代码量便于维护。那么好了,今天我们就来一起学习下如何使用Mecanim动画系统来控制2D动画吧!
在Unity3D中传统2D动画的实现方式是基于逐帧动画的原理实现的,这种实现方式在Unity3D没有推出Unity2D前甚至在Unity2D推出后相当长的一段时间内,基本上我们最为常用的实现方式,博主在刚开始学习Unity3D的时候通常是以2D形式来展开的,因为博主认为2D和3D在原理上基本是相通的,如果我们掌握了2D游戏的基本原理,那么在实现3D游戏的时候就会相对容易些。我们来看看一个最简单的2D动画的脚本实现:
1 |
//精灵渲染器 private SpriteRenderer mRenderer; //精灵集合 public Sprite[] Sprites; //FPS,即每秒钟的画面帧数 public float FPS = 24; //精灵索引 private int index = 0; //当前时间 private float currentTime = 0; void Start () { mRenderer = GetComponent<SpriteRenderer>(); } void Update () { //获取当前时间 currentTime += Time.deltaTime; //如果达到了更新画面的时间 if(currentTime >= 1 / FPS) { //使索引增加 index += 1; //清除时间记录 currentTime = 0; //当索引更新到最后一帧时,索引重置 if(index >= Sprites.Length) { index = 0; } } //更新画面 mRenderer.sprite = Sprites[index]; } |
通过分析,我们可以发现这段脚本存在以下问题:
为了解决传统的2D动画实现方式中存在的动画维护困难、状态维护困难这两个问题,我们需要一种更好的方案来实现2D动画的控制,这种方案需要提供较为方便的动画维护功能,即各个动画是独立的,当改变了某一个动画时,其余的动画不会发生改变。其次,这种方案需要提供较为方便的状态维护功能,即各个动画状态切换是方便的,我们可以更好地从这一种状态切换到另一种状态。关于动画状态切换,大家可以去了解下有限状态机(FSM)的概念,这里我们不做深入的探究,这里我们选择Unity3D的Mecanim动画系统,因为Mecanim动画系统正好解决了这两个问题。好了,下面我们来一起学习一个2D动画的实例:
首先我们在场景中创建一个名为PlayerController的空物体,然后在该物体的下面增加一个精灵组件(Sprite),并将其命名为PlayerSprite,这样做的好处是Unity3D将为我们自动创建较为规范的命名。好了,现在我们选择PlayerController这个物体,然后通过Window->Animation菜单打开Animation窗口:
首先我们点击AddCurve按钮,此时将弹出一个对话框让我们保存动画文件,这里我们存储为[email protected],并将其保存在项目目录下的Animations\Player目录下,这样可以方便我们维护和查找特定的动画文件。在保存完动画文件后,此时会弹出如下的界面,我们选择PlayerSprite节点下的SpriteRenderer,然后选择Sprite,因为这里我们的2D动画主要是通过改变SpriteRenderer的Sprite属性来实现的,最后我们点击Sprite节点后面的加号来完成对象的选取。此时会在动画窗口中显示时间轴和刻度线,我们将在这里完成动画的编辑。大家可以注意到默认情况下,动画面板添加了两帧,即第1帧和最后一帧,其总时间是1秒,同时我们注意到这里有一个采样率(Sample),其实这就是当前动画的FPS了。好了,现在我们开始制作第一个动画:
在资源文件夹中,我们可以找到当前动画的图片素材,注意到这个图片中总共有12帧画面,因此我们可以按照0.05s的间隔来分配整个时间轴,所以我们可以这样添加帧:
好了,现在我们就完成了一个Idle动画的制作,现在打开角色的动画控制器PlayerController,这是Unity3D为我们自动创建的一个动画控制器,因为我们现在只有一个Idle动画,所以在Animaotr窗口中我们可以看到只有一个Idle状态,现在我们将这个状态设为默认状态。好了,现在我们可以直接运行游戏,发现在场景中角色开始循环播放Idle动画了。好了,现在让我们重复刚才的步骤,来完成角色的其余动画。
经过一番努力,我们现在已经完成了角色所有动画的制作,现在我们来设计角色的动画状态机:
设计好角色的动画状态机后我们开始来编写脚本,以实现角色动画的控制:
1 |
using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public enum PlayerState { Idle, Move, LightAttack, WeightAttack } public PlayerState State=PlayerState.Idle; //玩家移动速度 public float WalkSpeed = 0.75f; public float RunSpeed = 1.5f; //玩家跳跃力的强度 public float JumpForce = 200f; //位置限制 public float MinX = -5.80f; public float MaxX = 5.80f; public float MinY = -1.80f; public float MaxY = 0.35f; //玩家朝向,默认朝右 public bool isFaceRight = true; //动画组件 private Animator mAnim; //2D刚体 private Rigidbody2D mRig2D; void Start () { mAnim=GetComponent<Animator>(); mRig2D=GetComponent<Rigidbody2D>(); } void Update() { SpriteMove(); SpriteAttack(); SpriteJump(); SpriteIdle(); } /// <summary> /// 精灵Idle /// </summary> private void SpriteIdle() { //当玩家无任何操作时恢复到Idle状态 if (!Input.anyKey) { mAnim.SetBool("Jump", false); mAnim.SetBool("Attack", false); mAnim.SetBool("BigAttack", false); mAnim.SetBool("Skill", false); mAnim.SetBool("BigSkill", false); State=PlayerState.Idle; } } /// <summary> /// 精灵攻击 /// </summary> private void SpriteAttack() { //轻击,键位J if(Input.GetKey(KeyCode.J)) { mAnim.SetBool("Attack", true); State=PlayerState.LightAttack; } //重击,键位K if(Input.GetKey(KeyCode.K)) { mAnim.SetBool("BigAttack", true); State=PlayerState.WeightAttack; } } /// <summary> /// 精灵跳跃 /// </summary> private void SpriteJump() { if (Input.GetKey(KeyCode.I)) { mAnim.SetBool("Jump", true); mRig2D.AddForce(new Vector2(0, Time.deltaTime * JumpForce), ForceMode2D.Impulse); } } private void SpriteMove() { float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); Vector2 mPos = mRig2D.position; mAnim.SetFloat("Speed", Mathf.Sqrt(h * h + v * v)); float mPosX, mPosY; if (Mathf.Sqrt(h * h + v * v) > 0.5f){ mPosX = mPos.x + h * Time.deltaTime * RunSpeed; mPosY = mPos.y + v * Time.deltaTime * RunSpeed; }else{ mPosX = mPos.x + h * Time.deltaTime * WalkSpeed; mPosY = mPos.y + v * Time.deltaTime * WalkSpeed; } mRig2D.MovePosition(new Vector2(mPosX, mPosY)); if (h > 0 && !isFaceRight) { FlipSrite(); } else if (h < 0 && isFaceRight) { FlipSrite(); } } void FlipSrite() { if(isFaceRight){ transform.rotation=Quaternion.Euler(0,180,0); isFaceRight=false; }else{ transform.rotation=Quaternion.Euler(0,0,0); isFaceRight=true; } } } |
好了,现在我们可以来看看最终的效果,博主这里是想利用这些素材来制作一个横板过关的游戏,可是因为文章篇幅有限,所以这部分内容只能留到以后再和大家分享了。
好了,到现在为止,基于Mecanim动画系统的2D动画控制基本上讲解完了。下面我们说说Mecaanim动画系统应用扩展。通过前面的学习,我们知道Unity2D使用的Mecanim动画系统主要是通过改变游戏体的属性来实现某种特定的动画效果的,例如我们这里的动画是通过改变角色精灵附加的SpriteRenderer组件的Sprite属性来实现的,因此从本质上来说Unity2D的动画控制器是一种属性动画。总体来说,Unity2D可以实现以下类型的动画:
好了,这就是今天这篇文章的全部内容了,希望大家喜欢!
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数