Unity turn-based combat system (intermediate level) - advanced

I found the project file. The old version of the script reported an error. I solved the error under the new version 2019.4.21f1c1, and the battle scene can run normally.

Students who need it can click the address below to download (just follow it, no points are required). I wish everyone will succeed in learning as soon as possible.

Project package download

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

This article is based on the advancement of the previous article " Unity Turn-Based Combat System (Intermediate) ". Please read this article after reading the previous article >> Portal

What content has been added to this upgrade? Please see Gif

The new content is unit actions, which are roughly as follows:

1. After receiving the attack command, the unit will run to the target

2. After performing an attack, run back to the original state again.

3. If it is a long-range unit, directly perform the attack action (since there are no archer resources here, only comments are added to the corresponding positions in the code)

Although there is not much content, after adding this part, the overall battle will be more complete and have a certain degree of enjoyment. It would be perfect if special effects are added.

This change only adjusts one script, which is the core turn control script BattleTurnSystem. The content is as follows:

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


The main changes are:

1. Improved RunToTarget() function, line 146

It is mainly used to save the initial position information of characters and targets, and the movement control is placed in Update.

2. FindTarget(), line 126

The original function ended by calling LanuchAttack(), but now it has been changed to RunToTarget().

3. Update function, line 213

Added state judgments about isUnitRunningToTarget and isUnitRunningBack, and action control of units in corresponding states.

The main loop ToBattle() is placed after the unit runs back to its original position;

Follow-up plan

Later, I plan to find resources for remote units to add to the battle, and then find some special effect resources for attacks and attacks. I will also optimize the UI.

Guess you like

Origin blog.csdn.net/c252270036/article/details/77171204