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

Unity第一人称3D游戏:按住F键稳定倾倒汽油的实现难题

Unity第一人称游戏:汽油罐拾取与稳定倒油功能修复方案

问题概述

  • 需求:实现玩家拾取汽油罐,按住F键时稳定生成汽油(倾倒效果)
  • 现有问题:
    • 汽油生成速率极不稳定,时快时慢
    • 移动检测逻辑误判:鼠标转动相机时,会触发汽油罐的“移动”判定,不符合预期

现有脚本核心问题分析

  1. InvokeRepeating重复注册:在Update每帧判断触发条件,满足时就调用InvokeRepeating,导致同一方法被多次重复注册,生成频率异常
  2. 移动检测精度不足:仅对比Transform的X/Y轴位置,浮点数微小误差或相机旋转导致的局部位置变化,都会频繁切换isMoving状态,进一步加剧生成速率的不稳定
  3. 移动检测逻辑错误:检测汽油罐的位置变化而非玩家的实际移动,导致相机旋转时误触发

修复后的完整脚本

using UnityEngine;

public class LiquidHoldScript : MonoBehaviour
{
    pop_up pickupToggle;
    cakeslice.Outline inRangeOutline;
    Ray spawnRay;
    RaycastHit spawnLoc;

    public GameObject player;
    public Transform holdPos;
    public float pickUpRange = 2.5f;
    public GameObject PickUpUI;
    public GameObject splash;
    public cakeslice.Outline destOutline;
    // 汽油生成间隔(秒),可在Inspector面板调整
    public float pourInterval = 0.5f;

    private bool isHeld;
    private bool isVisible;
    private bool canPickUIHide;
    private float playerDistance;
    private bool isMoving;
    // 记录玩家上一帧的位置,用于检测实际移动
    private Vector3 lastPlayerPos;
    // 倒油计时器
    private float pourTimer;


    void Start()
    {
        inRangeOutline = GetComponent<cakeslice.Outline>();
        pickupToggle = PickUpUI.GetComponent<pop_up>();
        // 初始化玩家上一位置
        lastPlayerPos = player.transform.position;
    }

    void FixedUpdate()
    {
        // 检测玩家的实际移动:对比当前位置与上一帧位置的距离平方(避免开根号,提升性能)
        Vector3 currentPlayerPos = player.transform.position;
        float moveDistanceSqr = (currentPlayerPos - lastPlayerPos).sqrMagnitude;
        // 设置微小阈值,避免浮点数精度问题导致的误判
        isMoving = moveDistanceSqr > 0.0001f;
        lastPlayerPos = currentPlayerPos;
    }

    void Update()
    {
        playerDistance = Vector3.Distance(player.transform.position, transform.position);

        if (isVisible && playerDistance <= pickUpRange)
        {
            canPickUIHide = true;
            if (isHeld)
            {
                HandlePouring();
            }
            else
            {
                pickupToggle.ForceShow();
                inRangeOutline.eraseRenderer = false;
                if (Input.GetKeyDown(KeyCode.F))
                {
                    isHeld = true;
                    HoldObject();
                }
            }
        }
        else
        {
            if (canPickUIHide)
            {
                pickupToggle.ForceHide();
                inRangeOutline.eraseRenderer = true;
                canPickUIHide = false;
            }
        }
    }

    // 处理倒油逻辑
    void HandlePouring()
    {
        if (Input.GetKey(KeyCode.F) && isMoving)
        {
            // 累加计时器
            pourTimer += Time.deltaTime;
            // 达到设定间隔则生成汽油
            if (pourTimer >= pourInterval)
            {
                SpawnPetrol();
                pourTimer = 0f;
            }
        }
        else
        {
            // 停止倒油时重置计时器
            pourTimer = 0f;
        }
    }

    void SpawnPetrol()
    {
        spawnRay = new Ray(transform.position, -Vector3.up);
        if (Physics.Raycast(spawnRay, out spawnLoc))
        {
            // 在射线命中位置生成汽油特效,微小偏移避免穿透地面
            Instantiate(splash, spawnLoc.point + Vector3.up * 0.001f, splash.transform.rotation);
        }
    }

    void HoldObject()
    {
        transform.SetPositionAndRotation(holdPos.position, holdPos.rotation);
        transform.parent = holdPos;
        inRangeOutline.eraseRenderer = true;
    }

    void OnBecameVisible() => isVisible = true;
    void OnBecameInvisible() => isVisible = false;
}

关键改进点

  • 替换InvokeRepeating为计时器:通过pourTimer累加时间,达到设定间隔时生成汽油,彻底避免重复注册导致的速率异常
  • 修正移动检测逻辑:改为检测玩家的位置变化,而非汽油罐的,解决相机旋转触发误判的问题;同时使用距离平方对比+阈值,避免浮点数精度问题
  • 代码结构优化:将倒油逻辑拆分到独立方法HandlePouring,提升可读性
  • 参数可配置:将pourInterval设为公开变量,可直接在Inspector中调整汽油生成速率

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 22:24:52