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; } } } }