Unity3D学习笔记(十七):IK动画、粒子系统和塔防
新动画系统:
反向动力学动画(IK功能):
魔兽世界(头部动画),神秘海域(手部动画),人类一败涂地(手部动画)
如何启用(调整)
1、必须是新动画系统Animator
设置头、手、肘的目标点
2、动画类型必须是Humanoid,除此之外其他类型都不可以
3、动画系统对应层级的IKPass必须开启
4、相应的IK调整方法只能写在OnAnimatorIK(脚本挂载和Animator同一级别)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DefaultAvatarIK : MonoBehaviour { public Animator anim; public Transform lookPoint; public Transform HandPoint; public Transform ElbowPoint; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } private void OnAnimatorIK(int layerIndex) { //用代码调整头部看向的方向 anim.SetLookAtPosition(lookPoint.position); //调整IK动画的权重 //如果是1代表完全按代码逻辑播放动画(完全融合) //如果是0完全按原动画播放 anim.SetLookAtWeight(1); //调整四肢IK的目标点 anim.SetIKPosition(AvatarIKGoal.LeftHand, HandPoint.position);//AvatarIKGoal是枚举 anim.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1); //调整四肢IK关节的目标点 anim.SetIKHintPosition(AvatarIKHint.RightElbow, ElbowPoint.position); anim.SetIKHintPositionWeight(AvatarIKHint.RightElbow, 1); //调整四肢IK的朝向 //anim.SetIKRotation(); } }
取消物体描边
粒子系统:
Particle System一统江湖,主流离子发射器思想,调整发射器参数发射离子,如AE
Legacy都是老的粒子系统
一个粒子效果由若干个Particle System构成
修改大小
我们没有办法通过GameObject的Scale改大小,Scale只是改变发射区域的大小
可以通过StartSize
可以通过SizeOverLife曲线
启用碰撞
碰撞系统,火焰溅射效果
打开粒子的Collision功能,选择Type为World,平面改3D
也可以选择Type为Planes,设置一个平面触发效果
拖尾效果
脚本使用:播放,停止,销毁
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ParticleTest : MonoBehaviour { public ParticleSystem ps; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKeyDown(KeyCode.Alpha1)) { //调用粒子系统播放 ps.Play(); } if (Input.GetKeyDown(KeyCode.Alpha2)) { //调用粒子系统停止 ps.Stop(); } if (Input.GetKeyDown(KeyCode.Alpha3)) { if (ps.isStopped) { //失活粒子 gameObject.SetActive(false); } } } }
重载,连同子级一起处理,填flase只负责自身
取消唤醒,改为代码播放
塔防游戏:
怪物管理器:
添加路点(路点放在拐点处),给怪物子类添加路点列表
currentPathNodeID:记录当前路点的变量
pathNodeList.Count-1:路点的最后一个点
移动逻辑
数组越界问题:调整currentPathNodeID++的位置到最后
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MonsterA : MonsterBase { public Animator anim; public Transform pathParent; public List<Transform> pathNodeList; public int currentPathNodeID; public float speed; // Use this for initialization void Start () { anim = GetComponent<Animator>(); //初始化路点列表 pathNodeList = new List<Transform>(); //把路点导入到路点列表 for (int i = 0; i < pathParent.childCount; i++) { pathNodeList.Add(pathParent.GetChild(i)); } //把自己放在路点的第一个位置 transform.position = pathNodeList[0].position; currentPathNodeID = 0; monsterSta = MonsterSta.Move; anim.SetBool("isMove", true); } // Update is called once per frame void Update () { #region 动画测试 //if (Input.GetKeyDown(KeyCode.Alpha1)) //{ // monsterSta = MonsterSta.Move; // anim.SetBool("isMove", true); //} //if (Input.GetKeyDown(KeyCode.Alpha2)) //{ // monsterSta = MonsterSta.Idle; // anim.SetBool("isMove", false); //} //if (Input.GetKeyDown(KeyCode.Alpha3)) //{ // monsterSta = MonsterSta.Death; // anim.SetTrigger("Death"); //} #endregion Action(); } public override void Action() { switch (monsterSta) { case MonsterSta.Idle: Idle(); break; case MonsterSta.Move: Move(); break; case MonsterSta.Death: Death(); break; default: break; } } public override void Move() { //如果怪物还没有到达路点中最后一个点 if (currentPathNodeID < pathNodeList.Count-1) { //向下一个点前进 float distance = Vector3.Distance(transform.position, pathNodeList[currentPathNodeID + 1].position); transform.position = Vector3.Lerp(transform.position, pathNodeList[currentPathNodeID + 1].position, speed/ distance*Time.deltaTime); //如果我离下一个点的距离到达某一个值 Quaternion targetRot = Quaternion.LookRotation(pathNodeList[currentPathNodeID + 1].position - pathNodeList[currentPathNodeID].position); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, 0.1f); //transform.LookAt(pathNodeList[currentPathNodeID + 1]); if (distance < speed * Time.deltaTime) { //改变我的当前点,进而改变目标点,成下一个点 currentPathNodeID++; } } } }
保留原动画状态机的逻辑,可以替换原动画片段
机枪塔,需要瞄准,攻击速快
炮塔,不用瞄准,攻速慢
塔基
外层(空物体):缩放(1,1,1)
内层(Tower_Base):缩放(0.4,0.4,0.4)
外层添加刚体
内层添加球形碰撞体,勾选Is Trigger
塔基父类代码逻辑
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GunBase : MonoBehaviour { public float attackRange; public MonsterBase tragetMonster; public SphereCollider attackTrigger; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } public virtual void Indit() { attackTrigger.radius = attackRange; } public virtual void Attack() { } }
塔基子类代码逻辑
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GunB : GunBase { public Transform gunPos; public ParticleSystem ps; // Use this for initialization void Start () { Indit(); } // Update is called once per frame void Update () { Attack(); fireCDTime += Time.deltaTime; } public override void Attack() { if (tragetMonster!=null) { if (AttackCheck()) { if (fireCDTime> fireCD) { Fire(); } } else { RotatGun(); } } else { ps.Stop(); } } public void RotatGun() { Vector3 dir = tragetMonster.transform.position - gunPos.position; dir.y = 0; Quaternion targetRot = Quaternion.LookRotation(dir); gunPos.rotation = Quaternion.Slerp(gunPos.rotation, targetRot, 0.5f); } float fireCD = 0.1f; float fireCDTime; public void Fire() { fireCDTime = 0; tragetMonster.Damage(); ps.Play();//特效代码逻辑 } public bool AttackCheck() { Vector3 monsterDir = tragetMonster.transform.position - gunPos.position; monsterDir.y = 0; if (Vector3.Angle(gunPos.forward, monsterDir)<5) { return true; } return false; } //如果有物体进入我的攻击范围 private void OnTriggerEnter(Collider other) { //如果我没有目标 if (tragetMonster==null) { //如果进入我攻击范围的Collider标签是"Monster" if (other.tag == "Monster") { //把这个Monster设置成我的目标 tragetMonster = other.GetComponent<MonsterBase>(); } } } //如果有物体离开我的攻击范围 private void OnTriggerExit(Collider other) { //如果我有目标 if (tragetMonster != null) { //如果离开的目标是我的目标 if (tragetMonster == other.GetComponent<MonsterBase>()) { //我的目标为空 tragetMonster = null; } } } }
设置攻击范围
塔基父类代码逻辑:设置Indit初始化,给碰撞器添加攻击范围
public virtual void Indit() { attackTrigger.radius = attackRange; }
攻击检测:炮塔转向
球形差值,正负转向
线性差值,只能正向
public override void Attack() { if (tragetMonster!=null) { if (AttackCheck()) { if (fireCDTime> fireCD) { Fire(); } } else { RotatGun(); } } else { ps.Stop(); } } public void RotatGun() { Vector3 dir = tragetMonster.transform.position - gunPos.position; dir.y = 0; Quaternion targetRot = Quaternion.LookRotation(dir); gunPos.rotation = Quaternion.Slerp(gunPos.rotation, targetRot, 0.5f); }
目标点不对的问题:
dir.y = 0;转向方法的y轴清零
monsterDir.y = 0;攻击检测的角度也要清零
public void RotatGun() { Vector3 dir = tragetMonster.transform.position - gunPos.position; dir.y = 0; Quaternion targetRot = Quaternion.LookRotation(dir); gunPos.rotation = Quaternion.Slerp(gunPos.rotation, targetRot, 0.5f); } float fireCD = 0.1f; float fireCDTime; public void Fire() { fireCDTime = 0; tragetMonster.Damage(); ps.Play();//特效代码逻辑 } public bool AttackCheck() { Vector3 monsterDir = tragetMonster.transform.position - gunPos.position; monsterDir.y = 0; if (Vector3.Angle(gunPos.forward, monsterDir)<5) { return true; } re