基于Character Controller实现FPS游戏滑铲功能的技术求助
问题描述
我正在开发一款FPS速通游戏,希望为玩家移动添加滑铲功能。目前已完成蹲伏功能的实现,但在滑铲功能开发上遇到瓶颈(使用Character Controller组件)。尝试过多个教程及论坛方案,但均未成功,多数情况下功能无响应。
当前PlayerMovement脚本如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UIElements; public class PlayerMovement : MonoBehaviour { public CharacterController controller; private Rigidbody rb; public Transform cameraTransform; public HeadBobController headBobController; [Header("Movement")] private float speedMultiplier = 1f; public float speed = 12f; public float sprintSpeed = 20f; public float slopeSpeed = 0.01f; public float gravity = -9.81f; public float jumpHeight = 3f; public float sensitivity = 2f; public float cameraRotationLimit = 85f; [Header("Countermovement")] public float counterMovement = 0.175f; private float threshold = 0.01f; public float maxSlopeAngle = 35f; [Header("Sliding / Crouch")] public float slideSpeed; float originalHeight; public float reducedHeight; float targetHeight = 2f; public float timeToCrouch = 0.25f; float timeElapsed = 0; bool canStopCrouching; bool wantStopCrouch; bool isRunning = false; public bool isCrouching = false; bool canUseHeadbob = true; bool WillSlideOnSlopes = true; bool IsSliding { get { if (controller.isGrounded && Physics.Raycast(transform.position, Vector3.down, out RaycastHit slopeHit, 3f)) { hitPointNormal = slopeHit.normal; return Vector3.Angle(hitPointNormal, Vector3.up) > controller.slopeLimit; } else { return false; } } } [Header("Headbob")] [SerializeField] private float walkBobSpeed = 14f; [SerializeField] private float walkBobAmount = 0.05f; [SerializeField] private float sprintBobSpeed = 18f; [SerializeField] private float sprintBobAmount = 0.11f; [SerializeField] private float crouchBobSpeed = 8f; [SerializeField] private float crouchBobAmount = 0.025f; private float defaultYPos = 0; private float timer; private Vector3 hitPointNormal; [Header("Ground Check")] public Transform groundCheck; public float groundDistance = 0.4f; public LayerMask groundMask; Vector3 velocity; public bool isGrounded; [Header("Camera")] float cameraRotationX = 0f; float cameraRotationY = 0f; public Camera camera; public Transform orientation; [Header("Other")] public GameObject speedlines; public ParticleSystem jumpParticle; void Start() { rb = GetComponent<Rigidbody>(); controller = GetComponent<CharacterController>(); originalHeight = controller.height; defaultYPos = camera.transform.localPosition.y; //Cursor.lockState = CursorLockMode.Locked; //Cursor.visible = false; } public void Update() { isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask); if (isGrounded && velocity.y < 0) { velocity.y = -2f; } float x = Input.GetAxis("Horizontal"); float z = Input.GetAxis("Vertical"); Vector3 move = transform.right * x + transform.forward * z; move = Vector3.ClampMagnitude(move, 1f); if (Input.GetKey(KeyCode.LeftShift) && !isCrouching) { controller.Move(move * sprintSpeed * Time.deltaTime); isRunning = true; headBobController.amplitude = 0.1f; headBobController.frequency = 30f; } else { controller.Move(move * speed * speedMultiplier * Time.deltaTime); isRunning = false; headBobController.amplitude = 0.0426f; headBobController.frequency = 19.1f; } if (Physics.Raycast(camera.transform.position, Vector3.up, 1f)) { canStopCrouching = false; } else { canStopCrouching = true; } if (canStopCrouching && wantStopCrouch && !Physics.Raycast(camera.transform.position, Vector3.up, 1f)) { wantStopCrouch = false; StopCrouch(); } if (Input.GetKeyDown(KeyCode.LeftControl) && isGrounded) { //controller.height = Mathf.Lerp(reducedHeight, originalHeight, timeElapsed); StartCrouch(); } else if(Input.GetKeyUp(KeyCode.LeftControl) && isGrounded && !Physics.Raycast(camera.transform.position, Vector3.up, 1f)) { //controller.height = Mathf.Lerp(originalHeight, targetHeight, timeElapsed); StopCrouch(); } else if(Input.GetKeyUp(KeyCode.LeftControl) && isGrounded && Physics.Raycast(camera.transform.position, Vector3.up, 1f)) { wantStopCrouch = true; } if (Input.GetKey(KeyCode.LeftControl) && isRunning && isGrounded) { //Sliding } if (canUseHeadbob && isGrounded) { if (Mathf.Abs(move.x) > 0.1f || Mathf.Abs(move.z) > 0.1f) { timer += Time.deltaTime * (isCrouching ? crouchBobSpeed : isRunning ? sprintBobSpeed : walkBobSpeed); camera.transform.localPosition = new Vector3(camera.transform.localPosition.x, defaultYPos + Mathf.Sin(timer) * (isRunning ? sprintBobAmount : walkBobAmount), camera.transform.localPosition.z); } } if(WillSlideOnSlopes && IsSliding) { move += new Vector3(hitPointNormal.x, -hitPointNormal.y, hitPointNormal.z) * slopeSpeed; } if (isRunning) { speedlines.active = true; } else { speedlines.active = false; } controller.Move(move * speed * speedMultiplier * Time.deltaTime); if (Input.GetButtonDown("Jump") && isGrounded) { controller.Move(-transform.forward * 0.1f); velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); jumpParticle.Play(); } velocity.y += gravity * Time.deltaTime; controller.Move(velocity * Time.deltaTime); // Rotate the player based on mouse input float mouseX = Input.GetAxis("Mouse X") * sensitivity; float mouseY = Input.GetAxis("Mouse Y") * sensitivity; cameraRotationY += mouseX; cameraRotationX -= mouseY; cameraRotationX = Mathf.Clamp(cameraRotationX, -cameraRotationLimit, cameraRotationLimit); cameraTransform.localEulerAngles = new Vector3(cameraRotationX, cameraRotationY, 0f); transform.eulerAngles = new Vector3(0f, cameraRotationY, 0f); } private void StartCrouch() { controller.height = reducedHeight; speedMultiplier = 0.5f; isCrouching = true; } private void StopCrouch() { controller.height = originalHeight; speedMultiplier = 1f; isCrouching = false; } }
期望玩家在**奔跑(isRunning)且处于地面(isGrounded)**状态时,按下左Ctrl触发滑铲,恳请指导如何将该功能整合至现有代码中!
解决方案
1. 添加滑铲状态变量
在Sliding / Crouch区域下补充滑铲相关变量:
[Header("Sliding / Crouch")] public float slideSpeed; public float slideDuration = 0.5f; // 滑铲持续时长 public float slideDecayRate = 1.5f; // 滑铲速度衰减系数 float originalHeight; public float reducedHeight; float targetHeight = 2f; public float timeToCrouch = 0.25f; float timeElapsed = 0; bool canStopCrouching; bool wantStopCrouch; bool isSliding = false; // 滑铲状态标记 float slideTimer = 0f; // 滑铲计时器
2. 修改输入检测逻辑
替换原左Ctrl输入判断,优先触发滑铲条件:
// 替换原左Ctrl输入代码块 if (Input.GetKeyDown(KeyCode.LeftControl) && isGrounded) { if (isRunning) { StartSlide(); } else { StartCrouch(); } } else if(Input.GetKeyUp(KeyCode.LeftControl) && isGrounded && !Physics.Raycast(camera.transform.position, Vector3.up, 1f)) { if (isSliding) { StopSlide(); } else { StopCrouch(); } } else if(Input.GetKeyUp(KeyCode.LeftControl) && isGrounded && Physics.Raycast(camera.transform.position, Vector3.up, 1f)) { wantStopCrouch = true; }
3. 实现滑铲核心方法
添加滑铲启动和停止的处理方法:
private void StartSlide() { isSliding = true; isCrouching = true; controller.height = reducedHeight; speedMultiplier = 1f; // 滑铲使用独立速度,不受蹲伏减速影响 slideTimer = slideDuration; // 调整滑铲时的头部晃动参数 headBobController.amplitude = 0.08f; headBobController.frequency = 25f; } private void StopSlide() { isSliding = false; // 检查头顶无障碍物再恢复站立 if (canStopCrouching) { StopCrouch(); } else { wantStopCrouch = true; } }
4. 处理滑铲运动逻辑
替换原代码中//Sliding注释区域,添加滑铲帧更新逻辑:
// 替换原滑铲注释代码块 if (isSliding) { slideTimer -= Time.deltaTime; // 沿移动方向施加衰减的滑铲速度 Vector3 slideDir = transform.forward * Input.GetAxis("Vertical") + transform.right * Input.GetAxis("Horizontal"); slideDir = Vector3.ClampMagnitude(slideDir, 1f); float currentSlideSpeed = slideSpeed * Mathf.Lerp(1f, 0f, 1 - (slideTimer / slideDuration)); controller.Move(slideDir * currentSlideSpeed * Time.deltaTime); // 滑铲时长结束自动停止 if (slideTimer <= 0f) { StopSlide(); } // 滑铲时强制取消奔跑状态 isRunning = false; speedlines.SetActive(false); }
5. 修复移动逻辑冲突
原代码存在重复的controller.Move调用,导致移动速度异常,删除以下重复行:
controller.Move(move * speed * speedMultiplier * Time.deltaTime);
6. 适配滑铲头部晃动
修改头部晃动逻辑,添加滑铲状态判断:
if (canUseHeadbob && isGrounded) { if (Mathf.Abs(move.x) > 0.1f || Mathf.Abs(move.z) > 0.1f) { float bobSpeed = isCrouching ? crouchBobSpeed : isRunning ? sprintBobSpeed : (isSliding ? 22f : walkBobSpeed); float bobAmount = isCrouching ? crouchBobAmount : isRunning ? sprintBobAmount : (isSliding ? 0.07f : walkBobAmount); timer += Time.deltaTime * bobSpeed; camera.transform.localPosition = new Vector3(camera.transform.localPosition.x, defaultYPos + Mathf.Sin(timer) * bobAmount, camera.transform.localPosition.z); } }
7. 参数配置建议
在Unity Inspector面板中设置:
slideSpeed:25-30(略高于奔跑速度)slideDuration:0.4-0.6秒slideDecayRate:保持1.5即可
内容的提问来源于stack exchange,提问作者Lennard
相关产品推荐
相关产品推荐

