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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 03:37:05