1. 攻击逻辑
在Entity中初始化两个变量,因为在每个角色几乎都拥有攻击状态。这两个变量分别是transform类,接收一个坐标和一个半径画一个圆作为攻击的判定范围
public Transform attackCheck;
public float attackCheckRadius;
为了可视化攻击范围,我们使用辅助画图帮助我们对范围进行设定
protected virtual void OnDrawGizmos()
{
Gizmos.DrawWireSphere(attackCheck.position,attackCheckRadius);
}
对于敌人的处理也是如此,不要忘记将我们实体Transform传入我们的变量。
之后将创建attack方法,作用于动画中的某一帧,在这一帧中才是真正造成了攻击,也就是应用到了某一函数方法。有经验的读者想必已经想到了事件功能。
创建一个新的脚本命名为PlayerAnimationTrigger,将动画中某一帧与这个脚本中的某一个方法相结合就可以产生当动画执行至某一帧后执行当前脚本事件的效果。
我们编写脚本在动画这一帧中检查二维空间中所有与这个圆重叠的Collider2D的组件,并将这些组件存储在colliders数组中。
private void AttackTrigger()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position,player.attackCheckRadius);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{hit.GetComponent<Enemy>().Damage();}
}
}
-
Physics2D.OverlapCircleAll
:这是Unity Physics2D模块中的一个静态方法,用于检测并返回与指定圆形区域相交的所有Collider2D组件。这个方法不依赖于物理引擎的碰撞检测(即不依赖于速度或力的计算),而是直接基于碰撞体的几何形状和位置进行判断。 -
注意:默认情况下,
Physics2D.OverlapCircleAll
会检测所有层的碰撞体。如果你只想检测特定层的碰撞体,可以使用Physics2D.OverlapCircleAll(Vector2 position, float radius, int layerMask)
的重载版本,其中layerMask
参数允许你指定要检测的层
然后获取敌人组件中Enemy脚本,若不为空,则执行其中的Damage方法。
2. 碰撞器检测
在默认情况下,两个碰撞体相互碰撞会造成位移,为了防止敌人与玩家之间造成位移,我们需要修改如下设定:点击左上角Edit,选中Project Settings,找到Physics2D中的layer Collision Matrix,将对应两个图层勾选取消即可。
3. 被击打效果
3.1 被击打变色
我们需要创建一个新的材料作为被击打时变换的样子,将shader(着色器)搜索设定为GUI
被击打变色的底层逻辑就是在某一事件发生后,将物体的材料改变,持续多久之后恢复原有状态。
新建一个协程EntityFX
public class EntityFX
{
private SpriteRenderer sr;
[SerializeField] private Material hitMat;
private Material originalMat;
private void Start()
{
sr = GetComponentInChildren<SpriteRenderer>();
originalMat = sr.material;
}
private IEumerator FlashFX()
{
sr.material = hitMat; //更改材料
yield return new WaitForSeconds(.2f); //暂停协程,等待0.2s
sr.material = originalMat;
}
}
sr.material = hitMat;
:这行代码将某个渲染器(假设为sr
)的材质更改为hitMat
。这通常用于显示一个“被击中”或“激活”的视觉效果。yield return new WaitForSeconds(.2f);
:这行代码暂停协程的执行,等待 0.2 秒。WaitForSeconds
是一个特殊的 yield 指令,用于在协程中创建等待时间。sr.material = originalMat;
:等待时间过后,将渲染器的材质改回原始材质originalMat
,从而完成闪烁效果
将上述类定义好之后,我们还需要在角色父类中实例化此类才可进行调用。
对于我们的damage方法,我们添加:
Public virtual void Damage()
{
fx.StartCoroutine("FlashFX");
}
其中StartCoroutine是一个协程函数,允许你在游戏运行时异步执行代码,而不需要使用多线程的复杂性。协程(Coroutine)可以视为一个可以暂停和恢复的函数。
协程函数通常返回一个IEnumerator
类型,这是通过System.Collections
命名空间提供的。在协程函数内部,你可以使用yield return
语句来暂停协程的执行,并在未来的某个时间点恢复执行。
3.2 击退状态
在角色父类中定义击退相关信息
[SerializeField] protected Vector2 knockbackDirection;
protected bool isKnocked;
[SerializeField] protected float knockbackDuration;
声明击退函数,被击打了向后退一定距离。
protected virtual IEnumerator Hitknockback()
{
isKnocked = true;
rb.velocity = new Vector2(knockbackDirection.x * -facingDir,knockbackDirection.y);
yield return new WaitForSeconds(knockbackDuration);
isKnocked = false;
}
//在前进函数中我们需要设定,如果被击退则无法继续前进
public void SetVelocity(float _xvelocity,float _yvelocity)
{
if (isKnocked)
{
return;
}
}
在damage方法中加入这一事件:
public virtual void Damage()
{
StartCoroutine("HitKnockback");
}
4. 防守与反击效果
4.1 敌人被反击效果
将相关动画以及动画参数,逻辑设定好之后,根据状态机创建一个新的状态,不要忘记在角色中声明该状态SkeltonStunnedState,然后声明相应的构造函数以及重构。
与之前一样,在主函数中声明相应信息。stunDuration,stunDirection。
public class SkeltonStunnedState
{
public override void Enter()
{
base.Enter();
stateTimer = enemy.StunDuration;
rb.velocity = new Vector2(-enemy.facingDir * enemy.stunDirection.x,enemy.stunDirection.y);
}
public override void Update()
{
base.Update();
if (stateTimer < 0)
{
statemachine.changeState(enemy.idleState);
}
}
}
让我们会到EntityFX渲染类中,对攻击效果进行进一步设定,有着闪烁的效果,相应的也要设定取消这个效果的方法。
private void RedcolorBlink()
{
if (sr.color != Color.white)
{
sr.color = Color.white;
}
else
{
sr.color = Color.red;
}
}
private void CancelRedBlink()
{
CancelInvoke();
sr.color = Color.white;
}
在进入状态时声明闪烁相应秒数:
public override void Enter()
{
enemy.fx.InvokeRepeating("RedColorBlink",0,.1f); //分别是调用方法,延迟时间,持续时间
}
public override void Exit()
{
base.Exit();
enemy.fx.Invoke("CancelRedBlink",0);
}
Invoke
函数是MonoBehaviour类中的一个非常有用的方法,它允许你在指定的延迟时间后自动调用另一个MonoBehaviour中的方法,而无需使用额外的线程或定时器。
4.2 防守反击
将动画的逻辑与相关参数设定好之后进行脚本的编辑
public class PlayerCounterAttackState:PlayerState
{
//构造函数
public override void Enter()
{
base.Enter();
stateTimer = player.counterAttackDuration;
player.anim.SetBool("SuccessfulCounterAttack",false); //反击成功参数先设置为0
}
public override void Exit()
{
}
public override void Update()
{
base.Update();
player.ZeroVelocity(); //防守期间不能移动
Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position,player.attackCheckRadius);
foreach (var hit in colliders) //如果防守期间检测到攻击
{
if (hit.GetComponent<Enemy>() != null)
{
stateTimer = 10;
player.anim.SetBool("SuccessfulCounterAttack",true);
}
}
if (stateTimer < 0 || triggerCalled)
{
stateMachine.ChangeState(player.idleState); //时间到了或者动画结束,自动结束该状态
}
}
}
后续对于玩家进行指定的事件操作即可。