当前位置:首页 > 学海无涯 > 正文内容

Unity 开发实战:实现完整的物品购买系统

清羽天2周前 (11-27)学海无涯18
在大多数游戏中,物品购买系统是连接玩家与游戏经济体系的核心环节,无论是商店购物、道具购买还是内购系统,都需要一个稳定可靠的购买逻辑。本文将详细介绍如何在 Unity 中构建一套完整的物品购买系统,包括货币管理、商品配置、购买流程、交易记录和安全验证等核心功能。

一、购买系统核心需求分析

一个完善的物品购买系统应包含以下核心要素:
  • 多类型货币支持(金币、钻石、代币等)

  • 商品信息管理(价格、折扣、限购等)

  • 购买流程控制(验证、扣款、发奖)

  • 交易记录与日志

  • 购买冷却与限购机制

  • 错误处理与提示(余额不足、购买失败等)

  • 商店 UI 展示(商品列表、详情、购买按钮)

  • 内购集成(可选,对接支付 SDK)

二、数据结构设计

采用 ScriptableObject 存储商品配置数据,使用运行时数据类管理购买状态和货币信息,实现数据与逻辑分离。

1. 基础定义

csharp
运行
// 货币类型public enum CurrencyType{
    Gold,       // 金币(游戏内货币)
    Diamond,    // 钻石( premium 货币)
    Token,      // 代币(活动货币)
    RealMoney   // 真实货币(内购)}// 商品类型public enum ProductType{
    Consumable, // 消耗品(如药水)
    Equipment,  // 装备
    Skin,       // 皮肤
    Bundle,     // 礼包
    Currency,   // 货币包
    Function    // 功能解锁}// 购买结果public enum PurchaseResult{
    Success,            // 成功
    InsufficientFunds,  // 资金不足
    PurchaseFailed,     // 购买失败
    OutOfStock,         // 缺货
    LimitExceeded,      // 超过限购
    NotUnlocked,        // 未解锁
    InvalidProduct      // 商品无效}

2. 商品配置数据(ScriptableObject)

csharp
运行
using UnityEngine;using System.Collections.Generic;[CreateAssetMenu(fileName = "ProductData", menuName = "Shop/ProductData")]public class ProductData : ScriptableObject{
    [Header("基础信息")]
    public string productId;              // 商品唯一ID
    public string productName;            // 商品名称
    public string description;            // 商品描述
    public Sprite icon;                   // 商品图标
    public ProductType productType;       // 商品类型

    [Header("价格信息")]
    public CurrencyType currencyType;     // 货币类型
    public int basePrice;                 // 基础价格
    public float discount = 1f;           // 折扣(1为无折扣)
    public bool isOnSale => discount < 1f; // 是否在打折

    [Header("库存与限购")]
    public int stock;                     // 库存(-1为无限)
    public int dailyPurchaseLimit = -1;   // 每日限购(-1为无限)
    public int totalPurchaseLimit = -1;   // 总限购(-1为无限)

    [Header("内容信息")]
    public List<ItemAmount> items;        // 包含的物品
    public int currencyAmount;            // 包含的货币数量(如果是货币包)
    public CurrencyType currencyPackageType; // 货币包类型

    [Header("其他设置")]
    public int requiredLevel = 1;         // 购买所需等级
    public string requiredUnlockedFeature; // 所需解锁的功能
    public bool isAvailable = true;       // 是否可购买
    public string iapProductId;           // 内购商品ID(如果是真实货币购买)}// 物品数量结构体[System.Serializable]public struct ItemAmount{
    public string itemId;                 // 物品ID
    public int amount;                    // 数量}

3. 购买记录与状态数据

csharp
运行
using System;using System.Collections.Generic;// 购买记录[System.Serializable]public class PurchaseRecord{
    public string productId;              // 商品ID
    public DateTime purchaseTime;         // 购买时间
    public int amount;                    // 购买数量
    public int pricePaid;                 // 实际支付价格
    public CurrencyType currencyType;     // 支付货币类型
    public string transactionId;          // 交易ID}// 商品购买状态(用于限购等)[System.Serializable]public class ProductPurchaseState{
    public string productId;              // 商品ID
    public int totalPurchased;            // 总购买数量
    public Dictionary<DateTime, int> dailyPurchases = new Dictionary<DateTime, int>(); // 每日购买记录}

三、核心逻辑实现

1. 货币管理器

负责管理玩家的各种货币余额和变动:
csharp
运行
using UnityEngine;using System.Collections.Generic;public class CurrencyManager : MonoBehaviour{
    public static CurrencyManager Instance { get; private set; }

    private Dictionary<CurrencyType, int> currencies = new Dictionary<CurrencyType, int>();

    // 事件定义
    public delegate void CurrencyChangedDelegate(CurrencyType type, int newAmount);
    public event CurrencyChangedDelegate OnCurrencyChanged;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeCurrencies();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    // 初始化货币
    private void InitializeCurrencies()
    {
        // 初始化所有货币类型为0
        foreach (CurrencyType type in Enum.GetValues(typeof(CurrencyType)))
        {
            if (!currencies.ContainsKey(type))
            {
                currencies[type] = 0;
            }
        }

        // 从存档加载货币数据
        LoadCurrencyData();
    }

    // 获取货币余额
    public int GetCurrencyBalance(CurrencyType type)
    {
        if (currencies.TryGetValue(type, out int amount))
        {
            return amount;
        }
        return 0;
    }

    // 检查是否有足够的货币
    public bool HasEnoughCurrency(CurrencyType type, int amount)
    {
        return GetCurrencyBalance(type) >= amount;
    }

    // 增加货币
    public void AddCurrency(CurrencyType type, int amount)
    {
        if (amount <= 0) return;

        currencies[type] += amount;
        OnCurrencyChanged?.Invoke(type, currencies[type]);
        SaveCurrencyData();
    }

    // 减少货币
    public bool SubtractCurrency(CurrencyType type, int amount)
    {
        if (amount <= 0) return true;

        if (HasEnoughCurrency(type, amount))
        {
            currencies[type] -= amount;
            OnCurrencyChanged?.Invoke(type, currencies[type]);
            SaveCurrencyData();
            return true;
        }
        return false;
    }

    // 保存货币数据
    private void SaveCurrencyData()
    {
        // 实际项目中应保存到持久化系统
        foreach (var currency in currencies)
        {
            PlayerPrefs.SetInt($"Currency_{currency.Key}", currency.Value);
        }
        PlayerPrefs.Save();
    }

    // 加载货币数据
    private void LoadCurrencyData()
    {
        foreach (CurrencyType type in Enum.GetValues(typeof(CurrencyType)))
        {
            if (PlayerPrefs.HasKey($"Currency_{type}"))
            {
                currencies[type] = PlayerPrefs.GetInt($"Currency_{type}");
                OnCurrencyChanged?.Invoke(type, currencies[type]);
            }
        }
    }}

2. 商店管理器(核心购买逻辑)

csharp
运行
using UnityEngine;using System;using System.Collections.Generic;using System.Linq;public class ShopManager : MonoBehaviour{
    public static ShopManager Instance { get; private set; }

    [SerializeField] private List<ProductData> allProducts; // 所有商品配置

    private Dictionary<string, ProductData> products = new Dictionary<string, ProductData>();
    private Dictionary<string, ProductPurchaseState> purchaseStates = new Dictionary<string, ProductPurchaseState>();
    private List<PurchaseRecord> purchaseHistory = new List<PurchaseRecord>();

    // 事件定义
    public delegate void PurchaseCompletedDelegate(PurchaseResult result, ProductData product, PurchaseRecord record);
    public event PurchaseCompletedDelegate OnPurchaseCompleted;
    public event Action OnShopDataUpdated;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeShop();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    // 初始化商店
    private void InitializeShop()
    {
        // 构建商品字典
        foreach (var product in allProducts)
        {
            if (!products.ContainsKey(product.productId))
            {
                products[product.productId] = product;
            }
        }

        // 加载购买状态和历史记录
        LoadPurchaseData();
    }

    // 获取所有商品
    public List<ProductData> GetAllProducts()
    {
        return new List<ProductData>(products.Values);
    }

    // 按类型获取商品
    public List<ProductData> GetProductsByType(ProductType type)
    {
        return products.Values.Where(p => p.productType == type && p.isAvailable).ToList();
    }

    // 获取商品详情
    public ProductData GetProductById(string productId)
    {
        if (products.TryGetValue(productId, out ProductData product))
        {
            return product;
        }
        return null;
    }

    // 计算商品实际价格(考虑折扣)
    public int CalculateActualPrice(ProductData product)
    {
        return Mathf.RoundToInt(product.basePrice * product.discount);
    }

    // 检查商品是否可购买
    public PurchaseResult CanPurchaseProduct(ProductData product, int quantity = 1)
    {
        // 检查商品是否有效
        if (product == null || !product.isAvailable)
            return PurchaseResult.InvalidProduct;

        // 检查等级限制
        if (PlayerLevel.Instance.Level < product.requiredLevel)
            return PurchaseResult.NotUnlocked;

        // 检查库存
        if (product.stock != -1 && product.stock < quantity)
            return PurchaseResult.OutOfStock;

        // 检查限购
        var state = GetOrCreatePurchaseState(product.productId);
        
        // 检查总限购
        if (product.totalPurchaseLimit != -1 && 
            state.totalPurchased + quantity > product.totalPurchaseLimit)
            return PurchaseResult.LimitExceeded;

        // 检查每日限购
        if (product.dailyPurchaseLimit != -1)
        {
            DateTime today = DateTime.Today;
            int todayPurchased = state.dailyPurchases.TryGetValue(today, out int count) ? count : 0;
            
            if (todayPurchased + quantity > product.dailyPurchaseLimit)
                return PurchaseResult.LimitExceeded;
        }

        // 检查货币是否足够
        int actualPrice = CalculateActualPrice(product) * quantity;
        if (!CurrencyManager.Instance.HasEnoughCurrency(product.currencyType, actualPrice))
            return PurchaseResult.InsufficientFunds;

        return PurchaseResult.Success;
    }

    // 购买商品
    public void PurchaseProduct(string productId, int quantity = 1)
    {
        ProductData product = GetProductById(productId);
        if (product == null)
        {
            OnPurchaseCompleted?.Invoke(PurchaseResult.InvalidProduct, null, null);
            return;
        }

        // 先检查是否可购买
        PurchaseResult preCheck = CanPurchaseProduct(product, quantity);
        if (preCheck != PurchaseResult.Success)
        {
            OnPurchaseCompleted?.Invoke(preCheck, product, null);
            return;
        }

        // 计算实际价格
        int actualPrice = CalculateActualPrice(product) * quantity;

        // 处理内购商品(真实货币购买)
        if (product.currencyType == CurrencyType.RealMoney)
        {
            // 调用IAP服务
            StartIAPurchase(product, quantity, actualPrice);
        }
        else
        {
            // 游戏内货币购买流程
            ProcessInGamePurchase(product, quantity, actualPrice);
        }
    }

    // 处理游戏内货币购买
    private void ProcessInGamePurchase(ProductData product, int quantity, int actualPrice)
    {
        // 扣减货币
        bool paymentSuccess = CurrencyManager.Instance.SubtractCurrency(product.currencyType, actualPrice);
        if (!paymentSuccess)
        {
            OnPurchaseCompleted?.Invoke(PurchaseResult.InsufficientFunds, product, null);
            return;
        }

        // 发放物品
        bool itemGrantSuccess = GrantProductItems(product, quantity);
        if (!itemGrantSuccess)
        {
            // 发放失败,退款
            CurrencyManager.Instance.AddCurrency(product.currencyType, actualPrice);
            OnPurchaseCompleted?.Invoke(PurchaseResult.PurchaseFailed, product, null);
            return;
        }

        // 更新购买状态
        UpdatePurchaseState(product, quantity);

        // 创建购买记录
        PurchaseRecord record = CreatePurchaseRecord(product, quantity, actualPrice);
        purchaseHistory.Add(record);

        // 保存数据
        SavePurchaseData();

        // 触发购买完成事件
        OnPurchaseCompleted?.Invoke(PurchaseResult.Success, product, record);
    }

    // 处理内购(示例)
    private void StartIAPurchase(ProductData product, int quantity, int actualPrice)
    {
        // 实际项目中应调用IAP SDK,如Unity IAP
        // 这里仅做示例
        #if UNITY_IAP
        // 初始化IAP购买流程
        IAPManager.Instance.PurchaseProduct(
            product.iapProductId,
            onSuccess: (transactionId) => 
            {
                // 内购成功,发放物品
                GrantProductItems(product, quantity);
                UpdatePurchaseState(product, quantity);
                
                PurchaseRecord record = CreatePurchaseRecord(product, quantity, actualPrice);
                record.transactionId = transactionId;
                purchaseHistory.Add(record);
                
                SavePurchaseData();
                OnPurchaseCompleted?.Invoke(PurchaseResult.Success, product, record);
            },
            onFailed: () => 
            {
                OnPurchaseCompleted?.Invoke(PurchaseResult.PurchaseFailed, product, null);
            }
        );
        #else
        // 没有IAP支持时的模拟
        ProcessInGamePurchase(product, quantity, actualPrice);
        #endif
    }

    // 发放商品包含的物品
    private bool GrantProductItems(ProductData product, int quantity)
    {
        try
        {
            // 发放物品
            foreach (var item in product.items)
            {
                InventoryManager.Instance.AddItem(item.itemId, item.amount * quantity);
            }

            // 发放货币(如果是货币包)
            if (product.productType == ProductType.Currency && product.currencyAmount > 0)
            {
                CurrencyManager.Instance.AddCurrency(product.currencyPackageType, product.currencyAmount * quantity);
            }

            return true;
        }
        catch (Exception e)
        {
            Debug.LogError($"发放物品失败: {e.Message}");
            return false;
        }
    }

    // 更新购买状态
    private void UpdatePurchaseState(ProductData product, int quantity)
    {
        var state = GetOrCreatePurchaseState(product.productId);
        
        // 更新总购买数量
        state.totalPurchased += quantity;
        
        // 更新每日购买数量
        DateTime today = DateTime.Today;
        if (state.dailyPurchases.ContainsKey(today))
        {
            state.dailyPurchases[today] += quantity;
        }
        else
        {
            state.dailyPurchases[today] = quantity;
        }
        
        // 更新库存
        if (product.stock != -1)
        {
            // 注意:ScriptableObject是资产文件,不应在运行时修改
            // 实际项目中应使用实例化的商品数据或单独的库存管理
        }
    }

    // 创建购买记录
    private PurchaseRecord CreatePurchaseRecord(ProductData product, int quantity, int actualPrice)
    {
        return new PurchaseRecord
        {
            productId = product.productId,
            purchaseTime = DateTime.Now,
            amount = quantity,
            pricePaid = actualPrice,
            currencyType = product.currencyType,
            transactionId = Guid.NewGuid().ToString()
        };
    }

    // 获取或创建购买状态
    private ProductPurchaseState GetOrCreatePurchaseState(string productId)
    {
        if (!purchaseStates.TryGetValue(productId, out ProductPurchaseState state))
        {
            state = new ProductPurchaseState { productId = productId };
            purchaseStates[productId] = state;
        }
        return state;
    }

    // 获取商品购买状态
    public ProductPurchaseState GetPurchaseState(string productId)
    {
        purchaseStates.TryGetValue(productId, out ProductPurchaseState state);
        return state;
    }

    // 获取购买历史
    public List<PurchaseRecord> GetPurchaseHistory()
    {
        return new List<PurchaseRecord>(purchaseHistory);
    }

    // 保存购买数据
    private void SavePurchaseData()
    {
        // 实际项目中应使用更高效的序列化方式
        // 这里仅做示例
        SaveSystem.Instance.SavePurchaseStates(purchaseStates);
        SaveSystem.Instance.SavePurchaseHistory(purchaseHistory);
    }

    // 加载购买数据
    private void LoadPurchaseData()
    {
        purchaseStates = SaveSystem.Instance.LoadPurchaseStates();
        purchaseHistory = SaveSystem.Instance.LoadPurchaseHistory();
    }

    // 刷新商店数据(如每日限购重置)
    public void RefreshShop()
    {
        // 清理过期的每日购买记录
        foreach (var state in purchaseStates.Values)
        {
            List<DateTime> expiredDates = state.dailyPurchases.Keys                .Where(date => date < DateTime.Today)
                .ToList();

            foreach (var date in expiredDates)
            {
                state.dailyPurchases.Remove(date);
            }
        }

        SavePurchaseData();
        OnShopDataUpdated?.Invoke();
    }}

四、UI 界面实现

1. 商店 UI 管理器

csharp
运行
using UnityEngine;using UnityEngine.UI;using TMPro;using System.Collections.Generic;public class ShopUI : MonoBehaviour{
    [Header("商店容器")]
    [SerializeField] private Transform productGrid;
    [SerializeField] private GameObject productCardPrefab;
    [SerializeField] private List<Button> categoryButtons;

    [Header("商品详情")]
    [SerializeField] private GameObject detailPanel;
    [SerializeField] private Image detailIcon;
    [SerializeField] private TextMeshProUGUI detailName;
    [SerializeField] private TextMeshProUGUI detailDescription;
    [SerializeField] private TextMeshProUGUI detailPrice;
    [SerializeField] private TextMeshProUGUI detailOriginalPrice;
    [SerializeField] private TextMeshProUGUI stockInfo;
    [SerializeField] private TextMeshProUGUI limitInfo;
    [SerializeField] private Button purchaseButton;
    [SerializeField] private Button closeDetailButton;

    [Header("购买数量")]
    [SerializeField] private Button increaseButton;
    [SerializeField] private Button decreaseButton;
    [SerializeField] private TextMeshProUGUI quantityText;

    [Header("提示信息")]
    [SerializeField] private GameObject messagePanel;
    [SerializeField] private TextMeshProUGUI messageText;

    private List<ProductCard> productCards = new List<ProductCard>();
    private ProductData selectedProduct;
    private int currentQuantity = 1;
    private ProductType currentCategory = ProductType.Consumable;

    private void Start()
    {
        // 注册事件
        ShopManager.Instance.OnPurchaseCompleted += OnPurchaseCompleted;
        ShopManager.Instance.OnShopDataUpdated += RefreshShopUI;
        CurrencyManager.Instance.OnCurrencyChanged += UpdateCurrencyDisplay;

        // 绑定按钮事件
        purchaseButton.onClick.AddListener(OnPurchaseButtonClicked);
        closeDetailButton.onClick.AddListener(HideDetailPanel);
        increaseButton.onClick.AddListener(IncreaseQuantity);
        decreaseButton.onClick.AddListener(DecreaseQuantity);

        // 绑定分类按钮事件
        for (int i = 0; i < categoryButtons.Count && i < Enum.GetValues(typeof(ProductType)).Length; i++)
        {
            int index = i;
            categoryButtons[i].onClick.AddListener(() => 
            {
                currentCategory = (ProductType)index;
                RefreshProductList();
            });
        }

        // 初始化UI
        RefreshShopUI();
        HideDetailPanel();
        HideMessagePanel();
    }

    // 刷新商店UI
    public void RefreshShopUI()
    {
        RefreshProductList();
        UpdateCurrencyDisplay(CurrencyType.Gold, CurrencyManager.Instance.GetCurrencyBalance(CurrencyType.Gold));
    }

    // 刷新商品列表
    private void RefreshProductList()
    {
        // 清除现有商品卡片
        foreach (var card in productCards)
        {
            Destroy(card.gameObject);
        }
        productCards.Clear();

        // 获取对应分类的商品
        List<ProductData> products = ShopManager.Instance.GetProductsByType(currentCategory);

        // 创建商品卡片
        foreach (var product in products)
        {
            GameObject cardObj = Instantiate(productCardPrefab, productGrid);
            ProductCard card = cardObj.GetComponent<ProductCard>();
            card.Initialize(product, OnProductCardClicked);
            productCards.Add(card);
        }
    }

    // 商品卡片点击事件
    private void OnProductCardClicked(ProductData product)
    {
        selectedProduct = product;
        currentQuantity = 1;
        ShowDetailPanel(product);
    }

    // 显示商品详情
    private void ShowDetailPanel(ProductData product)
    {
        detailPanel.SetActive(true);
        detailIcon.sprite = product.icon;
        detailName.text = product.productName;
        detailDescription.text = product.description;

        // 价格显示
        int actualPrice = ShopManager.Instance.CalculateActualPrice(product);
        detailPrice.text = $"{actualPrice} {product.currencyType}";
        
        // 原价(折扣时显示)
        if (product.isOnSale)
        {
            detailOriginalPrice.gameObject.SetActive(true);
            detailOriginalPrice.text = $"{product.basePrice} {product.currencyType}";
        }
        else
        {
            detailOriginalPrice.gameObject.SetActive(false);
        }

        // 库存信息
        if (product.stock == -1)
        {
            stockInfo.text = "库存: 无限";
        }
        else
        {
            stockInfo.text = $"库存: {product.stock}";
        }

        // 限购信息
        var state = ShopManager.Instance.GetPurchaseState(product.productId);
        string limitText = "";
        
        if (product.totalPurchaseLimit != -1)
        {
            int remainingTotal = product.totalPurchaseLimit - (state?.totalPurchased ?? 0);
            limitText += $"总限购: 剩余 {remainingTotal}/{product.totalPurchaseLimit}\n";
        }
        
        if (product.dailyPurchaseLimit != -1)
        {
            int todayPurchased = 0;
            if (state != null && state.dailyPurchases.TryGetValue(DateTime.Today, out int count))
            {
                todayPurchased = count;
            }
            int remainingDaily = product.dailyPurchaseLimit - todayPurchased;
            limitText += $"每日限购: 剩余 {remainingDaily}/{product.dailyPurchaseLimit}";
        }
        
        limitInfo.text = limitText;

        // 更新数量显示
        UpdateQuantityDisplay();
    }

    // 隐藏详情面板
    private void HideDetailPanel()
    {
        detailPanel.SetActive(false);
        selectedProduct = null;
    }

    // 增加购买数量
    private void IncreaseQuantity()
    {
        if (selectedProduct == null) return;
        
        // 检查是否可以增加数量
        int newQuantity = currentQuantity + 1;
        PurchaseResult result = ShopManager.Instance.CanPurchaseProduct(selectedProduct, newQuantity);
        
        if (result == PurchaseResult.Success)
        {
            currentQuantity = newQuantity;
            UpdateQuantityDisplay();
        }
        else
        {
            ShowMessage(GetResultMessage(result));
        }
    }

    // 减少购买数量
    private void DecreaseQuantity()
    {
        if (currentQuantity > 1)
        {
            currentQuantity--;
            UpdateQuantityDisplay();
        }
    }

    // 更新数量显示
    private void UpdateQuantityDisplay()
    {
        quantityText.text = currentQuantity.ToString();
        decreaseButton.interactable = currentQuantity > 1;
    }

    // 购买按钮点击事件
    private void OnPurchaseButtonClicked()
    {
        if (selectedProduct != null)
        {
            ShopManager.Instance.PurchaseProduct(selectedProduct.productId, currentQuantity);
        }
    }

    // 购买完成回调
    private void OnPurchaseCompleted(PurchaseResult result, ProductData product, PurchaseRecord record)
    {
        if (result == PurchaseResult.Success)
        {
            ShowMessage($"成功购买 {product.productName} x{record.amount}!");
            HideDetailPanel();
            RefreshShopUI();
        }
        else
        {
            ShowMessage(GetResultMessage(result));
        }
    }

    // 显示提示信息
    private void ShowMessage(string message)
    {
        messageText.text = message;
        messagePanel.SetActive(true);
        Invoke(nameof(HideMessagePanel), 3f);
    }

    // 隐藏提示信息
    private void HideMessagePanel()
    {
        messagePanel.SetActive(false);
    }

    // 更新货币显示(实际项目中应实现)
    private void UpdateCurrencyDisplay(CurrencyType type, int newAmount)
    {
        // 更新UI上的货币显示
    }

    // 获取购买结果对应的消息
    private string GetResultMessage(PurchaseResult result)
    {
        switch (result)
        {
            case PurchaseResult.InsufficientFunds:
                return "货币不足,无法购买";
            case PurchaseResult.OutOfStock:
                return "商品已售罄";
            case PurchaseResult.LimitExceeded:
                return "已超过限购数量";
            case PurchaseResult.NotUnlocked:
                return "等级不足,无法购买";
            case PurchaseResult.InvalidProduct:
                return "无效的商品";
            case PurchaseResult.PurchaseFailed:
                return "购买失败,请重试";
            default:
                return "未知错误";
        }
    }}// 商品卡片UI组件public class ProductCard : MonoBehaviour{
    [SerializeField] private Image icon;
    [SerializeField] private TextMeshProUGUI nameText;
    [SerializeField] private TextMeshProUGUI priceText;
    [SerializeField] private TextMeshProUGUI originalPriceText;
    [SerializeField] private GameObject saleTag;
    [SerializeField] private Button buyButton;

    private ProductData product;
    private Action<ProductData> onClickAction;

    public void Initialize(ProductData productData, Action<ProductData> onClick)
    {
        product = productData;
        onClickAction = onClick;

        // 设置卡片信息
        icon.sprite = product.icon;
        nameText.text = product.productName;

        // 价格显示
        int actualPrice = ShopManager.Instance.CalculateActualPrice(product);
        priceText.text = $"{actualPrice} {product.currencyType}";

        // 折扣显示
        if (product.isOnSale)
        {
            saleTag.SetActive(true);
            originalPriceText.gameObject.SetActive(true);
            originalPriceText.text = $"{product.basePrice}";
        }
        else
        {
            saleTag.SetActive(false);
            originalPriceText.gameObject.SetActive(false);
        }

        // 绑定点击事件
        buyButton.onClick.AddListener(OnClick);
    }

    private void OnClick()
    {
        onClickAction?.Invoke(product);
    }}

五、系统扩展与安全考虑

1. 优惠活动系统

扩展商店系统支持限时优惠和活动:
csharp
运行
public class ShopEventSystem : MonoBehaviour{
    [SerializeField] private List<EventProduct> eventProducts;
    [SerializeField] private float eventDuration = 86400f; // 24小时

    private float eventStartTime;
    private bool isEventActive = false;

    private void Start()
    {
        // 可以通过远程配置控制活动是否开启
        StartEvent();
    }

    // 开始活动
    public void StartEvent()
    {
        eventStartTime = Time.time;
        isEventActive = true;
        AddEventProducts();
    }

    // 结束活动
    public void EndEvent()
    {
        isEventActive = false;
        RemoveEventProducts();
    }

    // 添加活动商品
    private void AddEventProducts()
    {
        foreach (var eventProduct in eventProducts)
        {
            // 克隆商品数据并应用活动折扣
            ProductData eventData = Instantiate(eventProduct.originalProduct);
            eventData.productId = $"{eventData.productId}_event";
            eventData.discount = eventProduct.eventDiscount;
            eventData.isAvailable = true;

            // 添加到商店
            ShopManager.Instance.AddProduct(eventData);
        }
    }

    // 移除活动商品
    private void RemoveEventProducts()
    {
        foreach (var eventProduct in eventProducts)
        {
            string eventProductId = $"{eventProduct.originalProduct.productId}_event";
            ShopManager.Instance.RemoveProduct(eventProductId);
        }
    }

    private void Update()
    {
        // 检查活动是否结束
        if (isEventActive && Time.time - eventStartTime >= eventDuration)
        {
            EndEvent();
        }
    }

    // 获取活动剩余时间
    public float GetEventRemainingTime()
    {
        if (!isEventActive) return 0;
        return Mathf.Max(0, eventDuration - (Time.time - eventStartTime));
    }}[System.Serializable]public class EventProduct{
    public ProductData originalProduct;
    public float eventDiscount;}

2. 购买安全与验证

对于真实货币购买,需要添加安全验证机制:
csharp
运行
public class PurchaseValidator : MonoBehaviour{
    // 验证内购交易(示例)
    public bool ValidateIAPurchase(string productId, string transactionId, string receipt)
    {
        #if UNITY_EDITOR
        // 编辑器模式下直接返回成功
        return true;
        #else
        // 实际项目中应发送到服务器验证
        StartCoroutine(ValidatePurchaseCoroutine(productId, transactionId, receipt));
        return false; // 异步验证,结果通过回调返回
        #endif
    }

    // 服务器验证协程
    private IEnumerator ValidatePurchaseCoroutine(string productId, string transactionId, string receipt)
    {
        // 创建验证请求
        WWWForm form = new WWWForm();
        form.AddField("productId", productId);
        form.AddField("transactionId", transactionId);
        form.AddField("receipt", receipt);
        form.AddField("userId", PlayerPrefs.GetString("UserId"));

        // 发送到验证服务器
        using (UnityWebRequest www = UnityWebRequest.Post("https://yourserver.com/validate_purchase", form))
        {
            yield return www.SendWebRequest();

            if (www.result == UnityWebRequest.Result.Success)
            {
                // 解析验证结果
                string response = www.downloadHandler.text;
                if (response.Contains("valid"))
                {
                    // 验证成功,发放物品
                    OnPurchaseValidated(productId);
                }
                else
                {
                    // 验证失败
                    OnPurchaseInvalidated(productId, "服务器验证失败");
                }
            }
            else
            {
                // 网络错误
                OnPurchaseInvalidated(productId, "网络错误");
            }
        }
    }

    // 购买验证成功
    private void OnPurchaseValidated(string productId)
    {
        ProductData product = ShopManager.Instance.GetProductById(productId);
        if (product != null)
        {
            // 发放物品
            ShopManager.Instance.GrantProductItems(product, 1);
            // 更新购买记录等
        }
    }

    // 购买验证失败
    private void OnPurchaseInvalidated(string productId, string reason)
    {
        Debug.LogError($"购买验证失败: {productId}, 原因: {reason}");
        // 通知玩家购买失败
    }}

六、优化与体验建议

1. 性能优化

  • 商品列表使用对象池管理 UI 元素,避免频繁创建销毁

  • 大量商品时实现分页加载或滚动加载

  • 商品图标使用图集(Sprite Atlas)减少 Draw Call

  • 复杂的商品筛选和排序在后台线程处理

2. 购买体验优化

  • 重要购买添加二次确认步骤,防止误操作

  • 购买过程中显示加载动画,明确操作状态

  • 货币不足时提供快捷充值入口

  • 支持批量购买,减少重复操作

  • 限购商品明确显示剩余购买次数

  • 折扣商品使用醒目的视觉标记

3. 经济平衡要点

  • 确保商品价格与价值匹配,避免性价比失衡

  • 不同类型货币的兑换比例保持稳定

  • 定期更新商品列表,保持商店新鲜感

  • 活动商品提供更高性价比,促进参与度

  • 内购商品定价符合目标市场消费水平

七、结语

本文实现的物品购买系统提供了一个完整的基础框架,涵盖了商品管理、货币系统、购买流程和 UI 展示等核心功能。该系统采用数据驱动设计,通过 ScriptableObject 配置商品信息,使得后续扩展和调整变得简单高效。
在实际项目中,可以根据游戏类型和商业模式进一步扩展该系统,例如添加会员专属商品、限时抢购、积分系统、推荐算法等功能。关键是要平衡游戏内经济,提供良好的购买体验,同时确保交易安全可靠。
希望这个购买系统方案能为你的 Unity 项目提供有价值的参考,祝开发顺利!


分享给朋友:

“Unity 开发实战:实现完整的物品购买系统” 的相关文章

Linux常用命令大全

Linux常用命令大全

Linux是开发与运维工作中不可或缺的工具,掌握常用命令能显著提升效率。本篇整理了一些高频使用的命令,覆盖文件操作、系统监控、网络调试等核心场景,适合入门学习或作为日常参考使用。以下是一些常用的Linux命令:1. ls:列出当前目录中的文件和子目录ls2. pwd:显示当前工作目录的路径pwd3....

PHP 实现在线视频播放完整方案:从后端存储到前端适配

在 Web 开发中,在线视频播放是电商展示、教育平台、企业宣传等场景的核心需求。PHP 作为主流的后端脚本语言,具备开发高效、部署简单、生态完善的优势,配合前端播放器组件,可快速实现跨浏览器、高兼容性的视频播放功能。本文将从技术选型、后端核心实现、前端集成、优化部署四个维度,手把手教你搭建 PHP...

Python 链接数据库与基础增删改查(CRUD)操作详解

在 Python 开发中,数据库交互是后端开发、数据分析、自动化脚本等场景的核心能力 —— 无论是存储用户数据、处理业务逻辑,还是批量分析数据,都需要 Python 与数据库建立连接并执行操作。本文以 MySQL 数据库(Python 生态最常用的关系型数据库)为例,从环境准备、数据库连接...

Java 链接数据库与基础增删改查操作详解

在 Java 开发中,数据库交互是绝大多数应用的核心功能之一。无论是用户信息存储、业务数据统计还是日志记录,都需要通过 Java 程序与数据库建立连接并执行数据操作。本文将以 MySQL 数据库(最常用的关系型数据库之一)为例,从环境准备、数据库连接、基础增删改查(CRUD)操作到代码优化...

Unity 开发实战:在游戏中嵌入拼图玩法系统

拼图游戏作为一种经典的益智玩法,非常适合嵌入各类游戏中作为休闲模块、解谜环节或奖励机制。本文将详细介绍如何在 Unity 中设计并实现一个可复用的拼图游戏系统,包括核心逻辑、UI 交互和扩展功能。一、拼图游戏核心需求分析一个灵活的拼图系统应具备以下功能:支持不同尺寸的拼图(如 3x3、4x4、5x5...

Unity 开发实战:实现逼真的作物生长系统

作物生长系统是农场类、生存类游戏的核心玩法之一,一个设计精良的作物生长系统能极大提升游戏的沉浸感。本文将详细介绍如何在 Unity 中构建一个完整的作物生长系统,包括生长周期、环境影响、交互逻辑和可视化表现。一、作物生长系统核心需求分析一个真实的作物生长系统应包含以下核心要素:多阶段生长周期(种子→...