Unity3D之滑鼠控制角色移動與奔跑示例
最新補充。
一般在做滑鼠選擇時是從攝像機向目標點發送一條射線,然後取得射線與物件相交的點來計算3D目標點。後來在開發中發現了一個問題(射線被別的物件擋住了),就是如果主角的前面有別的遊戲物件擋著。此時如果使用射線的原理,滑鼠選擇被檔的物件,這樣主角就會向被當的物件的方向行走。為了解決這個問題,我放棄使用傳送射線的方法,最後通過2D的方法完美的處理了這個問題。
如下圖所示,我們先把主角的3D座標換算成螢幕中的2D座標,當滑鼠在螢幕中點選的時候取得一個目標點的2D座標,根據這2個座標換算出主角的2D向量。
然後我們在看看程式碼。
//將世界座標換算成螢幕座標
Vector3 vpos3 = Camera.main.WorldToScreenPoint(transform.position);
Vector2 vpos2 = new Vector2 (vpos3.x,vpos3.y);
//取得滑鼠點選的螢幕座標
Vector2 input = new Vector2 (Input.mousePosition.x,Input.mousePosition.y);
//取得主角到目標點的向量
Vector2 normalied = ((vpos2 – input)).normalized;
注意normalized是格式化向量,以為vpos2 – input是計算兩個向量之間的距離,格式化後才是它們的方向。格式化後向量的取值範圍在 -1 到 +1 之間。
//我們忽略Y軸的向量,把2D向量應用在3D向量中。
Vecotr3 targetDirection = new Vector3(normalied.x,0.0f,normalied.y) ;
//根據照相機的角度計算真實的方向
float y = Camera.main.transform.rotation.eulerAngles.y;
targetDirection = Quaternion.Euler(0f,y – 180,0f) * targetDirection;
攝像機的角度決定著主角移動的方向,y是攝像機當前角度,180是攝像機預設的角度,攝像機在旋轉的時候y是會動態改變的,所以需要 y – 180 。用Quaternion.Euler()方法計算一個rotation ,然後乘以預設的向量targetDirection就是主角在3D中真實需要移動的方向。
//最後使用角色控制器移動主角就可以
Vector3 movement = targetDirection * moveSpeed + new Vector3 (0, verticalSpeed, 0) + inAirVelocity;
CharacterController controller = GetComponent<CharacterController>();
collisionFlags = controller.Move(movement);
———————————————————–華麗的分割線—————————————-
看到這個標題我相信大家應該並不陌生,一般在PC網路遊戲中玩家通過滑鼠左鍵在遊戲世界中選擇角色目標移動位置,接著主角將面朝點選的那個方向移動。首先就本文來說我們應當掌握的知識點是“滑鼠揀選”。這是什麼概念呢?其實很簡單,就是玩家通過滑鼠在Game檢視中選擇了一個點,需要得到該點在3D世界中的三維座標系。Game檢視是一個2D的平面,所以滑鼠揀選的難點就是如何把一個2D座標換算成3D座標。我們可以使用射線的原理很好的解決這個問題,在平面中選擇一個點後從攝像機向該點發射一條射線。判斷:選擇的這個點是否為地面,如果是地面拿到這個點的3D座標即可。如下圖所示,在場景檢視中我們簡單的製作了帶坡度的地形,目標是使用者點選帶坡度或不帶坡度的地形都可以順利的到達目的地。
本文依然使用角色控制器元件,不知道這個元件的朋友請看MOMO之前的文章。因為官方提供的指令碼是JavaScript語言。MOMO比較喜歡C#所以放棄了在它的基礎上修改,而針對本文的知識點重寫編寫指令碼,這樣也方便大家學習,畢竟官方提供的程式碼功能比較多,程式碼量也比較多。廢話不多說了進入正題,首先在將模型資源載入工程,這裡沒有使用官方提供的包,而直接將模型資源拖拽入工程。如下圖所示,直接將角色控制器包中的模型資源拖拽如層次檢視當中。在Project檢視中滑鼠右鍵選擇Import Package ->Script引入官方提供的指令碼,這些指令碼主要是應用於攝像機朝向的部分。首先在Hierarchy檢視中選擇攝像機元件,接著在導航欄選單中選擇Compont -> Camera-Control ->SmoothFollow指令碼。實際意義是將跟隨指令碼繫結在攝像機之上,目的是主角移動後攝像機也能跟隨主角一併移動。如下圖所示,指令碼繫結完畢後可在右側監測面板檢視中看到Smooth Follow指令碼。Target 就是射向攝像機朝向的參照物,這裡把主角物件掛了上去意思是攝像機永遠跟隨主角移動。 由於官方提供的指令碼並不是特別的好,攝像機永遠照射在主角的後面,以至於控制主角向後回頭時也無法看到主角的面部表情,所以MOMO簡單的修改一下這條指令碼,請注意一下我修改的地方即可。 SmootFollow.js
// The target we are following
var target : Transform;
// The distance in the x-z plane to the target
var distance = 10.0;
// the height we want the camera to be above the target
var height = 5.0;
// How much we
var heightDamping = 2.0;
var rotationDamping = 3.0;
// Place the script in the Camera-Control group in the component menu
@script AddComponentMenu("Camera-Control/Smooth Follow")
function LateUpdate () {
// Early out if we don't have a target
if (!target)
return;
// Calculate the current rotation angles
var wantedRotationAngle = target.eulerAngles.y;
var wantedHeight = target.position.y + height;
var currentRotationAngle = transform.eulerAngles.y;
var currentHeight = transform.position.y;
// Damp the rotation around the y-axis
currentRotationAngle = Mathf.LerpAngle (currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime);
// Damp the height
currentHeight = Mathf.Lerp (currentHeight, wantedHeight, heightDamping * Time.deltaTime);
// Convert the angle into a rotation
//下面是原始程式碼。
//var currentRotation = Quaternion.Euler (0, currentRotationAngle, 0);
//這裡是我修改的,直接讓它等於1,
//攝像機就不會旋轉。
var currentRotation = 1;
// Set the position of the camera on the x-z plane to:
// distance meters behind the target
transform.position = target.position;
transform.position -= currentRotation * Vector3.forward * distance;
// Set the height of the camera
transform.position.y = currentHeight;
// Always look at the target
transform.LookAt (target);
}
OK ! 下面我們給主角模型新增角色控制器元件,請先把自帶的控制攝像機與鏡頭的控制指令碼刪除。如下圖所示主角物件身上掛著Character Controller(角色控制器元件)即可,Controller是我們自己寫的指令碼,用來控制主角移動。
下面看一下Controller.cs完整的指令碼,指令碼中我們將主角共分成三個狀態:站立狀態、行走狀態、奔跑狀態。預設情況下主角處於站立狀態,當滑鼠選擇一個目標時,主角將進入行走狀態面朝目標方向行走。當連續按下滑鼠左鍵時主角將進入奔跑狀態朝向目標方向奔跑。
using UnityEngine;
using System.Collections;
public class Controller : MonoBehaviour
{
//人物的三個狀態 站立、行走、奔跑
private const int HERO_IDLE = 0;
private const int HERO_WALK = 1;
private const int HERO_RUN = 2;
//記錄當前人物的狀態
private int gameState = 0;
//記錄滑鼠點選的3D座標點
private Vector3 point;
private float time;
void Start ()
{
//初始設定人物為站立狀態
SetGameState(HERO_IDLE);
}
void Update ()
{
//按下滑鼠左鍵後
if(Input.GetMouseButtonDown(0))
{
//從攝像機的原點向滑鼠點選的物件身上設法一條射線
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
//當射線彭轉到物件時
if (Physics.Raycast(ray, out hit))
{
//目前場景中只有地形
//其實應當在判斷一下當前射線碰撞到的物件是否為地形。
//得到在3D世界中點選的座標
point = hit.point;
//設定主角面朝這個點,主角的X 與 Z軸不應當發生旋轉,
//註解1
transform.LookAt(new Vector3(point.x,transform.position.y,point.z));
//使用者是否連續點選按鈕
if(Time.realtimeSinceStartup - time <=0.2f)
{
//連續點選 進入奔跑狀態
SetGameState(HERO_RUN);
}else
{
//點選一次只進入走路狀態
SetGameState(HERO_WALK);
}
//記錄本地點選滑鼠的時間
time = Time.realtimeSinceStartup;
}
}
}
void FixedUpdate()
{
switch(gameState)
{
case HERO_IDLE:
break;
case HERO_WALK:
//移動主角 一次移動長度為0.05
Move(0.05f);
break;
case HERO_RUN:
//奔跑時移動的長度為0.1
Move(0.1f);
break;
}
}
void SetGameState(int state)
{
switch(state)
{
case HERO_IDLE:
//播放站立動畫
point = transform.position;
animation.Play("idle");
break;
case HERO_WALK:
//播放行走動畫
animation.Play("walk");
break;
case HERO_RUN:
//播放奔跑動畫
animation.Play("run");
break;
}
gameState = state;
}
void Move(float speed)
{
//註解2
//主角沒到達目標點時,一直向該點移動
if(Mathf.Abs(Vector3.Distance(point, transform.position))>=1.3f)
{
//得到角色控制器元件
CharacterController controller = GetComponent<CharacterController>();
//註解3 限制移動
Vector3 v = Vector3.ClampMagnitude(point - transform.position,speed);
//可以理解為主角行走或奔跑了一步
controller.Move(v);
}else
{
//到達目標時 繼續保持站立狀態。
SetGameState(HERO_IDLE);
}
}
}
註解1:transform.LookAt()這個方法是設定主角物件的面朝方向,這裡設定的方向是滑鼠選擇的目標點在遊戲世界中點中的3D座標。為了避免主角X與Z軸發生旋轉(特殊情況)所以我們設定朝向的Y軸永遠是主角自身的Y軸。
註解2:在這裡判斷主角當前位置是否到達目標位置,然後取得兩點座標差的絕對值。未到達目的繼續向前行走或奔跑,達到目的主角進入站立狀態等待下一次移動。
註解3:在選中目標點後主角並不是直接移動過去,應當是經過一段行走或奔跑的時間才移動過去。所以我們需要得知主角行走或奔跑下一步的座標,那麼通過Vertor3.ClampMagnitude()方法即可取得。引數1為兩個座標點之間的距離差,引數2表示行走或奔跑一步的距離,最後通過角色控制器元件提供的Move方法來移動主角。