对象展示

Sui 对象展示是一种标准,它使链上资产的链外可视化表示成为可能。这在 NFT 项目中非常有用。

在启用结构体的展示之前,需要一个归属的发布者(Publisher)对象。发布者是“一次性见证”(One-Time Witness)模式的实现, 我们将在后续课程中介绍更多细节。查看发布者主题以了解更多信息。

sui::display::Display<T> 使用一组带有对象属性占位符的命名模板,例如:

{
    "name": "{name}",
    "image_url": "https://xxx/{image_id}"
}

nameimage_id 必须是对象类型 T 的属性。

官方文档建议的属性包括:

  • name - 对象名称。
  • description - 对象描述。
  • link - 项目中的对象链接。
  • image_url - 对象的视觉图像 URL。
  • thumbnail_url - 用作预览的小图像的 URL。
  • project_url - 项目链接。
  • creator - 对象创建者。

在我们的 Move Castle 游戏中,模板应为:

{
    "name": "{name}",
    "link": "https://movecastle.info/castles/{serial_number}",
    "image_url": "https://images.movecastle.info/static/media/castles/{image_id}.png",
    "description": "{description}",
    "project_url": "https://movecastle.info",
    "creator": "Castle Builder"
}

城堡图像是预生成的,托管在我们的项目网站 "https://movecastle.info" 下。image_id 是城堡序列号的视觉部分。

utils.move 模块中添加一个 serial_number_to_image_id 函数,以从序列号中检索图像 ID:

use std::string::{Self, String};

public(package) fun serial_number_to_image_id(serial_number: u64): String {
    let id = serial_number / 10 % 10000u64;
    u64_to_string(id, 4)
}

/// 将 u64 转换为字符串,如果长度小于固定长度,则在前面加 "0"
public(package) fun u64_to_string(n: u64, fixed_length: u64): String {
    let mut result: vector<u8> = vector::empty<u8>();
    if (n == 0) {
        vector::push_back(&mut result, 48);
    } else {
        while (n > 0) {
            let digit = ((n % 10) as u8) + 48;
            vector::push_back(&mut result, digit);
            n = n / 10;
        };

        // 在字符串前面添加 "0" 直到达到固定长度。
        while (vector::length(&result) < fixed_length) {
            vector::push_back(&mut result, 48);
        };

        vector::reverse<u8>(&mut result);
    };
    string::utf8(result)
}

这个函数从序列号中提取中间的 4 位数字。

castle.move 中,我们需要添加 OTW 逻辑,并声明 Publisher 对象:

module move_castle::castle {
    use sui::package;

    /// 模块的一次性见证,它必须是模块中的第一个结构体,
    /// 并且它的名称应该与模块名称相同,但全部大写。
    public struct CASTLE has drop {}
    
    fun init(otw: CASTLE, ctx: &mut TxContext) {
        let publisher = package::claim(otw, ctx);
        transfer::public_transfer(publisher, tx_context::sender(ctx));
    }

}

然后我们需要新建一个 Display<Castle> 对象,因此 castle.move 中的整个 init 函数应为:

module move_castle::castle {
    use std::string::{Self, utf8, String};
    use sui::package;
    use sui::display;
    
    /// 模块的一次性见证,它必须是模块中的第一个结构体,
    /// 并且它的名称应该与模块名称相同,但全部大写。
    public struct CASTLE has drop {}
    
    fun init(otw: CASTLE, ctx: &mut TxContext) {
        let keys = vector[
            utf8(b"name"),
            utf8(b"link"),
            utf8(b"image_url"),
            utf8(b"description"),
            utf8(b"project_url"),
            utf8(b"creator"),
        ];

        let values = vector[
            utf8(b"{name}"),
            utf8(b"https://movecastle.info/castles/{serial_number}"),
            utf8(b"https://images.movecastle.info/static/media/castles/{image_id}.png"),
            utf8(b"{description}"),
            utf8(b"https://movecastle.info"),
            utf8(b"Castle Builder"),
        ];

        let publisher = package::claim(otw, ctx);
        let mut display = display::new_with_fields<Castle>(&publisher, keys, values, ctx);

        display::update_version(&mut display);

        transfer::public_transfer(publisher, tx_context::sender(ctx));
        transfer::public_transfer(display, tx_context::sender(ctx));
    }
}

别忘了在 Castle 结构体中添加 image_id 属性:

module move_castle::castle {

    /// 城堡
    public struct Castle has key, store {
        id: UID,
        name: String,
        description: String,
        serial_number: u64,
        image_id: String,
    }
    
    /// 创建新城堡
    entry fun build_castle(...) {
        ...
    
        // 生成序列号和图像 ID
        let serial_number = utils::generate_castle_serial_number(size, &obj_id);
        let image_id = utils::serial_number_to_image_id(serial_number);
        
        // 新建城堡
        let castle = Castle {
            id: obj_id,
            name: string::utf8(name_bytes),
            description: string::utf8(desc_bytes),
            serial_number: serial_number,
            image_id: image_id,
        };
        
        ...
    }
}

将您的包部署到测试网开发网,建造一个城堡并在 Sui Explorer 上查看。