Unity第一人称3D游戏:按住F键稳定倾倒汽油的实现难题
Unity第一人称游戏:汽油罐拾取与稳定倒油功能修复方案
问题概述
- 需求:实现玩家拾取汽油罐,按住F键时稳定生成汽油(倾倒效果)
- 现有问题:
- 汽油生成速率极不稳定,时快时慢
- 移动检测逻辑误判:鼠标转动相机时,会触发汽油罐的“移动”判定,不符合预期
现有脚本核心问题分析
- InvokeRepeating重复注册:在Update每帧判断触发条件,满足时就调用
InvokeRepeating,导致同一方法被多次重复注册,生成频率异常 - 移动检测精度不足:仅对比Transform的X/Y轴位置,浮点数微小误差或相机旋转导致的局部位置变化,都会频繁切换
isMoving状态,进一步加剧生成速率的不稳定 - 移动检测逻辑错误:检测汽油罐的位置变化而非玩家的实际移动,导致相机旋转时误触发
修复后的完整脚本
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
相关产品推荐
相关产品推荐

