拆解记录《双人坦克大战》代码

双人坦克大战是unity官方做的一个教程,但原版是英文,有B站up丑萌气质狗丑萌气质狗在其基础上,做成了一个比较基础的教程,我跟着他做完了这一套,受益匪浅。

我认为外行人(不是程序员)做游戏,要积攒一些代码,在将来自己做游戏的时候,才好搭建框架。下面是我将游戏的代码做一些拆解记录,方便将来取用。

坦克

移动代码

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Tankmove2 : MonoBehaviour//挂载在坦克上,随便起的文件名,应该起的再规整一点
{
    public int PlayerNamber = 1;//设置一个游戏玩家编号,方便后续用代码直接创建一个2P玩家
    Rigidbody rb = null;//创立一个空的刚体
    public float moveSpeed = 6;//设置行动速度为6
    public float angularSpeed = 4;//设置转向速度为4
    public AudioSource MovementAudio;//设置播放音源
    public AudioClip EngineIdling;//设置引擎停止声音片段
    public AudioClip EngineDriving;//设置引擎发动声音片段
    public float PitchRange = 0.1f;//设置音域波动范围
    private string MovementAxisName;
    private string TurnAxisName;



   
    void Start()//游戏开始时读取代码
    {
        MovementAxisName = "Horizontal" + PlayerNamber;
        TurnAxisName = "Vertical" + PlayerNamber;
        //此处2个拼接出的字符正好对应游戏设置中,设置好的按键方案名字

        rb = transform.GetComponent<Rigidbody>();
        //空刚体rb被替换成代码挂载物体的刚体
    }

   
    void Update()//游戏中每一帧运行代码
    {
        float horizontalInput = Input.GetAxis(MovementAxisName);
        float verticalInput = Input.GetAxis(TurnAxisName);
        //代码内临时变量读取input方法读取案件方案中案件对应的数值


        //播放声音
        
        if (Mathf.Abs(horizontalInput) <0.2f && Mathf.Abs(verticalInput) < 0.2f)
        //如果同时满足前后左右小于0.2绝对值(即坦克运行已经很慢),进入下面代码
        {
            if (MovementAudio.clip == EngineDriving)
            //如果播放音源的片段是引擎运行代码,进入下面代码
            {
                MovementAudio.clip = EngineIdling;
                //音源片段变成机器怠机
                MovementAudio.pitch = Random.Range(1 - PitchRange, 1 + PitchRange);
                //音源的音域在随机范围内变动,打破过于重复的游戏体验
                MovementAudio.Play();
                //播放音源
            }
        }
        else
        //不满足前后左右小于0.2绝对值,也就是坦克在高速移动过程中
        {
            rb.velocity = transform.forward * verticalInput * moveSpeed;
            //挂载代码物体刚体的前进速度=物体朝向前进向量*垂直输入(前进或后退)*速度参数

            rb.angularVelocity = new Vector3(0, 1, 0) * horizontalInput * angularSpeed;
             //创建了一个只在Y轴方向有分量的向量,这意味着角速度将只围绕Y轴旋转
             //挂载代码物体转向=物体Y轴旋转的量*水平输入(左或右)*速度参数

            if (MovementAudio.clip == EngineIdling)
             //高速运动中如果音源是机器怠速,进入下面代码
            {
                MovementAudio.clip = EngineDriving;//音源片变成机器运行
                MovementAudio.pitch = Random.Range(1 - PitchRange, 1 + PitchRange);
                //音源的音域在随机范围内变动,打破过于重复的游戏体验
                MovementAudio.Play();
                //播放音源
            }
        }
    }    
}

血量代码

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class TankHealth : MonoBehaviour//挂载在坦克上
{
    public float m_StartingHealth = 100f;  //坦克初始血量        
    public Slider m_Slider;    //滑动条底层                    
    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()//是比start更先一步运行,在对象实例化时调用,适用于初始化和查找引用等操作
    {
        //从爆炸预制体中,获得爆炸粒子特效
        m_ExplosionParticles = Instantiate(m_ExplosionPrefab).GetComponent<ParticleSystem>();
        //从爆炸预制体中,获得声音播放组件
        m_ExplosionAudio = m_ExplosionParticles.GetComponent<AudioSource>();
        //把爆炸粒子特效置为不可用
        m_ExplosionParticles.gameObject.SetActive(false);
        
    }


    private void OnEnable()
//用途
//初始化: 当游戏对象被激活时,OnEnable()可以用来重新初始化那些在OnDisable()中被清理的变量或状态。
//注册事件: 如果你的脚本需要监听某些事件,可以在OnEnable()中注册这些事件,以确保当游戏对象被激活时,它能够接收到事件通知。
//恢复状态: 如果游戏对象在被禁用时改变了状态,OnEnable()可以用来恢复其之前的状态。
//工作机制
//当游戏对象被激活时,Unity会自动调用该对象上所有脚本的OnEnable()方法。
//如果游戏对象在场景切换时被禁用,再次激活时也会调用OnEnable()。
    {      
        m_CurrentHealth = m_StartingHealth; //当前血量设置为初始血量
        SetHealthUI();//使用UI方法
        m_Dead = false; //坦克是否死亡 true死亡 flase生存
    }


    public void TakeDamage(float amount)
    {
        if (amount < 1)  //如果收到的伤害小于1,最低伤害为1,方便计算和显示血量
            amount = 1; 
        m_CurrentHealth -= amount;//当前生命=当前生命-受到伤害

        if (m_CurrentHealth <= 0f && !m_Dead)//如果当前生命小于等于0,并且没有死亡,运行下列代码
        {
           OnDeath();//坦克死亡
        }
        
        SetHealthUI();//使用UI 方法

    }


    private void SetHealthUI()//显示血量UI的方法
    {      
        m_Slider.value = m_CurrentHealth;//滑动条的值=当前生命
        m_FillImage.color = Color.Lerp(m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);
        //覆盖的顶层颜色=颜色的线性插值方法
        //第一个参数 (m_ZeroHealthColor): 当健康值为0时的颜色,通常表示为红色或黑色,表示角色没有生命值。
        //第二个参数(m_FullHealthColor): 当健康值为满值时的颜色,通常表示为绿色,表示角色生命值满。
        //第三个参数: 这是一个介于0和1之间的值,表示当前健康值与起始健康值的比例。


        //if(m_CurrentHealth > 40)
        //    m_FillImage.color = m_FullHealthColor;
        //else
        //    m_FillImage.color = m_ZeroHealthColor;
        //另一种生命显示方法,低血量改变颜色
    }


    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);//挂载代码的物体设置为不可用
        //此时播放特效,因为特效是代码加载到场景中,与坦克没有父子关系,因此不影响爆炸特效播放
    }
}

开炮代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Tankshotting : MonoBehaviour//挂载在坦克
{
    public int PlayerNamber = 1;//设置一个游戏玩家编号,方便后续用代码直接创建一个2P玩家
    public GameObject firetrasnform;//开火的位置,游戏中设置到炮口的空物体上
    public GameObject fireshell;//炮弹模型,游戏中设置预制体炮弹上
    public Slider AimSlider;//开炮力度滑动条
    public AudioSource Shootingaduio;//开炮音源
    public AudioClip Charging;//积蓄能量的片段
    public AudioClip Fireclip;//开炮的片段
    private string FireButton;//开火按钮

    public float MinLaunchForce = 10f;//最小开火力度
    public float MaxLaunchForce = 20f;//最大开火力度
    public float ChargeTime = 1f;//最大积蓄力量时间
    private float ChargeSpeed;//积蓄力量的速度
    private float CurrentLaunchForce;//当前力量
    private bool Areyoufire=false;//判断是否开炮,默认为非

    void Start()
    {
        FireButton = "Fire" + PlayerNamber;//利用文本+玩家编号,凑成一个开火的操作方案名
        ChargeSpeed = (MaxLaunchForce - MinLaunchForce) / ChargeTime;//根据已有参数,算出长按开炮键,积蓄能量的速度    
    }

    void Update()
    {
        if (Input.GetButtonDown(FireButton))//当按下开火键执行以下代码
        {
            CurrentLaunchForce = MinLaunchForce;//当下力量从设定的最小力量开始累加
            Areyoufire = false;//开炮状态为非
            Shootingaduio.clip = Charging;//音源设置为积攒能量
            Shootingaduio.Play();//开始播放积攒能量片段
        }
        else if (Input.GetButton(FireButton) && !Areyoufire)//当开火键被按下,并且开火状态为非(ture = 非flase = !Areyoufire)执行以下代码
        {
            CurrentLaunchForce += ChargeSpeed * Time.deltaTime;//当下力量不断根据时间和速度被累加
            AimSlider.value = CurrentLaunchForce;//滑动条的值等于当前力量值
            
            if (CurrentLaunchForce >= MaxLaunchForce)//如果当前的力量大于等于设定的最大力量执行以下代码
            {
                CurrentLaunchForce = MaxLaunchForce;//当前力量为最大力量
                Fire();//开火
            }
        }
        else if (Input.GetButtonUp(FireButton) && !Areyoufire)//当开火键抬起,并且开火状态为非
        {
            Fire();//开火
        }
    }
    

    void Fire() {

        AimSlider.value=MinLaunchForce;//滑动条为最小力量值
       Areyoufire = true;//开炮为真,表示开过炮了,需要再按一次开炮键赋予非
        GameObject gameObjectInstance = Instantiate(fireshell, firetrasnform.transform.position, firetrasnform.transform.rotation);
        //在游戏中创建一个新的实例,设置实例的位置和旋转角度
        Rigidbody rigidbody = gameObjectInstance.GetComponent<Rigidbody>();
        //从新创建的实例中获取rigidbody
        rigidbody.velocity = firetrasnform.transform.forward * CurrentLaunchForce;
        //为新的实例施加一个前进方向上的力,将炮弹发射出去

        CurrentLaunchForce = MinLaunchForce;//当下力量重置为最小力量

        Shootingaduio.clip = Fireclip;//音源播放片段为开火
        Shootingaduio.Play();//播放开火片段
    } 
}

弹药

爆炸代码


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.GraphicsBuffer;

public class Myshellexplosion : MonoBehaviour//挂载在炮弹预制体中
{
    public LayerMask TankMask;//建立一个特殊的层来计算碰撞,将坦克手动挪到player层,并关联到这个层
    public float MaxLifeTime = 2f;//炮弹最大存在时间
    public ParticleSystem ExplosionParticle;//炮弹的粒子特效
    public static bool Bigbang = false;//布尔变量,判断是否爆炸,默认没有爆炸
    public float ExplosionRadius = 5f;//爆炸范围
    public float ExplosionForce = 1000f;//爆炸向外产生的推力
    public float MaxDamage = 100f;//爆炸的最大伤害

    void Start()//它在游戏对象的脚本中被调用,用于执行游戏开始时的初始化工作。这个方法在游戏对象被实例化并且场景被加载完成后自动调用一次。
    {
        Destroy(gameObject, MaxLifeTime);//炮弹被创造出后,在一段时间后自动销毁
    }

    void OnTriggerEnter(Collider other)//当另一个游戏对象的Collider与当前游戏对象的触发器Collider相交时,就会调用这个方法。这通常用于检测玩家是否进入了一个特定的区域,或者两个游戏对象是否相互接触。
    {
        //获取到半圆R的球里面的所有物体    坦克
        Collider[] targetCollider = Physics.OverlapSphere(transform.position, ExplosionRadius, TankMask);//返回一个碰撞体数组,包含所有范围内碰撞体
        Bigbang = true;//发生碰撞,产生爆炸
        for (int i = 0; i < targetCollider.Length; i++)//遍历范围内碰撞体数量
        {
            //获取刚体组件
            Rigidbody target = targetCollider[i].GetComponent<Rigidbody>();//获取每一个碰撞体的刚体
            if (target == null)//如果碰撞体没有rigibody组件,则跳过后续代码
            {
                continue;
            }
            target.AddExplosionForce(ExplosionForce, transform.position, ExplosionRadius);//对碰撞体施加爆炸力ExplosionForce是爆炸力的大小,transform.position是爆炸的位置,ExplosionRadius是爆炸影响的半径。   
            TankHealth targetTankHealth = targetCollider[i].GetComponent<TankHealth>();//提取每个碰撞体的血量  
            float damage = CalculateDamage(target.gameObject.transform.position);//计算坦克和爆炸的距离
            targetTankHealth.TakeDamage(damage);//根据距离计算扣血

        }
        //播放爆炸特效
        ExplosionParticle.transform.parent = null;//原在炮弹子集下的爆炸特效,脱离炮弹子集
        ExplosionParticle.Play();//播放炮弹爆炸特效

        Destroy(ExplosionParticle.gameObject, ExplosionParticle.main.duration);//爆炸结束后摧毁爆炸特效
        Destroy(gameObject);//摧毁代码挂载物品炮弹
    }
    private float CalculateDamage(Vector3 targetPosition)
    {
        //计算坦克和爆炸的距离
        Vector3 v3 = targetPosition - transform.position;//三维向量,碰撞发生位置(坦克位置)-炮弹落点
        float distance = v3.magnitude;//读取V3的长度距离
        float damage = ((ExplosionRadius - distance) / ExplosionRadius) * MaxDamage;//((爆炸范围-爆炸距离)/爆炸范围)*最大伤害=收到伤害
        damage = Mathf.Max(0, damage);//伤害确定为正数
        return damage;//返回damage值
    }
}

爆炸特效上的代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Exp: MonoBehaviour//挂载在炮弹爆炸的特效中
{

    public AudioSource ExpAudioSource;//爆炸的音源

    void Update()
    {
        if (Myshellexplosion.Bigbang)//当爆炸代码中的爆炸开关开启
        {    
            ExpAudioSource.Play();//爆炸音效开启,将音效单独提到炮弹音效中,为了防止音效播出中炮弹关闭后失效
            Myshellexplosion.Bigbang = false;//播放一次后关闭爆炸开关,防止出现声音每帧都播放的问题
        }
        
    }
}

摄像机

摄像机代码【注意代码挂载位置】

using UnityEngine;

public class CameraControl : MonoBehaviour//挂载在主摄像机的父级空物体CameraRig上,空物体在原点,旋转和摄像机保持一致后再建立父子级关系
{
    public float m_DampTime = 0.2f;//缩放的延迟时间
    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()
    {
        //获取camera组件 因为只有一个,直接就能获取
        m_Camera = GetComponentInChildren<Camera>();
    }


    private void FixedUpdate()
        //FixedUpdate是一个在物理更新循环中调用的特殊方法。它主要用于处理与物理计算相关的任务,如刚体的移动、碰撞检测和关节旋转等。
        //FixedUpdate在每一帧的物理计算之前调用,确保物理交互的一致性和准确性。
    {
        Move();
        Zoom();
    }


    private void Move()
    {
        FindAveragePosition();//运行FindAveragePosition方法,算出摄像机新位置m_DesiredPosition

        transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
        //摄像机的位置,平滑地插值当前位置transform.position向m_DesiredPosition。
        //第一个参数(transform.position): 当前位置,即移动对象的起始点。
        //第二个参数(m_DesiredPosition): 目标位置,即移动对象想要达到的位置。
        //第三个参数(ref m_MoveVelocity): 当前速度的引用,用于在连续的插值过程中保持速度信息。这是一个Vector3变量,存储了对象的当前移动速度。
        //第四个参数(m_DampTime): 阻尼时间,用于确定从当前位置移动到目标位置需要多长时间。数值越小,移动越快。
    }


    private void FindAveragePosition()
    {
        Vector3 averagePos = new Vector3();//设置一个私有变量
        int numTargets = 0;//设置一个int型坦克数量,默认为0
       
        for (int i = 0; i < m_Targets.Length; i++) //循环所有坦克
        {
            if (!m_Targets[i].gameObject.activeSelf)//如果遍历到一辆坦克的activeself属性为flase,则跳过本次遍历进行下一轮。即某个坦克爆炸了,该坦克位置不再参与运算
                continue;

            averagePos += m_Targets[i].position;//存在的坦克,各个坦克的三维位置相加
            numTargets++;//存在的坦克数量相加
        }

        if (numTargets > 0)//当当前坦克场上数量大于0
            averagePos /= numTargets;//各个坦克的位置之和除以坦克数量,等于坦克位置的中点

        averagePos.y = transform.position.y;//中点位置的Y轴用当前摄像机Y轴替换掉

        m_DesiredPosition = averagePos;//根据坦克位置计算出的中点位置的摄像机位置重新赋予m_DesiredPosition
    }


    private void Zoom()
    {
        float requiredSize = FindRequiredSize();//利用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;//默认正交尺寸0

        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变量,使其成为目标对象在Y轴方向上的最大距离或X轴方向上调整宽高比后的最大距离。有点费解,和摄像机画面xy比例有关
        }

        size += m_ScreenEdgeBuffer;//将边缘缓冲区m_ScreenEdgeBuffer添加到视野大小中,以确保目标对象与屏幕边缘之间有足够空间。

        size = Mathf.Max(size, m_MinSize);//确保计算出的尺寸不小于设定的最小尺寸m_MinSize。

        return size;//返回计算出的摄像机视野大小。
    }


    public void SetStartPositionAndSize()//游戏刚开始时校正摄像机位置和正交角度的方法,被游戏管理代码使用
    {
        FindAveragePosition();

        transform.position = m_DesiredPosition;

        m_Camera.orthographicSize = FindRequiredSize();
    }
}

游戏开始程序

GameManage【空物体】未完成

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using TMPro;
using static UnityEditor.ShaderGraph.Internal.KeywordDependentCollection;

public class GameManager : MonoBehaviour//挂载在游戏GameManage空物体上
{
    public int m_NumRoundsToWin = 3;//多少回合后游戏胜利        
    public float m_StartDelay = 1f; //游戏开始前等待时间         
    public float m_EndDelay = 2f;   //游戏结束后等待时间
    public CameraControl m_CameraControl;   //相机控制
    public TextMeshProUGUI m_MessageText;   //文本信息          
    public GameObject m_TankPrefab;       //坦克预制体
    public TankManager[] m_Tanks;        //坦克预制体集合  


    private int m_RoundNumber;     //第几回合     
    private WaitForSeconds m_StartWait;   //私有变量  游戏开始前等待时间   WaitForSeconds 是Unity中用于暂停协程执行一定时间的类。
    private WaitForSeconds m_EndWait;     //私有变量  游戏结束后等待时间   WaitForSeconds 是Unity中用于暂停协程执行一定时间的类。
    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()); 
//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) ;
            //创建游戏对象实例Object original:要实例化的对象,通常是预设(Prefab)。Vector3 position:新实例的位置。Quaternion rotation:新实例的旋转。
            //as GameObject   它将 Instantiate 方法返回的 Object 类型转换为 GameObject 类型。
            m_Tanks[i].m_PlayerNumber = i + 1;//返回坦克的操作UI名
            m_Tanks[i].Setup();//使用坦克的setup方法,附下
        }
    }
    /*    private Tankmove2 m_Movement;
        private Tankshotting m_Shooting;
        private GameObject m_CanvasGameObject;
        public void Setup()
        {
            m_Movement = m_Instance.GetComponent<Tankmove2>(); 获取坦克移动组件
            m_Shooting = m_Instance.GetComponent<Tankshotting>(); 获取坦克开炮组件
            m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas>().gameObject; 获取坦克画布组件,实际是UI

            m_Movement.PlayerNamber = m_PlayerNumber; 移动组件中设置玩家编号
            m_Shooting.PlayerNamber = m_PlayerNumber; 开炮组件中设置玩家编号

            m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>"; 一段文字,颜色为自定义的获胜玩家颜色 + player + 获胜忘记编号

            MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer>();这行代码获取 m_Instance 游戏对象及其子对象中所有的 MeshRenderer 组件,并存储在 renderers 数组中。

            for (int i = 0; i < renderers.Length; i++)这是一个循环,遍历所有的 MeshRenderer 组件。
            {
                renderers[i].material.color = m_PlayerColor;
            }
        }
        MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer>();在循环内部,这行代码设置每个 MeshRenderer 的材料颜色为 m_PlayerColor。这意味着坦克的所有网格渲染器将被着色,以显示玩家的颜色。
    */



    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;//将每个坦克的位置返回给相机代码的m_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;//协程返回需要yield
    }


    private IEnumerator RoundPlaying()
    {
        EnableTankControl();

        m_MessageText.text = "";

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

剩下点尾巴,以后再捋顺吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值