Unity3D第三人称摄像机

作为算法竞赛的蒟蒻在打了若干次铁后,和队友一起退役了.JPG

Unity3D设置一个第三人称摄像机,为了画面的有趣性,先选择下载一个带基本动作的角色包,笔者选择了免费的Warrior Pack Bundle 3 FREE.

将某个角色的prefab拖动到scene设置animator controller为其对应的动作。

 

 

尝试运行。

 

 角色只能在原地打转,因为这里并没有控制角色的位移的脚本,moving时仅仅设置了动画的播放。

控制角色转动的函数如下。

    void GetCameraRelativeMovement()
    {  
        Transform cameraTransform = Camera.main.transform;

        // Forward vector relative to the camera along the x-z plane   
        Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);
        forward.y = 0;
        forward = forward.normalized;
        //print(forward.x+' '+forward.y+' '+forward.z);
        // Right vector relative to the camera
        // Always orthogonal to the forward vector
        Vector3 right= new Vector3(forward.z, 0, -forward.x);

        //directional inputs
        float v = Input.GetAxisRaw("Vertical");
        float h = Input.GetAxisRaw("Horizontal");

        // Target direction relative to the camera
        targetDirection = h * right + v * forward;

    }

    //face character along input direction
    void RotateTowardMovementDirection()  
    {
        if(inputVec != Vector3.zero)
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDirection), Time.deltaTime * rotationSpeed);
        }
    }



    void UpdateMovement()
    {
        //get movement input from controls
        Vector3 motion = inputVec;

        //reduce input for diagonal movement
        motion *= (Mathf.Abs(inputVec.x) == 1 && Mathf.Abs(inputVec.z) == 1) ? 0.7f:1;
        RotateTowardMovementDirection();
        GetCameraRelativeMovement();
        ///CameraRotate();
        ///MoveTowardsFace();
        ///CameraMove();
    }

这些是下载角色自带的函数,控制角色根据和主摄像机的位置来决定转向。

以只狼的移动模式为目标。按下向右移动时,角色完成转向并向前走,摄像机始终和只狼保持距离,并且将狼放在中央。

为了模拟这个过程,首先加入按下方向键时向角色正面的方向进行移动。

    void MoveTowardsFace()
    {
        if (inputVec.x != 0 || inputVec.z != 0)
        {
            transform.Translate(Time.deltaTime * moveSpeed * transform.forward, Space.World);
        }
    }

控制角色在摄像机中央,此时摄像机只用完成看着角色旋转。

void CameraRotate()
    {
        cameraTargetDirection = transform.position - Camera.main.transform.position;
        cameraTargetDirection.y = 0;
        Camera.main.transform.rotation = Quaternion.Slerp(Camera.main.transform.rotation, Quaternion.LookRotation(cameraTargetDirection)
            , Time.deltaTime * rotationSpeed
            );
    }

Quaternion是四元组用来描述一个方向向量,其值分别为w,x,y,z,w为以标量,x,y,z分别为在方向上的投影,具体换算我拒绝书写......打算把这一使用暂时当黑盒运算。

接着是摄像机的移动,在前两部完成摄像机看着角色后,摄像机会准确地将角色控制在中间,应该说Unity神奇呢,还是该说微积分神奇呢。摄像机现在只需要使用负反馈调节来控制和人物间的距离。

 

    float GetDistance(Vector3 a, Vector3 b)
    {
        return Mathf.Sqrt(Mathf.Pow(a.x - b.x, 2) + Mathf.Pow(a.z - b.z, 2));
    }
    void CameraMove()
    {
        //if (inputVec.x != 0 || inputVec.z != 0)
        //{
        //print(Camera.main.transform.position);
        //print(transform.position);
        //print(GetDistance(Camera.main.transform.position, transform.position));
        Camera.main.transform.Translate(Camera.main.transform.forward * Time.deltaTime * 10 *
        (GetDistance(Camera.main.transform.position, transform.position) - distanceToCharacter)
        , Space.World);
        //  }
    }

 

值得注意的是,我们从小接触的平面直角坐标系甚至大学的教材是苏联人的那一套,x,y作为平面的两个轴,在Unity中的坐标系默认是西方的那一套也就是x,z作为平面的两个轴,这点害我出了很多问题还不知道在哪。

 

 

 

 这里不放动图视频进行展示了,除了纵轴y,平面上摄像机已经完全控制在范围中了。

这里考虑一个问题,负反馈调节时采用指数函数会不会效果更好?

 

整个脚本

 

using UnityEngine;
using System.Collections;

public class WarriorAnimationDemoFREE : MonoBehaviour 
{
    public Animator animator;
    float rotationSpeed = 30;
    float moveSpeed = 10;
    float distanceToCharacter = 10;
    Vector3 inputVec;
    Vector3 targetDirection;
    Vector3 cameraTargetDirection;

    //Warrior types
    public enum Warrior{Karate, Ninja, Brute, Sorceress, Knight, Mage, Archer, TwoHanded, Swordsman, Spearman, Hammer, Crossbow};
    public Warrior warrior;
    private void Start()
    {
       // animator = this.GetComponent<Animator>();
        
    }
    void Update()
    {
        //Get input from controls
        float z = Input.GetAxisRaw("Horizontal");
        float x = -(Input.GetAxisRaw("Vertical"));
        inputVec = new Vector3(x, 0, z);

        //Apply inputs to animator
        animator.SetFloat("Input X", z);
        animator.SetFloat("Input Z", -(x));

        if (x != 0 || z != 0 )  //if there is some input
        {
            print("hehe");
            //set that character is moving
            animator.SetBool("Moving", true);
        }
        else
        {
            //character is not moving
            animator.SetBool("Moving", false);
        }

        if (Input.GetButtonDown("Fire1"))
        {
            animator.SetTrigger("Attack1Trigger");
            if (warrior == Warrior.Brute)
                StartCoroutine (COStunPause(1.2f));
            else if (warrior == Warrior.Sorceress)
                StartCoroutine (COStunPause(1.2f));
            else
                StartCoroutine (COStunPause(.6f));
        }

        //update character position and facing
        UpdateMovement();
    }
    
    public IEnumerator COStunPause(float pauseTime)
    {
        yield return new WaitForSeconds(pauseTime);
    }
    void CameraRotate()
    {
        cameraTargetDirection = transform.position - Camera.main.transform.position;
        cameraTargetDirection.y = 0;
        Camera.main.transform.rotation = Quaternion.Slerp(Camera.main.transform.rotation, Quaternion.LookRotation(cameraTargetDirection)
            , Time.deltaTime * rotationSpeed
            );
    }

    void MoveTowardsFace()
    {
        if (inputVec.x != 0 || inputVec.z != 0)
        {
            transform.Translate(Time.deltaTime * moveSpeed * transform.forward, Space.World);
        }
    }
    float GetDistance(Vector3 a, Vector3 b)
    {
        return Mathf.Sqrt(Mathf.Pow(a.x - b.x, 2) + Mathf.Pow(a.z - b.z, 2));
    }
    void CameraMove()
    {
        //if (inputVec.x != 0 || inputVec.z != 0)
        //{
        //print(Camera.main.transform.position);
        //print(transform.position);
        //print(GetDistance(Camera.main.transform.position, transform.position));
        Camera.main.transform.Translate(Camera.main.transform.forward * Time.deltaTime * 10 *
        (GetDistance(Camera.main.transform.position, transform.position) - distanceToCharacter)
        , Space.World);
        //  }
    }
    //converts control input vectors into camera facing vectors
    void GetCameraRelativeMovement()
    {  
        Transform cameraTransform = Camera.main.transform;

        // Forward vector relative to the camera along the x-z plane   
        Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);
        forward.y = 0;
        forward = forward.normalized;
        //print(forward.x+' '+forward.y+' '+forward.z);
        // Right vector relative to the camera
        // Always orthogonal to the forward vector
        Vector3 right= new Vector3(forward.z, 0, -forward.x);

        //directional inputs
        float v = Input.GetAxisRaw("Vertical");
        float h = Input.GetAxisRaw("Horizontal");

        // Target direction relative to the camera
        targetDirection = h * right + v * forward;

    }

    //face character along input direction
    void RotateTowardMovementDirection()  
    {
        if(inputVec != Vector3.zero)
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDirection), Time.deltaTime * rotationSpeed);
        }
    }



    void UpdateMovement()
    {
        //get movement input from controls
        Vector3 motion = inputVec;

        //reduce input for diagonal movement
        motion *= (Mathf.Abs(inputVec.x) == 1 && Mathf.Abs(inputVec.z) == 1) ? 0.7f:1;
        RotateTowardMovementDirection();
        GetCameraRelativeMovement();
        CameraRotate();
        MoveTowardsFace();
        CameraMove();
    }

    //Placeholder functions for Animation events
    void Hit()
    {
    }

    void FootR()
    {
    }

    void FootL()
    {
    }

    void OnGUI () 
    {
        if (GUI.Button (new Rect (25, 85, 100, 30), "Attack1")) 
        {
            animator.SetTrigger("Attack1Trigger");
            //if character is Brute or Sorceress
            switch (warrior)
            {
                case Warrior.Brute:
                case Warrior.Sorceress:
                    StartCoroutine(COStunPause(1.2f));
                    break;
                default:
                    StartCoroutine(COStunPause(.6f));
                    break;
            }
            
            
        }
    }
}

 

 

 

posted @ 2019-11-13 08:05  DevilInChina  阅读(518)  评论(0编辑  收藏  举报