————————————
本篇是基于上一篇《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这块优化一下