Unity2D实现人物三连击

  之前写过一个系列《HTML5 2D平台游戏开发》,在此过程中发现有很多知识点没有掌握,而且用纯JavaScript来开发一个游戏效率极低,因为调试与地图编辑都没有可视化的工具,开发起来费时费力,加上业余时间有限,我决定暂且中止开发。为了弥补缺少的知识点,我打算先学习和借鉴一下Unity的开发思路,于是把原先的游戏素材移植了过来。首先还是先从人物的动作开始,Unity的动画与之前开发时的思路有很大不同,Unity没有“帧”这一概念,也就是说没有办法获取到当前动画播放到第几帧,只能通过normalizedTime来获取动画播放的百分比进度,一下子让适应这种模式有些困难。先不考虑代码实现细节,整理一下思路,人物实现三连击的状态机大致如下:

  1. Idle ⇢ attack_a ⇢ Idle
  2. Idle ⇢ attack_a ⇢ attack_b ⇢ Idle
  3. Idle ⇢ attack_a ⇢ attack_b ⇢ attack_c ⇢ Idle

在Idle状态下按下攻击键,过渡到attack_a,如果没有下一步操作,attack_a动画播放完毕后还原到Idle状态。如果在attack_a状态下再次按下攻击键,则过渡到attack_b,如果在attack_b状态下无操作,动画播放完毕后还原到Idle,依此类推,多段连击也是一样的。为了表示攻击的状态,需要为Animator添加一个attack参数:

attack等于0表示处于非攻击状态,attack等于1表示处于attack_a,2和3分别表示处于attack_b、attack_c。

接下来一步一步分析各个状态间的过渡。

Idle ⇢ attack_a

  Idle状态可以随时通过按下攻击键打断并过渡到attack_a,故没有 Has Exit Time ,其它项也都置为0。

 

attack_a ⇢ Idle

  最初我是这样考虑的,给这个过渡设置 Has Exit Time ,如果没有任何操作,让其还原到Idle,于是有了

 

但最终运行时攻击总是会卡在最后一帧一段时间,只有把Exit Time设置为0.1才流畅。也许是动画播放速度设置太快的缘故,但我总觉得通过Exit Time的方式来实现不太好。在查阅一番资料后,我觉得给动作添加Behaviour是比较好的方式。选中attack_a,为其添加一个名为SetNormalizeTime的Behaviour:

1public class SetNormalizedTime : StateMachineBehaviour { 2 private string targetParameter = "Normalized Time"; 3 4 // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state 5 override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) 6 { 7 animator.SetFloat(targetParameter, 0); 8 } 9 10 // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks 11 override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) 12 { 13 animator.SetFloat(targetParameter, stateInfo.normalizedTime); 14 } 15 16 // OnStateExit is called when a transition ends and the state machine finishes evaluating this state 17 override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) 18 { 19 animator.SetFloat(targetParameter, 0); 20 } 21}

每次进入该状态,先将Normalized Time重置为0,表示动画从头开始,然后在OnStateUpdate中更新它,最后退出时再置为0。同时也别忘了在Animator中添加一个名为Normalized Time的参数(float类型):

现在可以把attack_a ⇢ Idle的 Has Exit Time 去掉了,同时添加一个 Conditions

 

表示当动画播放完毕时(NormalizeTime > 1.0f) 过渡到 Idle。同样的,也给attack_b ⇢ Idle 和 attack_c ⇢ Idle 附加上这个 Behaviour。

代码实现

  常规操作:

1private Animator anim; 2private Rigidbody2D myRigidbody; 3private AnimatorStateInfo stateInfo; 4 5public int hitCount = 0; //0:表示idle状态。 1:表示当前正在进行attack_a。 2:attack_b。 3:attack_c。 6 7void Start () 8{ 9 anim = GetComponent<Animator>(); //获取动画组件 10 myRigidbody = GetComponent<Rigidbody2D>(); //获取刚体组件 11} 12 13 14void Update() 15{ 16 stateInfo = anim.GetCurrentAnimatorStateInfo(0); 17 HandleInput(); 18}

下面实现HandleInput方法:

1void HandleInput() 2{ 3 //若动画为三种状态之一并且已经播放完毕 4 if ((stateInfo.IsName("attack_a") || stateInfo.IsName("attack_b") || stateInfo.IsName("attack_c")) && stateInfo.normalizedTime > 1.0f) 5 { 6 hitCount = 0; //将hitCount重置为0,即Idle状态 7 anim.SetInteger("attack", hitCount); 8 attack = false; 9 } 10 11 //按下键盘J键攻击 12 if (Input.GetKeyDown(KeyCode.J)) 13 { 14 HandleAttack(); 15 } 16 17}

(这里踩了一个坑,实现这部分逻辑时我是远程操作完成的,发送的指令实际上有一定的延迟,这样就导致按住键盘J键不放可以连续触发攻击,也就是连发。让我误以为GetKeyDown是连续触发的,实际上GetKeyDown只触发一次。)

HandleAttack的实现:

1void HandleAttack() 2{ 3 //若处于Idle状态,则直接打断并过渡到attack_a(攻击阶段一) 4 if (stateInfo.IsName("Idle") && hitCount == 0) 5 { 6 hitCount = 1; 7 anim.SetInteger("attack", hitCount); 8 } 9 //如果当前动画处于attack_a(攻击阶段一)并且该动画播放进度小于80%,此时按下攻击键可过渡到攻击阶段二 10 else if(stateInfo.IsName("attack_a") && hitCount == 1 && stateInfo.normalizedTime < 0.8f) 11 { 12 hitCount = 2; 13 } 14 //同上 15 else if(stateInfo.IsName("attack_b") && hitCount == 2 && stateInfo.normalizedTime < 0.8f) 16 { 17 hitCount = 3; 18 } 19}

这里要注意,比如在触发第二段攻击时需要满足条件 normalizedTime < 0.8f ,但此时按下攻击键是不会马上播放第二段攻击动画的,如果马上播放就显得动作非常不协调了,应该等到第一阶段的攻击动画播放到一定阶段才播放第二段攻击动画。所以需要给关键帧添加一个方法,告诉动画系统在这一帧要执行某个指令。

1void GoToNextAttackAction() 2{ 3 anim.SetInteger("attack", hitCount); 4}

给第7帧添加一个事件,指向 GoToNextAttackAction 这个方法,动画将在第7帧的时候被打断并进入下一个攻击动画。如果hitCount没有改变,SetInteger("attack",hitCount) 不会影响当前正在播放的动画,动画会持续播放完毕(至第9帧)。

P.S. 虽然费了一些周折,但还是把效果实现出来了,Unity的开发效率比JS高出太多,大概100倍左右吧,这是在开发过程中的感觉。不知道离游戏成品还有多遥远,但我还是会继续学习。

点赞
收藏

评论区

加载中...

相关推荐

Unity小王子私藏的开发2D游戏的常用插件合集

Unity以开发3D游戏见长,早期版本的Unity在开发2D游戏时不慎方便,因此AssetStore出现了很多2D游戏开发引擎。现在Unity对2D游戏的支持越来越好,而这些开发2D游戏的Unity插件也得到了更多开发者的喜爱。1:RexEngine:Classic2DPlatformerEngine(https://www.os

Unity 4.x 2D游戏开发基础教程大学霸第一更

Unity4.x2D游戏开发基础教程大学霸第1章  Unity及其组成的介绍本书主要讲解的是,如何使用Unity开发2D游戏。但在开始讲解之前,最好先熟悉一下Unity这个工具。本章会首先介绍Unity的下载和安装,然后会介绍Unity界面的各组成部分,这些知

Cocos Creator基础教程(10)—预览调试

游戏预览是开发中的一个重要环节,CocosCreator游戏引擎基于JavaScript语言有着丰富强大的预览调试能力,这次我们介绍预览调试相关的技术,了解一下这方面的知识相信对你也非常有帮助。1\.游戏预览CocosCreator是跨平台的游戏开发引擎,从类别上主要分为Nativet和H5两大平台,游戏预览也分为这两大模式:

Unity 2D游戏开发快速入门第1章创建一个简单的2D游戏

Unity2D游戏开发快速入门第1章创建一个简单的2D游戏即使是现在,很多初学游戏开发的同学,在谈到Unity的时候,依然会认为Unity只能用于制作3D游戏的。实际上,Unity在2013年发布4.3版本的时候,就开始提供对制作2D游戏的支持了。例如,提供了一些专用于开发2D游戏的Unit

Unity2D游戏开发基础教程1.2项目、资源和场景

Unity2D游戏开发基础教程1.2项目、资源和场景如果使用Unity制作游戏,就一定会接触到项目(Project、资源(Asset)和场景(Scene)。本节将依次介绍它们。1.2.1项目Unity是一个基于项目的应用。这就意味着每开发一个新游戏,都要创建一个新项目。一个项目就代表

Unity 2D游戏开发教程之游戏精灵的开火状态

Unity2D游戏开发教程之游戏精灵的开火状态精灵的开火状态“开火”就是发射子弹的意思,在战争类型的电影或者电视剧中,主角们就爱这么说!本节打算为精灵添加发射子弹的能力。因为本游戏在后面会引入敌人,而精灵最好具备开火的能力,否则会被敌人轻易干掉!具体的实现方法是:(1)导入一个表