Unity 回合制战斗系统(中级篇)-进阶

项目文件找出来了,老版本的脚本有报错,我在新版2019.4.21f1c1下解决了报错,战斗场景可以正常跑的。

需要的同学点下面地址下载(关注就行啦不用积分),祝大家都早日学成

项目包下载

————————————————————————

本篇是基于上一篇《Unity 回合制战斗系统(中级篇)》的进阶,请在阅读上一篇文章后再看本篇>>传送门

这次进阶添加了什么内容呢?请看Gif

新增内容为单位行动,大致如下:

1. 在收到攻击指令后,单位会跑动到目标面前

2. 进行攻击动作后再次跑回原味

3. 如果是远程单位则直接进行攻击动作(这边由于没有弓箭手资源,只在代码里相应位置添加了注释)

内容虽然不多,但是加入这块之后,战斗整体就比较完整了,而且具备了一定的观赏性,要是在加入特效就完美了

本次改动只调整了一个脚本,那就是核心的回合控制脚本BattleTurnSystem,内容如下:

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

public class BattleTurnSystem : MonoBehaviour {

    private List<GameObject> battleUnits;           //所有参战对象的列表
    private GameObject[] playerUnits;           //所有参战玩家的列表
    private GameObject[] enemyUnits;            //所有参战敌人的列表
    private GameObject[] remainingEnemyUnits;           //剩余参战对敌人的列表
    private GameObject[] remainingPlayerUnits;           //剩余参战对玩家的列表

    private GameObject currentActUnit;          //当前行动的单位
    private GameObject currentActUnitTarget;            //当前行动的单位的目标

    public bool isWaitForPlayerToChooseSkill = false;            //玩家选择技能UI的开关
    public bool isWaitForPlayerToChooseTarget = false;            //是否等待玩家选择目标,控制射线的开关
    private Ray targetChooseRay;            //玩家选择攻击对象的射线
    private RaycastHit targetHit;           //射线目标

    private Vector3 currentActUnitInitialPosition;         //当前行动单位的初始位置
    private Quaternion currentActUnitInitialRotation;         //当前行动单位的初始朝向
    private Vector3 currentActUnitTargetPosition;           //当前行动单位目标的位置
    public bool isUnitRunningToTarget = false;            //玩家是否为移动至目标状态
    public bool isUnitRunningBack = false;          //玩家是否为移动回原点状态
    private float distanceToTarget;         //当前行动单位到攻击目标的距离
    private float distanceToInitial;         //当前行动单位到初始位置的距离
    public float unitMoveSpeed = 1f;         //单位战斗中的移动速度
    private Vector3 currentactUnitStopPosition;         //当前行动单位的移动后停下的位置

    public string attackTypeName;           //攻击技能名称
    public float attackDamageMultiplier;           //攻击伤害系数
    public float attackData;            //伤害值

    private GameObject endImage;            //游戏结束画面


    /// <summary>
    /// 创建初始参战列表,存储参战单位,并进行一次出手排序
    /// </summary>
    void Start ()
    {
        //禁用结束菜单
        endImage = GameObject.Find("ResultImage");
        endImage.SetActive(false);

        //创建参战列表
        battleUnits = new List<GameObject>();

        //添加玩家单位至参战列表
        playerUnits = GameObject.FindGameObjectsWithTag("PlayerUnit");
        foreach (GameObject playerUnit in playerUnits)
        {
            battleUnits.Add(playerUnit);
        }

        //添加怪物单位至参战列表
        enemyUnits = GameObject.FindGameObjectsWithTag("EnemyUnit");
        foreach (GameObject enemyUnit in enemyUnits)
        {
            battleUnits.Add(enemyUnit);
        }

        //对参战单位列表进行排序
        listSort();

        //开始战斗
        ToBattle();
    }

    /// <summary>
    /// 判断战斗进行的条件是否满足,取出参战列表第一单位,并从列表移除该单位,单位行动
    /// 行动完后重新添加单位至队列,继续ToBattle()
    /// </summary>
    public void ToBattle()
    {
        remainingEnemyUnits = GameObject.FindGameObjectsWithTag("EnemyUnit");
        remainingPlayerUnits = GameObject.FindGameObjectsWithTag("PlayerUnit");

        //检查存活敌人单位
        if (remainingEnemyUnits.Length == 0)
        {
            Debug.Log("敌人全灭,战斗胜利");
            endImage.SetActive(true);           //显示战败界面
        }
        //检查存活玩家单位
        else if (remainingPlayerUnits.Length == 0)
        {
            Debug.Log("我方全灭,战斗失败");
            endImage.SetActive(true);           //显示胜利界面
        }
        else
        {
            //取出参战列表第一单位,并从列表移除
            currentActUnit = battleUnits[0];
            battleUnits.Remove(currentActUnit);
            //重新将单位添加至参战列表末尾
            battleUnits.Add(currentActUnit);

            //Debug.Log("当前攻击者:" + currentActUnit.name);

            //获取该行动单位的属性组件
            UnitStats currentActUnitStats = currentActUnit.GetComponent<UnitStats>();

            //判断取出的战斗单位是否存活
            if (!currentActUnitStats.IsDead())
            {
                //选取攻击目标
                FindTarget();
            }
            else
            {
                //Debug.Log("目标死亡,跳过回合");
                ToBattle();
            }
        }
    }

    /// <summary>
    /// 查找攻击目标,如果行动者是怪物则从剩余玩家中随机
    /// 如果行动者是玩家,则获取鼠标点击对象
    /// </summary>
    /// <returns></returns>
    void FindTarget()
    {
        if (currentActUnit.tag == "EnemyUnit")
        {
            //如果行动单位是怪物则从存活玩家对象中随机一个目标
            int targetIndex = Random.Range(0, remainingPlayerUnits.Length);
            currentActUnitTarget = remainingPlayerUnits[targetIndex];
            //如果是远程单位直接在这里LaunchAttack,就不需要RunToTarget
            //LaunchAttack();
            RunToTarget();
        }
        else if (currentActUnit.tag == "PlayerUnit")
        {
            isWaitForPlayerToChooseSkill = true;
        }
    }

    /// <summary>
    /// 攻击者移动到攻击目标前(暂时没有做这块)
    /// </summary>
    void RunToTarget()
    {
        currentActUnitInitialPosition = currentActUnit.transform.position;
        currentActUnitInitialRotation = currentActUnit.transform.rotation;         //保存移动前的位置和朝向,因为跑回来还要用
        currentActUnitTargetPosition = currentActUnitTarget.transform.position;         //目标的位置
        //开启移动状态
        isUnitRunningToTarget = true;
        //移动的控制放到Update里,因为要每一帧判断离目标的距离
    }

    /// <summary>
    /// 绘制玩家选择技能的窗口
    /// </summary>
    void OnGUI()
    {
        if (isWaitForPlayerToChooseSkill == true)
        {
            GUI.Window(1, new Rect(Screen.width / 2 + 300, Screen.height / 2+100, 100, 100), PlayerSkillChoose, "选择技能");
        }
    }

    /// <summary>
    /// 技能选择窗口的回调函数
    /// </summary>
    /// <param name="ID"></param>
    void PlayerSkillChoose(int ID)
    {
        if (GUI.Button(new Rect(10, 20, 80, 30), "普通攻击"))
        {
            isWaitForPlayerToChooseSkill = false;
            isWaitForPlayerToChooseTarget = true;
            attackTypeName = "普通攻击";
            attackDamageMultiplier = 1f;
            Debug.Log("请选择攻击目标......");
        }
        if (GUI.Button(new Rect(10, 60, 80, 30), "英勇打击"))
        {
            isWaitForPlayerToChooseSkill = false;
            isWaitForPlayerToChooseTarget = true;
            attackTypeName = "英勇打击";
            attackDamageMultiplier = 1.5f;
            Debug.Log("请选择攻击目标......");
        }
    }

    /// <summary>
    /// 用户控制玩家选择目标状态的开启
    /// </summary>
    void Update()
    {
        if (isWaitForPlayerToChooseTarget)
        {
            targetChooseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(targetChooseRay, out targetHit))
            {
                if (Input.GetMouseButtonDown(0) && targetHit.collider.gameObject.tag == "EnemyUnit")
                {
                    currentActUnitTarget = targetHit.collider.gameObject;
                    isWaitForPlayerToChooseTarget = false;
                    //如果是远程单位直接在这里LaunchAttack,就不需要RunToTarget
                    //LaunchAttack();
                    RunToTarget();
                    
                }
            }
        }

        if (isUnitRunningToTarget)
        {
            currentActUnit.transform.LookAt(currentActUnitTargetPosition);           //单位移动的朝向
            distanceToTarget = Vector3.Distance(currentActUnitTargetPosition, currentActUnit.transform.position);           //到目标的距离,需要实时计算
            //避免靠近目标时抖动
            if (distanceToTarget > 1)
            {
                currentActUnit.GetComponent<Animator>().SetBool("Is_move", true);
                currentActUnit.transform.Translate(Vector3.forward * unitMoveSpeed * Time.deltaTime, Space.Self);         //Time.deltaTime保证速度单位是每秒
            }
            else
            {
                //停止移动
                currentActUnit.GetComponent<Animator>().SetBool("Is_move", false);
                //关闭移动状态
                isUnitRunningToTarget = false;
                //记录停下的位置
                currentactUnitStopPosition = currentActUnit.transform.position;
                //开始攻击
                LaunchAttack();
            }

        }

        if (isUnitRunningBack)
        {
            currentActUnit.transform.LookAt(currentActUnitInitialPosition);            //回来的朝向
            distanceToInitial=Vector3.Distance(currentActUnit.transform.position, currentActUnitInitialPosition);           //离初始位置的距离
            if (distanceToInitial > 1)
            {
                currentActUnit.GetComponent<Animator>().SetBool("Is_move", true);
                currentActUnit.transform.Translate(Vector3.forward * unitMoveSpeed * Time.deltaTime, Space.Self);         //Time.deltaTime保证速度单位是每秒
            }
            else
            {
                //停止移动
                currentActUnit.GetComponent<Animator>().SetBool("Is_move", false);
                //关闭移动状态
                isUnitRunningBack = false;
                //修正到初始位置和朝向
                currentActUnit.transform.position = currentActUnitInitialPosition;
                currentActUnit.transform.rotation = currentActUnitInitialRotation;

                //攻击单位回原位后行动结束,到下一个单位
                ToBattle();
            }
        }
    }
    
    /// <summary>
    /// 当前行动单位执行攻击动作
    /// </summary>
    public void LaunchAttack()
    {
        //存储攻击者和攻击目标的属性脚本
        UnitStats attackOwner = currentActUnit.GetComponent<UnitStats>();
        UnitStats attackReceiver = currentActUnitTarget.GetComponent<UnitStats>();
        //根据攻防计算伤害
        attackData = (attackOwner.attack - attackReceiver.defense + Random.Range(-2, 2)) * attackDamageMultiplier;
        //播放攻击动画
        currentActUnit.GetComponent<Animator>().SetTrigger("Attack");
        currentActUnit.GetComponent<AudioSource>().Play();

        Debug.Log(currentActUnit.name + "使用技能(" + attackTypeName + ")对" + currentActUnitTarget.name+"造成了"+ attackData + "点伤害");
        //攻击之后加个延时然后跑回来
        
        //在对象承受伤害前添加1s延迟(攻击动作和特效需要时间)
        StartCoroutine(WaitForTakeDamage(0.5f));
    }

    /// <summary>
    /// 对参战单位根据攻速计算值进行出手排序
    /// </summary>
    void listSort()
    {
        GameObject temp = battleUnits[0];
        for (int i = 0; i < battleUnits.Count - 1; i++)
        {
            float minVal = battleUnits[i].GetComponent<UnitStats>().attackTrun;       //假设i下标的是最小的值
            int minIndex = i;       //初始认为最小的数的下标

            for (int j = i + 1; j < battleUnits.Count; j++)
            {
                if (minVal > battleUnits[j].GetComponent<UnitStats>().attackTrun)
                {
                    minVal = battleUnits[j].GetComponent<UnitStats>().attackTrun;
                    minIndex = j;
                }
            }
            temp = battleUnits[i];       //把本次比较的第一个位置的值临时保存起来
            battleUnits[i] = battleUnits[minIndex];       //把最终我们找到的最小值赋给这一趟的比较的第一个位置
            battleUnits[minIndex] = temp;        //把本次比较的第一个位置的值放回这个数组的空地方,保证数组的完整性
        }

        for (int x = 0; x < battleUnits.Count; x++)
        {
            Debug.Log(battleUnits[x].name);
        }
    }

    /// <summary>
    /// 延时操作函数,避免在怪物回合操作过快
    /// </summary>
    /// <returns></returns>
    IEnumerator WaitForTakeDamage(float time)
    {
        //被攻击者承受伤害
        currentActUnitTarget.GetComponent<UnitStats>().ReceiveDamage(attackData);
        if (!currentActUnitTarget.GetComponent<UnitStats>().IsDead())
        {
            currentActUnitTarget.GetComponent<Animator>().SetTrigger("TakeDamage");
        }
        else
        {
            currentActUnitTarget.GetComponent<Animator>().SetTrigger("Dead");
        }
        yield return new WaitForSeconds(time);
        //此时开启跑回状态
        isUnitRunningBack = true;
    }
}


主要改动处为:

1. 完善了RunToTarget()函数,146行

主要用于保存角色和目标的初始位置信息,移动控制放到了Update里

2. FindTarget(),126行

原先函数结尾是调用LanuchAttack(),现在改为了RunToTarget(),

3. Update函数,213行

增加了关于isUnitRunningToTarget和isUnitRunningBack的状态判断,及对应状态下单位的行动控制

主循环ToBattle(),放到了单位跑回原位之后;

后续计划

后面准备找个远程单位的资源加到战斗里,然后再找一些攻击和受击的特效资源,顺便把UI这块优化一下

猜你喜欢

转载自blog.csdn.net/c252270036/article/details/77171204