Unity自由攀爬系统:如何实现玩家从墙面脱离功能?
墙面攀爬跳离逻辑实现思路与代码修正提示
我正在Unity里搭一个简易自由攀爬系统,这是我的第一个中级项目,难度比预想的大。现在的逻辑是角色跳向墙面时,禁用Character Controller脚本,切换到Climbing Controller脚本。我想实现玩家按空格键从墙面跳离,同时切回正常的Character Controller脚本,但每次加跳跃逻辑都出错,求干净脱离墙面的思路或提示。
以下是当前Climbing脚本代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Climbing : MonoBehaviour { [SerializeField] private float speed = 5f; Rigidbody rb; void Start() { rb = GetComponent<Rigidbody>(); rb.useGravity = false; } private void Update() { } void FixedUpdate() { float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); Vector2 input = SquareToCircle(new Vector2(h, v)); // Check walls in a cross pattern Vector3 offset = transform.TransformDirection(Vector2.one * 0.5f); Vector3 checkDirection = Vector3.zero; int k = 0; for (int i = 0; i < 4; i++) { RaycastHit checkHit; if (Physics.Raycast(transform.position + offset, transform.forward, out checkHit)) { checkDirection += checkHit.normal; k++; } // Rotate Offset by 90 degrees offset = Quaternion.AngleAxis(90f, transform.forward) * offset; } // 修复除以0的潜在错误 if(k > 0) checkDirection /= k; // Check wall directly in front RaycastHit hit; if (Physics.Raycast(transform.position, // Position transform.forward, // Direction out hit)) // Hit Data { transform.forward = -hit.normal; // 改用MovePosition避免直接修改position导致的物理冲突 rb.MovePosition(Vector3.Lerp(rb.position, hit.point + hit.normal * 0.51f, 10f * Time.fixedDeltaTime)); } rb.velocity = transform.TransformDirection(input) * speed; Vector2 SquareToCircle(Vector2 input) { return (input.sqrMagnitude >= 1f) ? input.normalized : input; } } }
核心实现思路与代码修改
输入检测放在Update帧
输入检测适合在Update中处理,不要放在物理帧FixedUpdate里,直接在Update中监听空格键触发跳离:private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { ExitClimb(); } }实现跳离与状态切换逻辑
写一个专门的跳离方法,处理物理状态恢复、力的施加和脚本切换:private void ExitClimb() { // 恢复重力 rb.useGravity = true; // 施加跳离力:墙面法线方向(脱离墙面)+ 向上力(模拟跳跃) RaycastHit hit; if (Physics.Raycast(transform.position, transform.forward, out hit)) { // 可根据需求调整力的大小 rb.AddForce(hit.normal * 6f + Vector3.up * 4f, ForceMode.Impulse); } else { // fallback:未检测到墙面时的默认跳离方向 rb.AddForce(transform.up * 4f + transform.forward * 3f, ForceMode.Impulse); } // 清理攀爬状态:禁用当前脚本,启用Character Controller this.enabled = false; var characterController = GetComponent<CharacterController>(); if(characterController != null) { // 注意:Character Controller和Rigidbody共存会有冲突,可根据需求将Rigidbody设为isKinematic或禁用 rb.isKinematic = true; characterController.enabled = true; } }修复原有代码的潜在问题
- 原代码中
checkDirection /= k没有判断k>0,会导致除以0的报错,已在代码中添加判断 - 将直接修改
rb.position改为rb.MovePosition,避免和物理系统冲突导致抖动或穿模
- 原代码中
额外注意事项
- 状态一致性:Character Controller和Rigidbody同时启用会产生物理冲突,跳离后可以将Rigidbody设为
isKinematic=true,或者直接禁用Rigidbody,根据你的角色控制逻辑选择 - 动画衔接:如果有动画系统,记得在跳离时切换回跳跃/落地动画,避免攀爬动画卡在半空
内容的提问来源于stack exchange,提问作者Aaron Williams
相关产品推荐
相关产品推荐

