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

Unity 3D FPS游戏:WASD移动、跳跃与鼠标视角控制实现问询

3D FPS游戏PlayerController脚本优化指导

需求回顾

  • WASD键控制玩家移动,空格键触发跳跃
  • 鼠标固定在游戏窗口中央,移动鼠标实现视角旋转
  • 启用刚体的freeze rotation功能,避免物理碰撞导致玩家旋转

现有脚本问题与优化方案

1. 鼠标锁定功能缺失

原脚本未实现鼠标固定窗口中央的核心需求,需在Start()方法中添加鼠标锁定逻辑:

Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;

建议补充游戏暂停/窗口失焦时的鼠标恢复逻辑,避免玩家操作受影响。

2. 视角旋转逻辑修正

原脚本将头部的X/Y轴旋转混合处理,不符合FPS游戏常规逻辑(身体负责左右转向,头部负责上下俯仰),优化后:

  • 玩家身体(transform)处理鼠标X轴输入,实现左右转向
  • 头部(head)仅处理鼠标Y轴输入,实现上下俯仰,避免身体与头部旋转混乱

修正后的视角旋转代码:

// 身体左右旋转
transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivity, 0);

// 头部上下俯仰
rotationY += Input.GetAxis("Mouse Y") * sensitivity;
rotationY = Mathf.Clamp(rotationY, headMinY, headMaxY);
head.localEulerAngles = new Vector3(-rotationY, head.localEulerAngles.y, 0);

3. 移动逻辑优化

  • 原FixedUpdate()中通过AddForce加速度限制的方式冗余,可直接设置水平速度并限制大小,更高效:
void FixedUpdate()
{
    if (h == 0 && v == 0) return;
    
    // 计算移动方向并转换为世界空间
    Vector3 moveDir = new Vector3(h, 0, v).normalized;
    moveDir = transform.TransformDirection(moveDir);
    
    // 设置目标速度,保留Y轴速度(重力/跳跃)
    Vector3 targetVelocity = new Vector3(moveDir.x * speed, body.velocity.y, moveDir.z * speed);
    body.velocity = targetVelocity;
}
  • 原代码使用head.TransformDirection会导致移动方向跟随头部上下俯仰,改为transform.TransformDirection保证移动方向与身体朝向一致,符合FPS操作习惯。

4. 跳跃逻辑修复

原脚本跳跃时直接清零水平速度(new Vector2(0, jumpForce)),导致空中无法移动,修正为保留水平速度:

if (Input.GetKeyDown(jumpButton) && GetJump())
{
    body.velocity = new Vector3(body.velocity.x, jumpForce, body.velocity.z);
}

同时优化GetJump()的射线检测起点,避免因玩家模型原点位置误判地面:

bool GetJump() 
{
    // 从玩家脚底附近发射射线,避免穿模
    Ray ray = new Ray(transform.position + Vector3.up * 0.1f, Vector3.down);
    return Physics.Raycast(ray, out RaycastHit hit, jumpDistance, layerMask);
}

5. LayerMask逻辑修正

原LayerMask设置可能导致射线检测忽略必要层,建议明确指定地面层,例如:

// 假设地面层名为"Ground"
layerMask = 1 << LayerMask.NameToLayer("Ground");
// 或者排除玩家层,避免检测自身
layerMask = ~(1 << LayerMask.NameToLayer("Player"));

优化后的完整脚本

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;
    public Transform head;
    public float sensitivity = 2f; 
    public float headMinY = -40f; 
    public float headMaxY = 40f;
    public KeyCode jumpButton = KeyCode.Space; 
    public float jumpForce = 7f; 
    public float jumpDistance = 1.2f; 

    private float h, v;
    private int layerMask;
    private Rigidbody body;
    private float rotationY;

    void Start()
    {
        body = GetComponent<Rigidbody>();
        body.freezeRotation = true;
        
        // 锁定鼠标到窗口中央
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;

        // 设置LayerMask,仅检测地面层(根据实际层名调整)
        layerMask = 1 << LayerMask.NameToLayer("Ground");
    }

    void FixedUpdate()
    {
        if (h == 0 && v == 0) return;
        
        // 计算移动方向并转换为世界空间
        Vector3 moveDir = new Vector3(h, 0, v).normalized;
        moveDir = transform.TransformDirection(moveDir);
        
        // 设置目标速度,保留Y轴速度
        Vector3 targetVelocity = new Vector3(moveDir.x * speed, body.velocity.y, moveDir.z * speed);
        body.velocity = targetVelocity;
    }

    bool GetJump() 
    {
        Ray ray = new Ray(transform.position + Vector3.up * 0.1f, Vector3.down);
        return Physics.Raycast(ray, jumpDistance, layerMask);
    }

    void Update()
    {
        h = Input.GetAxis("Horizontal");
        v = Input.GetAxis("Vertical");

        // 身体左右旋转
        transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivity, 0);

        // 头部上下俯仰
        rotationY += Input.GetAxis("Mouse Y") * sensitivity;
        rotationY = Mathf.Clamp(rotationY, headMinY, headMaxY);
        head.localEulerAngles = new Vector3(-rotationY, head.localEulerAngles.y, 0);

        // 跳跃逻辑
        if (Input.GetKeyDown(jumpButton) && GetJump())
        {
            body.velocity = new Vector3(body.velocity.x, jumpForce, body.velocity.z);
        }
    }

    void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawRay(transform.position + Vector3.up * 0.1f, Vector3.down * jumpDistance);
    }

    // 窗口失焦时恢复鼠标,避免操作异常
    void OnApplicationFocus(bool hasFocus)
    {
        if (hasFocus)
        {
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
        }
        else
        {
            Cursor.lockState = CursorLockMode.None;
            Cursor.visible = true;
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:30:16