精确获取碰撞点
核心是获取武器碰撞盒最顶点,然后获取敌人碰撞盒距离该点最近的点
/// <summary>
/// 获取获取武器前端位置 碰撞盒最左或最右顶点
/// </summary>
/// <param name="collider"></param>
/// <param name="facingRight"></param>
/// <returns></returns>
public static Vector3 GetCollider2DEdgePoint(Transform deployer, bool facingRight)
{
var collider = deployer.GetChild(0).GetComponentInChildren<CapsuleCollider2D>();
// 获取碰撞器的包围盒
Bounds bounds = collider.bounds;
// 计算面向的方向向量(右或左)
Vector2 attackDirection = facingRight ? Vector2.right : Vector2.left;
// 根据面向方向和碰撞器的边界来获取边缘点
Vector3 edgePoint = bounds.center + (Vector3)(attackDirection * bounds.extents.x);
// 如果需要在局部空间中获取边缘点,需要将世界坐标转换为局部坐标
// edgePoint = deployer.InverseTransformPoint(edgePoint);
return edgePoint;
}
//使用
public void CreateHit(Collider2D other, CharacterStats target, bool isCrit = false)
Vector3 edgePoint = Tool.GetCollider2DEdgePoint(transform, Entity.facingRight);
var hitPosition = other.ClosestPoint(edgePoint);
1. 给攻击帧添加碰撞盒
优点:配置直观,无需事件触发
缺点:无法定制,效率低
检测放在子物体,可以控制旋转
添加触发器事件
注意OnTriggerEnter2D只会在挂载了collider的组件上触发
protected virtual void OnTriggerEnter2D(Collider2D other)
{
Debug.Log(this.name + "命中" + other);
entity.AttackTrigger(other);
}
在角色脚本执行攻击检测
public override void AttackTrigger(Collider2D other)
{
if (other.gameObject.layer == LayerMask.NameToLayer("Enemy"))
{
base.AttackTrigger(other);
AttackColliderCheck<Enemy>(other, (enemy) => enemy.BeDamage(stats));
}
}
2. 为每个攻击动画状态使用射线检测
优点:效率高于碰撞盒,可以定制,比如增加攻击范围
缺点:配置麻烦不直观
部分代码,仅供参考
定义每个动画的检测范围
[Header("攻击状态对应的碰撞盒")]
public Dictionary<Entity.States, AttackRanges> attackRangeDict = new();
//索引是攻击的状态 + 连击数,获取对应攻击的范围
[System.Serializable]
public class AttackRanges
{
/// <summary>
/// 对应的连击次数 作为索引
/// </summary>
public AttackRange[] ranges = new AttackRange[1];
//索引器
public AttackRange this[int index]
{
get
{
// return ranges.FirstOrDefault(x => x.combo == index);
//如果没有,默认用第一个
if (index >= ranges.Length)
return ranges[0];
return ranges[index];
}
}
}
[System.Serializable]
public class AttackRange
{
public SerializableVector2 offset = new();
public SerializableVector2 size = new();
public float rotation = 0;
public UnityEngine.Vector2 Offset => offset.ToVector2();
public UnityEngine.Vector2 Size => size.ToVector2();
}
添加帧事件
private void AttackTrigger()
{
entity.AttackTrigger();
}
执行检测与绘制范围
/// <summary>
/// 攻击检测
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
protected void AttackColliderCheck<T>(Action<T> action) where T : Entity
{
AttackRange attackRange = stats.attackRangeDict[States.PrimaryAttack][ComboCounter];
var offset = facingRight ? attackRange.offset.ToVector2() : new Vector2(-attackRange.offset.x, attackRange.offset.y);
var rotation = facingRight ? attackRange.rotation : -attackRange.rotation;
Collider2D[] colliders = Physics2D.OverlapBoxAll(offset, attackRange.size.ToVector2(), rotation);
foreach (Collider2D hit in colliders)
{
if (hit.TryGetComponent<T>(out T entity))
{
action?.Invoke(entity);
}
}
}
// 默认情况下就会在编辑模式下运行
protected virtual void OnDrawGizmos()
{
// Vector3 boxCenter = groundCheck.position + Vector3.down * groundCheckDistance / 2;
// Gizmos.DrawCube(boxCenter, new Vector3(groundCheckWidth, groundCheckDistance, 0));
Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));//绘制一条从 from(前面的) 开始到 to(后面的) 的线。
Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));//绘制一条从 from(前面的) 开始到 to(后面的) 的线。
if (stats == null) return;
Gizmos.color = Color.red;
var attackRange = stats.attackRangeDict.ContainsKey(States.PrimaryAttack) ? stats.attackRangeDict?[States.PrimaryAttack]?[ComboCounter] : new AttackRange();
Vector3 center = (Vector3)attackRange.Offset;
// 设置 Gizmos 的变换矩阵
Matrix4x4 oldMatrix = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.TRS(center, Quaternion.Euler(0, 0, attackRange.rotation), Vector3.one);
Gizmos.DrawWireCube(transform.position + (Vector3)attackRange.Offset, (Vector3)attackRange.Size);
// 恢复原来的矩阵
Gizmos.matrix = oldMatrix;
}
3. 所有动画共用一个射线检测
优点:简单
缺点:不精确
在帧事件触发,调用检测方法即可
/// <summary>
/// 攻击检测
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
protected void AttackColliderCheck<T>(Action<T> action) where T : Entity
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(attackCheck.position, attckCheckRadius);
foreach (Collider2D hit in colliders)
{
if (hit.TryGetComponent<T>(out T entity))
{
action?.Invoke(entity);
}
}
}