Unity中的旋轉和縮放

一:鼠標旋轉被選中的物體

1.X軸在平面座標是左右方向,這裏得到的是左右移動距離
2.移動距離得到了,接下來要考慮以那個軸爲中心作旋轉
3.這裏要實現固定攝像機位置的情況下旋轉物體,就以Y軸爲中心,所以Rotate(0,mousX,0);

public float roate_Speed=100.0f;//旋轉速度
void Update()
{
Transform target_transform = null;//不用綁定對象,下面代碼動態獲取對象
if (Input.GetMouseButton(0))
            {
               
                //在屏幕上轉換座標:將鼠標點轉換成射線
                Ray rayObj = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hitObj;
                if (Physics.Raycast(rayObj, out hitObj))
                {
                    //Debug.Log("射線得到的對象名稱:" + hitObj.collider.name);
                    target_transform = hitObj.transform;
                }


                if (target_transform != null)
                {
                    //Debug.Log("射線取得對象");
                    float mousX = Input.GetAxis("Mouse X") * roate_Speed;//得到鼠標移動距離
                    target_transform.transform.Rotate(new Vector3(0, -mousX, 0));
                }
                else
                {
                    Debug.Log("無法取得對象");
                }
            }
}

二:鼠標控制一個攝像機圍繞一個物體旋轉

這裏主要用到RoateAround方法

transform.RotateAround (Vector3.zero, Vector3.up, 20 * Time.deltaTime);

參數1:目標的座標(圍繞哪個物體旋轉)

參數2:目標的軸向(是以X軸旋轉還是以Y軸旋轉還是以Z軸旋轉)

參數3:旋轉速度(這個好明白)

 

三:觸控控制物體旋轉和縮放

using UnityEngine;
using System.Collections;
using System.IO;

public class ScaleAndRotate : MonoBehaviour
{
	private Touch oldTouch1;  //上次觸摸點1(手指1)
	private Touch oldTouch2;  //上次觸摸點2(手指2)

	void Start()
	{

	}
	
	void Update () {

		//沒有觸摸
		if ( Input.touchCount <= 0 ){
			return;
		}

		//單點觸摸, 水平上下旋轉
		if( 1 == Input.touchCount ){
			Touch touch = Input.GetTouch (0);
			Vector2 deltaPos = touch.deltaPosition;			
			transform.Rotate(Vector3.down  * deltaPos.x , Space.World); 
			transform.Rotate(Vector3.right * deltaPos.y , Space.World);
		}

		//多點觸摸, 放大縮小
		Touch newTouch1 = Input.GetTouch (0);
		Touch newTouch2 = Input.GetTouch (1);
		
		//第2點剛開始接觸屏幕, 只記錄,不做處理
		if( newTouch2.phase == TouchPhase.Began ){
			oldTouch2 = newTouch2;
			oldTouch1 = newTouch1;
			return;
		}
		
		//計算老的兩點距離和新的兩點間距離,變大要放大模型,變小要縮放模型
		float oldDistance = Vector2.Distance(oldTouch1.position, oldTouch2.position);
		float newDistance = Vector2.Distance(newTouch1.position, newTouch2.position);

		//兩個距離之差,爲正表示放大手勢, 爲負表示縮小手勢
		float offset = newDistance - oldDistance;

		//放大因子, 一個像素按 0.01倍來算(100可調整)
		float scaleFactor = offset / 100f;
		Vector3 localScale = transform.localScale;
		Vector3 scale = new Vector3(localScale.x + scaleFactor,
		                            localScale.y + scaleFactor, 
		                            localScale.z + scaleFactor);

		//最小縮放到 0.3 倍
		if (scale.x > 0.3f && scale.y > 0.3f && scale.z > 0.3f) {
			transform.localScale = scale;
		}

		//記住最新的觸摸點,下次使用
		oldTouch1 = newTouch1;
		oldTouch2 = newTouch2;
	}

}

四:Unity3D遊戲開發之自由視角下的角色控制

        所謂自由視角是指玩家可以按照自身座標系向着四個不同的方向移動,當玩家按下鼠標右鍵時,可以繞Y軸按照一定的角度旋轉攝像機,在旋轉的過程中,角色將旋轉相應的角度。在移動的過程中,攝像機會保持與玩家間的一定距離,然後跟隨角色進行移動。在開始今天的內容前,首先讓我們來學習下Unity3D中較爲重要的一部分知識,理解這些知識是我們開始學習今天內容的基礎。

       1、Input.GetAxis():該方法用於在Unity3D中根據座標軸名稱返回虛擬座標系中的值,通常情況下,使用控制器和鍵盤輸入時此值範圍在-1到1之間。這段話怎麼理解呢?我們來看下面這段腳本:

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {

	//水平速度
	public float HorizontalSpeed = 2.0F;
	//垂直速度
	public float VerticalSpeed = 2.0F;

	void Update() 
	{
		//水平方向
		float h = HorizontalSpeed * Input.GetAxis("Mouse X");
		//垂直方向
		float v = VerticalSpeed * Input.GetAxis("Mouse Y");
		//旋轉
		transform.Rotate(v, h, 0);
	}
}

這段腳本呢是根據鼠標的位置來旋轉物體從而實現對物體的觀察,從這段腳本中我們可以看出,通過獲取輸入軸的辦法,我們可以獲得鼠標移動的方向進而實現對於物體的旋轉控制。在Unity3D中我們可以通過Edit->Project Setting->Input來查看項目中的座標軸名稱。

     2、歐拉角eulerAngles:該值是Vector3類型的值,x、y、z分別代表繞x軸旋轉x度,繞y軸旋轉y度,繞z軸旋轉z度。因此,該值最爲直觀的形式是可以允許我們直接以一個三維向量的形式來修改一個物體的角度,例如下面的腳本:

float mY = 5.0;

void Update () 
{
	mY += Input.GetAxis("Horizontal");
	transform.eulerAngles =new Vector3(0,mY, 0);
}
如果你已經理解了上面的話,那麼不出意外的,這段腳本會如你所願的,按照鼠標在水平方向上移動的方向繞Y軸旋轉。通常情況下,我們不會單獨設置歐拉角其中一個軸,例如eulerAngles.x = 10,因爲這將導致偏移和不希望的旋轉。當設置它們一個新的值時,要同時設置全部。好在我們可以通過Quaternion.Euler()方法將一個Vector3類型的值轉化爲一個四元數,進而通過修改Transform.Rotation來實現相同的目的。
     3、插值:所謂插值是指在離散數據的基礎上補插連續函數,使得這條連續曲線通過全部給定的離散數據點。插值是離散函數逼近的重要方法,利用它可通過函數在有限個點處的取值狀況,估算出函數在其他點處的近似值。在某些情況下,如果我們希望過程中處理得較爲平滑,此時我們就可以使用插值的方法來實現對中間過程的模擬。在Unity3D中我們可以使用兩種插值方法,即線性插值Lerp,球形插值SLerp。我們來看下面的腳本:

void Rotating (float horizontal, float vertical)

	{
		// Create a new vector of the horizontal and vertical inputs.
		Vector3 targetDirection = new Vector3(horizontal, 0f, vertical);
		
		// Create a rotation based on this new vector assuming that up is the global y axis.
		Quaternion targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up);
		
		// Create a rotation that is an increment closer to the target rotation from the player's rotation.
		Quaternion newRotation = Quaternion.Lerp(rigidbody.rotation, targetRotation, turnSmoothing * Time.deltaTime);
		
		// Change the players rotation to this new rotation.
		rigidbody.MoveRotation(newRotation);
	}

插值的方法很簡單,只要我們給出初始和結束的狀態、時間就可以了,大家可以自己看API。

 好了,有了這三部分的基礎,我們就可以開始今天的內容了,今天的腳本分爲兩個部分,第一部分是角色控制的部分,主要負責的角色在場景中的移動、轉身和動畫處理。第二部分是相機控制的部分,主要涉及相機旋轉、相機縮放的相關內容。下面,我們分別來講這兩個部分

在第一部分,主要的是完成角色向各個方向的轉身,這裏博主定義四個方向(其實八個方向是一樣的!),腳本如下:

using UnityEngine;
using System.Collections;

public class NoLockiVew_Player : MonoBehaviour {

	/*自由視角下的角色控制*/

	//玩家的行走速度
	public float WalkSpeed=1.5F;
	//重力
	public float Gravity=20;

	//角色控制器
	private CharacterController mController;
	//動畫組件
	private Animation mAnim;
	//玩家方向,默認向前
	private DirectionType mType=DirectionType.Direction_Forward;

	[HideInInspector]
	//玩家狀態,默認爲Idle
	public PlayerState State=PlayerState.Idle;

	//定義玩家的狀態枚舉
	public enum PlayerState
	{
		Idle,
		Walk
	}

	//定義四個方向的枚舉值,按照逆時針方向計算
	protected enum DirectionType
	{
		Direction_Forward=90,
		Direction_Backward=270,
		Direction_Left=180,
		Direction_Right=0
	}
	
	void Start () 
	{
	   //獲取角色控制器
	   mController=GetComponent<CharacterController>();
	   //獲取動畫組件
	   mAnim=GetComponentInChildren<Animation>();
	}
	
	
	void Update () 
	{
		MoveManager();
		//MouseEvent();
	}

	//玩家移動控制
	void MoveManager()
	{
		//移動方向
		Vector3 mDir=Vector3.zero;
		if(mController.isGrounded)
		{
			//將角色旋轉到對應的方向
			if(Input.GetAxis("Vertical")==1)
			{
				SetDirection(DirectionType.Direction_Forward);
				mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
				mAnim.CrossFade("Walk",0.25F);
				State=PlayerState.Walk;
			}
			if(Input.GetAxis("Vertical")==-1)
			{
				SetDirection(DirectionType.Direction_Backward);
				mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
				mAnim.CrossFade("Walk",0.25F);
				State=PlayerState.Walk;
			}
			if(Input.GetAxis("Horizontal")==-1)
			{
				SetDirection(DirectionType.Direction_Left);
				mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
				mAnim.CrossFade("Walk",0.25F);
				State=PlayerState.Walk;
			}
			if(Input.GetAxis("Horizontal")==1)
			{
				SetDirection(DirectionType.Direction_Right);
				mDir=Vector3.forward * Time.deltaTime * WalkSpeed;
				mAnim.CrossFade("Walk",0.25F);
				State=PlayerState.Walk;
			}
			//角色的Idle動畫
			if(Input.GetAxis("Vertical")==0 && Input.GetAxis("Horizontal")==0)
			{
				mAnim.CrossFade("Idle",0.25F);
				State=PlayerState.Idle;
			}

		}
		//考慮重力因素
		mDir=transform.TransformDirection(mDir);
		float y=mDir.y-Gravity *Time.deltaTime;
		mDir=new Vector3(mDir.x,y,mDir.z);
		mController.Move(mDir);
	}

	//設置角色的方向,有問題
	void SetDirection(DirectionType mDir)
	{
		if(mType!=mDir)
		{
			transform.Rotate(Vector3.up*(mType-mDir));
			mType=mDir;
		}
	}
}

這裏定義四個方向,是按照逆時針方向轉的,相鄰的兩個方向間相差90度,所以我們只需要將當前的角度和目標角度相減就可以轉到目標角度的方向(其實這是以前寫的代碼,現在回頭再看,直接用歐拉角似乎更爲簡單啊,呵呵)。這裏主要的內容就是這樣了。下面我們來看相機控制部分的代碼吧,這裏的代碼參考了MouseOrbit腳本,主要完成了鼠標右鍵旋轉控制,博主在此基礎上增加了相機縮放的代碼。提到相機縮放,其實就是根據鼠標滾輪滾動的方向和大小重新計算角色與相機的距離,與之類似地還有小地圖的放縮,其實同樣是通過修改距離來實現的。博主今天的一個體會是官方的代碼能自己寫一遍的最好自己寫一遍,這樣好多東西就能在這個過程中給理解了。我們一起來看腳本

using UnityEngine;
using System.Collections;

public class NoLockView_Camera : MonoBehaviour 
{
	//觀察目標
	public Transform Target;
	//觀察距離
	public float Distance = 5F;
	//旋轉速度
	private float SpeedX=240;
	private float SpeedY=120;
	//角度限制
	private float  MinLimitY = 5;
	private float  MaxLimitY = 180;

	//旋轉角度
	private float mX = 0.0F;
	private float mY = 0.0F;

    //鼠標縮放距離最值
	private float MaxDistance=10;
	private float MinDistance=1.5F;
	//鼠標縮放速率
	private float ZoomSpeed=2F;

	//是否啓用差值
	public bool isNeedDamping=true;
	//速度
	public float Damping=2.5F;

	void Start () 
	{
		//初始化旋轉角度
		mX=transform.eulerAngles.x;
		mY=transform.eulerAngles.y;
	}
	
	void LateUpdate () 
	{
		//鼠標右鍵旋轉
		if(Target!=null && Input.GetMouseButton(1))
		{
		    //獲取鼠標輸入
			mX += Input.GetAxis("Mouse X") * SpeedX * 0.02F;
			mY -= Input.GetAxis("Mouse Y") * SpeedY * 0.02F;
			//範圍限制
			mY = ClampAngle(mY,MinLimitY,MaxLimitY);
		}

		//鼠標滾輪縮放

		Distance-=Input.GetAxis("Mouse ScrollWheel") * ZoomSpeed;
		Distance=Mathf.Clamp(Distance,MinDistance,MaxDistance);

		//重新計算位置和角度
		Quaternion mRotation = Quaternion.Euler(mY, mX, 0);
		Vector3 mPosition = mRotation * new Vector3(0.0F, 0.0F, -Distance) + Target.position;

		//設置相機的角度和位置
		if(isNeedDamping){
		   //球形插值
		   transform.rotation = Quaternion.Lerp(transform.rotation,mRotation, Time.deltaTime*Damping); 
		   //線性插值
		   transform.position = Vector3.Lerp(transform.position,mPosition, Time.deltaTime*Damping); 
		}else{
		   transform.rotation = mRotation;
		   transform.position = mPosition;
		}
		//將玩家轉到和相機對應的位置上
		if(Target.GetComponent<NoLockiVew_Player>().State==NoLockiVew_Player.PlayerState.Walk)
		{
			Target.eulerAngles=new Vector3(0,mX,0);
		}
	}
	
	private float  ClampAngle (float angle,float min,float max) 
	{
		if (angle < -360) angle += 360;
		if (angle >  360) angle -= 360;
		return Mathf.Clamp (angle, min, max);
	}
}

這裏很多朋友可能對我設置一個狀態很不理解吧,這其實是爲了讓玩家有一個自由查看角色的機會,否則當玩家按下鼠標右鍵的話,角色就會轉向相機正對着的位置,這樣玩家就看不到角色的正面了。當然,這裏用到了插值,這樣能使角色在轉身的時候平滑一點,效果會更好。

Bug:


//設置玩家跟隨角度
		if(Target.GetComponent<NoLockiVew_Player>().State==NoLockiVew_Player.PlayerState.Walk)
		{
			Target.rotation=Quaternion.Euler(new Vector3(0,mX,0));
		}

該方法主要的作用是當玩家同時按下方向控制鍵和鼠標右鍵,玩家可以隨着鼠標旋轉到對應的角度,這主要是爲了滿足玩家雙手操作的需求,不過由於這行代碼,導致玩家在向左、向右、向後三個方向上的轉身失效,如果除去這行代碼,則原來的方向控制沒有任何問題,可是沒有這行代碼,玩家的操作感就會下降。後來博主想到我們對角色的旋轉實際上應該是放在鼠標右鍵事件裏的,所以博主將代碼修改如下,這樣就解決了這個Bug:

using UnityEngine;
using System.Collections;

public class NoLockView_Camera : MonoBehaviour 
{
	//觀察目標
	public Transform Target;
	//觀察距離
	public float Distance = 5F;
	//旋轉速度
	private float SpeedX=240;
	private float SpeedY=120;
	//角度限制
	private float  MinLimitY = 5;
	private float  MaxLimitY = 180;

	//旋轉角度
	private float mX = 0.0F;
	private float mY = 0.0F;

    //鼠標縮放距離最值
	private float MaxDistance=10;
	private float MinDistance=1.5F;
	//鼠標縮放速率
	private float ZoomSpeed=2F;

	//是否啓用差值
	public bool isNeedDamping=true;
	//速度
	public float Damping=10F;

	private Quaternion mRotation;

	void Start () 
	{
		//初始化旋轉角度
		mX=transform.eulerAngles.x;
		mY=transform.eulerAngles.y;
	}
	
	void LateUpdate () 
	{
		//鼠標右鍵旋轉
		if(Target!=null && Input.GetMouseButton(1))
		{
		    //獲取鼠標輸入
			mX += Input.GetAxis("Mouse X") * SpeedX * 0.02F;
			mY -= Input.GetAxis("Mouse Y") * SpeedY * 0.02F;
			//範圍限制
			mY = ClampAngle(mY,MinLimitY,MaxLimitY);
			//計算旋轉
			mRotation = Quaternion.Euler(mY, mX, 0);
			//根據是否插值採取不同的角度計算方式
			if(isNeedDamping){
				transform.rotation = Quaternion.Lerp(transform.rotation,mRotation, Time.deltaTime*Damping); 
			}else{
				transform.rotation = mRotation;
			}
			//處理同時按下鼠標右鍵和方向控制鍵
			if(Target.GetComponent<NoLockiVew_Player>().State==NoLockiVew_Player.PlayerState.Walk){
				Target.rotation=Quaternion.Euler(new Vector3(0,mX,0));
			}
		}

		//鼠標滾輪縮放
		Distance-=Input.GetAxis("Mouse ScrollWheel") * ZoomSpeed;
		Distance=Mathf.Clamp(Distance,MinDistance,MaxDistance);

		//重新計算位置
		Vector3 mPosition = mRotation * new Vector3(0.0F, 0.0F, -Distance) + Target.position;

		//設置相機的角度和位置
		if(isNeedDamping){
		   transform.position = Vector3.Lerp(transform.position,mPosition, Time.deltaTime*Damping); 
		}else{
		   transform.position = mPosition;
		}

	}


	//角度限制
	private float  ClampAngle (float angle,float min,float max) 
	{
		if (angle < -360) angle += 360;
		if (angle >  360) angle -= 360;
		return Mathf.Clamp (angle, min, max);
	}
}

五:攝像機平滑跟隨

using UnityEngine;
using System.Collections;

public class SmoothFollowerObj : MonoBehaviour {

    private Vector3 targetPosition;
    private Vector3 position;
    private Vector3 velocity;
    private float smoothingTime;
    private float prediction;

    public SmoothFollowerObj(float smoothingTime)
    {
        targetPosition = Vector3.zero;
        position = Vector3.zero;
        velocity = Vector3.zero;
        this.smoothingTime = smoothingTime;
        prediction = 1;
    }

    public SmoothFollowerObj(float smoothingTime, float prediction)
    {
        targetPosition = Vector3.zero;
        position = Vector3.zero;
        velocity = Vector3.zero;
        this.smoothingTime = smoothingTime;
        this.prediction = prediction;
    }

    // Update should be called once per frame
    public Vector3 Update(Vector3 targetPositionNew, float deltaTime)
    {
        Vector3 targetVelocity = (targetPositionNew - targetPosition) / deltaTime;
        targetPosition = targetPositionNew;

        float d = Mathf.Min(1, deltaTime / smoothingTime);
        velocity = velocity * (1 - d) + (targetPosition + targetVelocity * prediction - position) * d;

        position += velocity * Time.deltaTime;
        return position;
    }

    public Vector3 Update(Vector3 targetPositionNew, float deltaTime, bool reset)
    {
        if (reset)
        {
            targetPosition = targetPositionNew;
            position = targetPositionNew;
            velocity = Vector3.zero;
            return position;
        }
        return Update(targetPositionNew, deltaTime);
    }

    public Vector3 GetPosition() { return position; }
    public Vector3 GetVelocity() { return velocity; }
}

下面該段腳本賦給攝像機,被跟隨物體賦給character即可

using UnityEngine;
using System.Collections;

public class NoGravityCamera : MonoBehaviour {

    public GameObject character;
    public Vector3 positionVector;
    public Vector3 lookVector;
    private SmoothFollowerObj posFollow;
    private SmoothFollowerObj lookFollow;
    private Vector3 lastVelocityDir;
    private Vector3 lastPos;

    // Use this for initialization
    void Start()
    {
        //positionVector = new Vector3(0, 2, 4);
        //lookVector = new Vector3(0, 0, 1.5f);
        posFollow = new SmoothFollowerObj(0.5f, 0.5f);
        lookFollow = new SmoothFollowerObj(0.1f, 0.0f);
        posFollow.Update(transform.position, 0, true);
        lookFollow.Update(character.transform.position, 0, true);
        lastVelocityDir = character.transform.forward;
        lastPos = character.transform.position;
    }

    // Update is called once per frame
    void LateUpdate()
    {
        lastVelocityDir += (character.transform.position - lastPos) * 8;
        lastPos = character.transform.position;
        lastVelocityDir += character.transform.forward * Time.deltaTime;
        lastVelocityDir = lastVelocityDir.normalized;
        Vector3 horizontal = transform.position - character.transform.position;
        Vector3 horizontal2 = horizontal;
        Vector3 vertical = character.transform.up;
        Vector3.OrthoNormalize(ref vertical, ref horizontal2);
        if (horizontal.sqrMagnitude > horizontal2.sqrMagnitude) horizontal = horizontal2;
        transform.position = posFollow.Update(
            character.transform.position + horizontal * Mathf.Abs(positionVector.z) + vertical * positionVector.y,
            Time.deltaTime
        );

        horizontal = lastVelocityDir;
        Vector3 look = lookFollow.Update(character.transform.position + horizontal * lookVector.z - vertical * lookVector.y, Time.deltaTime);
        transform.rotation = Quaternion.FromToRotation(transform.forward, look - transform.position) * transform.rotation;
    }
}


六:處理攝像機跟隨避免相機穿牆拉近或透明的方法


      當攝像機處於跟隨主角狀態時,那麼主角移動後很有可能攝像機被別的模型擋住。這樣用戶體驗就非常不好,一般3D遊戲在處理視角的時候有兩種方法,第一種是讓被擋住的模型變成透明,第二種是拉近攝像機。前者時候固定視角遊戲使用,後者適合變化視角遊戲使用。

http://www.xuanyusong.com/archives/1991


發佈了16 篇原創文章 · 獲贊 16 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章