本系列文章是根據(jù)官方視頻教程而寫下的學(xué)習(xí)筆記,原官方視頻教程網(wǎng)址:https://unity3d.com/cn/learn/tutorials/s/tanks-tutorial
系列其他筆記傳送門
Unity官方教程《Tanks》學(xué)習(xí)筆記(一)
Unity官方教程《Tanks》學(xué)習(xí)筆記(三)
Unity官方教程《Tanks》學(xué)習(xí)筆記(四)
Unity官方教程《Tanks》學(xué)習(xí)筆記(五)
一、創(chuàng)建坦克以及控制坦克
首先凉敲,在Models文件夾內(nèi)找到Tank這個model票堵,把它拖拽到Hierarchy內(nèi)霸旗,我們在Tank的inspector視圖中颂跨,對其層級進行修改似炎,選擇Players,并僅對當(dāng)前對象修改掌呜。如下圖所示:
接著滓玖,我們選中Hierarchy中的Tank坪哄,為其添加若干個Component质蕉,分別是:Rigidbody、Box Collider翩肌、Audio Source模暗、Audio Source,并對這些部件進行設(shè)置如下:
然后念祭,我們把配置好的Tank從Hierarchy拖拽到Prefabs文件夾下担映,讓它成為一個預(yù)制件蛙紫,這樣以后我們可以重復(fù)利用該Tank秀鞭,而不用每次都重新配置兵琳。然后保存當(dāng)前場景。
因為整個游戲場景是在沙漠中的搜贤,所以坦克的行駛會有沙塵滾滾的效果,所以我們需要添加這一效果。在Prefabs文件夾內(nèi)濒旦,把DustTrail預(yù)制件拖拽到Hierarchy下的Tank內(nèi),讓其成為Tank的子對象再登,然后復(fù)制粘貼DustTrail尔邓,并分別重命名為LeftDustTrail和RightDustTrail,根據(jù)下面的官方教程锉矢,把兩個DustTrail的position進行調(diào)節(jié):
設(shè)置完畢后梯嗽,接下來就是對Tank的移動腳本進行設(shè)置。在/Assets/Scripts/Tank文件夾內(nèi)沽损,找到TankMoveMent.cs文件灯节,并把它拖拽到Hierarchy下的Tank內(nèi)。我們打開并編輯該腳本绵估,把里面的注釋符號去掉显晶,并添加邏輯如下:
using UnityEngine;
public class TankMovement : MonoBehaviour
{
public int m_PlayerNumber = 1; //游戲者的序號
public float m_Speed = 12f; //坦克移動速度
public float m_TurnSpeed = 180f; //坦克轉(zhuǎn)向的角速度
public AudioSource m_MovementAudio;
public AudioClip m_EngineIdling; //靜止的音效
public AudioClip m_EngineDriving; //移動的音效
public float m_PitchRange = 0.2f;
private string m_MovementAxisName;
private string m_TurnAxisName;
private Rigidbody m_Rigidbody;
private float m_MovementInputValue;
private float m_TurnInputValue;
private float m_OriginalPitch;
/**
* Scene加載的時候調(diào)用
*/
private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody>();
}
/**
* 在Awake()之后,Update()之前調(diào)用
*/
private void OnEnable ()
{
m_Rigidbody.isKinematic = false;
m_MovementInputValue = 0f;
m_TurnInputValue = 0f;
}
private void OnDisable ()
{
m_Rigidbody.isKinematic = true;
}
private void Start()
{
m_MovementAxisName = "Vertical" + m_PlayerNumber;
m_TurnAxisName = "Horizontal" + m_PlayerNumber;
m_OriginalPitch = m_MovementAudio.pitch;
}
private void Update()
{
// Store the player's input and make sure the audio for the engine is playing.
m_MovementInputValue = Input.GetAxis(m_MovementAxisName);
m_TurnInputValue = Input.GetAxis (m_TurnAxisName);
EngineAudio ();
}
private void EngineAudio()
{
// Play the correct audio clip based on whether or not the tank is moving and what audio is currently playing.
// 如果坦克處于靜止?fàn)顟B(tài)
if (Mathf.Abs (m_MovementInputValue) < 0.1f && Mathf.Abs (m_TurnInputValue) < 0.1f)
{
//如果坦克播放的是行駛狀態(tài)的音效壹士,則替換
if (m_MovementAudio.clip == m_EngineDriving)
{
// ... change the clip to idling and play it.
m_MovementAudio.clip = m_EngineIdling;
m_MovementAudio.pitch = Random.Range (m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play ();
}
}
else
{
// 如果坦克播放的是靜止?fàn)顟B(tài)的音效磷雇,則替換
if (m_MovementAudio.clip == m_EngineIdling)
{
// ... change the clip to driving and play.
m_MovementAudio.clip = m_EngineDriving;
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play();
}
}
}
/**
* 以固定的時間間隔調(diào)用,用于物理上的步驟躏救,比如行走唯笙、轉(zhuǎn)向
*/
private void FixedUpdate()
{
// Move and turn the tank.
Move ();
Turn ();
}
private void Move()
{
// Adjust the position of the tank based on the player's input.
Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;
m_Rigidbody.MovePosition (m_Rigidbody.position + movement);
}
private void Turn()
{
// Adjust the rotation of the tank based on the player's input.
float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
Quaternion turnRotation = Quaternion.Euler (0f,turn,0f);
m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation);
}
}
修改完畢并保存文件,下一步我們需要初始化在腳本中聲明的幾個公有變量:Movement Audio盒使、Engine Idling崩掘、Engine Driving:
二、控制攝像機
首先在Hierarchy的根目錄下創(chuàng)建一個空的GameObject少办,并重命名為“CameraRig”苞慢,修改其部分Transform數(shù)據(jù)如下:
接著,我們把Main Camera拖拽到CameraRig內(nèi)英妓,成為它的子對象挽放,并修改Main Camera的Transform數(shù)據(jù)如下:
接下來我們需要補充一些關(guān)于攝像機的知識:
1、perspective視圖和orthographic視圖
要想了解如何控制攝像機蔓纠,我們要首先知道攝像機的兩種不同視圖形式辑畦,上一章也有所提及:透視視圖和正交視圖,下面就用官方教程的一幅圖來直觀地解釋:
2纯出、正交攝像機的尺寸(Size)
調(diào)節(jié)Main Camera的size參數(shù),如果size變小,那么可視范圍變小且物體變大暂筝,有放大作用箩言。而size變大,可視范圍變大且物體變小焕襟,有縮小作用分扎。
3、攝像機的長寬比(aspect)
那么接下來胧洒,我們的攝像機應(yīng)該做些什么畏吓?
1、跟隨坦克卫漫。
找出兩輛坦克位置的中心點菲饼,把CameraRig移到該點。
2列赎、調(diào)整攝像機的尺寸以適應(yīng)坦克在屏幕上的位置宏悦。
從上面補充的知識可以知道,正交攝像機的視圖的長為Size包吝,寬為Size * aspect饼煞。接著,在正交攝像機視角看來诗越,坦克的運動可以分解為x軸和y軸的運動砖瞧,這時,我們需要把坦克的坐標(biāo)切換成攝像機視角的本地坐標(biāo)嚷狞。
從上面兩幅圖我們可以知道块促,size的選擇有兩種情況,分別是沿y軸方向(size1 = y)床未;以及沿x軸方向竭翠,而x軸方向需要做一步計算,即size2 = x / aspect薇搁。接著比較這兩個size的大小斋扰,用大的size值決定攝像機的縮放。當(dāng)然了啃洋,這也需要考慮到另外一個tank的不同size值传货,總之,取最大的size值作為攝像機的縮放范圍裂允。
我們來看一下腳本是如何對攝像機進行控制的损离,打開/Assets/Scripts/Camera文件夾哥艇,選中CameraControl绝编,把它拖拽到CameraRig中,而不是Main Camera。
using UnityEngine;
public class CameraControl : MonoBehaviour
{
public float m_DampTime = 0.2f; //移動Camera到目的position的時間
public float m_ScreenEdgeBuffer = 4f; //確保Tanks不會在屏幕邊界之外
public float m_MinSize = 6.5f; //Camera的最小尺寸
/*[HideInInspector]*/ public Transform[] m_Targets; //坦克十饥,先把[HideInInspector]注釋掉
private Camera m_Camera;
private float m_ZoomSpeed;
private Vector3 m_MoveVelocity;
private Vector3 m_DesiredPosition; //需要移動到的位置
private void Awake()
{
m_Camera = GetComponentInChildren<Camera>();
}
private void FixedUpdate()
{
Move();
Zoom();
}
private void Move()
{
FindAveragePosition();
/**
* function Vector3.SmoothDamp(Vector3 current,Vector3 target
* ,ref Vector3 currentVelocity,float smoothTime)
* @parameters
* current:當(dāng)前的位置
* target:試圖接近的位置
* currentVelocity:當(dāng)前速度窟勃,這個值由你每次調(diào)用這個函數(shù)時修改
* smoothTime:到達目標(biāo)的大約時間,較小的值將快速到達目標(biāo)
*/
transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
}
private void FindAveragePosition()
{
Vector3 averagePos = new Vector3();
int numTargets = 0;
for (int i = 0; i < m_Targets.Length; i++)
{
//判斷當(dāng)前坦克是否已經(jīng)不是激活狀態(tài)(死亡)逗堵,如果未激活秉氧,
//則不需要跟隨該坦克
if (!m_Targets[i].gameObject.activeSelf)
continue;
averagePos += m_Targets[i].position;
numTargets++;
}
if (numTargets > 0)
averagePos /= numTargets;
//CameraRig的Y position不會被改變
averagePos.y = transform.position.y;
m_DesiredPosition = averagePos;
}
private void Zoom()
{
//根據(jù)目標(biāo)位置來計算合適的Size
float requiredSize = FindRequiredSize();
m_Camera.orthographicSize = Mathf.SmoothDamp(m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime);
}
private float FindRequiredSize()
{
//把目標(biāo)位置的世界坐標(biāo)轉(zhuǎn)換成本地坐標(biāo)
Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);
float size = 0f;
for (int i = 0; i < m_Targets.Length; i++)
{
if (!m_Targets[i].gameObject.activeSelf)
continue;
//把坦克所在的位置轉(zhuǎn)換成CameraRig的本地坐標(biāo)
Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);
//在CameraRig的本地坐標(biāo)下,求出坦克與CameraRig的目標(biāo)位置的距離
Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;
size = Mathf.Max (size, Mathf.Abs (desiredPosToTarget.y));
size = Mathf.Max (size, Mathf.Abs (desiredPosToTarget.x) / m_Camera.aspect);
}
//加上ScreenEdgeBuffer值蜒秤,即坦克與屏幕邊界的距離
size += m_ScreenEdgeBuffer;
size = Mathf.Max(size, m_MinSize);
return size;
}
public void SetStartPositionAndSize()
{
FindAveragePosition();
transform.position = m_DesiredPosition;
m_Camera.orthographicSize = FindRequiredSize();
}
}
然后汁咏,我們返回Unity,把Tank拖拽到如下位置:
最后作媚,保存場景并運行攘滩。