一.自动门的制作

  首先将门模型挂载在一个空物体下,模型需要挂载一个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();
    }