小猪学U3D—塔防-模型和动画(TowerDefense-Models)

上一节,我们学习了如何在地图中建造防御塔、锁定射程内的敌人,并发射激光束攻击敌人。由于被吐槽敌人都是方块不好看,本节将先重点学习如何导入敌人模型,让画面好看些。

模型和动画(Models and Animations)

尽管可以在Unity编辑器中创建简单的动画,但通常会将它们与3D模型一起通过.fbx文件导入。你可以通过3DMax、Maya等程序中创建它们,也可以从Unity资产存储库等其他地方获取它们。在本文,我将从Unity的3D Game Kit中导入掷弹兵。

1.掷弹兵模型(Grenadier Model)

1.1.导入模型(Importing Model)

访问https://assetstore.unity.com/,搜索“3D Game Kit – Character Pack”,收藏并下载资源到Unity包管理器。不要导入整个3D游戏工具包,因为它太大了,会弄乱你的项目,只需导入Grenadier掷弹兵模型+依赖的Shaders目录即可。

掷弹兵对我们来说太大了。通过选择模型来缩小它,转到模型选项卡,将它的比例系数减少到0.25。对于我们最终使用的所有动画,你也需要这样做,因为否则模型将会分裂引发异常表现。

1.2.替换敌人(Replace Enemy Cube)

把掷弹兵模型拖到Enemy预置敌人中来取代立方体。添加TargetPoint和SphereCollider碰撞器到Grenadier_Sphere对象,因为那是它的质心。将碰撞器的半径设置为0.125,因为我们还没有像导入模型时那样的缩放参数。同时设置Grenadier模型的图层为Enemy层。

运行后可以看到方块被替换为了Grenadier模型:

调下相机位置和角度:

现在还有个问题,敌人的腿不动,是在平移,需要通过动画让他走起来。

2.动画(Animations)

我们需要给掷弹兵配置自己的动画。导入的所有动画资源均位于Grenadier/AnimationClips目录下,其名称前带有@。我们可以使用@GrenadierWalk动画进行运动,使用@GrenadierCloseRangeAttack进行intro和outro,以及使用@GrenadierDeath进行死亡。确保将所有这些动画的缩放系数设置为0.25。另外,请转到其Animation选项卡并删除Events下的所有条目,因为将其保留会导致错误。

2.1.行走动画(Walk Animation)

a.选择GrenadierWalk动画,播放看下效果:

b.改为原地行走

你会发现,它已经加入了向前运动,而我们只需要一个可以原地行走的动画。我们需要修改两处:

取消Grenadier模型-Animator-Apply Root Motion应用根运动选项的选中状态:

参考下图修改@GrenadierWalk/@GrenadierWalkFast动画的Animation属性:

单独播放这个动画就可以看到变为原地行走了

c.创建动画配置/控制器脚本

创建动画配置EnemyAnimationConfig.cs、控制器脚本EnemyAnimatorController.cs。创建EnemyAnimationConfig.cs对应的预制,配置Move属性为@GrenadierWalk。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 敌人动画配置
[CreateAssetMenu]
public class EnemyAnimationConfig : ScriptableObject{
  // 行走动画
  [SerializeField]
  AnimationClip move = default;
  
  public AnimationClip Move => move;

}



using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

// 敌人动画控制器
[System.Serializable]
public struct EnemyAnimatorController {
  // 可播放视图
  PlayableGraph graph;

  // 初始化Animator组件和动画配置
  public void Configure(Animator animator, EnemyAnimationConfig config){
    // 创建视图
    graph = PlayableGraph.Create();
    graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); //应设置更新模式
    // 创建播放对象,传入视图和要播放的动画剪辑
    var clip = AnimationClipPlayable.Create(graph, config.Move);
    var output = AnimationPlayableOutput.Create(graph, "Enemy", animator);
    output.SetSourcePlayable(clip); //将剪辑设置为输出源
  }

  // 播放
  public void Play(float speed){
    graph.GetOutput(0).GetSourcePlayable().SetSpeed(speed);
    graph.Play();
  }

  // 停止
  public void Stop(){
    graph.Destroy();  // 如果需要复用,也可以调用Stop
  }
}

d.播放和停止动画

// 敌人
public class Enemy : MonoBehaviour{
    ...
    // 速度
    public float Speed { get; private set;}
    // 动画配置
    [SerializeField]
    EnemyAnimationConfig animationConfig = default;
    // 动画控制器
    EnemyAnimatorController animator = default;

    void Awake(){
      // 初始化敌人动画对象
      animator.Configure(model.GetChild(0).gameObject.GetComponent<Animator>(), animationConfig); //注意这里的GetChild(0),把之前的Cube删掉,一定要确保Grenadier在Model下变第一个
    }

    // 初始化
    public void Initialize(float scale, float pathOffset, float speed=1){
      ...
      Speed = speed;
      // 播放动画
      animator.Play(Speed * Scale);
    }

    // 回收
    public void Reclaim(){
      animator.Stop();  //停止播放动画
      OriginFactory.Reclaim(this);
    }
    ...
}

e.运行可以看到腿开始交替行走了

2.2.消失动画(Death Animation)

添加其他动画方法和行走类似。

a.添加动画配置

// 敌人动画配置
[CreateAssetMenu]
public class EnemyAnimationConfig : ScriptableObject{
  // 动画
  [SerializeField]
	AnimationClip move = default, attack = default, death = default;
	public AnimationClip Move => move;     //行走
	public AnimationClip Attack => attack; //攻击
	public AnimationClip Death => death;   //死亡
  ...
}

b.实现多动画剪辑选择性播放

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

// 敌人动画控制
[System.Serializable]
public struct EnemyAnimator {
  // 可播放视图
  PlayableGraph graph;
  // 剪辑分类
  public enum Clip { Move, Attack, Death }
  // 混剪管理器
  AnimationMixerPlayable mixer;
  //AnimationClipPlayable = clipMove, clipAttack, clipDeath;

  // 初始化Animator组件和动画配置
  public void Configure(Animator animator, EnemyAnimationConfig config){
    // 创建视图
    graph = PlayableGraph.Create();
    graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); //应设置更新模式
    // 创建剪辑
    int size = System.Enum.GetNames(typeof(Clip)).Length;
    var clipMove = AnimationClipPlayable.Create(graph, config.Move);
    var clipAttack = AnimationClipPlayable.Create(graph, config.Attack);
    var clipDeath = AnimationClipPlayable.Create(graph, config.Death);
    clipMove.Pause();
    clipAttack.Pause();
    clipDeath.Pause();
    // 添加剪辑到可播放项中
    mixer = AnimationMixerPlayable.Create(graph, size, true);
    mixer.ConnectInput((int)Clip.Move, clipMove, 0);
    mixer.ConnectInput((int)Clip.Attack, clipAttack, 0);
    mixer.ConnectInput((int)Clip.Death, clipDeath, 0);
    // 创建动画输出对象
    var output = AnimationPlayableOutput.Create(graph, "EnemyOutput", animator);
    output.SetSourcePlayable(mixer); //clipMove); // 将可播放项连接到输出
    // 开始播放(因所有剪辑都被设置了暂停,所以实际并不会播放,需要执行剪辑.play才可以)
    graph.Play();

    Debug.Log("mixer.GetInputCount: " + mixer.GetInputCount());
  }

  // 行走
  public void Move(float speed){
    Debug.Log("Move! " + (int)Clip.Move);
    // 单动画播放
    //graph.GetOutput(0).GetSourcePlayable().SetSpeed(speed);
    //graph.Play();
    // 播放移动剪辑
    mixer.SetInputWeight((int)Clip.Move, 1);  // 设置剪辑播放权重
    var clip = mixer.GetInput((int)Clip.Move);  //返回对应剪辑
    clip.SetSpeed(speed);
    clip.Play();  //播放剪辑
  }

  // 攻击
  public void Attack(){
    Debug.Log("Attack! " + (int)Clip.Attack);
    mixer.GetInput((int)Clip.Attack).Play();
  }

  // 死亡
  public void Death(){
    Debug.Log("Death! " + (int)Clip.Death);
    // 播放死亡剪辑
    //mixer.GetInput((int)Clip.Move).Pause();
    mixer.SetInputWeight((int)Clip.Death, 1);  // 设置剪辑播放权重
    var clip = mixer.GetInput((int)Clip.Death);
    clip.SetSpeed(5); //加速5倍播放
    clip.Play();  //播放剪辑

    // 销毁
    //graph.Destroy();  // 如果需要复用,也可以调用Stop
  }
}

c.敌人播放行走和死亡动画

// 敌人
public class Enemy : MonoBehaviour{
    ...
    // 初始化
    public void Initialize(float scale, float pathOffset, float speed=1){
      ...
      // 播放行走动画
      animator.Move(animationConfig.MoveAnimationSpeed * Speed * Scale);
    }

    // 更新敌人位置等信息(敌人死亡时返回false)
    public bool GameUpdate(){
      ...
      if(Health <= 10f){
        animator.Death();  //提前播放死亡动画(播放动画如何阻塞enemy/graph的销毁需要研究下)
      }
      ...
    }
    ...
}

*注意:死亡动画的播放位置应该在Reclaim的时候,但是暂时没找到动画阻塞敌人销毁的方法,先临时在血量不足时播放,后边找到方法再改。

d.运行效果

 

yan 21.8.29

参考:

Tower Defense – Importing Models and Animations

【Unity3D】3D模型的使用——FBX的使用与Animation设置

欢迎关注下方“非著名资深码农“公众号进行交流~

发表评论

邮箱地址不会被公开。