【Unity】动作游戏开发实战详细分析-26-组合攻击
基本思想
在游戏中,有些攻击方法必须要等待条件触发后才可以执行,并且根据情况的不同会发挥不同的效果,这是一种有前置条件要求的组合攻击方式。
脚本逻辑结构
我们先看一下这个模块的脚本结构
ComposeAttackController脚本中存放着不同的攻击组合类型,通过Update事件函数每帧更新当前可触发的组合攻击,并信息存储在索引字段中。上下文结构信息存放了组合攻击所需要的角色自身组件,例如Animator、Transform等。
模块代码实现
首先这是组合攻击的抽象类,定义了基本的方法
- CanTrigger,用于判断组合技是否达到了触发条件,他的输入参数要求信息上下文
- Trigger,用于处理触发逻辑
public abstract class ComposeAttackBase : ScriptableObject
{
public abstract bool CanTrigger(ComposeAttackContext context, bool prepareTrigger);
public abstract IEnumerator Trigger(ComposeAttackContext context);
}
public struct ComposeAttackContext
{
public Transform CasterTransform { get; set; }
public Animator Animator { get; set; }
}
然后就是组合攻击控制器
该控制器用于控制当前控制器下所有的攻击组件,并且通过Update进行每帧判断,并将可使用的攻击索引存储在字段中。并提供了外部的触发接口对对应的攻击组件进行触发
public class ComposeAttackController : MonoBehaviour
{
[SerializeField] Animator animator = null;//上下文所需接口,面板暴露参数
[SerializeField] ComposeAttackBase[] composeAttackArray = null;//组件列表面板暴露参数
public int TriggerableComposeAttackIndex { get; private set; }//当前已触发的组合技能索引
public ComposeAttackBase[] GetComposeAttackArray()//对外提供组合技能数组列表
{
return composeAttackArray;
}
public void Update()//每一帧更新组合技能是否触发逻辑,但可修改.enabled关闭脚本更新
{
var context = new ComposeAttackContext() { Animator = animator, CasterTransform = transform };
for (int i = 0; i < composeAttackArray.Length; i++)
{
var item = composeAttackArray[i];
if (item.CanTrigger(context, true))//触发条件检测
{
TriggerableComposeAttackIndex = i;
break;
}
}
}
public IEnumerator TriggeredComposeSkill(int index)//组合技能的触发接口
{
if (index > composeAttackArray.Length - 1)
throw new ArgumentOutOfRangeException();
var context = new ComposeAttackContext() { Animator = animator, CasterTransform = transform };
yield return composeAttackArray[index].Trigger(context);
}
}
测试代码
public class ComposeAttackController_Test : MonoBehaviour
{
public ComposeAttackController composeAttackController;
public Animator[] animators;
void Update()
{
if (InputCache.AttackButton)
{
StartCoroutine(composeAttackController.TriggeredComposeSkill(composeAttackController.TriggerableComposeAttackIndex));
for (int i = 0; i < animators.Length; i++)
{
animators[i].enabled = false;
animators[i].enabled = true;
animators[i].Play("LightHit", 0, 0);
}
}
}
}
public class InputCache : MonoBehaviour
{
public static bool AttackButton { get; set; }
void Update()
{
AttackButton = Input.GetKeyDown(KeyCode.J);
}
}
[CreateAssetMenu(fileName = "ComposeAttack1", menuName = "ComposeAttacks/Attack1")]
public class ComposeAttack1 : ComposeAttackBase
{
public float yOffset = 1f;//检测碰撞的y轴偏移
public Vector3 size = new Vector3(1f, 2f, 1f);//检测碰撞的大小
public Vector4 aroundOffset = new Vector4(0.5f, 0.5f, 0.5f, 0.5f);//前后左右检测距离偏移
public LayerMask layerMask;
bool mIsForwardAndBackword;
public override bool CanTrigger(ComposeAttackContext context, bool prepareTrigger)
{
var upAxis = -Physics.gravity.normalized;//垂直轴
var right = Vector3.ProjectOnPlane(context.CasterTransform.right, upAxis);//投影的右侧方向
var forward = Vector3.ProjectOnPlane(context.CasterTransform.forward, upAxis);//投影的前方
var upAxisOffset = upAxis * yOffset;//垂直轴偏移
var forwardFlag = Physics.CheckBox(context.CasterTransform.position
+ upAxisOffset + forward * aroundOffset.x
, size
, Quaternion.identity
, layerMask);
var backwardFlag = Physics.CheckBox(context.CasterTransform.position
+ upAxisOffset + (-forward) * aroundOffset.y
, size
, Quaternion.identity
, layerMask);
if (forwardFlag && backwardFlag)//前后都有敌人
{
if (prepareTrigger) mIsForwardAndBackword = true;
return true;
}
var leftFlag = Physics.CheckBox(context.CasterTransform.position
+ upAxisOffset + (-right) * aroundOffset.z
, size
, Quaternion.identity
, layerMask);
var rightFlag = Physics.CheckBox(context.CasterTransform.position
+ upAxisOffset + right * aroundOffset.w
, size
, Quaternion.identity
, layerMask);
if (rightFlag && leftFlag)//左右都有敌人
{
if (prepareTrigger) mIsForwardAndBackword = false;
return true;
}
return false;
}
public override IEnumerator Trigger(ComposeAttackContext context)
{
Debug.Log("Trigger!");
yield return null;
if (mIsForwardAndBackword)//前后都有敌人的情况
{
context.Animator.Play("Range_Attack", 0, 0);
}
else//左右都有敌人的情况
{
context.CasterTransform.forward = context.CasterTransform.right;
//先将角色朝向切至右边
context.Animator.Play("Range_Attack", 0, 0);
}
}
}