一.自动门的制作
首先将门模型挂载在一个空物体下,模型需要挂载一个collider,使门具有物理属性,玩家不能穿过,这个collider不能挂载到空物体上,它需要能随着门一起移动,接下来通过动画或者脚本实现门的开合。在挂载了模型的空物体上添加一个collider组件,一般是球形碰撞器,设置为trigger类型,用于检测玩家是否走入门的范围。
private void OnTriggerEnter(Collider other) { if (other.tag == Tags.enemy || other.tag == Tags.player) count++; } private void OnTriggerExit(Collider other) { if (other.tag == Tags.enemy || other.tag == Tags.player) count--; }
使用一个计数器count记录走入的门碰撞体内的玩家数目。
private void Update() { if(count > 0) { anim.SetBool("IsDoorOpen", true); } else { anim.SetBool("IsDoorOpen",false); } if (anim.IsInTransition(0) && !doorAudio.isPlaying) { doorAudio.Play(); } }
在update函数中更新动画参数,如果检测到门周围玩家数大于0,播放门开启动画,如果检测到周围玩家数为0则播放门关闭动画。同时门在开启和关闭过程中播放动画音效。
二.物体在被拾起时的动画音效播放
物品在被拾起时需要销毁物品,这时如果采用Audio Source组件播放音效,组件会随着物品一同被销毁,音效实际上没有播放,采用静态方法AudioSource.PlayClipAtPoint播放音效就能避免这个问题。
public AudioClip video_pickup; private void OnTriggerEnter(Collider other) { if(other.tag == Tags.player) { Player._instance.hasKey = true; AudioSource.PlayClipAtPoint(video_pickup, transform.position); Destroy(gameObject); } }
三.简单的镜头跟随
通过保持镜头和玩家的偏移不变使镜头始终跟随玩家。
//记录镜头的偏移 private Vector3 offset; //镜头的跟随对象 public Transform player; private void Start() { //初始化偏移量 offset = player.position - transform.position; //修正偏移量,使跟随对象始终在镜头中间位置,这个根据具体的镜头和跟随对象的相对位置确定怎么修正,不修正也没关系 offset.x = 0; } private void Update() { //始终使镜头位置和跟随对象的偏移量保持一致 //这个计算和初始化偏移量的计算是互为逆运算,初始化时镜头位置为减数,这里用被减数减去差计算,如果是被减数则采用减数加差(好基础的问题,但还是想记录下来) transform.position = player.position - offset; }
四.解决视野的盲区
镜头跟随的情况下可能产生视角的盲区,因此可以通过射线检测得到镜头一个合适位置。
首先修改偏移量计算方式,这样设置计算摄像机的合适位置更简单一些。
//初始化偏移量 offset = transform.position - player.position;
然后计算并设置镜头的位置
/// <summary> /// 检测得到合适的镜头位置,并采用插值运算的方式设置好镜头的位置 /// </summary> private void SetCameraTransform() { //计算并记录可以用于检测的位置,即可能的摄像机位置 //位于玩家正上方的位置 Vector3 position0 = player.position + offset.magnitude * Vector3.up; //当前镜头位置 Vector3 position4 = player.position + offset; //插值运算得到镜头和玩家的中间的可能位置 Vector3 position1 = Vector3.Slerp(position0, position4, 0.25f); Vector3 position2 = Vector3.Slerp(position0, position4, 0.5f); Vector3 position3 = Vector3.Slerp(position0, position4, 0.75f); //最终找到的合适的摄像机位置 Vector3 suitablePosition = position4; //用一个数组记录可能的摄像机位置 Vector3[] positions = new Vector3[] { position0, position1, position2, position3, position4 }; //倒序遍历数组,记录下可能的摄像机位置 for(int i = 4;i >= 0;i--) { //射线检测 RaycastHit hit; if(Physics.Raycast(positions[i], player.position - positions[i], out hit)) { //如果射线第一个碰撞的不是玩家,代表玩家和摄像机之间有遮挡,这个位置不可用 if(hit.transform.tag != Tags.player) { continue; } //如果射线第一个遇到的碰撞体是玩家,则这个位置为可用的位置,找到了合适的位置就可以也必须直接结束循环了 else { suitablePosition = positions[i]; break; } } //意外情况下,射线未检测到任何碰撞体,还是将当前位置作为合适的位置 else { suitablePosition = positions[i]; break; } } //通过插值使摄像机平滑运动到合适的位置 transform.position = Vector3.Slerp(transform.position, suitablePosition, Time.deltaTime * cameraMoveSpeed); //摄像机也需要望向玩家,通过插值运算使摄像机平滑旋转 Quaternion nowRotation = transform.rotation; transform.LookAt(player.position); transform.rotation = Quaternion.Slerp(nowRotation, transform.rotation, Time.deltaTime); }
最后在update中调用这个方法
private void Update() { SetCameraTransform(); }