动画生硬切换:animation.play();//极少使用,常用融合方法
动画融合淡入:animation.CrossFade(“Idle”, 0.2f);//0.2f为与前一动画的融合百分比为20%
枚举做状态机:
AI系统 - 寻路系统:
关键要素:
1、寻路元件
挂载寻路组件
![](https://i-blog.csdnimg.cn/blog_migrate/1f6dac4ac8a2ba21261655e74ef16054.png)
2、寻路平面(地形)
地形设置成导航静态,进入预备役(以此为基准提取数据,可以修改删减,显示数据)
![](https://i-blog.csdnimg.cn/blog_migrate/8b02419db1605c56e930ff9809fcb2de.png)
打开Navigation窗口在Bake栏下点Bake
烘焙数据:蓝色区域就是寻路平面,烘焙数据可以与显示数据分离
设置障碍物:障碍物勾选Navigation Static,重新烘焙,障碍物被添加
设置目标点,引用UnityEngine.AI,引用寻路系统
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI;//引用UnityEngine.AI public enum EnemySta { Move, Idle, Death } public class EnemyControl : MonoBehaviour { public Animation anim; public EnemySta enemySta; //因为我们需要经常用到目标的状态,所以把TransForm改成了PlayerControl public PlayerControl moveTarget;//设置目标点 public NavMeshAgent navMeshAgent;//引用寻路系统 public MonsterManager manager;//怪物属于那个管理器 private int enemyHealth = 30;//设置怪物生命值 // Use this for initialization void Start () { enemySta = EnemySta.Move; } // Update is called once per frame void Update () { if (Input.GetKeyDown(KeyCode.Alpha1)) { enemySta = EnemySta.Move; } if (Input.GetKeyDown(KeyCode.Alpha2)) { enemySta = EnemySta.Idle; } if (Input.GetKeyDown(KeyCode.Alpha3)) { enemySta = EnemySta.Death; } Action(); } void Action() { switch (enemySta) { case EnemySta.Move: Move(); break; case EnemySta.Idle: Idle(); break; case EnemySta.Death: Death(); break; } } void Move() { anim.CrossFade("Move", 0.2f); navMeshAgent.SetDestination(moveTarget.transform.position);//让对应组件调用一个SetDastination(目标点)方法 //如果怪物的目标死了 if (moveTarget.playSta == PlaySta.Death) { //寻路停止 navMeshAgent.isStopped = true; //状态切换到闲置 enemySta = EnemySta.Idle; } } void Idle() { anim.CrossFade("Idle", 0.2f); } public void Damage() { //受伤减血 enemyHealth -= 10; //Debug.Log("怪物生命"+enemyHealth); //如果血到了0 if (enemyHealth <= 0) { //状态变成死亡状态 enemySta = EnemySta.Death;//怪物死亡:停止寻路,失效碰撞体(防止实体造成伤害) navMeshAgent.isStopped = true; gameObject.GetComponent<MeshCollider>().enabled = false; //gameObject.tag = "Untagged"; } } void Death() { anim.CrossFade("Death", 0.2f); } public void DeathEvent() { //在管理器列表中移除自己 manager.monsterList.Remove(this); //销毁自己 Destroy(this.gameObject); } }
Max Slope:坡度设置
Step Height:高度设置,低于高度可忽略
![](https://i-blog.csdnimg.cn/blog_migrate/89e5a19ab85246c0edfaf25f04cd0c9f.png)
Speed:移动速度
Angular Speed:转向速度
Acceleration:启动速度,加速度
摄像机跟随
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target;//目标点 public Vector3 distance;//目标点的距离,和目标点保持一定距离 public Vector3 lookAtOffSet;//看向点与目标点的偏移量 private Vector3 targetPos; // Use this for initialization void Start () { distance = transform.position - target.position; } // Update is called once per frame void Update () { } private void LateUpdate() { //目标点加距离 = 摄像机的目标位置 targetPos = target.position + distance; //向目标位置移动,每次百分之10 transform.position = Vector3.Lerp(transform.position, targetPos, 0.1F); //看向目标点 transform.LookAt(target.position + lookAtOffSet); } }
lookAtOffSet:设置摄像机看向点的偏移量(点),避免人物总是在画面中央
射击功能:
核心功能(射线,碰撞,调用掉血方法)+交互功能(射线闪光特效,音效)
空物体Fire
----音效Audio
----动画
----射线
----粒子特效
--------子物体:Spotlight聚光灯,开闭,range变化
![](https://i-blog.csdnimg.cn/blog_migrate/1e406ad4aa88497b7120c9624906ca3a.png)
设置灯光动画
using System.Collections; using System.Collections.Generic; using UnityEngine; public enum PlaySta { Move, Idle, Death } public class PlayerControl : MonoBehaviour { public Animation anim; public PlaySta playSta; // Use this for initialization void Start() { playSta = PlaySta.Move; } // Update is called once per frame void Update() { float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); moveDir.x = h; moveDir.z = v; cd += Time.deltaTime; Action(); } void Action() { switch (playSta) { case PlaySta.Move: Move(); Attack(); Rot(); break; case PlaySta.Idle: Idle(); Attack(); Rot(); break; case PlaySta.Death: Death(); break; } } Vector3 moveDir; Vector3 lookPoint; RaycastHit hit; public float moveSpeed = 3; void Move() { anim.Play("Move"); transform.position += moveDir * moveSpeed * Time.deltaTime; if (moveDir.magnitude < 0.01f) { playSta = PlaySta.Idle; } } //把转向功能提炼出来 void Rot() { if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100, 1 << 8)) { lookPoint = hit.point; lookPoint.y = transform.position.y; transform.LookAt(lookPoint); } } void Idle() { anim.Play("Idle"); if (moveDir.magnitude >= 0.01f) { playSta = PlaySta.Move; } } float cd; public void Damage() { cd = 0;//无敌时间,受伤间隔 playSta = PlaySta.Death; } void Death() { anim.Play("Death"); } //触发器判定死亡 //public void OnTriggerEnter(Collider other) //{ // if (other.tag == "Monster") // { // if (cd > 0.5f) // { // Damage(); // } // } //} //改良版:碰撞器判定死亡 public void OnCollisionEnter(Collision other) { if (other.gameObject.tag == "Monster") if (cd > 0.5f) { Damage(); } } public AudioSource fireAudio; public Animation fireAnim; public Transform firePos; public LineRenderer lineRenderer; private RaycastHit fireHit; void Attack() { if (Input.GetMouseButtonDown(0)) { fireAudio.Play(); fireAnim.Stop();//Stop避免前一动画未执行完,后一动画不会执行 fireAnim.Play(); if (Physics.Raycast(new Ray(firePos.position,firePos.forward),out fireHit,100)) { //如果我打中的目标是怪物 if (fireHit.collider.tag == "Monster") { //这个目标受伤 fireHit.collider.GetComponent<EnemyControl>().Damage(); } SetFirePos(fireHit.point); } else { SetFirePos(firePos.transform.position + firePos.forward * 100); } } } void SetFirePos(Vector3 hitPos) { lineRenderer.SetPosition(0, firePos.position); lineRenderer.SetPosition(1, hitPos); } //还原射线到枪口位置 public void ReSetFirePos() { lineRenderer.SetPosition(0, firePos.position); lineRenderer.SetPosition(1, firePos.position); } }
----射线
positions默认世界位置
Element 0:枪口位置
Element 1:目标点位置
![](https://i-blog.csdnimg.cn/blog_migrate/7625bf1dc063838b5771bda9d2177915.png)
Materials射线材质:LineRenderMaterial
width射线宽度:
SetPosition方法:0,1参数是element0,element1
![](https://i-blog.csdnimg.cn/blog_migrate/1d8d76b463dfb2e86e6983d8abeee6d1.png)
射线关联:
枪口位置,绑定在骨骼的武器位上
如果没有,就放在正前方上0.15,0.3,0.7
重置射线:
设置在动画的第10帧,调用消失方法
![](https://i-blog.csdnimg.cn/blog_migrate/2da3306e898e4f53c1d216c92f2143dc.png)
新建脚本FireEvent,调用父级Player的脚本
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FireEvent : MonoBehaviour { public PlayerControl player; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } public void Fire() { player.ReSetFirePos(); } }
射线宽度调整:添加关键帧,调整曲线,效果先细后粗
怪物管理器;
添加空物体和脚本类,生成点
![](https://i-blog.csdnimg.cn/blog_migrate/184ab095eaf74a9196e9e50977ee6a4d.png)
给怪物管理器添加预制体的引用
注意:对象在场景中和类在资源中的区别
![](https://i-blog.csdnimg.cn/blog_migrate/ffc88c774dd3cd2e306b52f97bb82cc6.png)
怪物属于那个管理器
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class MonsterManager : MonoBehaviour { List<Transform> birthPoint; public GameObject monsterPreGo; public List<EnemyControl> monsterList; public int MaxMonsterCount = 3;//最大怪物数 public PlayerControl player; // Use this for initialization void Start () { birthPoint = new List<Transform>(); monsterList = new List<EnemyControl>(); for (int i = 0; i < transform.childCount; i++)3 { birthPoint.Add(transform.GetChild(i)); } } // Update is called once per frame void Update () { } private void FixedUpdate() { CheckMonster(); } void CreateMonster() { //实例化一个怪物 GameObject go = Instantiate<GameObject>(monsterPreGo); //怪物的位置设置任意出生点 go.transform.position = birthPoint[Random.Range(0,birthPoint.Count)].position; //取得怪物脚本 EnemyControl monster = go.GetComponent<EnemyControl>(); //把怪物放到管理器列表里 monsterList.Add(monster); //指定怪物的目标 monster.moveTarget = player; //指定怪物的管理者 monster.manager = this; } void CheckMonster() { if (monsterList.Count < MaxMonsterCount) { CreateMonster(); } } }
角色穿墙问题:
Drag=1防止被墙弹飞,XZ轴解锁
![](https://i-blog.csdnimg.cn/blog_migrate/757522e66d20fcd61bebaf7c2602b27e.png)
怪物死亡:移出列表,摧毁游戏物体,放在死亡动画最后一帧调用
public void DeathEvent() { //在管理器列表中移除自己 manager.monsterList.Remove(this); //销毁自己 Destroy(this.gameObject); }
更换场景,重新烘焙,并添加floor和碰撞体
怪物管理器添加积分系统