本人游戏策划一枚,爱好游戏设计开发.
今天的这篇名字升级到“高级篇”了,因为战斗这块已经实现了原本的学习预期,接下来准备去搞搞角色创建、数据存储和主场景控制这些东西了。
这次高级篇的效果请看Gif:
新增内容如下:
1. 增加了远程兵种--弓箭手
2. 在单位等待行动阶段添加了特效(显示当前等待指令的是那个单位)
3. 最明显的就是场景加了些修饰
4. Gif因为没有音效不带感,实际是有场景和各种攻击音效的
特效资源也是从Assest Store找的免费的KTK Effect Sample Set.unitypackage,关于特效预制体的使用和参数修改看看教程就行,这里就不介绍啦。
本次涉及修改的内容如下:
UnitStats脚本,增加了一个用于区别单位攻击类型的变量。
public int attackType = 0; //单位攻击类型,0为近战,1远程
BattleTurnSystem脚本,
···在选中操作单位时根据单位当前position实例化特效预制体
···在得到攻击目标信息后(玩家的指令已经输入完毕)删除特效
下面先标出改动的几个函数,首先是ToBattle函数中获取当前行动对象时的修改
public void ToBattle()
{
remainingEnemyUnits = GameObject.FindGameObjectsWithTag("EnemyUnit");
remainingPlayerUnits = GameObject.FindGameObjectsWithTag("PlayerUnit");
//检查存活敌人单位
if (remainingEnemyUnits.Length == 0)
{
Debug.Log("敌人全灭,战斗胜利");
endImageText.text = "战斗胜利";
endImageText.color = Color.green;
endImage.SetActive(true); //显示胜利界面
}
//检查存活玩家单位
else if (remainingPlayerUnits.Length == 0)
{
Debug.Log("我方全灭,战斗失败");
endImageText.text = "战斗失败";
endImageText.color = Color.red;
endImage.SetActive(true); //显示失败界面
}
else
{
//取出参战列表第一单位,并从列表移除
currentActUnit = battleUnits[0];
battleUnits.Remove(currentActUnit);
//重新将单位添加至参战列表末尾
battleUnits.Add(currentActUnit);
//Debug.Log("当前攻击者:" + currentActUnit.name);
//获取该行动单位的属性组件
UnitStats currentActUnitStats = currentActUnit.GetComponent();
//判断取出的战斗单位是否存活
if (!currentActUnitStats.IsDead())
{
//在该单位脚底下显示特效
thisPartical = Instantiate(partical_show, currentActUnit.transform.position, Quaternion.identity) as GameObject;
//选取攻击目标
FindTarget();
}
else
{
//Debug.Log("目标死亡,跳过回合");
ToBattle();
}
}
}
然后是获取到攻击目标后销毁特效,由于玩家和怪物的选取目标方式不一样,所以有2处要增加销毁命令
void FindTarget()
{
if (currentActUnit.tag == "EnemyUnit")
{
//如果行动单位是怪物则从存活玩家对象中随机一个目标
int targetIndex = Random.Range(0, remainingPlayerUnits.Length);
currentActUnitTarget = remainingPlayerUnits[targetIndex];
Destroy(thisPartical); //选择完目标后删除标识当前行动单位的特效
attackTypeName = "无情撕咬";
//如果是远程单位直接在这里LaunchAttack,就不需要RunToTarget
if (currentActUnit.GetComponent().attackType == 1)
{
LaunchAttack();
}
else
{
RunToTarget();
}
}
else if (currentActUnit.tag == "PlayerUnit")
{
isWaitForPlayerToChooseSkill = true;
}
}
以及
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;
Destroy(thisPartical); //选择完目标后删除标识当前行动单位的特效
//如果是远程单位直接在这里LaunchAttack,就不需要RunToTarget
if (currentActUnit.GetComponent().attackType == 1)
{
LaunchAttack();
}
else
{
RunToTarget();
}
}
}
}
if (isUnitRunningToTarget)
{
currentActUnit.transform.LookAt(currentActUnitTargetPosition); //单位移动的朝向
distanceToTarget = Vector3.Distance(currentActUnitTargetPosition, currentActUnit.transform.position); //到目标的距离,需要实时计算
//避免靠近目标时抖动
if (distanceToTarget > 1)
{
currentActUnit.GetComponent().SetBool("Is_move", true);
currentActUnit.transform.Translate(Vector3.forward * unitMoveSpeed * Time.deltaTime, Space.Self); //Time.deltaTime保证速度单位是每秒
}
else
{
//停止移动
currentActUnit.GetComponent().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().SetBool("Is_move", true);
currentActUnit.transform.Translate(Vector3.forward * unitMoveSpeed * Time.deltaTime, Space.Self); //Time.deltaTime保证速度单位是每秒
}
else
{
//停止移动
currentActUnit.GetComponent().SetBool("Is_move", false);
//关闭移动状态
isUnitRunningBack = false;
//修正到初始位置和朝向
currentActUnit.transform.position = currentActUnitInitialPosition;
currentActUnit.transform.rotation = currentActUnitInitialRotation;
//攻击单位回原位后行动结束,到下一个单位
ToBattle();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class BattleTurnSystem : MonoBehaviour {
private List battleUnits; //所有参战对象的列表
private GameObject[] playerUnits; //所有参战玩家的列表
private GameObject[] enemyUnits; //所有参战敌人的列表
private GameObject[] remainingEnemyUnits; //剩余参战对敌人的列表
private GameObject[] remainingPlayerUnits; //剩余参战对玩家的列表
private GameObject currentActUnit; //当前行动的单位
public 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; //游戏结束画面
private Text endImageText; //结束画面文本内容
public GameObject bloodText; //保存血条预制体
public GameObject partical_show; //展示当前行动单位的特效
private GameObject thisPartical; //存储生成的特效
///
/// 创建初始参战列表,存储参战单位,并进行一次出手排序
///
void Start ()
{
//获取结束菜单的引用,并禁用结束菜单
endImage = GameObject.Find("ResultImage");
endImageText = endImage.transform.Find("ResultText").GetComponent();
endImage.SetActive(false);
//创建参战列表
battleUnits = new List();
//添加玩家单位至参战列表
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();
}
///
/// 判断战斗进行的条件是否满足,取出参战列表第一单位,并从列表移除该单位,单位行动
/// 行动完后重新添加单位至队列,继续ToBattle()
///
public void ToBattle()
{
remainingEnemyUnits = GameObject.FindGameObjectsWithTag("EnemyUnit");
remainingPlayerUnits = GameObject.FindGameObjectsWithTag("PlayerUnit");
//检查存活敌人单位
if (remainingEnemyUnits.Length == 0)
{
Debug.Log("敌人全灭,战斗胜利");
endImageText.text = "战斗胜利";
endImageText.color = Color.green;
endImage.SetActive(true); //显示胜利界面
}
//检查存活玩家单位
else if (remainingPlayerUnits.Length == 0)
{
Debug.Log("我方全灭,战斗失败");
endImageText.text = "战斗失败";
endImageText.color = Color.red;
endImage.SetActive(true); //显示失败界面
}
else
{
//取出参战列表第一单位,并从列表移除
currentActUnit = battleUnits[0];
battleUnits.Remove(currentActUnit);
//重新将单位添加至参战列表末尾
battleUnits.Add(currentActUnit);
//Debug.Log("当前攻击者:" + currentActUnit.name);
//获取该行动单位的属性组件
UnitStats currentActUnitStats = currentActUnit.GetComponent();
//判断取出的战斗单位是否存活
if (!currentActUnitStats.IsDead())
{
//在该单位脚底下显示特效
thisPartical = Instantiate(partical_show, currentActUnit.transform.position, Quaternion.identity) as GameObject;
//选取攻击目标
FindTarget();
}
else
{
//Debug.Log("目标死亡,跳过回合");
ToBattle();
}
}
}
///
/// 查找攻击目标,如果行动者是怪物则从剩余玩家中随机
/// 如果行动者是玩家,则获取鼠标点击对象
///
///
void FindTarget()
{
if (currentActUnit.tag == "EnemyUnit")
{
//如果行动单位是怪物则从存活玩家对象中随机一个目标
int targetIndex = Random.Range(0, remainingPlayerUnits.Length);
currentActUnitTarget = remainingPlayerUnits[targetIndex];
Destroy(thisPartical); //选择完目标后删除标识当前行动单位的特效
attackTypeName = "无情撕咬";
//如果是远程单位直接在这里LaunchAttack,就不需要RunToTarget
if (currentActUnit.GetComponent().attackType == 1)
{
LaunchAttack();
}
else
{
RunToTarget();
}
}
else if (currentActUnit.tag == "PlayerUnit")
{
isWaitForPlayerToChooseSkill = true;
}
}
///
/// 攻击者移动到攻击目标前
///
void RunToTarget()
{
currentActUnitInitialPosition = currentActUnit.transform.position;
currentActUnitInitialRotation = currentActUnit.transform.rotation; //保存移动前的位置和朝向,因为跑回来还要用
currentActUnitTargetPosition = currentActUnitTarget.transform.position; //目标的位置
//开启移动状态
isUnitRunningToTarget = true;
//移动的控制放到Update里,因为要每一帧判断离目标的距离
}
///
/// 绘制玩家选择技能的窗口
///
void OnGUI()
{
if (isWaitForPlayerToChooseSkill == true)
{
GUI.Window(1, new Rect(Screen.width / 2 + 300, Screen.height / 2+100, 100, 100), PlayerSkillChoose, "选择技能");
}
}
///
/// 技能选择窗口的回调函数
///
///
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("请选择攻击目标......");
}
}
///
/// 用户控制玩家选择目标状态的开启
///
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;
Destroy(thisPartical); //选择完目标后删除标识当前行动单位的特效
//如果是远程单位直接在这里LaunchAttack,就不需要RunToTarget
if (currentActUnit.GetComponent().attackType == 1)
{
LaunchAttack();
}
else
{
RunToTarget();
}
}
}
}
if (isUnitRunningToTarget)
{
currentActUnit.transform.LookAt(currentActUnitTargetPosition); //单位移动的朝向
distanceToTarget = Vector3.Distance(currentActUnitTargetPosition, currentActUnit.transform.position); //到目标的距离,需要实时计算
//避免靠近目标时抖动
if (distanceToTarget > 1)
{
currentActUnit.GetComponent().SetBool("Is_move", true);
currentActUnit.transform.Translate(Vector3.forward * unitMoveSpeed * Time.deltaTime, Space.Self); //Time.deltaTime保证速度单位是每秒
}
else
{
//停止移动
currentActUnit.GetComponent().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().SetBool("Is_move", true);
currentActUnit.transform.Translate(Vector3.forward * unitMoveSpeed * Time.deltaTime, Space.Self); //Time.deltaTime保证速度单位是每秒
}
else
{
//停止移动
currentActUnit.GetComponent().SetBool("Is_move", false);
//关闭移动状态
isUnitRunningBack = false;
//修正到初始位置和朝向
currentActUnit.transform.position = currentActUnitInitialPosition;
currentActUnit.transform.rotation = currentActUnitInitialRotation;
//攻击单位回原位后行动结束,到下一个单位
ToBattle();
}
}
}
///
/// 当前行动单位执行攻击动作
///
public void LaunchAttack()
{
//存储攻击者和攻击目标的属性脚本
UnitStats attackOwner = currentActUnit.GetComponent();
UnitStats attackReceiver = currentActUnitTarget.GetComponent();
//根据攻防计算伤害
attackData = (attackOwner.attack - attackReceiver.defense + Random.Range(-2, 2)) * attackDamageMultiplier;
//播放攻击动画
currentActUnit.GetComponent().SetTrigger("Attack");
currentActUnit.GetComponent().Play();
//获取攻击动画长度
float attackAnimationTime=currentActUnit.GetComponent().GetCurrentAnimatorStateInfo(0).length;
float damageAnimationTime = attackAnimationTime*0.4f;
Debug.Log(currentActUnit.name + "使用技能(" + attackTypeName + ")对" + currentActUnitTarget.name+"造成了"+ attackData + "点伤害");
//在对象承受伤害前添加延迟(伤害在动作砍下去就得出现)
StartCoroutine(WaitForTakeDamage(damageAnimationTime));
//下一个单位行动前延时
StartCoroutine(WaitForRunBack(attackAnimationTime));
}
///
/// 对参战单位根据攻速计算值进行出手排序
///
void listSort()
{
GameObject temp = battleUnits[0];
for (int i = 0; i < battleUnits.Count - 1; i++)
{
float minVal = battleUnits[i].GetComponent().attackTrun; //假设i下标的是最小的值
int minIndex = i; //初始认为最小的数的下标
for (int j = i + 1; j < battleUnits.Count; j++)
{
if (minVal > battleUnits[j].GetComponent().attackTrun)
{
minVal = battleUnits[j].GetComponent().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);
}
}
///
/// 延时操作函数,避免在怪物回合操作过快
///
///
IEnumerator WaitForTakeDamage(float time)
{
yield return new WaitForSeconds(time);
//被攻击者承受伤害
currentActUnitTarget.GetComponent().ReceiveDamage(attackData);
//实例化伤害字体并设置到画布上(字体位置和内容的控制放在它自身的脚本中)
GameObject thisText = Instantiate(bloodText) as GameObject;
thisText.transform.SetParent(GameObject.Find("BloodTextGroup").transform, false);
if (!currentActUnitTarget.GetComponent().IsDead())
{
currentActUnitTarget.GetComponent().SetTrigger("TakeDamage");
}
else
{
currentActUnitTarget.GetComponent().SetTrigger("Dead");
}
}
IEnumerator WaitForRunBack(float time)
{
yield return new WaitForSeconds(time);
//远程单位不需要跑回来,延迟后直接下个单位行动
if (currentActUnit.GetComponent().attackType == 1)
{
ToBattle();
}
else
{
//此时开启跑回状态
isUnitRunningBack = true;
}
}
}
至此战斗部分的学习和实现告一段落。
同样在学习Unity的同志们如果有疑问可以留言哈,共同学习进步