Unity官方教程《Tanks》学习笔记(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a553181867/article/details/79299703

本系列文章是根据官方视频教程而写下的学习笔记,原官方视频教程网址:https://unity3d.com/cn/learn/tutorials/s/tanks-tutorial

系列其他笔记传送门
Unity官方教程《Tanks》学习笔记(一)
Unity官方教程《Tanks》学习笔记(三)
Unity官方教程《Tanks》学习笔记(四)
Unity官方教程《Tanks》学习笔记(五)

一、创建坦克以及控制坦克

首先,在Models文件夹内找到Tank这个model,把它拖拽到Hierarchy内,我们在Tank的inspector视图中,对其层级进行修改,选择Players,并仅对当前对象修改。如下图所示:
image.png
image.png

接着,我们选中Hierarchy中的Tank,为其添加若干个Component,分别是:Rigidbody、Box Collider、Audio Source、Audio Source,并对这些部件进行设置如下:

Tank设置

然后,我们把配置好的Tank从Hierarchy拖拽到Prefabs文件夹下,让它成为一个预制件,这样以后我们可以重复利用该Tank,而不用每次都重新配置。然后保存当前场景。

因为整个游戏场景是在沙漠中的,所以坦克的行驶会有沙尘滚滚的效果,所以我们需要添加这一效果。在Prefabs文件夹内,把DustTrail预制件拖拽到Hierarchy下的Tank内,让其成为Tank的子对象,然后复制粘贴DustTrail,并分别重命名为LeftDustTrail和RightDustTrail,根据下面的官方教程,把两个DustTrail的position进行调节:
DustTrail
DustTrail设置

设置完毕后,接下来就是对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数据如下:
CameraRig
接着,我们把Main Camera拖拽到CameraRig内,成为它的子对象,并修改Main Camera的Transform数据如下:
Main Camera
接下来我们需要补充一些关于摄像机的知识:

1、perspective视图和orthographic视图
要想了解如何控制摄像机,我们要首先知道摄像机的两种不同视图形式,上一章也有所提及:透视视图和正交视图,下面就用官方教程的一幅图来直观地解释:
左侧为透视视图,右侧为正交视图

扫描二维码关注公众号,回复: 3297268 查看本文章

2、正交摄像机的尺寸(Size)
正交摄像机的尺寸
调节Main Camera的size参数,如果size变小,那么可视范围变小且物体变大,有放大作用。而size变大,可视范围变大且物体变小,有缩小作用。

3、摄像机的长宽比(aspect)
aspect

那么接下来,我们的摄像机应该做些什么?
1、跟随坦克。
Follow the tanks
找出两辆坦克位置的中心点,把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拖拽到如下位置:
拖拽Tank
最后,保存场景并运行。

猜你喜欢

转载自blog.csdn.net/a553181867/article/details/79299703