NFT盲盒游戏开发(思路及源码详解)

NFT盲盒游戏开发(思路及源码详解)

盲盒是什么?

就是 你永远猜不到盒子里面是什么 , 这就是盲盒的魅力所在。迎合了大众的 好奇 心理,追求未知的刺激 。而如今 盲盒 游戏 也开始转移到线上 , 再次掀起 新的 浪潮 。

就拿现在的NFT 项目与盲盒结合举例来说,盲盒与挖矿的结合保证了项目的稳定发展和社区的持续活力。项目通过 DeFi 生态系统上流动性、产量矿池和 NFT 的独特性将其与盲盒游戏模式结合。

NFT 通过区块链技术将养成类游戏与盲盒玩法结合,通过线下盲盒购买实物获得相关人物道具,每一个人物和道具都拥有的身份识别码,通过线上游戏兑换获得相关道具人物,每一个盲盒可以开出一个人物和两张道具,同一人物在游戏中可进行升级,升级分三种形态,当到达一定形态后可兑换更高形态人物模型,邮寄到家。

人物池:

这个池子设置了人物生长周期,日常可进行人物打怪,进食等获取用户成长经验,而人物的催化成长速度是根据获取人物的数量决定,线下盲盒获取该人物越多成长越快 。

道具池:

这个池子设置了人物的相关道具,武器及装扮道具,通过武器及装扮道具提高人物打怪速度和战斗属性。

NFT   主要是因为每一枚代币在智能合约中都有一个 tokenID   来表示,以此便可以通过 tokenID   来对代币进行属性设置,这些属性描述为 metadata json 。如下代码所示:

mapping(uint256 => string) private _tokenURIs;  

_tokenURIs   的 key   就是 tokenID , value   则是 metadata json   文件的地址。

metadata json   文件中存储着该 NFT   的图片资源地址等信息,若要实现盲盒本质上就是在未开盲盒之前给所有的盲盒设置一个默认的 metadata json   文件地址或不设置,开盲盒的时候再返回具体的文件地址。

function tokenURI(uint256 tokenId) public view override returns (string memory) {

  // 默认的文件地址

  if (!canOpen) return unrevealURI;

  // 具体的文件地址

  return _tokenURIs[tokenId];

}

方案一:直接设置

项目方在开盲盒之前首先准备好每个 tokenID   所对应的 metadata json   文件。文件名为 ${tokenID}.json , 之后将这些文件放到一个文件夹中并存储到 ipfs   中,通过 ipfs://{ 文件夹的 CID}/${tokenID}.json   即可访问文件。

同时将文件的 baseURL ( ipfs://{ 文件夹的 CID}/ ) 保存到智能合约中。开盲盒的时候直接对地址进行拼接就能达到开盲盒的目的。

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

import “@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol”;

import “@openzeppelin/contracts/utils/Counters.sol”;

import “@openzeppelin/contracts/utils/Strings.sol”;

contract GameItem is ERC721URIStorage {

  using Counters for Counters.Counter;

  // 自增的 tokenId

  Counters.Counter private _tokenIds;

  // 是否可以开盲盒

  bool public canOpen = false;

  constructor() ERC721(“GameItem”, “ITM”) {}

  function tokenURI(uint256 tokenId) public view override returns (string memory) {

    // 判断是否可以开盲盒

    require(canOpen, ‘can not open now’);

    // 确保已被 mint

    require(_exists(tokenId), ‘token not minted’);

    string memory baseURI = _baseURI();

    if (bytes(baseURI).length > 0) {

        // 拼接 json 文件地址

        string memory path = string.concat(baseURI, Strings.toString(tokenId));

        return string.concat(path, ‘.json’);

    } else {

        return ”

    }

  }

}  

这种方式虽然需要的 gas   低,但存在一个很明显的问题:如何证明项目方开盒后的图片在出售前就是确定的。例如,项目方出售了10 个盲盒,在未开盲盒的情况下,这 10 个盲盒的图片应该都是确定的。但当前这种方案就没法确定,在盲盒出售后未开盲盒的情况下,项目方大可以随意调换或更改文件的内容。例如将 3 号盲盒和 5 号盲盒的内容调换,而此时各自对应的 tokenURI   却没有变化。

另一个问题是 nft   的属性对应关系是人为编造的,并不是真正的随机。

方案二:随机数 + 洗牌算法

当项目方已经准备好了 nft   的配置文件并已上传到了 ipfs 。开盲盒的时候,只需要随机的从文件池中取出不重复的文件就能解决随机性的问题。例如,当用户开 1 号盲盒的时候,随机对应的配置文件地址是 ipfs://{ 文件夹的 CID}/5.json   。因此可以一定程度上解决方案一随机性的问题。

其次就是 baseURI   的设置,由于 ipfs   的特殊性,文件夹的 CID   是由其内部文件决定的,一旦内部文件修改了则文件夹的 CID 必然会变化, 所以为了防止修改文件内容,部署智能合约的时候的就需要去设置 baseURI ,并且其是不可修改的。

针对方案二有两个问题需要解决:

如何获取随机数 – chainlink

如何不重复的抽取文件 – 洗牌算法

随机数

使用 chainlink   服务获取随机数:在   上创建订阅会得到一个 订阅ID , 在此订阅中充值 link   代币( 每次获取随机数都需要消耗 LINK 代币 ) , 最后并绑定合约地址。

如果你的项目是使用 hardhat   框架,需要安装 chainlink   的合约库

$ yarn add @chainlink/contracts  

获取随机数示例:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import “@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol”;

import “@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol”;

contract RoboNFT is VRFConsumerBaseV2 {

  // 协调器

  VRFCoordinatorV2Interface COORDINATOR;

  struct ChainlinkParams {

    // 订阅 ID

    uint64 subId;

    // 要使用的 gas 通道

    // 不同网络的 gas 通道 :

    bytes32 keyHash;

    // 回调的 gas 限制,其值取决于要获取的随机数的数量

    // 获取一个随机数需要 20000 wei

    uint32 gasLimit;

    // 请求确认的次数 – 设置为 3 即可

    uint16 requestConfirms;

    // 每次请求获得的随机数数量

    uint32 numWords;

  }

  ChainlinkParams public chainlinkParams;

  // 存储返回的随机数的数组

  uint256[] public randomNums;

  // _vrfCoordinator 是协调器地址,不同网络地址查看

  constructor(

    ChainlinkParams memory _chainlinkParams,

    address _vrfCoordinator

  ) VRFConsumerBaseV2(_vrfCoordinator) {

    // 创建协调器合约实例

    COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator);

    // 初始化 chainlink 参数

    chainlinkParams = _chainlinkParams;

  }

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Proudly powered by WordPress | Theme: HoneyWaves by SpiceThemes