ccruiの博客

ccruiの博客

实践学期塔防游戏大作业功能点

73
2020-08-18

实践学期塔防游戏大作业功能点

姓名:张程瑞
学号:19107350111
项目名称:塔防游戏

功能点1-控制相机移动

实现方法:通过鼠标和键盘的输入,来控制摄像机的移动
可以通过WASD和按住右键拖动移动相机的位置,鼠标滚轮控制相机的缩放

实现代码:

/// <summary>
/// 调用摄像头移动
/// 通过WASD,鼠标滚轮,点击右键拖动,进行移动
/// </summary>
public void Move()
{

    //通过WASD按键控制摄像机的前后左右移动
    float h = Input.GetAxis("Horizontal");//读取横向操作,赋值给h
    float v = Input.GetAxis("Vertical");//读取纵向操作,赋值给v
    transform.Translate(new Vector3(h, 0, v) * Time.deltaTime * speed, Space.World);//使被挂载对象的坐标按照h,v的操作,在世界坐标系中移动

    //通过滑轮控制摄像机的缩放
    float mouse = Input.GetAxis("Mouse ScrollWheel");//读取鼠标滑轮操作,赋值给mouse
    transform.Translate(new Vector3(0, 0, mouse * mousespeed) * Time.deltaTime);//使被挂载对象的坐标mouse的操作,在坐标系中移动

    //通过鼠标右键按下拖动视角平移

    //交换初始和结束鼠标位置
    MousePositionLast = MousePositionNew;
    MousePositionNew = Input.mousePosition;

    MouseMoveDirection = MousePositionNew - MousePositionLast; //用两个坐标相减,得出鼠标的移动方向向量

    if (Input.GetMouseButtonDown(1))//判断鼠标右键是否按下
    {
        whetherTheRightButtonIsPressed = true;//标记鼠标右键为按下
        isItTheFirstTime = false;
    }
    else if (Input.GetMouseButtonUp(1))//判断鼠标右键是否按下
    {
        whetherTheRightButtonIsPressed = false;//标记鼠标右键为松开
    }


    //第一次数据识别会有错误,需要第二次以后的数据
    if (isItTheFirstTime)//判断是否第一次
    {
        if (whetherTheRightButtonIsPressed)//判断鼠标右键是否按下
        {
            transform.Translate(new Vector3(-MouseMoveDirection.x, 0, -MouseMoveDirection.y) * Time.deltaTime * speed, Space.World);//跟着鼠标移动物体
        }
    }
    else
    {
        isItTheFirstTime = true;
    }
}

功能点2-防止相机移动超过限制

实现方法:通过限制摄像机在空间坐标系中的位置,来防止相机超出移动限制

if (transform.position.x > 100f || transform.position.x < -60f
|| transform.position.y > 100f || transform.position.y < 5f
|| transform.position.z > 70f || transform.position.z < -80f)
//判断是否超出规定范围
        {//若超出范围

            //超出限制后位置调整 防止相机越位
            if (transform.position.x > 100f)
            {
                transform.Translate(new Vector3(-5f, 0, 0), Space.World);
            }
            if (transform.position.x < -60f)
            {
                transform.Translate(new Vector3(5f, 0, 0), Space.World);
            }
            if (transform.position.y > 100f)
            {
                transform.Translate(new Vector3(0, -5f, 0), Space.World);
            }
            if (transform.position.y < 5f)
            {
                transform.Translate(new Vector3(0, 5f, 0), Space.World);
            }
            if (transform.position.z > 70f)
            {
                transform.Translate(new Vector3(0, 0, -5), Space.World);
            }
            if (transform.position.z < -80f)
            {
                transform.Translate(new Vector3(0, 0, 5), Space.World);
            }
        }
        else
        {
            Move();//调用摄像头移动
        }

功能点3-怪物的生成以及按照路径点移动

实现方法:通过代码保存在unity中设置的路径点,并且通过跨脚本定义数组,并调用的方式,利用保存好的路径点位置,生成怪物,并让其按照路径点移动

保存路径点

 public static Transform[] positions;//定义一个静态Transform数组保存位置

/// <summary>
/// Awake is called when the script instance is being loaded.
/// 在加载脚本实例时调用aware。
/// </summary>
void Awake()
{
    positions = new Transform[transform.childCount];//获取子物体的数量作为positions的长度
    for (int i = 0; i < positions.Length; i++)//遍历positions
    {
        positions[i] = transform.GetChild(i);//使positions的第i位等于子物体的索引,根据子物体的索引获取物体位置
    }
}

怪物生成

/// <summary>
/// 携程函数 控制生成敌人数量与间隔
/// </summary>
/// <returns></returns>
IEnumerator SpawnEnemy()//携程函数
{
    foreach (Wave wave in waves)//循环遍历Wave
    {
        for (int i = 0; i < wave.count; i++)
        {
            GameObject.Instantiate(wave.enemyPrefab, START.position, Quaternion.identity);//根据enmyPrefab在开始位置,生成无旋转的物体
            CountEnemyAlive++;//使敌人数量增加
            if (i != wave.count - 1)
            {
                yield return new WaitForSeconds(wave.rate);//暂停rate秒
            }
        }
        while (CountEnemyAlive > 0)
        {
            yield return 0;//返回
        }
        yield return new WaitForSeconds(WaveRate);//暂停WaveRate秒
    }
    while (CountEnemyAlive > 0)//说明敌人还有存货
    {
        yield return 0;//返回
    }
    GameManager.instance.Win();//调用胜利
}

怪物移动

/// <summary>
/// 使怪物根据路径点移动
/// </summary>
void Move()
{
    if (index > positions.Length - 1)//判断路径点序号是都大于路径点数组长度-1
    {
        return;//返回
    }
    transform.Translate((positions[index].position - transform.position).normalized * Time.deltaTime * speed);//让脚本挂载物体向着 路径点位置-自身位置 的矢量方向进行移动
    if (Vector3.Distance(positions[index].position, transform.position) < 0.2f)//判断脚本所挂载物体与路径点的距离是否小于0.2
    {
        index++;//+=1
    }
    if (index > positions.Length - 1)//判断是否超出路径数量
    {
        ReachDestination();
    }
}

功能点4-炮台数据保存以及建造

实现方式:通过定义数据类的方式,定义枚举类型,保存需要建造的炮塔,并且在按下unity中设定的按键时,获取所保存的炮塔预制体,通过射线检测,检测鼠标与游戏中所设定的可以建造炮台的物体(GameObject)的碰撞,来获取mapcube的坐标,并通过获取的坐标与保存的炮塔预制体,在mapcube上实例化炮台对象,从而完成炮塔的建造操作

判断鼠标是否与mapcub重合

/// <summary>
/// Start is called on the frame when a script is enabled just before
/// any of the Update methods is called the first time.
/// </summary>
void Start()
{
    _renderer = GetComponent<Renderer>();//获取材质渲染器
    originalMaterialColor = _renderer.material.color;//保存初始颜色
}
/// <summary>
/// Called when the mouse enters the GUIElement or Collider.
/// 当鼠标进入GUIElement或对撞机时调用。
/// </summary>
private void OnMouseEnter()
{
    if (turretGo == null && EventSystem.current.IsPointerOverGameObject() == false)//判断是否为空并且鼠标不在UI上
    {
        _renderer.material.color = Color.red;//使材质变为红色
    }
}

/// <summary>
/// Called when the mouse is not any longer over the GUIElement or Collider.
/// 当鼠标不再位于GUIElement或对撞机上时调用。
/// </summary>
void OnMouseExit()
{
    _renderer.material.color = originalMaterialColor;//使材质变为原始颜色
}

炮塔的数据保存

//创建数据类保存炮塔数据
public class TurretData
{

public GameObject turretPrefab;//原始预制体
public int cost;//价格
public GameObject turretUpgradedPrefab;//升级预制体
public int cosUpgraded;//升级价格
public int dismantlingPrice;//拆解价格
public TurretType type;//定义枚举
}

/// <summary>
/// 定义枚举类型
/// 内部保存炮塔
/// </summary>
public enum TurretType//定义枚举类型
{
    LasetTurret,
    MissileTurret,
    standardTurret,
    Boom
}

按下按键获取炮塔

/// <summary>
/// 接受按键数据
/// </summary>
/// <param name="isOn">
/// 接收按钮传递的bool类型数据
/// </param>
public void OnlaserSelected(bool isOn)//接收按钮数据
{
    if (isOn)//判断按钮是否按下
    {
        selectedTurretData = laserTurretData;//使当前炮塔等于选择的炮塔
    }
}

/// <summary>
/// 接受按键数据
/// </summary>
/// <param name="isOn">
/// 接收按钮传递的bool类型数据
/// </param>
public void OnMissileSelected(bool isOn)//接收按钮数据
{
    if (isOn)//判断按钮是否按下
    {
        selectedTurretData = mssileTurretData;//使当前炮塔等于选择的炮塔
    }
}

/// <summary>
/// 接受按键数据
/// </summary>
/// <param name="isOn">
/// 接收按钮传递的bool类型数据
/// </param>
public void OnStandardSelected(bool isOn)//接收按钮数据
{
    if (isOn)//判断按钮是否按下
    {
        selectedTurretData = standaraTurretData;//使当前炮塔等于选择的炮塔
    }
}

/// <summary>
/// 接受按键数据
/// </summary>
/// <param name="isOn">
/// 接收按钮传递的bool类型数据
/// </param>
public void OnBoom(bool isOn)//接收按钮数据
{
    if (isOn)//判断按钮是否按下
    {
        selectedTurretData = Boom;//使当前炮塔等于选择的炮塔
    }
}

炮塔的建造

/// <summary>
/// Update is called every frame, if the MonoBehaviour is enabled.
/// </summary>
void Update()
{
    if (Input.GetMouseButtonDown(0))//判断鼠标左键按下
    {
        if (EventSystem.current.IsPointerOverGameObject() == false)//判断鼠标不在UI上
        {
            //开发炮台开发的创造
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//创建射线
            RaycastHit hit;
            bool isCollider = Physics.Raycast(ray, out hit, 1000, LayerMask.GetMask("MapCube"));//定义是否指向MapCube
            if (isCollider)//判断是否指向MapCube
            {
                MapCube mapCube = hit.collider.GetComponent<MapCube>();//得到点击的mapcube
                if (selectedTurretData != null && mapCube.turretGo == null)//判断是否选择了炮台 和mapcube上是否有炮塔
                {
                    //可以创建
                    if (money >= selectedTurretData.cost)//判断钱是否够
                    {
                        ChangeMoney(-selectedTurretData.cost);//消耗金钱
                        mapCube.BuildTurret(selectedTurretData);//调用MapCube中的函数创建炮台
                    }
                    else
                    {
                        //提示钱不够
                        moneyAnimator.SetTrigger("Flicker");//将动画机的Flicker激活
                    }
                }
                else if (mapCube.turretGo != null)//判断上面有炮台
                {
                    // Debug.Log(mapCube.turretGo);
                    // Debug.Log(selectedMapcube);
                    if (mapCube.turretGo == theCurrentlySelectedTurret && upgradeCanvas.activeInHierarchy)//如果选择同一个炮台
                    {
                        StartCoroutine(HideUpgradeUI());//调用携程函数隐藏UI
                                                        // HideUpgradeUI();//隐藏UI
                    }
                    else
                    {
                        //升级处理
                        if (mapCube.isUpgraded)//判断是否升级过
                        {
                            ShowUpgradeUI(mapCube.transform.position, true);//调用方法实例化
                        }
                        else
                        {
                            ShowUpgradeUI(mapCube.transform.position, false);//调用方法实例化
                        }
                    }
                    selectedMapcube = mapCube;//更新保存炮台所在的mapCube
                    theCurrentlySelectedTurret = mapCube.turretGo;//更新保存的炮台
                }
            }
        }
    }
}
/// <summary>
/// 创建炮塔并播放特效
/// </summary>
/// <param name="turretData">
/// 炮塔的TurretData
/// </param>
public void BuildTurret(TurretData turretData)
{
    this.turretData = turretData;//保存炮塔属性
    isUpgraded = false;//定义炮塔未升级
    turretGo = GameObject.Instantiate(turretData.turretPrefab, transform.position, Quaternion.identity);//实例化炮塔并赋值给turretGo
    GameObject effect = GameObject.Instantiate(buildEffect, transform.position, Quaternion.identity);//实例化特效并赋值给effect
    Destroy(effect, 1.0f);//一秒后销毁
}

功能点5-炮塔价格的定义,以及炮塔升级与拆解

实现方式:通过功能点4中提到的炮塔数据保存方式,保存炮塔建造与升级所需要的价格,并且通过unity中创建的UI面板,通过先销毁原先炮塔,再重新实例化新炮塔的方式,进行炮塔的升级。或者通过销毁原先炮塔的方式,实现炮塔的拆除

升级面板的显示

/// <summary>
/// 显示升级面板
/// </summary>
/// <param name="pos">
/// 定义面板显示位置
/// </param>
/// <param name="isDisableUpgrade">
/// 是否禁用按钮
/// </param>
void ShowUpgradeUI(Vector3 pos, bool isDisableUpgrade = false)
{
    StopCoroutine("HideUpgradeUI");//暂停携程
    upgradeCanvas.SetActive(false);//控制显示与隐藏
    upgradeCanvas.SetActive(true);//控制显示与隐藏
    upgradeCanvas.transform.position = pos;//定义显示位置
    buttonUpgrade.interactable = !isDisableUpgrade;//是否禁用按钮
}

/// <summary>
/// 携程函数 隐藏升级面板
/// </summary>
/// <returns></returns>
IEnumerator HideUpgradeUI()
{
    upgradeCanvasAnimator.SetTrigger("Hide");//更改状态机状态
    yield return new WaitForSeconds(0.3f);//等待0.3秒
    upgradeCanvas.SetActive(false);//控制显示与隐藏
}

按下升级面板的按键触发

/// <summary>
/// 按下升级按键触发
/// </summary>
public void OnUpgradeButtonDown()
{
    if (money >= selectedMapcube.turretData.cosUpgraded)//判断钱够不够
    {
        ChangeMoney(-selectedMapcube.turretData.cosUpgraded);
        selectedMapcube.UpgradeTurret();//调用升级Mapcube上的升级方法
    }
    else
    {
        //提示钱不够
        moneyAnimator.SetTrigger("Flicker");//将动画机的Flicker激活
    }
    StartCoroutine(HideUpgradeUI());//隐藏UI
}

/// <summary>
/// 按下拆解按键触发
/// </summary>
public void OnDestroyButtonDown()
{
    ChangeMoney(selectedMapcube.turretData.dismantlingPrice);//返还钱
    selectedMapcube.DestroyTurret();//调用升级Mapcube上的拆解方法
    StartCoroutine(HideUpgradeUI());//隐藏UI
}

升级与拆解方法

    /// <summary>
/// 升级炮塔并播放特效
/// </summary>
public void UpgradeTurret()
{
    if (isUpgraded == true) { return; }//若升级过就销毁

    Destroy(turretGo);//拆解就得炮塔
    isUpgraded = true;//定义炮塔未升级
    turretGo = GameObject.Instantiate(turretData.turretUpgradedPrefab, transform.position, Quaternion.identity);//实例化炮塔并赋值给turretGo
    GameObject effect = GameObject.Instantiate(buildEffect, transform.position, Quaternion.identity);//实例化特效并赋值给effect
    Destroy(effect, 1.0f);//一秒后销毁

}

/// <summary>
/// 拆解炮塔并播放特效
/// </summary>
public void DestroyTurret()
{
    Destroy(turretGo);//拆解炮塔
                      //恢复初始值
    {
        isUpgraded = false;
        turretGo = null;
        turretData = null;
    }
    GameObject effect = GameObject.Instantiate(buildEffect, transform.position, Quaternion.identity);//实例化特效并赋值给effect
    Destroy(effect, 1.0f);//一秒后销毁

}

功能点6-控制非激光炮台的攻击

实现方式:在Unity中对炮台添加碰撞体,并且勾选IsTrigger选项,使用OnTriggerEnter方法进行碰撞检测,利用Tag检测在炮塔碰撞范围内的物体是否是怪物,若是怪物,获取怪物的位置,使炮塔朝向怪物,并且实例化子弹模型,向子弹模型上挂载的Bullet脚本传递敌人位置,从而实现子弹追踪敌人的效果

检测敌人

public List<GameObject> enemys = new List<GameObject>();//创建列表保存

/// <summary>
/// OnTriggerEnter is called when the Collider other enters the trigger.
/// 当对撞机other进入触发器时调用OnTriggerEnter。
/// </summary>
/// <param name="col">
/// The other Collider involved in this collision.
/// 参与这次碰撞的另一个对撞机。
/// </param>
void OnTriggerEnter(Collider col)//碰撞检测
{
    if (col.tag == "Enemy")//判断撞上来的物体标签是否为Enemy
    {
        enemys.Add(col.gameObject);//向列表中添加
    }
}

/// <summary>
/// OnTriggerExit is called when the Collider other has stopped touching the trigger.
/// 当对撞机他方停止接触触发器时,调用OnTriggerExit。
/// </summary>
/// <param name="col">
/// The other Collider involved in this collision.
/// 参与这次碰撞的另一个对撞机。
/// </param>
void OnTriggerExit(Collider col)//判断是否出检测范围
{
    if (col.tag == "Enemy")//判断是否为Enemy
    {
        enemys.Remove(col.gameObject);//从列表中删除
    }
}

更改炮台朝向

if (enemys.Count > 0 && enemys[0] != null && isItABomb == false)//判断是否有攻击对象
    {
        //保证y轴一致
        Vector3 targetPosition = enemys[0].transform.position;//定义targetPosition为数组中第0个物体的vector3
        targetPosition.y = head.position.y;//保持y轴一致

        head.LookAt(targetPosition);
    }

实例化子弹模型

/// <summary>
/// 实例化炮弹攻击敌人
/// </summary>
void Attack()
{
    if (enemys[0] == null)//判断集合第0位是否为空
    {
        enemys = UpdateEnemys(enemys);//更新敌人
    }
    if (enemys.Count > 0)//判断列表长度是否大于0
    {
        if (isItABomb == false)
        {
            GameObject bullet = GameObject.Instantiate(bulletPrefab, firPosition.position, firPosition.rotation);//实例化炮弹
            bullet.GetComponent<Bullet>().SetTarget(enemys[0].transform);//调用方法传递列表中第一个物体
        }
    }
    else
    {
        timer = attackRateTime;//计时器复位
    }
}

功能点7-子弹对敌人的伤害

实现方式:通过获取Turret脚本上的敌人位置,使子弹跟踪敌人进行射击,并且通过距离检测的方式,判断子弹是否撞击到敌人,从而调用敌人身上的扣血方法,对敌人进行伤害

获取Turret脚本上的敌人位置

/// <summary>
/// 用于跨脚本传递 敌人位置
/// </summary>
/// <param name="_target">
/// 传递敌人位置
/// </param>
public void SetTarget(Transform _target)//定义方法 用来传递目标
{
    this.target = _target;//传递目标
}

子弹追踪敌人射击与计算敌人距离

/// <summary>
/// Update is called every frame, if the MonoBehaviour is enabled.
/// </summary>
void Update()
{
    timer += Time.deltaTime;//计时器增加
    if (allEnemies != null && allEnemies.Count > 0)//判断列表长度是否大于0
    {
        Boom();
    }

    if (target == null)//判断目标不存在
    {
        Die();
        return;
    }

    if (isItABomb == false)//判断是否为炸弹
    {
        transform.LookAt(target.position);//面向敌人
        transform.Translate(Vector3.forward * speed * Time.deltaTime);//移动

        Vector3 dir = target.position - transform.position;//计算与敌人的距离
        if (dir.magnitude <= distanceArriveTarget)//判断是否到达敌人
        {
            target.GetComponent<Enemy>().TakeDamage(damage);//使敌人掉血
            Die();
        }
    }
}

对敌人造成伤害

target.GetComponent<Enemy>().TakeDamage(damage);//使敌人掉血
Die();
/// <summary>
/// 使敌人掉血
/// </summary>
/// <param name="damage">
/// 掉血血量
/// </param>
public void TakeDamage(float damage)//收到伤害
{
    if (hp <= 0)//判断是否死亡
    {
        return;
    }
    hp -= damage;//扣血
    hpSlider.value = (float)hp / totahHp;//显示血条
    if (hp <= 0)//判断是否死亡
    {
        Die();
    }
}

功能点8-激光炮台的攻击

实现方式:在Unity中对炮台添加碰撞体,并且勾选IsTrigger选项,使用OnTriggerEnter方法进行碰撞检测,利用Tag检测在炮塔碰撞范围内的物体是否是怪物,若是怪物,获取怪物的位置,使炮塔朝向怪物,并且向敌人方向绘制激光,对敌人造成伤害

检测敌人:同 功能点6-控制非激光炮台的攻击

更改炮台朝向:同 功能点6-控制非激光炮台的攻击

绘制激光并对敌人造成伤害

    if (useLaser == false)//判断是否为激光
{
    timer += Time.deltaTime;//计时器增加
    if (enemys.Count > 0)//判断敌人数量是否大于0
    {
        Attack("Boom");//调用攻击
    }
    if (enemys.Count > 0 && timer >= attackRateTime)//判断敌人数量是否大于0并且时间是否到
    {
        timer = 0;//时间复位
        Attack();//调用攻击
    }
}
else if (enemys.Count > 0)//判断是否有敌人
{
    if (laserRenderer.enabled == false)
    {
        laserRenderer.enabled = true;
    }
    laserEffect.SetActive(true);//开启特效
    if (enemys[0] == null)//判断集合第0位是否为空
    {
        enemys = UpdateEnemys(enemys);//更新敌人
    }
    if (enemys.Count > 0)//判断是否有敌人
    {
        laserRenderer.SetPositions(new Vector3[] { firPosition.position, enemys[0].transform.position });//朝向敌人绘制激光
        enemys[0].GetComponent<Enemy>().TakeDamage(damageRate * Time.deltaTime);//使敌人掉血
        laserEffect.transform.position = enemys[0].transform.position;//在敌人位置播放动画
        Vector3 pos = transform.position;//获取当前位置
        pos.y = enemys[0].transform.position.y;//锁定y轴位置保证y轴与敌人高度一致
        laserEffect.transform.LookAt(pos);//定义特效播放朝向

    }
}
else
{
    laserEffect.SetActive(false);//关闭特效
    laserRenderer.enabled = false;//关闭激光显示
}

功能点9-炸弹攻击

实现方式:在Unity中对炮台添加碰撞体,并且勾选IsTrigger选项,使用OnTriggerEnter方法进行碰撞检测,利用Tag检测在炮塔碰撞范围内的物体是否是怪物,若是怪物,获取怪物的列表,传递到Bullet脚本,对在列表范围内的全部怪物进行伤害

获取并传递列表

/// <summary>
/// 炸弹攻击敌人
/// </summary>
/// <param name="i">
/// 没用,单纯为了重载
/// </param>
void Attack(string i)
{
    if (enemys[0] == null)//判断集合第0位是否为空
    {
        enemys = UpdateEnemys(enemys);//更新敌人
    }
    if (enemys.Count > 0)//判断列表长度是否大于0
    {
        if (isItABomb)
        {
            this.GetComponent<Bullet>().EnemyList(enemys);//调用方法传递列表
        }
    }
}
/// <summary>
/// 用于跨脚本传递 敌人数组
/// </summary>
/// <param name="_enemys">
/// 传递敌人数组
/// </param>
public void EnemyList(List<GameObject> _enemys)//定义方法 用来传递 敌人数组
{
    this.allEnemies = _enemys;//传递目标
}

对范围敌人进行伤害

/// <summary>
/// 炸弹攻击与销毁流程
/// </summary>
void Boom()
{
    if (isItABomb == true)//判断是否为炸弹
    {
        if (timer >= attackRateTime)//判断延时爆炸
        {
            if (whetherItIsTriggeredForTheFirstTime)//判断是否为第一次爆炸 防止多次触发造成多次伤害
            {
                // Debug.Log(("敌人数量:") + allEnemies.Count);
                for (int i = 0; i < allEnemies.Count; i++)
                {
                    allEnemies = this.GetComponent<Turret>().UpdateEnemys(allEnemies);//刷新列表
                    allEnemies[i].GetComponent<Enemy>().TakeDamage(boomDamage);//使敌人掉血
                }
                whetherItIsTriggeredForTheFirstTime = false;
                GameObject effect = GameObject.Instantiate(explosionEffctPrefab, transform.position, transform.rotation);//创建特效
                Destroy(effect, 5.0f);//销毁特效
            }
            Destroy(this.gameObject, 0.2f);//销毁子弹
            GameObject.Find("GameManager").GetComponent<BuildManager>().CallCtripFunction();//调用隐藏面板
        }
    }

}

功能点10-怪物死亡

实现方式:判断怪物的血量是否小于等于0,从而销毁怪物,并移出列表

/// <summary>
/// 使敌人掉血
/// </summary>
/// <param name="damage">
/// 掉血血量
/// </param>
public void TakeDamage(float damage)//收到伤害
{
    if (hp <= 0)//判断是否死亡
    {
        return;
    }
    hp -= damage;//扣血
    hpSlider.value = (float)hp / totahHp;//显示血条
    if (hp <= 0)//判断是否死亡
    {
        Die();
    }
}

/// <summary>
/// 怪物死亡后调用
/// </summary>
void Die()
{
    GameObject effect = GameObject.Instantiate(exlosionEffect, transform.position, transform.rotation);//实例化
    Destroy(effect, 1.5f);//销毁特效
    Destroy(this.gameObject);//销毁自身
}
/// <summary>
/// This function is called when the MonoBehaviour will be destroyed.
/// 当物体被销毁后调用该函数
/// </summary>
void OnDestroy()
{
    EnemySpawner.CountEnemyAlive--;//敌人数量减少
}

功能点11-怪物到达终点与全部死亡时的处理与UI显示

实现方式:怪物死亡后销毁自身,并且在全部怪物死亡后,显示游戏胜利UI。通过检测坐标点的方式,检测怪物是否到达终点,从而停止游戏进程,并且显示游戏失败UI

怪物死亡后的处理

/// <summary>
/// 怪物死亡后调用
/// </summary>
void Die()
{
    GameObject effect = GameObject.Instantiate(exlosionEffect, transform.position, transform.rotation);//实例化
    Destroy(effect, 1.5f);//销毁特效
    Destroy(this.gameObject);//销毁自身
}
    ```

``` csharp
/// <summary>
/// This function is called when the MonoBehaviour will be destroyed.
/// 当物体被销毁后调用该函数
/// </summary>
void OnDestroy()
{
    EnemySpawner.CountEnemyAlive--;//敌人数量减少
}

怪物全部死亡后的处理

/// <summary>
/// 携程函数 控制生成敌人数量与间隔
/// </summary>
/// <returns></returns>
IEnumerator SpawnEnemy()//携程函数
{
    foreach (Wave wave in waves)//循环遍历Wave
    {
        for (int i = 0; i < wave.count; i++)
        {
            GameObject.Instantiate(wave.enemyPrefab, START.position, Quaternion.identity);//根据enmyPrefab在开始位置,生成无旋转的物体
            CountEnemyAlive++;//使敌人数量增加
            if (i != wave.count - 1)
            {
                yield return new WaitForSeconds(wave.rate);//暂停rate秒
            }
        }
        while (CountEnemyAlive > 0)
        {
            yield return 0;//返回
        }
        yield return new WaitForSeconds(WaveRate);//暂停WaveRate秒
    }
    while (CountEnemyAlive > 0)//说明敌人还有存货
    {
        yield return 0;//返回
    }
    GameManager.instance.Win();//调用胜利
}
/// <summary>
/// 游戏胜利 显示界面和文字
/// </summary>
public void Win()
{
    endUI.SetActive(true);//显示界面
    endMessage.text = "游戏胜利";//显示文字
}

判断怪物到达终点

if (index > positions.Length - 1)//判断是否超出路径数量
    {
        ReachDestination();
    }

怪物到达终点后的处理

/// <summary>
/// 怪物到达终点后执行
/// </summary>
void ReachDestination()
{
    GameManager.instance.Failed();//调用游戏失败
    GameObject.Destroy(this.gameObject);//清除
}
    ```

``` csharp
/// <summary>
/// 游戏失败 显示界面和文字 并停止生成
/// </summary>
public void Failed()
{
    enemySpawner.Stop();//停止生成
    endUI.SetActive(true);//显示界面
    endMessage.text = "游戏失败";//显示文字
}

功能点12-游戏暂停与UI显示

实现方式:使用脚本,检测是否按下esc键,并在按下按键后,显示在Unity中设计好的游戏暂停UI,并将游戏的时间刻度停止,从而达到暂停游戏的目的

检测是否按下esc并显示游戏UI

/// <summary>
/// Update is called every frame, if the MonoBehaviour is enabled.
/// </summary>
void Update()
{
    PauseTheGame();
}

/// <summary>
/// 暂停游戏
/// </summary>
void PauseTheGame()
{
    if (Input.GetKeyDown(KeyCode.Escape))//判断是否按下ESC
    {
        Time.timeScale = 0;//游戏暂停
        whetherTheGameIsPaused = true;//保存游戏暂停状态
        gamePauseUi.SetActive(true);//显示游戏暂停UI
    }
}

游戏继续

/// <summary>
/// 继续游戏
/// </summary>
public void ContinueTheGame()
{
    if (this.GetComponent<GameManager>().IsItDoubleSpeed() == false)
    {
        Time.timeScale = 1;//游戏继续
    }
    else
    {
        Time.timeScale = 2;//游戏继续
    }
    gamePauseUi.SetActive(false);//隐藏游戏暂停UI
    whetherTheGameIsPaused = false;//保存游戏暂停状态
}

返回主界面

/// <summary>
/// 返回主界面
/// </summary>
public void BackToMainInterface()
{
    Time.timeScale = 1;//游戏继续
    this.GetComponent<GameManager>().OnButtonMenu();//返回主界面
}

功能点13-切换游戏倍速

实现方式:利用在Unity中建立的按钮,控制游戏Time.timeScale属性的值,从而达到控制切换游戏倍速的目的

/// <summary>
/// 使游戏倍速运行
/// </summary>
public void DoubleSpeed()
{
    if (isItDoubleSpeed == false && this.GetComponent<PauseUiControl>().WhetherTheGameIsPaused() == false)//判断倍速状态与暂停状态
    {
        isItDoubleSpeed = true;//使其为倍速
        Time.timeScale = 2;//游戏倍速
        speedDisplay.text = "当前:二倍速";
    }
    else if (isItDoubleSpeed == true && this.GetComponent<PauseUiControl>().WhetherTheGameIsPaused() == false)//判断倍速状态与暂停状态
    {
        isItDoubleSpeed = false;//使其为倍速
        Time.timeScale = 1;//游戏倍速
        speedDisplay.text = "当前:一倍速";
    }
}

功能点15-界面设计

实现方式:在Unity中进行Ui设计,并且利用Button进行界面的切换

/// <summary>
/// 姓名:张程瑞
/// 学号:19107350111
/// 脚本作用:控制游戏UI
/// </summary>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{

    public GameObject endUI;//结束界面
    public Text endMessage;//文字

    private bool isItDoubleSpeed;//是否为倍速

    public Text speedDisplay;//倍速显示

    /// <summary>
    /// 从其他脚本中调用是否为倍速
    /// </summary>
    /// <returns>
    /// 返回bool类型是否为倍速
    /// </returns>
    public bool IsItDoubleSpeed()
    {
        return isItDoubleSpeed;//返回是否为倍速
    }

    //单例模式
    private EnemySpawner enemySpawner;
    public static GameManager instance;

    /// <summary>
    /// Awake is called when the script instance is being loaded.
    /// 在加载脚本时调用
    /// </summary>
    void Awake()
    {
        instance = this;
        enemySpawner = GetComponent<EnemySpawner>();
    }

    /// <summary>
    /// 游戏胜利 显示界面和文字
    /// </summary>
    public void Win()
    {
        endUI.SetActive(true);//显示界面
        endMessage.text = "游戏胜利";//显示文字
    }

    /// <summary>
    /// 游戏失败 显示界面和文字 并停止生成
    /// </summary>
    public void Failed()
    {
        enemySpawner.Stop();//停止生成
        endUI.SetActive(true);//显示界面
        endMessage.text = "游戏失败";//显示文字
    }

    /// <summary>
    /// 按下按钮触发 重新加载场景
    /// </summary>
    public void OnButtonRetry()
    {
        Time.timeScale = 1;//游戏速度默认
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);//重新加载当前场景
    }

    /// <summary>
    /// 按下按钮触发 返回主界面
    /// </summary>
    public void OnButtonMenu()
    {
        Time.timeScale = 1;//游戏速度默认
        SceneManager.LoadScene(0);//返回主界面
    }

    /// <summary>
    /// 使游戏倍速运行
    /// </summary>
    public void DoubleSpeed()
    {
        if (isItDoubleSpeed == false && this.GetComponent<PauseUiControl>().WhetherTheGameIsPaused() == false)//判断倍速状态与暂停状态
        {
            isItDoubleSpeed = true;//使其为倍速
            Time.timeScale = 2;//游戏倍速
            speedDisplay.text = "当前:二倍速";
        }
        else if (isItDoubleSpeed == true && this.GetComponent<PauseUiControl>().WhetherTheGameIsPaused() == false)//判断倍速状态与暂停状态
        {
            isItDoubleSpeed = false;//使其为倍速
            Time.timeScale = 1;//游戏倍速
            speedDisplay.text = "当前:一倍速";
        }
    }

}

/// <summary>
/// 姓名:张程瑞
/// 学号:19107350111
/// 脚本作用:控制游戏暂停UI
/// </summary>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class PauseUiControl : MonoBehaviour
{

    public GameObject gamePauseUi;

    private bool whetherTheGameIsPaused = false;//游戏是否暂停

    /// <summary>
    /// 从其他脚本中调用是否游戏暂停
    /// </summary>
    /// <returns>
    /// 返回游戏是否暂停
    /// </returns>
    public bool WhetherTheGameIsPaused()
    {
        return whetherTheGameIsPaused;
    }

    /// <summary>
    /// Update is called every frame, if the MonoBehaviour is enabled.
    /// </summary>
    void Update()
    {
        PauseTheGame();
    }

    /// <summary>
    /// 暂停游戏
    /// </summary>
    void PauseTheGame()
    {
        if (Input.GetKeyDown(KeyCode.Escape))//判断是否按下ESC
        {
            Time.timeScale = 0;//游戏暂停
            whetherTheGameIsPaused = true;//保存游戏暂停状态
            gamePauseUi.SetActive(true);//显示游戏暂停UI
        }
    }

    /// <summary>
    /// 继续游戏
    /// </summary>
    public void ContinueTheGame()
    {
        if (this.GetComponent<GameManager>().IsItDoubleSpeed() == false)
        {
            Time.timeScale = 1;//游戏继续
        }
        else
        {
            Time.timeScale = 2;//游戏继续
        }
        gamePauseUi.SetActive(false);//隐藏游戏暂停UI
        whetherTheGameIsPaused = false;//保存游戏暂停状态
    }

    /// <summary>
    /// 返回主界面
    /// </summary>
    public void BackToMainInterface()
    {
        Time.timeScale = 1;//游戏继续
        this.GetComponent<GameManager>().OnButtonMenu();//返回主界面
    }

}

功能点14-各种特效制作

实现方式:在Unity中进行特效制作

游戏运行截图

开始界面

开始界面

游戏界面

游戏界面

胜利界面

胜利界面

失败界面

失败界面

暂停界面

暂停界面