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

Unity 2D游戏商店购买物品后如何实现地图拖拽放置功能

Unity 2D商店购买后拖拽放置物品实现方案

不需要把所有拖拽逻辑都堆在现有ShopManagerScript里,按职责拆分脚本维护成本更低,具体实现如下:

一、现有ShopManagerScript的调整

原脚本只需要做3处小改动,保留核心的购买、金币、库存逻辑即可:

  • 缓存EventSystem引用,避免每次购买都全局查找对象产生性能开销
  • 修正初始金币文本的显示问题
  • 新增库存查询方法、库存变更后的UI/拖拽状态更新逻辑

调整后的完整代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ShopManagerScript : MonoBehaviour
{
    public int[,] shopItems = new int[5,5];
    public float coins;
    public Text CoinsTXT;
    private EventSystem _eventSystem;

    void Start()
    {
        _eventSystem = GameObject.FindGameObjectWithTag("Event").GetComponent<EventSystem>();
        CoinsTXT.text = "Coins:" + coins.ToString();

        // 物品ID配置
        shopItems[1, 1] = 1;
        shopItems[1, 2] = 2;
        shopItems[1, 3] = 3;
        shopItems[1, 4] = 4;

        // 物品价格配置
        shopItems[2, 1] = 10;
        shopItems[2, 2] = 20;
        shopItems[2, 3] = 30;
        shopItems[2, 4] = 40;

        // 物品持有数量初始化
        shopItems[3, 1] = 0;
        shopItems[3, 2] = 0;
        shopItems[3, 3] = 0;
        shopItems[3, 4] = 0;
    }

    public void Buy()
    {
        GameObject ButtonRef = _eventSystem.currentSelectedGameObject;
        ButtonInfo itemInfo = ButtonRef.GetComponent<ButtonInfo>();
        int itemId = itemInfo.ItemID;
        
        if (coins >= shopItems[2, itemId])
        {
            coins -= shopItems[2, itemId];
            shopItems[3, itemId]++;
            CoinsTXT.text = "Coins:" + coins.ToString();
            itemInfo.QuantityTxt.text = shopItems[3, itemId].ToString();
            
            // 第一次获得该物品时启用拖拽功能
            if (shopItems[3, itemId] == 1)
            {
                ButtonRef.GetComponent<DragPlaceItem>().enabled = true;
            }
        }
    }

    // 供拖拽组件调用的库存扣减方法
    public bool ConsumeItem(int itemId)
    {
        if (shopItems[3, itemId] <= 0) return false;
        shopItems[3, itemId]--;
        // 更新对应物品的数量显示
        Transform itemBtn = transform.Find("ItemBtn_" + itemId);
        itemBtn.GetComponent<ButtonInfo>().QuantityTxt.text = shopItems[3, itemId].ToString();
        // 库存为0时禁用拖拽
        if (shopItems[3, itemId] == 0)
        {
            itemBtn.GetComponent<DragPlaceItem>().enabled = false;
        }
        return true;
    }
}

二、独立拖拽组件实现

新建DragPlaceItem.cs脚本,挂在商店的每个物品按钮上,初始状态设为禁用,购买后自动解锁。脚本需要实现UGUI的三个拖拽事件接口,代码如下:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class DragPlaceItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public int itemId;
    public GameObject itemPrefab; // 对应地图上要生成的物品实体预制体
    public Sprite dragIcon; // 拖拽时显示的图标
    public LayerMask placeableLayer; // 可放置区域的层
    
    private GameObject _dragDummy; // 拖拽时跟随鼠标的虚拟图标
    private RectTransform _canvasRect;
    private Camera _mainCam;
    private bool _canPlace;

    void Awake()
    {
        _mainCam = Camera.main;
        _canvasRect = GetComponentInParent<Canvas>().GetComponent<RectTransform>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        // 生成拖拽虚拟图标
        _dragDummy = new GameObject("DragDummy");
        _dragDummy.transform.SetParent(_canvasRect, false);
        Image img = _dragDummy.AddComponent<Image>();
        img.sprite = dragIcon;
        img.raycastTarget = false; // 不阻挡射线检测
        _dragDummy.GetComponent<RectTransform>().sizeDelta = new Vector2(50,50);
    }

    public void OnDrag(PointerEventData eventData)
    {
        // 虚拟图标跟随鼠标
        _dragDummy.transform.position = eventData.position;
        
        // 检测当前鼠标位置是否为可放置区域
        Vector2 worldPos = _mainCam.ScreenToWorldPoint(eventData.position);
        RaycastHit2D hit = Physics2D.Raycast(worldPos, Vector2.zero, 0f, placeableLayer);
        _canPlace = hit.collider != null;
        
        // 简单的放置合法性提示:可放置为白色,不可放置为红色
        _dragDummy.GetComponent<Image>().color = _canPlace ? Color.white : Color.red;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        Destroy(_dragDummy);
        if (!_canPlace) return;
        
        // 扣减库存,库存足够才生成实体
        ShopManagerScript shop = FindObjectOfType<ShopManagerScript>();
        if (shop.ConsumeItem(itemId))
        {
            Vector2 spawnPos = _mainCam.ScreenToWorldPoint(eventData.position);
            // 如果是网格地图,这里可以加坐标对齐逻辑,比如spawnPos = Grid.WorldToCell对齐网格
            Instantiate(itemPrefab, spawnPos, Quaternion.identity);
        }
    }
}

三、配置注意事项

  • 给商店每个物品按钮挂ButtonInfo组件时,对应填写好ItemID,和shopItems数组里的配置保持一致
  • 地图上所有可以放置物品的地块,统一设置到你指定的可放置层,拖拽组件的placeableLayer只勾选这个层
  • 如果是网格类游戏(比如塔防、种田),在生成物品前加一步网格坐标转换,让物品自动对齐格子,不会出现放歪的情况
  • 初始状态下所有物品按钮上的DragPlaceItem组件设为禁用,只有第一次购买对应物品后才会自动启用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 14:42:18