【Unity】【0-1】【单机小坦克】

场景搭建

开始摆摆摆

摆个大概

Shit!为啥没有先加碰撞再CoCoCopy(手动微笑)

 

Window——Rendering——Lighting Setting 搞个氛围

到位!

GameManager

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

namespace Complete
{
    public class GameManager : MonoBehaviour
    {
        public int m_NumRoundsToWin = 5;            // 赢得x回合即胜利
        public float m_StartDelay = 3f;             // 回合开始前等待x秒
        public float m_EndDelay = 3f;               // 回合结束后等待x秒
        public CameraControl m_CameraControl;       // 获取不同阶段的相机操控
        public Text m_MessageText;                  // UI
        public GameObject m_TankPrefab;             // 坦克预制体
        public TankManager[] m_Tanks;               // 创建坦克玩家
  
        private int m_RoundNumber;                  // 当前回合
        private WaitForSeconds m_StartWait;         // 回合开始前等待x秒
        private WaitForSeconds m_EndWait;           // 回合结束后等待x秒
        private TankManager m_RoundWinner;          // 当前回合胜者
        private TankManager m_GameWinner;           // 总回合胜者


        private void Start()
        {
            m_StartWait = new WaitForSeconds (m_StartDelay);
            m_EndWait = new WaitForSeconds (m_EndDelay);

            //生成坦克
            SpawnAllTanks();
            //将坦克集合的变换数据传递给观察相机
            SetCameraTargets();
            //开始游戏
            StartCoroutine (GameLoop ());
        }


        private void SpawnAllTanks()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].m_Instance =
                    Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;
                m_Tanks[i].m_PlayerNumber = i + 1;
                m_Tanks[i].Setup();
            }
        }


        private void SetCameraTargets()
        {
            Transform[] targets = new Transform[m_Tanks.Length];
            for (int i = 0; i < targets.Length; i++)
            {
                targets[i] = m_Tanks[i].m_Instance.transform;
            }
            m_CameraControl.m_Targets = targets;
        }

        private IEnumerator GameLoop ()
        {
            yield return StartCoroutine (RoundStarting ());
            yield return StartCoroutine (RoundPlaying());
            yield return StartCoroutine (RoundEnding());

            if (m_GameWinner != null)
            {
                //游戏结束
                SceneManager.LoadScene (0);
            }
            else
            {
                StartCoroutine (GameLoop ());
            }
        }

        private IEnumerator RoundStarting ()
        {
            //重置坦克和观察相机
            ResetAllTanks ();
            DisableTankControl ();
            m_CameraControl.SetStartPositionAndSize ();

            //叠加回合数
            m_RoundNumber++;
            m_MessageText.text = "ROUND " + m_RoundNumber;

            yield return m_StartWait;
        }


        private IEnumerator RoundPlaying ()
        {
            EnableTankControl ();
            m_MessageText.text = string.Empty;

            while (!OneTankLeft())
            {
                yield return null;
            }
        }


        private IEnumerator RoundEnding ()
        {
            DisableTankControl ();
            m_RoundWinner = null;

            //获取回合胜者
            m_RoundWinner = GetRoundWinner ();

            //胜者加分
            if (m_RoundWinner != null)
                m_RoundWinner.m_Wins++;

            //获取游戏胜者
            m_GameWinner = GetGameWinner ();

            //编辑游戏胜者告示
            string message = EndMessage ();
            m_MessageText.text = message;

            yield return m_EndWait;
        }


        //是否决出胜负
        private bool OneTankLeft()
        {
            //统计幸存者
            int numTanksLeft = 0;
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                if (m_Tanks[i].m_Instance.activeSelf)
                    numTanksLeft++;
            }

            return numTanksLeft <= 1;
        }
        
        
        private TankManager GetRoundWinner()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                if (m_Tanks[i].m_Instance.activeSelf)
                    return m_Tanks[i];
            }

            return null;
        }


        private TankManager GetGameWinner()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                if (m_Tanks[i].m_Wins == m_NumRoundsToWin)
                    return m_Tanks[i];
            }

            return null;
        }


        private string EndMessage()
        {
            string message = "DRAW!";

            if (m_RoundWinner != null)
                message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";

            message += "\n\n\n\n";

            for (int i = 0; i < m_Tanks.Length; i++)
            {
                message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";
            }

            if (m_GameWinner != null)
                message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!";

            return message;
        }


        private void ResetAllTanks()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].Reset();
            }
        }


        private void EnableTankControl()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].EnableControl();
            }
        }


        private void DisableTankControl()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].DisableControl();
            }
        }
    }
}

CameraControl

using UnityEngine;

namespace Complete
{
    public class CameraControl : MonoBehaviour
    {
        public float m_DampTime = 0.2f;                 // 每x秒调整对焦
        public float m_ScreenEdgeBuffer = 4f;           // 被观察对象与屏幕边缘预留空隙
        public float m_MinSize = 6.5f;                  // 最小正交尺寸
        [HideInInspector] public Transform[] m_Targets; // 被观察对象


        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 ();

            // 平滑移动到目标位置
            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;

            averagePos.y = transform.position.y;
            m_DesiredPosition = averagePos;
        }


        private void Zoom ()
        {
            // 当前大小平滑缩放到目标大小
            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);

            // Start the camera's size calculation at zero.
            float size = 0f;

            for (int i = 0; i < m_Targets.Length; i++)
            {
                if (!m_Targets[i].gameObject.activeSelf)
                    continue;

                Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);

                Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;

                size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y));
                size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / m_Camera.aspect);
            }

            // 增加边缘空隙
            size += m_ScreenEdgeBuffer;

            // 确保不小于最小尺寸
            size = Mathf.Max (size, m_MinSize);

            return size;
        }


        public void SetStartPositionAndSize ()
        {
            FindAveragePosition ();
            transform.position = m_DesiredPosition;
            m_Camera.orthographicSize = FindRequiredSize ();
        }
    }
}

敲黑板-知识点 transform.InverseTransformPoint

transform.localToWorldMatrix本地坐标转世界坐标的矩阵信息
transform.worldToLocalMatrix世界坐标转本地坐标的矩阵信息
transform.TransformDirection将向量从本地坐标转世界坐标,不受缩放影响
transform.InverseTransformDirection将向量从世界坐标转本地坐标,不受缩放影响
transform.TransformPoint将点从本地坐标转世界坐标,受缩放影响
transform.InverseTransformPoint将点从世界坐标转本地坐标,受缩放影响
transform.TransformVector本地坐标转世界坐标,受缩放影响,不受位置影响
transform.InverseTransformVector世界坐标转本地坐标,受缩放影响,不受位置影响

TankManager

using System;
using UnityEngine;

namespace Complete
{
    [Serializable]
    public class TankManager
    {
        public Color m_PlayerColor;                             // 皮肤颜色
        public Transform m_SpawnPoint;                          // 出生点
        [HideInInspector] public int m_PlayerNumber;            // 玩家ID
        [HideInInspector] public string m_ColoredPlayerText;    // 颜色信息
        [HideInInspector] public GameObject m_Instance;         // 不解释
        [HideInInspector] public int m_Wins;                    // 胜局统计
        

        private TankMovement m_Movement;                        // 坦克操控系统
        private TankShooting m_Shooting;                        // 坦克射击系统
        private GameObject m_CanvasGameObject;                  // 血条UI


        public void Setup ()
        {
            m_Movement = m_Instance.GetComponent<TankMovement> ();
            m_Shooting = m_Instance.GetComponent<TankShooting> ();
            m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas> ().gameObject;

            // 统一ID信息
            m_Movement.m_PlayerNumber = m_PlayerNumber;
            m_Shooting.m_PlayerNumber = m_PlayerNumber;

            // 编辑颜色信息
            m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>";

            // 坦克染色
            MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer> ();
            for (int i = 0; i < renderers.Length; i++)
            {
                renderers[i].material.color = m_PlayerColor;
            }
        }


        public void DisableControl ()
        {
            m_Movement.enabled = false;
            m_Shooting.enabled = false;

            m_CanvasGameObject.SetActive (false);
        }


        public void EnableControl ()
        {
            m_Movement.enabled = true;
            m_Shooting.enabled = true;

            m_CanvasGameObject.SetActive (true);
        }


        public void Reset ()
        {
            m_Instance.transform.position = m_SpawnPoint.position;
            m_Instance.transform.rotation = m_SpawnPoint.rotation;

            m_Instance.SetActive (false);
            m_Instance.SetActive (true);
        }
    }
}

敲黑板-知识点 [Serializable][HideInInspector]

当我们在Inspectorsan上操作数据时,Unity便将这些数据序列化为文件。游戏运行时,Unity会反序列化文件,获取数据赋给对象。继承MonoBehaviour的对象在instantiate()时,同样会触发序列化和反序列化。

 

[Serializable]:用于自定义的非抽象类/结构体,使其支持序列化;

[HideInInspector]:在Inspector面板中隐藏公有成员

 

有关Unity序列化内容请先参考https://www.cnblogs.com/devhyj/p/4342592.html,待我日后深究更博~

TankMovement

using UnityEngine;

namespace Complete
{
    public class TankMovement : MonoBehaviour
    {
        public int m_PlayerNumber = 1;              // ID
        public float m_Speed = 12f;                 // 移动速度
        public float m_TurnSpeed = 180f;            // 旋转速度
        public AudioSource m_MovementAudio;         // 音效素材
        public AudioClip m_EngineIdling;            // Idle音效
        public AudioClip m_EngineDriving;           // Move音效
	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;              // 初始音调
        private ParticleSystem[] m_particleSystems; // 粒子系统


        private void Awake ()
        {
            m_Rigidbody = GetComponent<Rigidbody> ();
        }


        private void OnEnable ()
        {
            m_Rigidbody.isKinematic = false;
            m_MovementInputValue = 0f;
            m_TurnInputValue = 0f;

            // 开启粒子系统
            m_particleSystems = GetComponentsInChildren<ParticleSystem>();
            for (int i = 0; i < m_particleSystems.Length; ++i)
            {
                m_particleSystems[i].Play();
            }
        }


        private void OnDisable ()
        {
            m_Rigidbody.isKinematic = true;

            // 关闭粒子系统
            for(int i = 0; i < m_particleSystems.Length; ++i)
            {
                m_particleSystems[i].Stop();
            }
        }


        private void Start ()
        {
            // 编辑控制轴名称
            m_MovementAxisName = "Vertical" + m_PlayerNumber;
            m_TurnAxisName = "Horizontal" + m_PlayerNumber;

            // 存储初始音调
            m_OriginalPitch = m_MovementAudio.pitch;
        }


        private void Update ()
        {
            // 读取控制轴数据
            m_MovementInputValue = Input.GetAxis (m_MovementAxisName);
            m_TurnInputValue = Input.GetAxis (m_TurnAxisName);
            
            // 播放音效    
            EngineAudio ();
        }


        private void EngineAudio ()
        {
            if (Mathf.Abs (m_MovementInputValue) < 0.1f && Mathf.Abs (m_TurnInputValue) < 0.1f)
            {
                if (m_MovementAudio.clip == m_EngineDriving)
                {
                    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)
                {
                    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 ();
            Turn ();
        }


        private void Move ()
        {
            // 计算位移
            Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;
            m_Rigidbody.MovePosition(m_Rigidbody.position + movement);
        }


        private void Turn ()
        {
            // 计算转角
            float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
            Quaternion turnRotation = Quaternion.Euler (0f, turn, 0f);
            m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation);
        }
    }
}

TankShooting

using UnityEngine;
using UnityEngine.UI;

namespace Complete
{
    public class TankShooting : MonoBehaviour
    {
        public int m_PlayerNumber = 1;              // ID
        public Rigidbody m_Shell;                   // 弹药
        public Transform m_FireTransform;           // 弹药生成点
        public Slider m_AimSlider;                  // 瞄准UI
        public AudioSource m_ShootingAudio;         // 射击音效素材
        public AudioClip m_ChargingClip;            // 蓄力音效
        public AudioClip m_FireClip;                // 开火音效
        public float m_MinLaunchForce = 15f;        // 最小发射力度
        public float m_MaxLaunchForce = 30f;        // 最大发射力度
        public float m_MaxChargeTime = 0.75f;       // 最长蓄力时间


        private string m_FireButton;                // 开火键
        private float m_CurrentLaunchForce;         // 发射力度
        private float m_ChargeSpeed;                // 蓄力速度
        private bool m_Fired;                       // 开火状态


        private void OnEnable()
        {
            m_CurrentLaunchForce = m_MinLaunchForce;
            m_AimSlider.value = m_MinLaunchForce;
        }


        private void Start ()
        {
            // 编辑开火键名称
            m_FireButton = "Fire" + m_PlayerNumber;

            // 计算蓄力速度
            m_ChargeSpeed = (m_MaxLaunchForce - m_MinLaunchForce) / m_MaxChargeTime;
        }


        private void Update ()
        {
            // 蓄力状态可视化
            m_AimSlider.value = m_MinLaunchForce;

            // 蓄满力自动开火
            if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired)
            {
                m_CurrentLaunchForce = m_MaxLaunchForce;
                Fire ();
            }
            // 按下开火键
            else if (Input.GetButtonDown (m_FireButton))
            {
                m_Fired = false;
                m_CurrentLaunchForce = m_MinLaunchForce;
                m_ShootingAudio.clip = m_ChargingClip;
                m_ShootingAudio.Play ();
            }
            // 蓄力中
            else if (Input.GetButton (m_FireButton) && !m_Fired)
            {
                m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;
                m_AimSlider.value = m_CurrentLaunchForce;
            }
            // 松开开火
            else if (Input.GetButtonUp (m_FireButton) && !m_Fired)
            {
                Fire ();
            }
        }


        private void Fire ()
        {
            m_Fired = true;

            // 生成弹药
            Rigidbody shellInstance =
                Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) as Rigidbody;

            // 发射弹药
            shellInstance.velocity = m_CurrentLaunchForce * m_FireTransform.forward; 

            // 射击音效
            m_ShootingAudio.clip = m_FireClip;
            m_ShootingAudio.Play ();

            // 重置蓄力状态
            m_CurrentLaunchForce = m_MinLaunchForce;
        }
    }
}

TankHealth

using UnityEngine;
using UnityEngine.UI;

namespace Complete
{
    public class TankHealth : MonoBehaviour
    {
        public float m_StartingHealth = 100f;               // 初始血量
        public Slider m_Slider;                             // 血条UI
        public Image m_FillImage;                           // 血条样式
        public Color m_FullHealthColor = Color.green;       // 满血颜色
        public Color m_ZeroHealthColor = Color.red;         // 缺血颜色
        public GameObject m_ExplosionPrefab;                // 爆炸特效
        
        
        private AudioSource m_ExplosionAudio;               // 爆炸音效
        private ParticleSystem m_ExplosionParticles;        // 爆炸特效
        private float m_CurrentHealth;                      // 当前血量
        private bool m_Dead;                                // 是否死亡


        private void Awake ()
        {
            m_ExplosionParticles = Instantiate (m_ExplosionPrefab).GetComponent<ParticleSystem> ();
            m_ExplosionAudio = m_ExplosionParticles.GetComponent<AudioSource> ();
            m_ExplosionParticles.gameObject.SetActive (false);
        }


        private void OnEnable()
        {
            //信息重置
            m_CurrentHealth = m_StartingHealth;
            m_Dead = false;
            SetHealthUI();
        }


        public void TakeDamage (float amount)
        {
            // 掉血
            m_CurrentHealth -= amount;
            SetHealthUI ();

            // 判断血槽已空
            if (m_CurrentHealth <= 0f && !m_Dead)
            {
                OnDeath ();
            }
        }


        private void SetHealthUI ()
        {
            // 刷新血条UI
            m_Slider.value = m_CurrentHealth;

            // 根据血量插值求出血条颜色
            m_FillImage.color = Color.Lerp (m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);
        }


        private void OnDeath ()
        {
            m_Dead = true;

            // 死亡特效
            m_ExplosionParticles.transform.position = transform.position;
            m_ExplosionParticles.gameObject.SetActive (true);
            m_ExplosionParticles.Play ();
            m_ExplosionAudio.Play();

            gameObject.SetActive (false);
        }
    }
}

ShellExplosion

using UnityEngine;

public class ShellExplosion : MonoBehaviour
{
    public LayerMask m_TankMask;                            // 坦克掩码
    public ParticleSystem m_ExplosionParticles;             // 爆炸特效
    public AudioSource m_ExplosionAudio;                    // 爆炸音效
    public float m_MaxDamage = 100f;                        // 最大伤害
    public float m_ExplosionForce = 1000f;                  // 爆炸力
    public float m_MaxLifeTime = 2f;                        // 最大持续时间
    public float m_ExplosionRadius = 5f;                    // 伤害半径


    private void Start()
    {
        // 一段时间后销毁自身
        Destroy(gameObject, m_MaxLifeTime);
    }


    private void OnTriggerEnter(Collider other)
    {
        // 伤害半径内所有受击坦克
    }


    private float CalculateDamage(Vector3 targetPosition)
    {
        // 根据距离计算伤害
        return 0f;
    }
}

UIDirectionControl

using UnityEngine;

public class UIDirectionControl : MonoBehaviour
{
    public bool m_UseRelativeRotation = true;  


    private Quaternion m_RelativeRotation;     


    private void Start()
    {
        m_RelativeRotation = transform.parent.localRotation;
    }


    private void Update()
    {
        if (m_UseRelativeRotation)
            transform.rotation = m_RelativeRotation;
    }
}

完整项目:Asset Store —— Tanks! Tutorial

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值