本系列文章是根据官方视频教程而写下的学习笔记,原官方视频教程网址:https://unity3d.com/cn/learn/tutorials/s/tanks-tutorial
系列其他笔记传送门
Unity官方教程《Tanks》学习笔记(一)
Unity官方教程《Tanks》学习笔记(三)
Unity官方教程《Tanks》学习笔记(四)
Unity官方教程《Tanks》学习笔记(五)
一、创建坦克以及控制坦克
首先,在Models文件夹内找到Tank这个model,把它拖拽到Hierarchy内,我们在Tank的inspector视图中,对其层级进行修改,选择Players,并仅对当前对象修改。如下图所示:
接着,我们选中Hierarchy中的Tank,为其添加若干个Component,分别是:Rigidbody、Box Collider、Audio Source、Audio Source,并对这些部件进行设置如下:
然后,我们把配置好的Tank从Hierarchy拖拽到Prefabs文件夹下,让它成为一个预制件,这样以后我们可以重复利用该Tank,而不用每次都重新配置。然后保存当前场景。
因为整个游戏场景是在沙漠中的,所以坦克的行驶会有沙尘滚滚的效果,所以我们需要添加这一效果。在Prefabs文件夹内,把DustTrail预制件拖拽到Hierarchy下的Tank内,让其成为Tank的子对象,然后复制粘贴DustTrail,并分别重命名为LeftDustTrail和RightDustTrail,根据下面的官方教程,把两个DustTrail的position进行调节:
设置完毕后,接下来就是对Tank的移动脚本进行设置。在/Assets/Scripts/Tank文件夹内,找到TankMoveMent.cs文件,并把它拖拽到Hierarchy下的Tank内。我们打开并编辑该脚本,把里面的注释符号去掉,并添加逻辑如下:
using UnityEngine;
public class TankMovement : MonoBehaviour
{
public int m_PlayerNumber = 1; //游戏者的序号
public float m_Speed = 12f; //坦克移动速度
public float m_TurnSpeed = 180f; //坦克转向的角速度
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加载的时候调用
*/
private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody>();
}
/**
* 在Awake()之后,Update()之前调用
*/
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.
// 如果坦克处于静止状态
if (Mathf.Abs (m_MovementInputValue) < 0.1f && Mathf.Abs (m_TurnInputValue) < 0.1f)
{
//如果坦克播放的是行驶状态的音效,则替换
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
{
// 如果坦克播放的是静止状态的音效,则替换
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();
}
}
}
/**
* 以固定的时间间隔调用,用于物理上的步骤,比如行走、转向
*/
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的根目录下创建一个空的GameObject,并重命名为“CameraRig”,修改其部分Transform数据如下:
接着,我们把Main Camera拖拽到CameraRig内,成为它的子对象,并修改Main Camera的Transform数据如下:
接下来我们需要补充一些关于摄像机的知识:
1、perspective视图和orthographic视图
要想了解如何控制摄像机,我们要首先知道摄像机的两种不同视图形式,上一章也有所提及:透视视图和正交视图,下面就用官方教程的一幅图来直观地解释:
2、正交摄像机的尺寸(Size)
调节Main Camera的size参数,如果size变小,那么可视范围变小且物体变大,有放大作用。而size变大,可视范围变大且物体变小,有缩小作用。
3、摄像机的长宽比(aspect)
那么接下来,我们的摄像机应该做些什么?
1、跟随坦克。
找出两辆坦克位置的中心点,把CameraRig移到该点。
2、调整摄像机的尺寸以适应坦克在屏幕上的位置。
从上面补充的知识可以知道,正交摄像机的视图的长为Size,宽为Size * aspect。接着,在正交摄像机视角看来,坦克的运动可以分解为x轴和y轴的运动,这时,我们需要把坦克的坐标切换成摄像机视角的本地坐标。
从上面两幅图我们可以知道,size的选择有两种情况,分别是沿y轴方向(size1 = y);以及沿x轴方向,而x轴方向需要做一步计算,即size2 = x / aspect。接着比较这两个size的大小,用大的size值决定摄像机的缩放。当然了,这也需要考虑到另外一个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:当前的位置
* target:试图接近的位置
* currentVelocity:当前速度,这个值由你每次调用这个函数时修改
* smoothTime:到达目标的大约时间,较小的值将快速到达目标
*/
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++)
{
//判断当前坦克是否已经不是激活状态(死亡),如果未激活,
//则不需要跟随该坦克
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()
{
//根据目标位置来计算合适的Size
float requiredSize = FindRequiredSize();
m_Camera.orthographicSize = Mathf.SmoothDamp(m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime);
}
private float FindRequiredSize()
{
//把目标位置的世界坐标转换成本地坐标
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;
//把坦克所在的位置转换成CameraRig的本地坐标
Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);
//在CameraRig的本地坐标下,求出坦克与CameraRig的目标位置的距离
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拖拽到如下位置:
最后,保存场景并运行。