Dungeon Move
  • 🎮Welcome
  • đŸ•šī¸Introduction
  • đŸ‘ĨInvitation
  • 🎁Treasure Chest
  • đŸ§™â€â™‚ī¸Resident
    • đŸĻ„Dynamic NFT
    • đŸĒĒIdentity
    • đŸ’¯Rating
    • đŸŽ¯Rarity
    • đŸ’ĢSkill*
    • 🔄Rebirth*
  • 🚩Territory
  • 👹Dungeon Boss
  • 💎Gems
  • đŸžī¸Caves
  • đŸĒMarketplace
  • đŸĒ™$DGG Token
  • 🎲Randomness
  • â›“ī¸Fully on chain & Dynamic NFT
  • âš”ī¸PVP*
  • 📡Contact Us
Powered by GitBook
On this page
  • Randomness
  • Image storage
  • Frame
  • Animation

Was this helpful?

Fully on chain & Dynamic NFT

Some technical details about fully on-chain dynamic NFT

PreviousRandomnessNextPVP*

Last updated 11 months ago

Was this helpful?

In this section, we will introduce the technical details of how Resident NFT are generated, as well as how we store a fully animated NFT on the Sui blockchain.

Randomness

The skin color of the resident is determined by the main color and secondary color. By randomly generating the main color, we can have a vast array of residents with diverse skin tones!

Colors sets total combination:

255∗255∗255=16581375 255*255*255 = 16581375255∗255∗255=16581375

By controlling the offset of RGB color values, we can ensure that each randomly generated main color and secondary color falls within a controllable range. This ensures that the skin tones of the generated residents will not be too strange.

/// A code snippet that generate random color
let r_m = makeRandomRGB(random_key, object_bytes,b"r_m");
let g_m = makeRandomRGB(random_key, object_bytes,b"g_m");
let b_m = makeRandomRGB(random_key, object_bytes,b"b_m");

let contrast_color = rgbToHex(255-r_m, 255-g_m, 255-b_m);

(rgbToHex(r_m,g_m,b_m), rgbToHex(r_s,g_s,b_s), contrast_color)

Image storage

We use SVG as the format for storing NFTs. By leveraging Sui's object display feature, we can associate the Resident object ID with the SVG image that needs to be displayed.

We will use this resident NFT as an example.

We can break down the aforementioned SVG into two parts:

  1. Frame: Indicates the numerical values of the Resident's attributes, as well as the rarity represented by corresponding colors.

  2. Animation: Represents the animated part of the Resident.

Frame

In the frame, the properties with corresponding numerical values and the rarity colors are rendered based on the Resident's object fields.

  • Level - 1

  • Rating - 9999

  • Rarity - UR

  • Frame Border - #E50C0C (UR Color)

/// A code snippet that associates the properties of object fields with SVG 
public fun create_display<T: key>(publisher: &Publisher, identity_index: u64, ctx: &mut TxContext){
        let keys = vector[
            utf8(b"image_url"),
            utf8(b"Lv."),
            utf8(b"rating"),
            utf8(b"identity"),
        ];
        let image_metadata = identity_source::generateSVG(identity_index,b"{rarity_color}", b"{level}", b"{rarity}", b"{rating}", b"{main_color}", b"{sec_color}", b"{contrast_color}");
        let values = vector[
            utf8(image_metadata),
            utf8(b"{level}"),
            utf8(b"{rating}"),
            utf8(b"{identity}"),
        ];
        let display = display::new_with_fields<T>(
            publisher, keys, values, ctx
        );
        display::update_version(&mut display);
        transfer::public_transfer(display, tx_context::sender(ctx));
}

Additionally, we have published a corresponding contract module for each type of Resident identity to store the compressed frame images corresponding to that identity (see the Animation below).

/// A simple code snippet of dungeon_resource
module dungeon_resource_arcane_trickster::svg {
  use std::vector;

  const Path1:vector<u8> = b"...";
  
  // {{rarity-color}}

  const Path2:vector<u8> = b"...";

  // {{level}}

  const Path3:vector<u8> = b"..."

  // {{rarity-string}} N R SR SSR UR

  const Path4:vector<u8> = b"...";

  // {{rarity-color}}

  const Path5:vector<u8> = b"...";

  // {{rating}}

  const Path6:vector<u8> = b"...";

  // {{rarity-color}}

  const Path7:vector<u8> = b"...";
  
  // {{sec_color}}
  
  const Path8:vector<u8> = b"...";
  
  // {{main_color}}
  
  const Path9:vector<u8> = b"...";

  public fun generateSVG(rarity_color:vector<u8>, ..., sec_color:vector<u8>):vector<u8>{
    let metadata = Path1;
    vector::append(&mut metadata, rarity_color);
    vector::append(&mut metadata, Path2);
    .
    .
    .
    vector::append(&mut metadata, main_color);
    vector::append(&mut metadata, Path9);
    metadata
  }
}

Animation

As you can see, the idle animation of the Resident is actually composed of 4 frames of images. By controlling with CSS within the SVG, the animation effect of the Resident can be created from these four frames.

@keyframes animation {
    from {
        background-position-x:0;
    }
     to {
        background-position-x:-128px;
    }
}

We have created a unique compression method to compress these frame images, maximizing the reduction in file size, allowing them to be fully stored on the blockchain at a low cost.

// a simple compressed data
[
        18,
        40,
        2,
        7,
        20,
        21,
        7,
        20,
        21,
        31,
        26,
        2,
        20,
        2,
        2,
        31,
        26,
        2,
        12
]
//
[
    "M14",
    "13h1v1h-1zm1",
    "0h1v1h-1zm1",
    "0h1v1h-1zm29",
    "0h1v1h-1zm65",
    "0h1v1h-1zM13",
    "14h1v1h-1zm1",
    "0h1v1h-1zm12",
    "0h1v1h-1zm25",
    "0h1v1h-1zm11",
    "0h1v1h-1zm26",
    "0h1v1h-1zm5",
    "0h1v1h-1z",
    "M15",
    "0h1v1h-1zm36",
    "0h1v1h-1zm73",
    "M19",
    "0h1v1h-1zm38",
    "M16",
    "M17",
    "16h1v1h-1zm1",
    "0h1v1h-1zm37",
    "17h1v1h-1zm1",
]
â›“ī¸
Random A
Random B
Frame
Resident Animation