【从01 千万级直播项目实战开箱子玩法:技术实现与策略指南

2023年 10月 13日 36.5k 0

背景与介绍

随着社交应用的日益发展,开箱子玩法逐渐成为了一种流行的互动方式。它提供了丰富的用户体验,吸引了各类用户(小R、中R、大R)的广泛参与,成为了应用的重要收益来源。本文将深入探讨这一玩法的设计与实现,希望为广大开发者提供一些有益的参考。

需求分析

开箱子玩法需要满足以下基本需求:

  • 用户可以支付xx金币来进行一次开箱体验。
  • 开奖过程要足够刺激,以吸引用户持续参与。
  • 系统应支持连续开奖,如10连、30连等,并可自动连续开奖,减少用户的操作。
  • 为了营造社交氛围,应有全服榜单展示开出高级奖励的玩家。
  • 应有兜底机制保证用户在一定次数后必定获得某种奖励。
  • 隐藏奖池兜底:当某种奖励库存不足时,应自动为用户提供替代奖励。
  • 特定时段(如晚上xx点到xx点)为“闪耀时刻”,某些奖励的中奖概率会提高。
  • image.png

    数据模型设计

    奖励物品

    • 用户可以查看当前每个奖励物品及其概率。
    • 用户可以选择开箱子并获得随机奖励。
    • 奖励物品有不同的稀有度,如S级、A级和B级。
    public class RewardItem {
        private Long id;
        private String name;
        private String description;
        private String imageUrl;
        private Rarity rarity;  // 稀有度枚举: S级, A级, B级, ...
        private AtomicInteger stock;//库存
        ...
    }
    

    奖池

    • 用户可以查看当前的奖池和每个奖励物品及其概率。
    • 管理员可以创建、修改和切换奖池。
    public class RewardPool {
        private Long poolId;
        private Map itemsWithProbability;  // 奖励物品与其概率
        private Boolean isActive;  // 是否为当前生效的奖池
        ...
    }
    

    用户开奖记录

    记录用户的开奖历史,用于判断是否需要兜底奖励。

    public class UserRewardRecord {
        private Long userId;
        private Long rewardItemId;
        private LocalDateTime time;
        ...
    }
    

    image.png

    开奖逻辑

    权重随机算法

    为了公正地为用户分配奖励,我们需要一个权重随机算法来决定用户获得哪种奖励。每种奖励都有一个与其相关联的权重,这个权重决定了用户获得这种奖励的概率。

    public class WeightedRandom {
        
        // 打开箱子并使用权重随机算法
        public RewardItem openBox(RewardPool pool) {
            double totalWeight = pool.getItemsWithProbability().values().stream().mapToDouble(Double::doubleValue).sum();
            double randomValue = Math.random() * totalWeight;
            double weightSum = 0;
            for (Map.Entry entry : pool.getItemsWithProbability().entrySet()) {
                weightSum += entry.getValue();
                if (randomValue  0) {
                    return entry.getKey(); // 返回随机选中的奖励物品
                }
            }
            return null;  // 若所有物品都无库存,返回null
        }
    }
    
    
    
    

    image.png

    全服兜底策略

    为了确保用户在长时间的游戏过程中不会感到沮丧,我们设计了一个全服兜底策略。系统会记录每位用户的连续未中高级奖励的次数,当这个次数达到一个设定的阈值时,系统会确保用户下一次开奖能够获得一个高级奖励。

    public class GlobalFallbackStrategy {
        private final int MAX_TRIES = 50;
        
        // 打开箱子并使用回退策略
        public RewardItem openBoxWithFallback(User user) {
            int tries = user.getConsecutiveTries();
            if (tries >= MAX_TRIES) {
                // 如果尝试次数超过了最大尝试次数(MAX_TRIES)
                user.resetConsecutiveTries(); // 重置用户的连续尝试次数
                return rewardPoolService.getGuaranteedSRarityItem(); // 获取保底的S级稀有物品
            }
            RewardItem item = openBox(); // 打开箱子
            if (item.getRarity() == Rarity.S) {
                user.resetConsecutiveTries(); // 如果获得了S级稀有物品,重置连续尝试次数
            } else {
                user.incrementConsecutiveTries(); // 如果没有获得S级稀有物品,增加连续尝试次数
            }
            return item;
        }
    }
    
    

    image.png

    隐藏奖池兜底

    当奖池的某种奖励库存不足时,为了避免用户长时间得不到这种奖励,我们引入了隐藏奖池兜底策略。当库存低于一个设定的阈值时,系统会为用户提供一个替代奖励。

    public class HiddenPoolFallback {
        private Map fallbackItems;
        private int threshold; // 库存阈值
    
        public HiddenPoolFallback(Map fallbackItems, int threshold) {
            this.fallbackItems = fallbackItems;
            this.threshold = threshold;
        }
    
        // 返回替代奖励
        public RewardItem getFallback(RewardItem outOfStockItem) {
            int currentStock = outOfStockItem.getStock();
    
            // 如果库存低于阈值,尝试获取替代奖励
            if (currentStock  0) {
                    return fallbackItem;
                }
            }
    
            // 如果没有可用的替代奖励,返回null或者一个默认的替代奖励
            // 这里返回null,可以根据需求修改
            return null;
        }
    }
    
    
    

    image.png

    奖池与库存管理优化

    为了实现实时的库存管理和优化,我们选择了Redis作为我们的数据存储解决方案。

    Redis结构设计

    • reward_stock:{rewardId}: 奖励物品的库存
    • user_try_count:{userId}: 用户的连续未中高级奖励的次数

    库存操作

    使用Redis的哈希结构来高效地进行库存操作。

    import org.redisson.api.RAtomicLong;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class RedisStockManager {
    
        private final String STOCK_KEY_PREFIX = "reward_stock:";
        private final String USER_TRY_COUNT_PREFIX = "user_try_count:";
    
        @Autowired
        private RedissonClient redisson;
    
        /**
         * 获取指定奖励物品的库存数量
         * @param rewardItemId 奖励物品ID
         * @return 库存数量
         */
        public long getStock(Long rewardItemId) {
            RAtomicLong stock = redisson.getAtomicLong(STOCK_KEY_PREFIX + rewardItemId);
            return stock.get();
        }
    
        /**
         * 减少指定奖励物品的库存数量
         * @param rewardItemId 奖励物品ID
         * @param count 减少的数量
         */
        public void decreaseStock(Long rewardItemId, int count) {
            RAtomicLong stock = redisson.getAtomicLong(STOCK_KEY_PREFIX + rewardItemId);
            stock.addAndGet(-count);
        }
    
        /**
         * 增加指定奖励物品的库存数量
         * @param rewardItemId 奖励物品ID
         * @param count 增加的数量
         */
        public void increaseStock(Long rewardItemId, int count) {
            RAtomicLong stock = redisson.getAtomicLong(STOCK_KEY_PREFIX + rewardItemId);
            stock.addAndGet(count);
        }
    
        /**
         * 获取用户连续未中高级奖励的次数
         * @param userId 用户ID
         * @return 连续未中次数
         */
        public long getUserTryCount(Long userId) {
            RAtomicLong count = redisson.getAtomicLong(USER_TRY_COUNT_PREFIX + userId);
            return count.get();
        }
    
        /**
         * 重置用户连续未中高级奖励的次数
         * @param userId 用户ID
         */
        public void resetUserTryCount(Long userId) {
            RAtomicLong count = redisson.getAtomicLong(USER_TRY_COUNT_PREFIX + userId);
            count.set(0);
        }
    
        /**
         * 增加用户连续未中高级奖励的次数
         * @param userId 用户ID
         */
        public void incrementUserTryCount(Long userId) {
            RAtomicLong count = redisson.getAtomicLong(USER_TRY_COUNT_PREFIX + userId);
            count.incrementAndGet();
        }
    }
    
    

    image.png

    后台管理

    管理员可以轻松配置奖池、设置奖励物品的概率和库存等。

    3.4.1 奖池配置

    管理员可以为奖池添加或删除奖励物品,调整其概率和库存。

    3.4.2 全服兜底设置

    管理员可以设置用户连续未中高级奖励的次数的阈值,调整兜底策略。

    image.png

    需求优化考虑

    性能优化

    缓存策略:使用Redi缓存技术,将频繁访问的数据(如奖励物品的库存、用户的开奖记录等)存储在内存中,减少对数据库的访问。

    数据库优化:使用索引优化查询速度,定期清理和归档旧数据,使用分库分表策略处理大量数据。

    安全性考虑

    服务器验证:所有的开奖操作都应在服务器端完成,客户端只负责展示结果,避免客户端作弊。

    数据加密:使用HTTPS协议传输数据,确保数据在传输过程中的安全性。对敏感数据进行加密存储。

    数据分析与调整

    数据分析:定期分析用户的开奖数据,找出用户最喜欢的奖励、最活跃的时间段等信息。

    游戏调整:根据数据分析结果,调整奖励的权重和概率,优化游戏设计。

    总结

    在实现这种玩法时,我们需要考虑多种因素,包括但不限于奖励的权重分配、库存管理、兜底策略等。为了确保公平性和用户满意度,我们引入了权重随机算法、全服兜底策略和隐藏奖池兜底策略。同时,为了实时管理库存,我们选择了Redis作为数据存储解决方案。

    此外,后台管理也是实现这种玩法的关键部分,它允许管理员轻松配置奖池、设置奖励物品的概率和库存等,确保游戏的持续运营和用户的持续参与。"开箱子"玩法虽然看似简单,但其背后涉及的技术和策略都是经过深思熟虑的。对于希望在这一领域取得成功的开发者来说,深入理解这些技术和策略是至关重要的。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论