本文将深入解析一个包含多种高级移动机制的Unity 2D角色控制系统,涵盖基础移动、跳跃优化、闪避机制、墙壁碰撞检测等核心功能,结合动画状态管理与物理特效实现
核心移动
private void HandleMovement()
{
// 状态检查:在闪避、招架或攻击动画期间禁止移动
if (isDodging || isParrying || anim.GetBool("IsAttacking")) return;
// 获取水平输入并计算移动速度(根据奔跑状态切换速度)
dirX = Input.GetAxisRaw("Horizontal") * (isRunning ? runSpeed : walkSpeed);
// 空中移动限制:空中时移动速度不超过行走速度
if (!IsGrounded())
{
dirX = Mathf.Clamp(dirX, -walkSpeed, walkSpeed);
}
// 墙壁碰撞检测:当面向墙壁时禁止横向移动
if (isTouchingWall && Mathf.Sign(dirX) == (sprite.flipX ? -1 : 1))
{
dirX = 0;
}
// 平滑速度过渡:在地面时快速加速,空中时较慢调整速度
float currentSpeed = Mathf.MoveTowards(
rigidbody2D.linearVelocity.x, // 当前水平速度
dirX, // 目标速度
IsGrounded() ? 50f : 25f * Time.deltaTime // 加速度系数(地面/空中不同)
);
// 应用最终速度到刚体,保持原有垂直速度不变
rigidbody2D.linearVelocity = new Vector2(currentSpeed, rigidbody2D.linearVelocity.y);
}
- 状态检查:优先处理角色特殊状态,确保在这些状态下无法移动
- 速度计算:根据输入方向和状态(行走/奔跑)计算基础移动速度
- 空中限制:防止空中移动速度超过地面行走速度,增加游戏真实性
- 墙壁检测:基于角色朝向和墙壁位置,阻止穿墙现象
- 平滑过渡:使用MoveTowards实现速度渐变效果,地面加速更快,空中惯性更大
- 物理更新:最终将计算后的速度应用到刚体组件,保持物理系统的正确性
地面检测判断
private bool IsGrounded()
{
// 创建地面检测区域的尺寸(横向保留90%碰撞体宽度,高度为0.1单位的薄层)
Vector2 boxSize = new Vector2(coll.bounds.size.x * 0.9f, 0.1f);
// 计算检测区域中心点:
// X轴:保持与碰撞体中心对齐
// Y轴:位于碰撞体底部下方(偏移量为检测距离的一半)
Vector2 boxCenter = new Vector2(
coll.bounds.center.x,
coll.bounds.min.y - groundCheckDistance * 0.5f
);
// 执行盒型碰撞检测:
// - 检测位置:计算得到的中心点
// - 检测范围:预设的盒子尺寸
// - 检测角度:0度(不旋转)
// - 检测图层:仅限可跳跃地面层
return Physics2D.OverlapBox(
boxCenter,
boxSize,
0f,
jumpableGround
);
}
注释说明:
- 区域设计原理:
• 横向保留90%宽度:避免两侧墙壁的误判
• 极薄的高度(0.1单位):创建精确的地面接触平面
• 下移检测位置:确保检测区域延伸到角色底部下方 - 参数细节:
• groundCheckDistance:控制检测区域下移距离的参数
• jumpableGround:专门的地面层级,过滤不可跳跃的表面 - 检测特性:
• 使用OverlapBox相比射线检测:更稳定地处理平台边缘
• 适合各种地面形状:斜坡、移动平台等复杂地形
• 性能优化:仅检测指定图层,避免不必要的碰撞计算 - 返回值说明:
• true:角色底部与可跳跃地面存在有效接触
• false:角色处于空中或接触不可跳跃表面
攻击模块
private void HandleAttack()
{
// 攻击条件检查:招架状态/冷却中/攻击动画播放期间禁止发起新攻击
if (isParrying || Time.time < lastAttackTime + attackCooldown || anim.GetBool("IsAttacking")) return;
// 鼠标左键攻击触发检测
if (Input.GetMouseButtonDown(0))
{
isAttacking = true; // 设置攻击状态标志
lastAttackTime = Time.time; // 记录本次攻击时间戳
anim.SetTrigger("Attacking"); // 触发攻击动画状态机
}
}
注释说明:
- 状态优先级管理:
• 招架状态优先于攻击(isParrying)
• 冷却时间机制确保攻击间隔(attackCooldown)
• 动画状态检查防止动画叠加(IsAttacking) - 输入检测策略:
• 使用GetMouseButtonDown确保精确的单次点击响应
• 限定主攻击按钮为鼠标左键(0号按钮) - 动画同步机制:
• 通过Animator触发器驱动攻击动画
• 动画控制器应自动重置IsAttacking状态
• 攻击动画需包含实际伤害判定帧 - 冷却系统实现:
• 基于Unity引擎时间(Time.time)
• 计算公式:当前时间 > 上次攻击时间 + 冷却时长
• 支持通过attackCooldown参数灵活调整平衡性
墙壁检测
private void UpdateWallStatus()
{
// 保存上一帧的墙壁接触状态用于状态变化检测
bool wasTouchingWall = isTouchingWall;
isTouchingWall = false;
// 确定检测方向(根据角色朝向)
Vector2 checkDirection = sprite.flipX ? Vector2.left : Vector2.right;
// 计算射线总长度(碰撞体半宽 + 额外检测距离)
float rayLength = coll.bounds.extents.x + wallCheckDistance;
// 多射线平行检测系统(覆盖角色高度范围)
for (int i = 0; i < wallRaysCount; i++)
{
// 计算每条射线的起点:
// - 水平位置:碰撞体边缘(根据朝向)
// - 垂直位置:沿角色高度等间距分布(中心对称)
Vector2 origin = (Vector2)coll.bounds.center +
(checkDirection * coll.bounds.extents.x) +
Vector2.up * (wallRaySpacing * (i - (wallRaysCount - 1) / 2f));
// 执行射线检测:
RaycastHit2D hit = Physics2D.Raycast(
origin, // 射线起点
checkDirection, // 检测方向
wallCheckDistance, // 检测距离
wallLayer // 限定墙壁图层
);
// 命中处理:任意射线命中即视为接触墙壁
if (hit.collider != null)
{
isTouchingWall = true;
break;
}
}
// 墙壁接触即时响应:当新接触墙壁且在空中时,立即停止横向移动
if (!wasTouchingWall && isTouchingWall && !IsGrounded())
{
rigidbody2D.linearVelocity = new Vector2(0, rigidbody2D.linearVelocity.y);
}
}
注释说明:
- 多射线检测设计:
• wallRaysCount:控制射线数量,影响检测精度
• wallRaySpacing:射线垂直间距,适应不同角色高度
• 中心对称分布:确保上下方都有检测覆盖 - 方向计算逻辑:
• sprite.flipX 决定角色左右朝向
• checkDirection 动态跟随角色转向
• rayLength 包含碰撞体自身宽度避免穿模 - 物理响应机制:
• 仅在状态变化时(首次接触)重置速度
• 保留垂直速度实现墙滑效果
• 地面状态豁免(避免与地面碰撞冲突) - 参数协同工作:
• wallCheckDistance:控制检测灵敏度
• wallLayer:过滤非墙壁物体
• extents.x:自动适配不同尺寸碰撞体
闪避
private void HandleDodgeInput()
{
// 闪避输入检测系统
// 监听左右方向键的瞬时按下事件(A/D 或 左/右箭头)
if (Input.GetKeyDown(KeyCode.A))
{
// 检测左方向双击操作
CheckDoubleTap(KeyCode.A);
}
else if (Input.GetKeyDown(KeyCode.D))
{
// 检测右方向双击操作
CheckDoubleTap(KeyCode.D);
}
}
注释说明:
- 输入响应策略:
• 使用GetKeyDown
确保精确捕获按键按下瞬间
• 分离处理左右方向输入(避免同时触发双向操作)
• 基于键位设计:A/D 对应左右闪避,符合多数键盘控制方案 - 双击检测机制:
•CheckDoubleTap
方法应包含时间阈值判断(例如:0.3秒内两次按下)
• 方向键参数传递确保区分左右闪避动作
• 状态机处理:dodgeCooldown、dodgeDirection等参数 - 条件分支设计:
• else if 结构防止同时响应多个方向输入
• 优先级:优先检测最后按下的有效方向
• 与移动系统协同:闪避方向应与当前输入方向一致 - 可扩展性考虑:
• 新增闪避方向只需添加新分支(例如:上闪避加入W键检测)
• 可通过KeyCode参数化设计支持键位重映射
• 与游戏手柄输入系统兼容(可替换为JoystickButton检测)
该实现通过分离输入检测与核心逻辑,使闪避系统具备以下特性:
• 明确的输入方向性
• 防止误触发的条件过滤
• 可配置的双击灵敏度
• 平滑集成到角色状态机中
双击判断
private void CheckDoubleTap(KeyCode currentKey)
{
// 双击判定逻辑:满足所有条件时触发闪避
if (Time.time - lastKeyPressTime < doubleTapThreshold && // 时间间隔在有效范围内
currentKey == lastKeyPressed && // 连续按下相同方向键
!isDodging && // 非闪避状态中
Time.time >= lastDodgeTime + dodgeCooldown && // 冷却时间已结束
IsGrounded()) // 地面状态验证
{
StartCoroutine(PerformDodge(currentKey)); // 启动闪避协程
}
// 更新按键记录(无论是否触发都需更新)
lastKeyPressed = currentKey; // 记录最后一次按下的方向键
lastKeyPressTime = Time.time; // 记录最后一次按键时间戳
}
注释说明:
- 核心判定条件:
• 时间窗口:doubleTapThreshold
控制两次按键最大间隔(典型值0.2-0.3秒)
• 方向一致性:确保两次输入为同方向操作
• 状态验证:过滤空中闪避/冷却中/重复触发等情况 - 参数协同机制:
•lastDodgeTime
记录上次闪避结束时间
•dodgeCooldown
控制闪避技能冷却时间
•IsGrounded()
确保仅地面允许闪避 - 执行流程:
• 满足条件时调用异步闪避协程(处理位移/动画/无敌帧)
• 无论是否触发都更新按键历史,为下次检测做准备 - 防误触设计:
• 时间戳比对防止长按触发
• 状态标志位防止动作叠加
• 地面检测防止空中滥用
该实现通过分层验证确保精确输入识别,同时具备以下特性:
• 自然的手感响应(符合玩家双击直觉)
• 防止按键抖动造成的误触发
• 可灵活调整的平衡参数(阈值/冷却时间)
• 与角色状态机的深度集成
闪避与特效
private IEnumerator PerformDodge(KeyCode directionKey)
{
// 闪避状态初始化
isDodging = true; // 设置闪避状态标志
lastDodgeTime = Time.time; // 记录闪避起始时间(用于冷却计算)
anim.SetTrigger("Dodge"); // 触发闪避动画状态机
// 特效轨迹起点计算(降低到角色脚部位置)
Vector3 startPos = transform.position + new Vector3(0, -0.3f, 0);
// 运动参数设置
float dodgeDirection = directionKey == KeyCode.A ? -1f : 1f; // 方向转换(A键左,D键右)
rigidbody2D.linearVelocity = new Vector2(
dodgeDirection * dodgeSpeed, // 设置高速水平移动
rigidbody2D.linearVelocity.y // 保持原有垂直速度(支持空中闪避)
);
// 闪避动作持续时间控制
yield return new WaitForSeconds(dodgeDuration);
// 残影特效生成(仅在完整完成闪避时触发)
if (isDodging)
{
Vector3 endPos = transform.position + new Vector3(0, -0.3f, 0);
GameObject lineObj = Instantiate(
lineEffectPrefab,
Vector3.zero, // 世界坐标原点(通过SetPositions精确定位)
Quaternion.identity
);
LineRenderer line = lineObj.GetComponent<LineRenderer>();
line.SetPositions(new Vector3[] {
endPos + Vector3.right * 0.2f, // 终点右侧偏移(增强视觉效果)
startPos + Vector3.left * 0.2f // 起点左侧偏移(形成动态拖尾)
});
StartCoroutine(FadeLine(line)); // 启动渐隐效果协程
}
// 状态重置
rigidbody2D.linearVelocity = new Vector2(0, rigidbody2D.linearVelocity.y); // 停止水平运动
isDodging = false; // 清除闪避状态
}
代码解析:
- 状态管理机制
•isDodging
标志位控制角色状态机,阻止其他动作(如攻击/跳跃)的触发
•lastDodgeTime
记录用于冷却时间计算,通过dodgeCooldown
参数控制闪避频率 - 运动控制
• 通过修改linearVelocity
实现高速位移,保持物理系统交互性
•dodgeSpeed
参数控制移动速度,dodgeDuration
控制位移时间(移动距离 = speed × duration) - 视觉效果实现
• 轨迹特效:在闪避路径生成带有偏移的线条,Vector3(0, -0.3f, 0)
将锚点下移到脚部
• 动态拖尾:通过起点/终点的左右偏移(±0.2f)创建斜线效果,增强速度感
• 渐隐处理:FadeLine
协程应实现透明度渐变和对象销毁,避免内存泄漏 - 安全防护设计
•yield return
后二次检查isDodging
状态,防止被外部系统中断导致的异常特效
• 强制重置水平速度为0,确保闪避结束后立即停止,避免惯性滑动
该实现通过协程精确控制闪避流程,整合了运动、动画与特效系统,建议配合以下参数范围使用:
•dodgeSpeed
: 15-25(根据角色尺寸调整)
•dodgeDuration
: 0.3-0.5秒(兼顾响应速度与表现力)
•doubleTapThreshold
: 0.2-0.3秒(符合人体操作习惯)
跳跃
private void HandleJump()
{
// 跳跃输入处理系统
// 状态检查:当按下跳跃键且角色着地时触发跳跃
if (Input.GetButton("Jump") && IsGrounded())
{
anim.SetTrigger("Jump"); // 触发跳跃动画状态机
rigidbody2D.linearVelocity = new Vector2(
rigidbody2D.linearVelocity.x, // 保持原有水平速度
jumpForce // 应用垂直跳跃力
);
}
}
- 输入检测机制
• 使用Input.GetButton("Jump")
检测跳跃键(默认空格键)
• 支持按键缓冲:允许在着地前几帧预输入跳跃指令
• 与GetButtonDown
的区别:持续按压也会触发(根据项目需求选择) - 跳跃条件验证
•IsGrounded()
确保地面接触状态
• 防止空中连跳的二段跳限制
• 可与 coyote time 机制配合使用(离地后短暂时间仍允许跳跃) - 动画系统集成
• 通过 Animator Trigger 驱动跳跃动画
• 动画控制器应包含落地检测自动切换回 idle 状态 - 物理实现方式
• 直接设置垂直速度(替代 AddForce 实现更可控的跳跃高度)
• 保留水平速度:允许空中移动影响跳跃轨迹
• jumpForce 参数建议范围:12-15(根据重力比例调整)
格挡系统
private void HandleParry()
{
// 招架输入检测系统
// 条件:鼠标右键按下 + 冷却时间结束
if (Input.GetMouseButtonDown(1) && Time.time > lastParryTime + parryCooldown)
{
StartCoroutine(ParryAction()); // 启动招架协程
}
}
private System.Collections.IEnumerator ParryAction()
{
// 招架状态初始化
isParrying = true; // 设置全局招架标志
lastParryTime = Time.time; // 记录本次招架时间戳
parryCollider.enabled = true; // 激活招架判定区域
anim.SetTrigger("Parry"); // 触发招架动画
// 维持招架状态持续时间
yield return new WaitForSeconds(parryDuration);
// 状态重置
isParrying = false; // 清除招架状态
parryCollider.enabled = false; // 关闭招架判定区域
Debug.Log("Parry cooldown started"); // 调试信息(发布时可移除)
}
- 核心机制
• 鼠标右键(1号按钮)触发瞬间招架
• 冷却系统通过lastParryTime
+parryCooldown
实现
• 独立碰撞体parryCollider
处理招架判定区域 - 参数协同
•parryDuration
:招架有效帧持续时间(通常0.2-0.5秒)
•parryCooldown
:招架硬直时间(平衡性关键参数)
• 动画时长应与parryDuration
保持一致 - 状态管理
•isParrying
标志影响角色其他行为(如移动/攻击)
• 碰撞体精准控制判定时机,避免持续激活
• 协程实现精准的时序控制