Unity移动端塔防游戏:多TowerPlacement脚本冲突问题求助
问题解决:多塔楼选择对象的实例化异常
核心问题原因
- 多个
TowerPlacement实例同时监听同一个全局输入动作,触发时所有实例都会执行OnPressed逻辑,导致第一个实例的状态先被激活,后续操作被干扰 - 输入动作的订阅/取消订阅逻辑有漏洞,旧实例的回调未被完全清除,残留逻辑影响新实例
- 缺少实例间的状态互斥,同一时间允许多个实例进入放置状态,造成预制体实例化混乱
解决方案步骤
1. 改用独立点击触发(最稳妥)
抛弃全局输入绑定,直接给每个塔楼选择对象绑定点击事件,避免多实例抢输入:
- 给每个塔楼选择GameObject添加
Button组件(如果是UI)或EventTrigger(如果是3D对象) - 将按钮的
OnClick事件直接绑定到该对象上TowerPlacement脚本的StartPlacement方法(替换原来的OnPressed)
2. 修复输入订阅逻辑
如果坚持用Input System全局输入,必须确保每个实例只在自己激活时绑定输入,且销毁时完全取消:
private void OnDisable() { // 彻底取消所有输入回调绑定 _touchPressedAction.started -= OnPlacing; _touchPressedAction.canceled -= Hold; _touchPlacePressAction.canceled -= OnPressed; _touchPressAction.performed -= OnPressed; // 销毁时自动取消当前放置 CancelCurrentPlacement(); }
3. 增加实例状态互斥
确保同一时间只有一个实例处于放置状态,避免逻辑冲突:
public void StartPlacement() { // 检查其他实例,强制取消正在进行的放置 TowerPlacement[] allPlacements = FindObjectsOfType<TowerPlacement>(); foreach (var placement in allPlacements) { if (placement != this && placement._isPlacing) { placement.CancelCurrentPlacement(); break; } } if (_isPlacing || currentState != State.Default) return; _instPreviewObj = Instantiate(_previewTower, transform.position, Quaternion.identity); _instPreviewObj.transform.parent = transform; _isPlacing = true; currentState = State.Placing; _touchPressedAction.started += OnPlacing; } // 新增全局取消方法 public void CancelCurrentPlacement() { if (_isPlacing) { _isPlacing = false; Destroy(_instPreviewObj); _CharConfirmPlacementCanvas.SetActive(false); currentState = State.Default; _touchPressedAction.started -= OnPlacing; _touchPressedAction.canceled -= Hold; } }
4. 检查预制体赋值
确认每个TowerPlacement组件的_previewTower和_tower字段都正确赋值为对应类型的塔楼预制体,避免错拖第一个对象的预制体到第二个上。
调整后的完整脚本
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using Cinemachine; public class TowerPlacement : MonoBehaviour { PlayerInput playerInput; private InputAction _touchPressedAction; private InputAction _touchPlacePressAction; private InputAction _touchPressAction; public GameObject[] _meleePlatform; CanvasEnabler canvasEnabler; [Space(5)] [Header("Canvas")] [SerializeField] GameObject _CharConfirmPlacementCanvas; [SerializeField] GameObject _charInfoCanvas; [SerializeField] string _platformTag; float placingGameTimeSpeed = 0.1f; float normalGameTimeSpeed = 1f; Ray _position; [Space(5)] [Header("Character Object")] [SerializeField] private GameObject _previewTower; [SerializeField] private GameObject _tower; [Space(5)] [Header("Camera")] [SerializeField] private Camera _mainCam; [Header("Object Material")] [SerializeField] Material[] _materia; [Space(5)] [Header("Raycast")] [SerializeField] LayerMask _layerPlatform; [SerializeField] CinemachineVirtualCamera vcam1; [SerializeField] CinemachineVirtualCamera vcam2; bool rayCastHit; public bool _isPlacing; private GameObject _instPreviewObj; private GameObject _instObj; public enum State { Default, Placing, HoldPlacing, Placed } private State currentState; private void Awake() { playerInput = GetComponent<PlayerInput>(); _touchPressedAction = playerInput.actions["Pressed"]; _touchPressAction = playerInput.actions["Press"]; _touchPlacePressAction = playerInput.actions["PlacePress"]; currentState = State.Default; _meleePlatform = GameObject.FindGameObjectsWithTag(_platformTag); _CharConfirmPlacementCanvas.SetActive(false); } private void Update() { switch (currentState) { case State.Default: Time.timeScale = normalGameTimeSpeed; vcam1.Priority = 1; vcam2.Priority = 0; _charInfoCanvas.SetActive(false); DisablePlatformCanvas(); break; case State.Placing: Time.timeScale = placingGameTimeSpeed; EnablePlatformCanvas(); vcam1.Priority = 0; vcam2.Priority = 1; _charInfoCanvas.SetActive(true); break; case State.HoldPlacing: _CharConfirmPlacementCanvas.SetActive(true); _CharConfirmPlacementCanvas.transform.position = _instPreviewObj.transform.position; break; case State.Placed: break; } } private void OnDisable() { _touchPressedAction.started -= OnPlacing; _touchPressedAction.canceled -= Hold; _touchPlacePressAction.canceled -= OnPressed; _touchPressAction.performed -= OnPressed; CancelCurrentPlacement(); } public void StartPlacement() { TowerPlacement[] allPlacements = FindObjectsOfType<TowerPlacement>(); foreach (var placement in allPlacements) { if (placement != this && placement._isPlacing) { placement.CancelCurrentPlacement(); break; } } if (_isPlacing || currentState != State.Default) return; _instPreviewObj = Instantiate(_previewTower, transform.position, Quaternion.identity); _instPreviewObj.transform.parent = transform; _isPlacing = true; currentState = State.Placing; _touchPressedAction.started += OnPlacing; } public void ConfirmPlacement() { if (currentState == State.HoldPlacing) { DisablePlatformCanvas(); _isPlacing = false; _instObj = Instantiate(_tower, _instPreviewObj.transform.position, Quaternion.identity); Destroy(_instPreviewObj); _CharConfirmPlacementCanvas.SetActive(false); _touchPressedAction.canceled -= Hold; currentState = State.Default; } } public void OnCancelPlacement() { _touchPressedAction.started += OnPlacing; _CharConfirmPlacementCanvas.SetActive(false); _instPreviewObj.SetActive(true); currentState = State.Placing; } public void CancelCurrentPlacement() { if (_isPlacing) { _isPlacing = false; Destroy(_instPreviewObj); _CharConfirmPlacementCanvas.SetActive(false); currentState = State.Default; _touchPressedAction.started -= OnPlacing; _touchPressedAction.canceled -= Hold; } } public void Hold(InputAction.CallbackContext context) { if (currentState == State.Placing) { _touchPressedAction.started -= OnPlacing; currentState = State.HoldPlacing; } } public void OnPlacing(InputAction.CallbackContext context) { if (_instPreviewObj == null) return; _instPreviewObj.SetActive(true); _position = _mainCam.ScreenPointToRay(Touchscreen.current.position.ReadValue()); rayCastHit = Physics.Raycast(_position, out RaycastHit raycastHit, Mathf.Infinity, _layerPlatform); if (rayCastHit) { _instPreviewObj.transform.position = raycastHit.transform.position; _touchPressedAction.canceled += Hold; } } void EnablePlatformCanvas() { foreach (GameObject platform in _meleePlatform) { if(platform.TryGetComponent(out CanvasEnabler enabler)) { enabler.EnableCanvas(); } } } void DisablePlatformCanvas() { foreach (GameObject platform in _meleePlatform) { if(platform.TryGetComponent(out CanvasEnabler enabler)) { enabler.DisableCanvas(); } } } }
内容的提问来源于stack exchange,提问作者Slimeue
相关产品推荐
相关产品推荐

