下面添加传送门,把门拖到地图,添加normalDoorController的animator,拖动门打开和关闭的动画到animator,连线链接两个动画,设好条件,设好bool参数close,添加sphere collider,勾选id trigger,把这个碰撞器弄成半球状,这样人物靠近半球状区域门自动打开,给门添加door的脚本,
private int count = 0;
private Animator anim;
void Awake()
{
anim = GetComponent<Animator>();
}
void Start () {
}
void Update () {
anim.SetBool("close", count <= 0);
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player" || other.tag == "Enemy")
{
count++;
}
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player" || other.tag == "Enemy")
{
count--;
}
}
然后添加拿钥匙机器,添加2个box collider,一个用于碰撞一个用于在范围内检测,添加音频,添加SwitchUnit脚本,
public GameObject laser;
public Material unLockMat;// 解锁的图标
public GameObject screen;
void OnTriggerStay(Collider other)
{
if (other.tag == "Player")
{
if (Input.GetKeyDown(KeyCode.Z)) {
laser.SetActive(false);
GetComponent<AudioSource>().Play();
screen.GetComponent<Renderer>().material = unLockMat;
}
}
}
下面添加跟猪脚移动的相机,给main camera添加FellowPlayer脚本,大致的原理是,相机和人物之间有距离差值,计算出差值,人物坐标+差值就等于相机的坐标,由于x轴向上,所以差值为0
private Vector3 offset;
private Transform player;
void Awake()
{
player=GameObject.FindGameObjectWithTag("Player").transform;
offset = transform.position - player.position;
offset = new Vector3(0, offset.y, offset.z);
}
void Update () {
this.transform.position = player.position + offset;
}
}
这里注意的是写代码时写gameobject自动提示gameobject和Gameobject,选择Gameobject而不是gameobject。还有findGameObjectWithTag这个方法很容易选成
findGameObjectWithTags这样的话接收的是一个数组,不对的
这里有个bug,人物rigidbody的is kinematic不勾选,但是走动时乱晃,这是好像由于还受到了transform的影响,所以勾选constraints锁定这些坐标,这样人物的rigidbody和collider不会穿透到其他物体中
下面解决摄像机的视野问题,人物到墙角时视野变成垂直向下
原理如下:
原本是5点位置观察猪脚,如果有墙挡着人物,5-0连线射线检测发现碰撞物体,那么看4-0连线,直到1-0连线没有碰撞为止
private Vector3 offset;
private Transform player;
void Awake()
{
player=GameObject.FindGameObjectWithTag("Player").transform;
offset = transform.position - player.position;
offset = new Vector3(0, offset.y, offset.z);
}
void Update () {
Vector3 beginpos = player.position + offset; //5点坐标
Vector3 endpos = player.position + offset.magnitude*Vector3.up;//1点坐标,offset.magnitude
//为偏移的长度,乘以vector向上的向量等于1点的坐标
Vector3 pos4 = Vector3.Lerp(beginpos,endpos,0.25f);//4点位置是1和5点取差值除以4得到的
Vector3 pos3 = Vector3.Lerp(beginpos, endpos, 0.5f);
Vector3 pos2 = Vector3.Lerp(beginpos, endpos, 0.75f);
Vector3[] postions = new Vector3[] {beginpos,pos4,pos3,pos2,endpos };
Vector3 targetPos = postions[0];
for (int i=0;i<5;i++)
{
RaycastHit hitinfo;
if (Physics.Raycast(postions[i],player.position- postions[i],out hitinfo))
{
if (hitinfo.collider.tag!="Player")
{
continue;
}
else
{
targetPos = postions[i];
break;
}
}
else
{
targetPos = postions[i];
break;
}
}
this.transform.position = targetPos;
transform.LookAt(player.position); //最后让相机面向一直朝向人物的位置
}
虽然通过多点的方式解决了镜头视角问题,但是视角的快速切换在游戏中感觉卡卡的,所以需要平滑处理:
this.transform.position = Vector3.Lerp(transform.position,targetPos,Time.deltaTime*3);
transform.LookAt(player.position); //最后让相机面向一直朝向人物的位置
下面设置navigation,把地图属性设置为静态,window--->navigation设置好Radius ,height然后bake
添加机器人,添加rigidbody和capsule collider,添加导航Nav Mesh Agent,添加sphere collider,勾选is trigger,范围为10,来判断10m内有没有人
创建animator Controller名为EnemyController,
给animator添加一个locomotion二维混合树。给混合树添加17个动画(包括所有的转向动画和站立动画),在compute position选择speed and angular speed,给混合树添加2个参数speed和angularspeed(转向速度),第一层是运动再添加一层,该层为射击。添加shoot的bool参数用于切换2个层,第二层拖动射击的所有动画,设置好转换条件,
由于射击层独立于移动层,所以创建身体遮罩,创建avatar Mask名为EnemyShootMask,weight改为1,注意blendtree的parameters选择angularspeed和speed
创建EnemySight脚本,
public bool playerInsight = false;
public float fieldOfView = 110;
public Vector3 alertPosition = Vector3.zero;
private Animator playerAnim;
private UnityEngine.AI.NavMeshAgent agent;
private SphereCollider collider;
void Awake()
{
playerAnim = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
agent = this.GetComponent<UnityEngine.AI.NavMeshAgent>();
collider = this.GetComponent<SphereCollider>();
}
private void OnTriggerStay(Collider other)
{
if (other.tag=="Player")
{
//这里得到机器人的前方,得到机器人和角色的向量,如果前方和向量的夹角小于110/2,
//那么就认为机器人看到了猪脚
Vector3 forward = transform.forward;
Vector3 playerDir = other.transform.position - transform.position;
float temp=Vector3.Angle(forward,playerDir);
if (temp<0.5f*fieldOfView)
{
playerInsight = true;
alertPosition = other.transform.position;
}
else
{
playerInsight = false;
}
}
//如果机器人在附件判断主角是否在快跑状态,就算有墙挡着主角,也可以通过自动导航来检测折线的距离
//总和是否在sphereCollider范围内,是就保存主角的当前位置
if(playerAnim.GetCurrentAnimatorStateInfo(0).IsName("locomotion")){
UnityEngine.AI.NavMeshPath path = new UnityEngine.AI.NavMeshPath();
if (agent.CalculatePath(other.transform.position,path))
{
Vector3[] wayPoints = new Vector3[path.corners.Length+2];
wayPoints[0] = transform.position;
wayPoints[wayPoints.Length - 1] = other.transform.position;
for (int i=0;i<path.corners.Length;i++)
{
wayPoints[i + 1] = path.corners[i];
}
float length = 0;
for (int i=1;i<wayPoints.Length;i++)
{
length += (wayPoints[i] - wayPoints[i - 1]).magnitude;
}
if (length<collider.radius)
{
alertPosition = other.transform.position;
}
}
}
}
private void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
{
playerInsight = false;
}
}
在GameController脚本中添加一个方法
public void SeePlayer(Transform player)
{
alermOn = true;
lastPlayerPosition = player.position;
}
在EnemySight添加一行代码
if (temp<0.5f*fieldOfView)
{
playerInsight = true;
alertPosition = other.transform.position;
GameController._instance.SeePlayer(other.transform); //发现主角时会叫其他同伴过来
}
接下来,定义一个preLastPlayPosition在awake时候来保存取得的player坐标,当有人发现了主角,调用了seePlayer函数,修改了player坐标,在EnemySight脚本中update函数不断检测,当发现坐标修改时候,把坐标给警报坐标
private Vector3 preLastPlayPosition;
void Awake()
{
playerAnim = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
agent = this.GetComponent<UnityEngine.AI.NavMeshAgent>();
collider = this.GetComponent<SphereCollider>();
preLastPlayPosition = GameController._instance.lastPlayerPosition;
}
void Update()
{
if (preLastPlayPosition!=GameController._instance.lastPlayerPosition)
{
alertPosition = GameController._instance.lastPlayerPosition;
preLastPlayPosition= GameController._instance.lastPlayerPosition;
}
}
运行脚本时候发现一个报空指针错误
preLastPlayPosition = GameController._instance.lastPlayerPosition;
这是因为该代码在awake函数中,而在gameController中awake有
_instance = this;
当运行Enemy的awake函数时候还没有触发gameController的awake,所以拿到空指针。解决方法把
preLastPlayPosition = GameController._instance.lastPlayerPosition;
放到start函数中
下面添加机器人智能行走功能。创建4个waypoint的Gameobject,图标改成小旗子,放到地图中
给机器人创建EnemyMove脚本,声明
public Transform[] wayPoints;
把4个waypoint拖动到脚本属性中
public Transform[] wayPoints;
public float patrolTime = 3f;
private float patrolTimer = 0;
private int index = 0;
private UnityEngine.AI.NavMeshAgent agent;
void Awake()
{
agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
agent.destination = wayPoints[index].position;
}
void Update () {
Patrolling();
}
//机器人巡逻函数
private void Patrolling()
{
if (agent.remainingDistance<0.5f)
{
patrolTimer += Time.deltaTime;
if (patrolTimer>patrolTime)
{
index++;
Debug.Log(patrolTimer);
index %= 4;
Debug.Log(patrolTimer);
agent.destination = wayPoints[index].position;
patrolTimer = 0;
}
}
}
如果在代码中添加了agent.stop();那么自动导航会永远停止,不会再运行。
运行游戏,看到机器人会先走到第一个位置,过3秒移动到第二个位置,再到第三位置,第四位置。。。如此循环
但现在问题是机器人像幽灵一样飘过去,没有行走动画