You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
        }
    }
}

核心实现思路与代码修改

  1. 输入检测放在Update帧
    输入检测适合在Update中处理,不要放在物理帧FixedUpdate里,直接在Update中监听空格键触发跳离:

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ExitClimb();
        }
    }
    
  2. 实现跳离与状态切换逻辑
    写一个专门的跳离方法,处理物理状态恢复、力的施加和脚本切换:

    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;
        }
    }
    
  3. 修复原有代码的潜在问题

    • 原代码中checkDirection /= k没有判断k>0,会导致除以0的报错,已在代码中添加判断
    • 将直接修改rb.position改为rb.MovePosition,避免和物理系统冲突导致抖动或穿模

额外注意事项

  • 状态一致性:Character Controller和Rigidbody同时启用会产生物理冲突,跳离后可以将Rigidbody设为isKinematic=true,或者直接禁用Rigidbody,根据你的角色控制逻辑选择
  • 动画衔接:如果有动画系统,记得在跳离时切换回跳跃/落地动画,避免攀爬动画卡在半空

内容的提问来源于stack exchange,提问作者Aaron Williams

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 14:39:15