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
相关产品推荐
相关产品推荐

